Oct 03

SNI 分流后获取客户端 IP 一般通过 proxy_protocol 来实现,但分流后的某些程序不能识别 proxy_protocol 怎么办?比如我的 DoH 服务器要 IP 地址,但某木头马并不支持,我开启 proxy_protocol 这马就死了。

我们看代码片段,注意注释。

nginx.conf 主配置文件片段:

stream {
    # 这里就是 SNI 识别,将域名映射成一个配置名
    map $ssl_preread_server_name $backend_name {
        我的域名.坑 web;
        马.我的域名.坑 马;
        # 域名都不匹配情况下的默认值
        default web;
    }
    # 转发到 web 服务器
        upstream web {
        server 127.0.0.1:444;
    }
    # 转发到 马 前置服务器
        upstream 马 {
        server 127.0.0.1:446;
    }
    # 为 马 去除 proxy_protocol
    server {
        #nginx server 443 开启 proxy_protocol 后,分流后的所有服务也必须开启 proxy_protocol,否则会报错
        listen 127.0.0.1:446 proxy_protocol so_keepalive=on;
        proxy_protocol off; #然而,我们在这儿把 proxy_protocol 关闭掉,因为 马 不支持!这是关键
        proxy_connect_timeout 300s;
        proxy_timeout 300s;
        proxy_pass 127.0.0.1:445; #这就是 马 实际吃草的地方
    }

     # 监听 443 并开启 ssl_preread
     server {
         listen 443 reuseport;
         listen [::]:443 reuseport;
         proxy_pass $backend_name;
         ssl_preread on; #开启了分流
         proxy_protocol on; #开启了 proxy_protocol
    }
}

虚拟站点配置文件代码块,大致如下:

server
{
     #nginx server 443 开启 proxy_protocol 后,分流后的所有服务也必须开启 proxy_protocol,否则会报错
    listen 127.0.0.1:444 ssl http2 reuseport proxy_protocol;
    #下面三行给反代的 DoH 服务器传递了客户端 IP
    set_real_ip_from 127.0.0.1;
    real_ip_recursive on;
    real_ip_header proxy_protocol;

    server_name 三达不溜.我的域名.坑 我的域名.坑;
    index index.html index.htm index.php default.html default.htm default.php;
    root  /home/wwwroot/我的域名.坑;

    ssl_certificate /usr/local/nginx/conf/ssl/fullchain.cer;
    ssl_certificate_key /usr/local/nginx/conf/ssl/我的域名.坑_ssl.key;

    #反代 DoH 服务器
    location /dns-query {
        proxy_pass       http://127.0.0.1:8053/dns-query;
        proxy_set_header Host      $host;
        proxy_set_header X-Real-IP $remote_addr; #我要,真实的,IP!
    }

差不多这样。 :evil: :twisted: :cool:

来自 https://github.com/trojan-gfw/trojan/issues/433#issuecomment-692878138 的方法更加精妙:

stream {
    log_format basic '$remote_addr - $remote_user [$time_local] '
                     '$protocol $status $bytes_sent $bytes_received '
                     '$session_time';
    map $ssl_preread_server_name $backend {
        trojan6.domain.com unix:/run/nginx-trojan-stream.sock;
        trojan.domain.com unix:/run/nginx-trojan-stream.sock;
        default 127.0.0.1:443;
    }
    server {
        listen unix:/run/nginx-trojan-stream.sock proxy_protocol;
        proxy_pass 127.0.0.1:8443;
    }
    server {
        listen 0.0.0.0:443;
        listen [::]:443;
        proxy_pass $backend;
        ssl_preread on;
        proxy_protocol on;
    }
}

http {
    log_format combined '$proxy_protocol_addr - $remote_user [$time_local] '
                        '"$request" $status $body_bytes_sent '
                        '"$http_referer" "$http_user_agent"';
    server {
        listen 127.0.0.1:80 proxy_protocol;
        listen [::1]:443 ssl proxy_protocol;
        ...
    }
}

http 的 log_format 中,原来的将原来的 $remote_addr 替换成 $proxy_protocol_addr 就成了。

stream 中的第一个 server 就是为了接收带 proxy_protocol 的 stream,然后发出不带 proxy_protocol 的 stream 给 trojan。

另外,我用的是在 Ubuntu 20.04上 的 Nginx v.1.18.0,来自官方 apt 源的。


May 04

CoreDNS 1.5 彻底废除了 Proxy 插件,用 Forward 插件替代,但想不到怎么写才能实现分流,它不允许一个服务器块中用两次 forward。

只好重新把 Proxy 插件编译进去,添加原 coredns/proxy 插件,编译会出错:

proxy.go:107:22: state.ErrorMessage undefined (type request.Request has no field or method ErrorMessage)

暴力修改过的版本,可以编译通过并且可用:https://github.com/rampageX/proxy

编译方法:

按照官方的方法,git clone 下来后,修改 plugin.cfg ,添加一句:

proxy:github.com/rampageX/proxy

然后 make 即可。

root@phicomm-n1:/devel/src/coredns# ./coredns -plugins
Server types:
dns

Caddyfile loaders:
flag
default

Other plugins:
dns.alternate
dns.any
dns.auto
dns.autopath
dns.bind
dns.cache
dns.cancel
dns.chaos
dns.debug
dns.dnssec
dns.dnstap
dns.erratic
dns.errors
dns.etcd
dns.federation
dns.file
dns.forward
dns.grpc
dns.health
dns.hosts
dns.k8s_external
dns.kubernetes
dns.loadbalance
dns.log
dns.loop
dns.metadata
dns.nsid
dns.pprof
dns.prometheus
dns.proxy
dns.ready
dns.reload
dns.rewrite
dns.root
dns.route53
dns.secondary
dns.template
dns.tls
dns.trace
dns.whoami
on

:mrgreen: :mrgreen: :mrgreen:


Dec 03

项目地址:
https://github.com/aarond10/https_dns_proxy

特色功能:
自从 Google 发布 DNS-Over-HTTPS 服务以来,其实有很多基于此服务写的程序,但是目前看起来这个项目实现的比较好:
C++ 实现,执行文件很小 (30kiB,静态编译的版本约为 900k),比大多数用 Go 写的小得多
基于 Curl HTTP/2 API , 解析延迟极小
单线程无阻塞式查询,适用于嵌入式系统如路由器等
最好作为 DNSMASQ 这样带缓存的 DNS 服务器上游.
由于 Google 这个服务支持 EDNS SUBNET 的查询,所以理论上通过这个服务器查询的结果不会有 CDN 的问题,当然实际还需时间来证明。

Usage: https_dns_proxy [-a ] [-p ]
[-e ] [-d] [-u ] [-g ] [-b ]
[-l ]
-a listen_addr 监听地址. (127.0.0.1)
-p listen_port 监听端口. (5053)
-e subnet_addr edns-client-subnet edns 子网 “203.31.0.0/16”. 建议通过路由器外网地址来计算()
-d 后台运行.
-u user 用户名. (nobody)
-g group 用户组. (nobody)
-b dns_servers 用来解析 dns.google.com 的域名服务器. (8.8.8.8,8.8.4.4)
-t proxy_server 代理服务器,例如: socks5://127.0.0.1:1080,http://127.0.0.1:8080 (注意上面 dns.google.com 的解析不通过此代理!)
-l logfile 日志文件. (-)
-v 开启调试信息. (INFO)

上面的 subnet_addr 可以通过一些命令行获取,例如:

$(nvram get wan_ipaddr | cut -d "." -f 1-2).0.0/16

如果是内网,则应该用:

curl -sS ifconfig.co | cut -d "." -f 1-2
或者
wget http://ipecho.net/plain -O - -q | cut -d "." -f 1-2

这个在 Tomatoware 上静态编译稍微有点麻烦:

1. 首先要编译 curl (及其库)支持 http2,这样用到 nghttp2:

git clone https://github.com/tatsuhiro-t/nghttp2.git
cd nghttp2
autoreconf -i
automake
autoconf
./configure --prefix=/mmc
make
make install

2. 编译 curl :

#!/bin/sh

[ ! -e ./curl.pem ] && wget -qO curl.pem https://curl.haxx.se/ca/cacert.pem

[ -n "$1" ] && ssVersion=$1 || ssVersion="git"

mkdir -p done/${ssVersion}/OpenSSL-opt

echo "Compiling OpenSSL Version..."
make clean
[ ! -e  /opt/sbin/curl.pem ] && cp ./curl.pem /opt/sbin/
./configure --prefix=/mmc --with-ca-bundle=/opt/sbin/curl.pem --with-nghttp2 
--disable-ldap
make -j2 LDFLAGS="-all-static -s" LIBS="-ldl"
[ $? -eq 0 ] || { echo "Compiling OpenSSL failed."; exit 1; }
make install
mv -f src/curl done/${ssVersion}/OpenSSL-opt/

echo -e "Compile Result:n"

file done/${ssVersion}/OpenSSL-opt/curl

echo ""

done/${ssVersion}/OpenSSL-opt/curl -V

3. 静态编译 https_dns_proxy,修改过的 CMakeList.txt:

project(HttpsDnsProxy)
cmake_minimum_required(VERSION 2.8)

#set(CMAKE_BUILD_TYPE "Debug")
set(CMAKE_BUILD_TYPE "Release")

# set(CMAKE_C_FLAGS "-Wall --pedantic -Wno-strict-aliasing")

set(NXJSON_DIR lib/nxjson/)
set(NXJSON_SRC ${NXJSON_DIR}/nxjson.c)

find_path(LIBCARES_INCLUDE_DIR ares.h)
find_path(LIBCURL_INCLUDE_DIR curl/curl.h)
find_path(LIBEV_INCLUDE_DIR ev.h)
include_directories(
${LIBCARES_INCLUDE_DIR} ${LIBCURL_INCLUDE_DIR}
${LIBEV_INCLUDE_DIR} ${NXJSON_DIR} src)

# The main binary
set(TARGET_NAME "https_dns_proxy")
aux_source_directory(src SRC_LIST)
set(SRC_LIST ${SRC_LIST} ${NXJSON_SRC})
add_executable(${TARGET_NAME} ${SRC_LIST})
#set(LIBS ${LIBS} cares curl ev resolv ssl crypto dl z m)
set(LIBS ${LIBS} cares curl ev resolv ssh2 ssl psl crypto dl z m nghttp2)
target_link_libraries(${TARGET_NAME} ${LIBS})

# Link to static libraries if needed
IF(STATIC_LIB)
SET(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s -static")
ENDIF(STATIC_LIB)

install(CODE "MESSAGE(\"Please install manually for now.\")")

然后:

mkdir b
cd b
cmake -DSTATIC_LIB=ON ..
make

file ./https_dns_proxy
./https_dns_proxy: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, stripped