chika0801 / sing-box-examples

sing-box 配置示例
https://github.com/SagerNet/sing-box
1.61k stars 270 forks source link

[萌新求助]vless-tcp-xtls-vision-reality的方式fq, 添加前置Nginx筛选SNI无法fq; #115

Closed scavenger-caesar closed 1 month ago

scavenger-caesar commented 1 month ago

(希望能请教一下nginx和xray server的配置要如何修改可以实现如下结果; 目的是防止伪装到套了CDN的网站被别人扫出来用作反代了)

1. 期望结果

1) 流入xray请求的SNI和伪装的目标网站一致 2) 通过nginx筛选SNI, 不符合伪装的SNI连接均拒绝

2. 尝试过程

2.1 实现思路

前置nginx筛选SNI

2.2 测试环境

利用docker部署测试环境

2.2.1 docker compose内容

networks:
  xray_network:
    external: false

services:
  xray_server:
    image: ghcr.io/xtls/xray-core:latest
    command: run -config /etc/xray/config.jsonc
    restart: unless-stopped
    networks:
      - xray_network
    volumes:
      - ./xray/logs/:/var/log/xray/ # xry的日志输出目录
      - ./xray/config/:/etc/xray/ # xray的配置目录
    environment: 
      TZ: Asia/Shanghai

  nginx_server:
    image: nginx:latest
    restart: unless-stopped
    networks:
      - xray_network
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro # nginx的配置路径
      - ./nginx/logs/:/var/log/nginx/ # nginx的日志输出目录
    environment: 
      TZ: Asia/Shanghai
    depends_on:
      - xray_server 

2.2.2 xray server内容

{
    "log": {
        "loglevel": "debug"
    },
    "inbounds": [
        {
            "listen": "0.0.0.0", // "0.0.0.0" 代表同时监听所有的IPv4和IPv6
            "port": 4443, 
            "protocol": "vless",
            "settings": {
                "clients": [
                    {
                        "id": "3dc3b7b3-56c1-4d8a-9f15-a4831bf8f7a1",
                        "flow": "xtls-rprx-vision"
                    }
                ],
                "decryption": "none"
            },
            "streamSettings": {
                "network": "tcp",
                "security": "reality",
                "realitySettings": {
                    // "xver": 1, // 发送 PROXY protocol; 专用于传递请求的真实来源 IP 和端口
                    "dest": "pytorch.org:443", // A website that support TLS1.3 and h2. You can also use `1.1.1.1:443` as dest
                    // 证书列表里允许的域名
                    "serverNames": [
                        "pytorch.org"    // A server name in the cert of dest site. If you use `1.1.1.1:443` as dest, then you can leave `serverNames` empty, it is a possible ways to bypass Iran's internet speed restrictions.
                    ],
                    "privateKey": "kIuej6RPn2Hgcg_VN9gAhC_w5i-MjecCkizkRJtJzlE", // run `xray x25519` to generate. Public and private keys need to be corresponding.
                    "shortIds": [// Required, list of shortIds available to clients, can be used to distinguish different clients
                        "d88a233765897aa6" // openssl rand -hex <1-8>
                    ]
                }
                // "tcpSettings": {
                //     // 仅用于 inbound,指示是否接收 PROXY protocol
                //     "acceptProxyProtocol": true 
                // }
            },
            "sniffing": {
                "enabled": true,
                "destOverride": [
                    "http",
                    "tls",
                    "quic"
                ]
                // "routeOnly": true
            }
        }
    ],
    "outbounds": [
        {
            "protocol": "freedom",
            "tag": "direct"
        }
    ]
}

2.2.3 nginx配置内容

# /etc/nginx/nginx.conf

# 指定nginx进程以哪个身份运行
user nginx;
# 使用多少个 worker 进程来处理请求。auto 表示 nginx 将根据可用的 CPU 核心数量自动确定工作进程的数量
worker_processes auto;

# 错误日志文件的路径和日志级别
error_log /etc/stderr debug; # 只记录notice级别的错误
# nginx 主进程的 PID 文件路径
pid /var/run/nginx.pid;

events {
    # 指定了每个 worker 进程的最大连接数; 
    worker_connections 1024;
}

stream {
    # 若接收到的SNI为伪装网站则传给xray, 否则阻断
    map $ssl_preread_server_name $backend {
        pytorch.org reality;
        default   close_connection;
    }
    upstream reality {
        server xray_server:4443;
    }
    upstream close_connection { 
        server 127.0.0.1:6767;
    }
    # 分析接收到请求的SNI
    server {
        # reuseport选项允许多个Nginx worker进程在同一个监听端口上同时接收连接; 可以帮助提高Nginx在高并发环境下的网络性能,特别是在多核系统上
        listen 443;
        listen [::]:443;
        proxy_pass      $backend;
        ssl_preread     on; # Enables extracting information from the ClientHello message at the preread phase
        proxy_protocol  on; # Enables the PROXY protocol for connections to a backend
    }
}

http {
    # 自定义的日志格式 main, 用于访问日志
    log_format main '[$time_local] $proxy_protocol_addr "$http_referer" "$http_user_agent"'; # [时间] 客户端地址 引用页 用户代理信息
    # 访问日志文件的路径和使用的日志格式
    access_log /var/log/nginx/access.log main;

    # 用于设置在 HTTP 升级时如何处理连接
    map $http_upgrade $connection_upgrade { 
        default upgrade; # 如果$http_upgrade存在且非空,则将$connection_upgrade设置为upgrade(用于WebSocket连接)
        ""      close; # 如果$http_upgrade为空,则将$connection_upgrade设置为close(关闭连接)
    } # 它在 HTTP 升级时将连接升级为 HTTP/2

        # 用于处理代理协议头(Proxy Protocol)
    map $proxy_protocol_addr $proxy_forwarded_elem {
        ~^[0-9.]+$        "for=$proxy_protocol_addr"; # 如果$proxy_protocol_addr是IPv4地址(匹配正则表达式~^[0-9.]+$),则将其格式化为for=$proxy_protocol_addr
        ~^[0-9A-Fa-f:.]+$ "for=\"[$proxy_protocol_addr]\""; # 如果$proxy_protocol_addr是IPv6地址(匹配正则表达式~^[0-9A-Fa-f:.]+$),则将其格式化为for="[$proxy_protocol_addr]"
        default           "for=unknown"; # 如果地址格式不匹配上述两种情况,则设置为for=unknown
    } # 确保正确地传递客户端的真实 IP 地址

    # 用于处理 HTTP Forwarded 头;map $http_forwarded $proxy_add_forwarded: 使用map指令将现有的HTTP Forwarded头($http_forwarded)与新生成的代理协议元素($proxy_forwarded_elem)结合,生成新的$proxy_add_forwarded变量
    map $http_forwarded $proxy_add_forwarded {
        # 如果$http_forwarded已经存在且格式正确,则将新生成的$proxy_forwarded_elem追加到现有的$http_forwarded头中
        "~^(,[ \\t]*)*([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?(;([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?)*([ \\t]*,([ \\t]*([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?(;([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?)*)?)*$" "$http_forwarded, $proxy_forwarded_elem";
        # 如果$http_forwarded不存在或格式不正确,则仅使用新生成的$proxy_forwarded_elem
        default "$proxy_forwarded_elem";
    }

    server {
        listen 80;
        listen [::]:80;
        return 301 https://$host$request_uri; #  当有 HTTP 请求时,强制重定向到 HTTPS
    }

    # 阻断部分
    server {
        # proxy_protocol:启用了PROXY协议。这意味着Nginx会期望收到来自代理服务器的连接,其中包含客户端的原始连接信息。这对于获取客户端的真实IP地址等信息是有用的
        listen  127.0.0.1:6767 ssl proxy_protocol;
        # ssl_reject_handshake on;:启用这个选项后,Nginx会在接收到客户端的SSL握手请求时立即关闭连接,而不执行实际的SSL握手。这对于拒绝不必要的或不被允许的SSL连接非常有用
        ssl_reject_handshake    on;
        # ssl_protocols TLSv1.2 TLSv1.3;:指定Nginx只支持TLS 1.2和TLS 1.3协议。这有助于确保连接使用现代、安全的加密协议,避免使用已知存在安全漏洞的旧版本TLS
        ssl_protocols              TLSv1.2 TLSv1.3;
    }
}

2.2.4 xray client配置内容

{
    "log": {
        // error 日志的级别
        "loglevel": "debug"
    },
    // 路由功能模块可以将入站数据按不同规则由不同的出站连接发出,以达到按需代理的目的
    "routing": {
        "domainStrategy": "IPIfNonMatch", // 当域名没有匹配任何规则时,将域名解析成 IP(A 记录或 AAAA 记录)再次进行匹配;
        // 域名匹配算法
        "domainMatcher": "hybrid", // 使用新的域名匹配算法,速度更快且占用更少
        // 匹配规则; TIPS: 当没有匹配到任何规则时,流量默认由第一个 outbound 发出
        "rules": [
            {
                "type": "field",
                // 数组每一项是一个域名的匹配
                "domain": [
                    "geosite:cn",
                    "geosite:private"
                ],
                // 对应一个 outbound 的标识
                "outboundTag": "direct"
            },
            {
                "type": "field",
                // 一个数组,数组内每一项代表一个 IP 范围
                "ip": [
                    "geoip:cn",
                    "geoip:private"
                ],
                "outboundTag": "direct"
            }
        ]
    },
    "inbounds": [
        {
            "listen": "0.0.0.0", // Fill in 0.0.0.0 to allow connections from LAN
            "port": 7891, // local socks listening port
            "protocol": "socks",
            "settings": {
                // 是否开启 UDP 协议的支持
                "udp": true // 开启UDP支持
            },
            // 流量探测
            "sniffing": {
                // 是否开启流量探测
                "enabled": true,
                // 当流量为指定类型时,按其中包括的目标地址重置当前连接的目标
                "destOverride": [
                    "http",
                    "tls",
                    "quic"
                ]
            }
        },
        {
            "listen": "0.0.0.0", // Fill in "0.0.0.0" to allow connections from LAN
            "port": 7890, // Local http listening port
            "protocol": "http",
            "sniffing": {
                "enabled": true,
                "destOverride": [
                    "http",
                    "tls",
                    "quic"
                ]
            }
        }
    ],
    "outbounds": [
        {
            "protocol": "vless",
            "settings": {
                "vnext": [
                    {
                        "address": "x.x.x.x", 
                        "port": 443, 
                        "users": [
                            {
                                "id": "3dc3b7b3-56c1-4d8a-9f15-a4831bf8f7a1", // Needs to match server side
                                "encryption": "none",
                                "flow": "xtls-rprx-vision"
                            }
                        ]
                    }
                ]
            },
            "streamSettings": {
                // 连接的数据流所使用的传输方式类型
                "network": "tcp",
                // 是否启用传输层加密
                "security": "reality", // 表示使用 REALITY
                "realitySettings": {
                    "fingerprint": "chrome", 
                    "serverName": "pytorch.org", // A website that support TLS1.3 and h2.
                    "publicKey": "7QLvKnnIpzVG_27P3Ixy4BiL3pdLO_15AmoxEvdGSGM", // run `xray x25519` to generate. Public and private keys need to be corresponding.
                    "shortId": "d88a233765897aa6" // Required
                }
            },
            "tag": "proxy"
        },
        {
            "protocol": "freedom", // 可以用来向任意网络发送(正常的) TCP 或 UDP 数据
            "tag": "direct"
        },
        {
            "protocol": "blackhole", // 它会阻碍所有数据的出站,配合 路由配置 一起使用,可以达到禁止访问某些网站的效果
            "tag": "block"
        }
    ]
}

2.3 测试结果

无法正常fq

2.3.1 服务端log (nginx + xray server)

xray_server-1  | Xray 1.8.11 (Xray, Penetrates Everything.) Custom (go1.22.2 linux/amd64)
xray_server-1  | A unified platform for anti-censorship.
xray_server-1  | 2024/05/21 14:54:15 [Info] infra/conf/serial: Reading config: /etc/xray/config.jsonc
xray_server-1  | 2024/05/21 14:54:15 [Debug] app/log: Logger started
xray_server-1  | 2024/05/21 14:54:15 [Debug] app/proxyman/inbound: creating stream worker on 0.0.0.0:4443
xray_server-1  | 2024/05/21 14:54:15 [Info] transport/internet/tcp: listening TCP on 0.0.0.0:4443
xray_server-1  | 2024/05/21 14:54:15 [Warning] core: Xray 1.8.11 started
xray_server-1  | 2024/05/21 14:56:31 [Info] transport/internet/tcp: REALITY: processed invalid connection
...(由于太长无法发表issue,所以去掉了重复内容)
xray_server-1  | 2024/05/21 14:59:31 [Info] transport/internet/tcp: REALITY: processed invalid connection
nginx_server-1  | /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
nginx_server-1  | /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
nginx_server-1  | /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
nginx_server-1  | 10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
nginx_server-1  | 10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
nginx_server-1  | /docker-entrypoint.sh: Sourcing /docker-entrypoint.d/15-local-resolvers.envsh
nginx_server-1  | /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
nginx_server-1  | /docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
nginx_server-1  | /docker-entrypoint.sh: Configuration complete; ready for start up

2.3.2 客户端log (xray client)

startedxray_client-xray_server-1  | Xray 1.8.11 (Xray, Penetrates Everything.) Custom (go1.22.3 linux/amd64)
xray_client-xray_server-1  | A unified platform for anti-censorship.
xray_client-xray_server-1  | 2024/05/21 14:54:30 [Info] infra/conf/serial: Reading config: /etc/xray/client_config.jsonc
xray_client-xray_server-1  | 2024/05/21 14:54:30 [Debug] app/log: Logger started
xray_client-xray_server-1  | 2024/05/21 14:54:30 [Debug] app/router: MphDomainMatcher is enabled for 75384 domain rule(s)
xray_client-xray_server-1  | 2024/05/21 14:54:30 [Debug] app/proxyman/inbound: creating stream worker on 0.0.0.0:7891
xray_client-xray_server-1  | 2024/05/21 14:54:30 [Debug] app/proxyman/inbound: creating stream worker on 0.0.0.0:7890
xray_client-xray_server-1  | 2024/05/21 14:54:30 [Info] transport/internet/tcp: listening TCP on 0.0.0.0:7891
xray_client-xray_server-1  | 2024/05/21 14:54:30 [Info] transport/internet/udp: listening UDP on 0.0.0.0:7891
xray_client-xray_server-1  | 2024/05/21 14:54:30 [Info] transport/internet/tcp: listening TCP on 0.0.0.0:7890
xray_client-xray_server-1  | 2024/05/21 14:54:30 [Warning] core: Xray 1.8.11 started
xray_client-xray_server-1  | 2024/05/21 14:55:07 [Info] [1663647110] proxy/socks: TCP Connect request to tcp:api.github.com:443
xray_client-xray_server-1  | 2024/05/21 14:55:07 [Info] [3384687550] proxy/socks: TCP Connect request to tcp:collector.github.com:443
xray_client-xray_server-1  | 2024/05/21 14:55:07 [Info] [1663647110] app/dispatcher: sniffed domain: api.github.com
xray_client-xray_server-1  | 2024/05/21 14:55:07 [Info] [3384687550] app/dispatcher: sniffed domain: collector.github.com
xray_client-xray_server-1  | 2024/05/21 14:55:07 [Info] [3290573928] proxy/socks: TCP Connect request to tcp:www.google.com:443
xray_client-xray_server-1  | 2024/05/21 14:55:07 [Info] [3290573928] app/dispatcher: sniffed domain: www.google.com
xray_client-xray_server-1  | 2024/05/21 14:55:07 [Info] [3290573928] app/dispatcher: default route for tcp:www.google.com:443
xray_client-xray_server-1  | 2024/05/21 14:55:07 tcp:192.168.80.1:53524 accepted tcp:www.google.com:443 [proxy]
xray_client-xray_server-1  | 2024/05/21 14:55:07 [Info] [3290573928] transport/internet/tcp: dialing TCP to tcp:<my vps ip>:443
xray_client-xray_server-1  | 2024/05/21 14:55:07 [Debug] transport/internet: dialing to tcp:<my vps ip>:443
xray_client-xray_server-1  | 2024/05/21 14:55:08 [Info] [1663647110] app/dispatcher: default route for tcp:api.github.com:443
xray_client-xray_server-1  | 2024/05/21 14:55:08 [Info] [1663647110] transport/internet/tcp: dialing TCP to tcp:<my vps ip>:443
xray_client-xray_server-1  | 2024/05/21 14:55:08 [Debug] transport/internet: dialing to tcp:<my vps ip>:443
chika0801 commented 1 month ago

发重复了

scavenger-caesar commented 1 month ago

sorry!我发错仓库了,昨天我以为是网络原因没有发成功

GreatBigWhiteWorld commented 3 weeks ago

sorry!我发错仓库了,昨天我以为是网络原因没有发成功

你这个解决了吗?最后怎么弄的?

scavenger-caesar commented 3 weeks ago

解决了,请参考https://github.com/chika0801/Xray-examples/issues/33;问题出在xray需要配置接受nginx stream的proxy protocol获取真实的用户ip发给伪装网站,在xray配置里加上就可以了