惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

aimingoo的专栏
aimingoo的专栏
Google DeepMind News
Google DeepMind News
S
SegmentFault 最新的问题
Project Zero
Project Zero
D
DataBreaches.Net
I
InfoQ
L
Lohrmann on Cybersecurity
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
酷 壳 – CoolShell
酷 壳 – CoolShell
Stack Overflow Blog
Stack Overflow Blog
The Register - Security
The Register - Security
Recorded Future
Recorded Future
Vercel News
Vercel News
博客园 - 司徒正美
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
I
Intezer
The Hacker News
The Hacker News
F
Fortinet All Blogs
Microsoft Azure Blog
Microsoft Azure Blog
P
Proofpoint News Feed
Help Net Security
Help Net Security
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Scott Helme
Scott Helme
T
Threatpost
爱范儿
爱范儿
N
Netflix TechBlog - Medium
D
Docker
云风的 BLOG
云风的 BLOG
C
Cisco Blogs
K
Kaspersky official blog
H
Help Net Security
S
Secure Thoughts
T
Threat Research - Cisco Blogs
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
S
Security @ Cisco Blogs
Cyberwarzone
Cyberwarzone
N
News and Events Feed by Topic
G
Google Developers Blog
Forbes - Security
Forbes - Security
博客园 - 三生石上(FineUI控件)
博客园 - 叶小钗
B
Blog
Google DeepMind News
Google DeepMind News
Recent Announcements
Recent Announcements
Simon Willison's Weblog
Simon Willison's Weblog
S
Securelist
P
Privacy International News Feed
Spread Privacy
Spread Privacy
The Last Watchdog
The Last Watchdog

atpX

自建 S3 兼容对象存储服务 Garage 听风的歌 偶尔是深夜就好了 新玩具 AirPods Pro 3 公交车上的时间 从 WHOIS 到 RDAP 通过 WireGuard 访问 NFS 共享文件 自建音乐串流服务探索 我停止了探索 Fediverse 互联网背景噪音 我的数字生活降级 小城与确定性的墙 一只特立独行的猪 Fediverse 与社交 承认的勇气 mediaX - 轻量书影音记录管理工具 我的博客写作流程 网站新增 Misc 页面 擅长对线的鲍勃 在 Chroot 环境下使用 Rsync 同步 再见 JavaScript 当我玩博客时我在玩什么 为什么我的博客没有友链页面 小熊猫与大熊猫 (HDR 照片测试) 是时候为网站开启 HTTP/3 支持了吗 使用 AdGuard Home 搭建自用 DoH 服务 谈谈读书与消遣 2023 年终总结 西安两日游 从 AirPods「升级」到 EarPods Docker 搭建去中心化的微博客平台 Mastodon 聊聊 iPhone 15 Pro 使用感受 能否将 TOTP 二次验证存放在密码管理器里 认识史蒂夫·乔布斯 与“锤哥”克里斯·海姆斯沃斯一起养生 逃离爆炸的信息 你想活出怎样的人生 重温《龙珠》动画 为什么关闭评论 从域名注册商到 DNS 服务,找到自己的组合 如何提高用户网页阅读体验 从 Typecho 迁移到 Hugo 个人博客的最终归宿是静态网站吗 我选择了放弃 jQuery 关于我的小破站折腾速度优化这件事 记一次家庭网络折腾 贴一些 SmokePing 记录 我的域名邮箱选择 不存在的语录 Typecho 评论验证插件 CaptchaPlus 谈谈写字这件事 新的网站 Logo 探索 曲线解决 Typecho 图片占位抖动问题 自建 vaultwarden / bitwarden_rs 密码管理器 使用 Plausible 自建网站流量统计分析工具 如何优雅地徒手剥开火腿肠 简单的集中隔离生活记录 新玩具 M2 MacBook Air 使用 acme.sh 申请 Google 公共证书 Docker 搭建去中心化的微博客平台 Misskey 搭建 Lsky Pro 兰空图床 折腾树莓派系统的一天 我的吉卜力之旅 使用 Uptime Kuma 自建服务器/网站在线率监控 Spartan Host Review - Best Hosting & Service Support Debian 下 Nginx 配合 Fail2Ban 减少恶意扫描和攻击 升级 Typecho 到最新开发版本 v1.2.0 Linux rm 命令详解 Debian 系统安装 Docker 教程 BandwagonHost - Reliable Blog and Business VPS Hosting 使用 Isso 为 Hugo/Hexo 等静态网站添加评论功能 推荐 5 款免费开源的网站流量分析统计工具 搭建 Shynet 网站流量统计分析工具 聊聊 DDoS 攻击那些事 Hugo 使用 Fancybox 实现图片灯箱/放大功能 从 Debian 10 升级到 Debian 11 教程 使用 Umami 自建网站流量统计分析工具 Linux 一次执行多个命令的 3 种方式 Typecho 修改永久链接后旧链接 301 跳转到新链接 从不同需求推荐几家稳定可靠的 VPS 服务商 Build EchoIP service with Docker 浅谈一下这些年折腾过的 VPS 单线复用解决一根网线同时 IPTV 和宽带问题 不完全吃灰的树莓派 开发一个自己的 Telegram Bot 画画真难 我读村上春树 使用 acme.sh 自动签发和更新证书 卡拉马佐夫式悲剧 安装 File Browser 轻量网盘工具 新玩具树莓派到手 Linux 定时自动备份数据到 OneDrive/Google Drive 记一次服务器崩溃 使用 rsync 同步文件 短信的消失 幻灭的艺术家 新玩具戴尔 S2721DGF 开箱 蝴蝶 用 Docker 整合 SeafilePro 搭建私人云盘 搭建自己的 kms 服务器
使用 Nginx 实现 TCP 四层反向代理
ATP · 2021-01-22 · via atpX

众所周知,海外高性能服务器加不错的线路等于贵得离谱,然而不看网络的话,价格就便宜不少,再选择一台低配的线路不错的 VPS 作为前端进行代理的就划算很多。做站的话一般通过 nginx 进行 http 或者 tcp 代理,以前一直都在使用 http 反向代理,好处是配置简单效果也不差,但如果进行 https 代理,前端也需要配置 ssl 证书,从安全的角度来说显然是不合理的。因此折腾了下通过 Nginx Stream 模块进行 tcp 代理,因为只是传输层协议,便可以不用配置 ssl 证书。

然而无论那种方式,对于做站来说,都有获取到真实 ip 的需求。如果是 http 代理的话,可以通过编译 ngx_http_realip_module 模块并设置 proxy_set_header 实现,这里不再讨论。这篇文章着重说下 tcp 代理的实现方法和过程。

要使用 stream 模块,编译 Nginx 的时候需要加上 --with-stream 参数,然后在 nginx.conf 中配置,一个简单的代理模型如下:

stream{
    ...
    upstream backend{
        server your.domain.com:443
    }

    server{
        listen 443 ssl;
        proxy_pass backend;
        ...
    }
}

而要实现代理多个后端还需要用到 ngx_stream_ssl_preread_module 模块,需要自行编译,该模块可以在不解密的情况下,通过 SNI 获取到访问的域名,之后在 server 中加入 ssl_preread on,示例如下:

stream{
    ...

    map $ssl_preread_server_name $backend {
        server-1.com server_1;
        server-2.com server_2;
        ...
        default server_1;
    }

    upstream server_1{
        server server-1.com:443;
    }

    upstream server_2{
        server server-2.com:443;
    }

    ...

    server{
        listen 443 ssl;
        ssl_preread on;
        proxy_pass $backend;
        ...
    }
}

那么 tcp 代理如何获取到真实 ip 呢,这里就需要用到 proxy_protocol 协议,该协议是 HAProxy 作者开发的一个开源协议,通过添加 tcp 头信息传递客户端的参数。具体的实现主要是参考了 Nginx 官方的文档 Accepting the PROXY Protocol。具体的实现不算复杂,proxy_protocol 需要两个角色 sender 和r eceiver,都指定 proxy_protocol 协议即可,示例如下:

stream{
    ...
    # 这里修改记录格式为$proxy_protocol_addr
    log_format basic '$proxy_protocol_addr - $remote_addr [$time_local] '
                '$protocol $status $bytes_sent $bytes_received '
                '$session_time';

    map $ssl_preread_server_name $backend {
        server-1.com server_1;
        server-2.com server_2;
        ...
        default server_1;
    }

    upstream server_1{
        server server-1.com:443;
    }

    upstream server_2{
        server server-2.com:443;
    }

    ...

    server{
        listen 443 ssl;
        ssl_preread on;
        proxy_protocol on; # 这里打开proxy_protocol协议
        proxy_pass $backend;
        ...
    }
}

正确的接受信息,需要在 server 的 listen 端口后加上 proxy_protocol,如 listen 443 ssl http2 proxy_protocol; 并设置 tcp 代理的 CIDR 地址:

set_real_ip_from 192.168.1.0/24;
real_ip_header   proxy_protocol;

对于 http,记录真实 ip 需要设置 proxy_set_header

http {
    proxy_set_header X-Real-IP       $proxy_protocol_addr;
    proxy_set_header X-Forwarded-For $proxy_protocol_addr;
}

以上设置便可以通过 tcp 反向代理并获取到真实 ip 了,由于我是国内外分线路解析,国内走前端代理,国外直连,因此我的问题并没有解决。最开始发现前端能访问,后端无法直接访问,也就是国内能访问到,而国外则无法连接,查看 error.log 日志也发现乱码并提示 broken header:

proxy_protocol 错误信息

而后在这篇帖子中发现问题,研究发现 proxy_protocol 的接收端必须在接收到完整有效的 proxy_protocol 头部后才能开始处理连接数据,sender 在与 receiver 之间建立连接后,会先发送一个带有客户信息的 tcp header,因为更改了 tcp 协议,receiver 也必须支持 proxy_protocol,否则不能识别 tcp 包头,导致无法成功建立连接。也就是如果服务器接收到的第一个数据包不符合 proxy_protocol 的格式,服务器会直接终止连接。而我的策略是国外走的是直连,并不需要 proxy_protocol,添加上的话没有前端发送对应的协议,就会无法打开,而如果去掉的话,就会变成国外能访问,国内无法访问。

最终的解决方案与 stackoverflow 上的一篇帖子中提到的类似,国外保留 443 接口直连,使用传统协议,而前端将请求转发到后端的另一个端口,设置防火墙该端口只能通过前端的 ip 访问,并通过 Nginx 监听接受 proxy_protocol 协议。这样就解决了在 tcp 代理的同时分线路的问题,最终实现了国内访问解析到前端,然后通过 tcp 四层反向代理到后端并传递真实 ip,而国外访问直接解析到后端源站。