转载请注明出处,本文仅用于学习交流,不对之处,恳请指正,部分图片摘取网络,如有侵权请联系
群晖在内网穿透工具下,如果使用的是HTTPS,或者TCP的方式,则无法正确获取用户的真实IP,这将影响群晖基于IP的功能,如防火墙拉黑暴力破解的IP等。那么我们可以考虑通过Proxy Protocol的方式来解决该问题。
前言
我们在将内网的NAS发布到公网的方式一般有使用公网IP,或者通过内网穿透等方式。我使用的是Frp(高性能反向代理应用)这个工具。今天刚好跟朋友林陈陈的小站闲聊的时候:提到了群晖在内网穿透的情况下,如何获取真实IP的问题。
刚好我之前有写过群晖WebDAV使用,并在穿透下获取真实IP,于是便想顺便将群晖网页服务如何获取真实IP的方式也记录下来。
需求
-
群晖默认通过Header中的X-Forwarded-For来获取用户的真实IP,而Frp的HTTP类型也默认开启该功能。获取用户真实 IP。所以如果你使用的是HTTP类型来访问你的NAS,则将Frp的流量指向对应的NAS IP地址即可
-
如果你使用的是TCP或者HTTPS的方式来访问你的NAS,则需要使用Proxy Protocol协议来获取用户的真实IP。刚好群晖使用的是Nginx作为网页服务器,Nginx支持Proxy Protocol协议。
-
随着现在IPv6的普及,我们大多数宽带都拥有IPv6的公网IP,那么我们再通过IPv6直连NAS的时候,也应该获取到对应的真实IP。
-
附加需求:当我们回到局域网的时候,应自动切换到局域网的IP进行访问,以保证我们的最佳访问速度。
测试环境
- OpenWrt:Frpc插件 0.42.0 版本
- Debian11:Frps服务端 0.42.0 版本
- 群晖:DS3617xs 6.2.3版本
方案
随便画,将就看...
Frpc 配置
本文着重于如何使用Proxy Protocol协议
Frpc 代码配置
-
如果你使用的是独立的Frpc客户端,则你可以通过以下代码配置来开启Proxy Protocol
# 中括号内填入你的服务的唯一标识 [alainlam.cn] # 类型可以是http,https,tcp等 type=https # 自定义域名,需要将你的域名解析到对应的FRPS服务器IP custom_domains=alainlam.cn # 局域网内NAS服务器的IP local_ip=192.168.1.1 # 为了满足公网IPv6以及局域网的IPv4也能够获取正确的真实IP # 我们可以将Nginx监听的Proxy Protocol协议放在其他端口上 local_port=55001 # 开启Proxy Protocol V2版本 proxy_protocol_version=v2 # 启用加密 use_encryption=true # 启用压缩 use_compression=true
-
重启Frpc客户端
systemctl restart frpc.service
OpenWrt中配置Frpc
OpenWrt支持Frpc插件,我们可以使用该插件来配置Frpc。
-
在服务中,找到Frp内网穿透
-
在基础配置中写入你的服务器配置信息
-
在服务列表中新增一项服务,并启用Proxy Protocol协议
-
点击保存,Frpc会自动重启
群晖使用Proxy Protocol协议
-
使用ssh连接到群晖
-
切换到root用户
sudo -i
-
阅读群晖的nginx的配置文件
cat /etc/nginx/nginx.conf
我们可以在配置文件的结尾看到以下代码,故我们可以将我们的Nginx配置文件放置在/etc/nginx/conf.d/目录下,并以http.*.conf格式命名,这样群晖的Nginx会自动引用相应的配置文件,且不会被系统所覆盖修改。
注意我这里使用的是DS3617xs 6.2.3版本,请注意查阅你的版本是否相同!
-
编写反向代理配置,并开启Proxy Protocol协议,请根据你的具体情况以及以下注释说明进行适当调整
server { # 监听IPv4,并开启Proxy Protocol协议 listen 55001 ssl proxy_protocol; # 监听IPv6,并开启Proxy Protocol协议 listen [::]:55001 ssl proxy_protocol; # 监听的域名,与你的Frpc服务所配置的custom_domains一致 server_name alainlam.cn; # 排除Cloudflare CDN的IP # 如果你有使用到CDN的服务的话,一般NAS不会使用到此类IP # 仅供参考 set_real_ip_from 173.245.48.0/20; set_real_ip_from 103.21.244.0/22; set_real_ip_from 103.22.200.0/22; set_real_ip_from 103.31.4.0/22; set_real_ip_from 141.101.64.0/18; set_real_ip_from 108.162.192.0/18; set_real_ip_from 190.93.240.0/20; set_real_ip_from 188.114.96.0/20; set_real_ip_from 197.234.240.0/22; set_real_ip_from 198.41.128.0/17; set_real_ip_from 162.158.0.0/15; set_real_ip_from 104.16.0.0/13; set_real_ip_from 104.24.0.0/14; set_real_ip_from 172.64.0.0/13; set_real_ip_from 131.0.72.0/22; set_real_ip_from 2400:cb00::/32; set_real_ip_from 2606:4700::/32; set_real_ip_from 2803:f800::/32; set_real_ip_from 2405:b500::/32; set_real_ip_from 2405:8100::/32; set_real_ip_from 2a06:98c0::/29; set_real_ip_from 2c0f:f248::/32; # 排除本地IP,请根据你的具体情况配置 set_real_ip_from 192.168.1.0/24; set_real_ip_from 10.0.0.0/8; # 排除服务器IP set_real_ip_from 你运行Frps的服务器IP地址 # 真实IP使用proxy_protocol协议 real_ip_header proxy_protocol; # 开启排除IP功能 real_ip_recursive on; proxy_headers_hash_max_size 512; proxy_headers_hash_bucket_size 128; # 反向代理 location / { # 目标地址,群晖默认HTTPS地址为5001,请根据实际情况调整 proxy_pass https://localhost:5001; # 兼容http # proxy_set_header Upgrade-Insecure-Requests 1; # 告诉后端使用ssl proxy_ssl_server_name on; # 客户端使用的http协议 proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Scheme $scheme; # 客户端host proxy_set_header Host $host; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-Host $http_host; # 完整URI proxy_set_header X-Original-URI $request_uri; # 客户端使用的端口 proxy_set_header X-Real-Port $proxy_protocol_port; # 多层代理IP proxy_set_header X-Forwarded-For $proxy_protocol_addr; # 客户端IP,群晖默认会通过X-Real-IP获取用户IP proxy_set_header X-Real-IP $proxy_protocol_addr; # 支持Websocket # 如果你使用诸如Docker bash此类的功能,则需要开启Websocket proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection Upgrade; proxy_connect_timeout 60s; proxy_read_timeout 60s; proxy_send_timeout 12s; } }
-
测试nginx配置是否正确
nginx -t
正确则返回
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful
-
重载Nginx
nginx -s reload
-
完成
局域网与公网自动切换
如果我们的电脑或者手机的网关以及DNS服务器指向是我们的主路由局域网IP地址,则我们可以在路由器中对相应的域名进行拦截。
比如在OpenWrt中:
文章评论
看了你这篇文章,我有两个问题:
1、listen 55001 ssl proxy_protocol; 这个proxy_protocol我一添加,网站就打不开了,去掉就好了。
2、看你的配置文件里,还有反向代理,这是群晖直接反向代理自己吗?
@皇家元林 FRP在使用TCP或者HTTPS的时候需要使用proxy_protocol才能获取到真实ip(我用的是0.42的版本),如果打开proxy_protocol,那么没有经过proxy_protocol代理的流量确实是不能打开的。所以才会放在一个特殊的端口55001,专门给frp的流量使用。frp内网端口设置为55001,外网端口设置为5001,那么你正常访问5001就可以了。形成的访问模式就跟我上图画的一样。
1. 在外面通过frp访问的时候走的线路是服务器的5001->frp->群晖的55001(这里会把拿到的真实ip设置到X-Forwarded-For)->群晖的5001(会从X-Forwarded-For拿到ip地址)
2. 通过ipv6或者lan访问的时候,因为用的仍然是5001端口直连,所以群晖可以正确获取ip。
总而言之,我们访问的仍然是5001端口,只是当我们没办法直接访问,走的是frp的时候,那么就会通过服务器的5001访问frps,frpc再去访问群晖的55001,再反向到5001。
@Alain 说实话,你这逻辑,好复杂!
当frp添加proxy_protocol_version = v2这个时候,不应该就是走proxy_protocol代理的流量吗?
既然listen 55001 ssl proxy_protocol;后面加这个proxy_protocol有效的话,直接穿透这个端口就可以了啊。而我现在遇到的问题就是这个,后面加这个proxy_protocol无效,出现502错误。
我刚本地测试了。
server
{
listen 80;
listen 443 ssl http2;
listen [::]:443 ssl http2; #proxy_protocol
listen [::]:80;
server_name 192.168.0.108 rsshub.iyl.me;
index index.php index.html index.htm default.php default.htm default.html;
root /www/wwwroot/rss/;
#SSL-START SSL相关配置,请勿删除或修改下一行带注释的404规则
#error_page 404/404.html;
#HTTP_TO_HTTPS_START
#if ($server_port !~ 443){
# rewrite ^(/.*)$ https://$host$1 permanent;
#}
#HTTP_TO_HTTPS_END
ssl_certificate /www/server/panel/vhost/cert/192.168.1.60/fullchain.pem;
ssl_certificate_key /www/server/panel/vhost/cert/192.168.1.60/privkey.pem;
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
add_header Strict-Transport-Security "max-age=31536000";
error_page 497 https://$host$request_uri;
#SSL-END
#ERROR-PAGE-START 错误页配置,可以注释、删除或修改
error_page 404 /404.html;
error_page 502 /502.html;
#ERROR-PAGE-END
#PHP-INFO-START PHP引用配置,可以注释或修改
include enable-php-81.conf;
#PHP-INFO-END
#REWRITE-START URL重写规则引用,修改后将导致面板设置的伪静态规则失效
include /www/server/panel/vhost/rewrite/192.168.1.60.conf;
#REWRITE-END
#禁止访问的文件或目录
location ~ ^/(\.user.ini|\.htaccess|\.git|\.env|\.svn|\.project|LICENSE|README.md)
{
return 404;
}
#一键申请SSL证书验证目录相关设置
location ~ \.well-known{
allow all;
}
#禁止在证书验证目录放入敏感文件
if ( $uri ~ "^/\.well-known/.*\.(php|jsp|py|js|css|lua|ts|go|zip|tar\.gz|rar|7z|sql|bak)$" ) {
return 403;
}
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
{
expires 30d;
error_log /dev/null;
access_log /dev/null;
}
location ~ .*\.(js|css)?$
{
expires 12h;
error_log /dev/null;
access_log /dev/null;
}
access_log /www/wwwlogs/192.168.1.60.log;
error_log /www/wwwlogs/192.168.1.60.error.log;
}
server {
# 监听IPv4并开启Proxy Protocol协议
listen 8443 ssl proxy_protocol;
# 监听IPv6并开启Proxy Protocol协议
listen [::]:8443 ssl proxy_protocol;
# 监听的域名
server_name rsshub.iyl.me;
#HTTP_TO_HTTPS_END
ssl_certificate /www/server/panel/vhost/cert/192.168.1.60/fullchain.pem;
ssl_certificate_key /www/server/panel/vhost/cert/192.168.1.60/privkey.pem;
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
add_header Strict-Transport-Security "max-age=31536000";
error_page 497 https://$host$request_uri;
#SSL-END
# 真实IP使用proxy_protocol协议
real_ip_header proxy_protocol;
# 开启排除IP功能
real_ip_recursive on;
# 排除服务器IP
set_real_ip_from 127.0.0.1;
# 排除CDN的IP
# 根据你的实际情况配置
# set_real_ip_from 你.的.CDN.IPS
proxy_headers_hash_max_size 512;
proxy_headers_hash_bucket_size 128;
# 反向代理
location / {
# 代理的目标地址
proxy_pass https://127.0.0.1:443;
# 客户端host
proxy_set_header Host $host;
# 兼容http
proxy_set_header Upgrade-Insecure-Requests 1;
# 告诉后端使用ssl
proxy_ssl_server_name on;
# 完整URI
proxy_set_header X-Original-URI $request_uri;
# 客户端host
proxy_set_header X-Forwarded-Host $host;
# 客户端IP
proxy_set_header X-Real-IP $proxy_protocol_addr;
# 客户端使用的http协议
proxy_set_header X-Forwarded-Proto $scheme;
# 客户端使用的端口
proxy_set_header X-Real-Port $proxy_protocol_port;
# 客户端及多层代理IP
proxy_set_header X-Forwarded-For $proxy_protocol_addr;
# 客户端使用的http协议
proxy_set_header X-Scheme $scheme;
# 支持websocket
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection Upgrade;
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
proxy_send_timeout 12s;
}
}
应该是这样的意思把,加个端口,这个端口是frpc的监听端口,然后这个端口又反向代理到这个网站的端口。但是网站还是502错误。跟直接穿透一样的结果。
@皇家元林 确实只是穿透了55001端口,我只是跟你解释了不同场景下的流量走向。我在外,晚点看下你的配置,或者你可以通过网站底部的邮件联系我
@Alain 请问有解决吗,我也遇到同样的问题
@abc 元林遇到的问题是因为启用了https2https插件导致了冲突,你可以将配置、nginx日志发我邮件,我可以帮你看一下
@abc 还有你用的名字太马虎了
@Alain 我也是开启了https2https插件,请问是什么冲突(起名字太麻烦了>>>)
@Alain 去掉https2https插件后正常了,请问这是为什么,https2https插件和proxy protocol有什么冲突吗
@abc 我没有深入研究过FRP的原理,有空了可以抓包看看是不是启用了https2https会导致没有向后端服务器发送代理协议头部信息