由于DOH服务器端需要客户端IP来更好地向upstream请求距离客户端比较近的IP地址——亦即所谓的edns,或者edns client subnet——所以需要让nginx能够在反向代理的时候传递客户端IP给后端。由于特殊需要,需要保持SNI分流即第4层分流。之前一直搞不了,直到今天看到stackoverflow的回答,在第4层分流的情况下,nginx是无法获知客户端IP的,所以给后端的IP永远都是nginx自己的IP;但是如果使用proxy protocol,那就可以知道客户端IP甚至Port。总而言之,现在需要实现的是:1. 打开proxy protocol的同时兼容不支持proxy protocol的后端;2. 在proxy protocol下传递客户端真实IP。
首先是第一方面,需要在打开proxy protocol的情况下,再由nginx本身去掉这一层protocol,然后传给后端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| stream { log_format stream_basic '$remote_addr [$time_local] ' '$protocol $status $bytes_sent $bytes_received ' '$session_time';
map $ssl_preread_server_name $backend_name { some.domain.com unix:/run/nginx-remove-proxy-stream.sock; default 127.0.0.1:48443; } server { listen unix:/run/nginx-remove-proxy-stream.sock proxy_protocol; proxy_pass 127.0.0.1:8772; } server { listen 443 reuseport; proxy_pass $backend_name; ssl_preread on; proxy_protocol on; } }
http { log_format proxy_main '$proxy_protocol_addr [$time_local] ' '"$request" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent"'; access_log /var/log/nginx/access.log proxy_main; include /etc/nginx/sites-enabled/*; }
|
需要注意的有两点:1. http
部分中,log_format
不再写$remote_addr
了,而是$proxy_protocol_addr
,这就是客户端IP;2. 注释中“注意末端的proxy_protocol,每一个都需要有这个才能正常提供服务”,在上面的配置示例中,直接用一个server
块来去掉了这个proxy_protocol
,从而完成了本文需要解决的第一个问题;而对于由nginx提供服务的其他网站,每一个都需要在listen
那一行里有一个proxy_protocol
指令才能正常工作。如果不明白,看看下面这个虚拟网站的conf就知道了——同时,这个配置也体现了怎么传递真实IP给后端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| server { listen 48443 ssl http2 proxy_protocol; server_name some2.domain.com; location /dns-query { proxy_redirect off; proxy_http_version 1.1; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $proxy_protocol_addr; proxy_set_header X-Forwarded-For $proxy_protocol_addr; proxy_pass http://127.0.0.1:8080/dns-query; } }
|
这样配置好之后,DOH服务器的日志里就显示出了客户端的IP,就能够利用起edns的好处了。