由于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';
# 这里就是 SNI 识别,将域名映射成一个配置名
map $ssl_preread_server_name $backend_name {
# 为不支持proxy protocol的后端设置一个匹配规则
some.domain.com unix:/run/nginx-remove-proxy-stream.sock;
# 域名都不匹配情况下的默认值,亦即nginx提供的ssl网站们
default 127.0.0.1:48443;
}
# 去掉proxy protocol后转发给后端
server {
# 注意末端的proxy_protocol,每一个都需要有这个才能正常提供服务
# 否则虽然nginx -t通过了,但是运行起来就会显示fail to read proxy xxx的错误
listen unix:/run/nginx-remove-proxy-stream.sock proxy_protocol;
proxy_pass 127.0.0.1:8772;
}
# 监听 443 并开启 ssl_preread
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;
# 这里写其他配置
# 这里是转发给DOH的示例
location /dns-query {
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
# show real IP
# 把$proxy_protocol_addr所代表的真实IP用X-Real-IP传递给后端
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的好处了。