chika0801 / sing-box-examples

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

Nginx SNI diversion with sing-box reality #29

Closed msshn closed 11 months ago

msshn commented 12 months ago

Hello, and thanks for your great examples. I recently tried to divert traffic to sing-box based on SNI but was unsuccessful, however the same thing works with xray without problem. I wanted to know your opinion on this. SNI diversion works with other protocols in sing-box such as naive or trojan, but not reality. I am also using reality with my own domain. The reason for this is I have other services on the vps and i need nginx to listen on 443.

Here are configs

Nginx

user nobody nogroup;
worker_processes auto;

error_log /var/log/nginx/error.log;

pid /run/nginx.pid;

events {
    worker_connections 1024;
}

stream {
    map $ssl_preread_server_name $backend_name {
        xx.mydomain.com  vless;
        mydomain.com  http2;
        www.mydomain.com  http2;
    }
    upstream vless {
        server 127.0.0.1:10000;
    }
    upstream http2 {
        server 127.0.0.1:20000;
    }
    server {
        listen 443;
        listen [::]:443;
        ssl_preread on;
        proxy_pass $backend_name;
        proxy_protocol on;
    }
}

http {
    include mime.types;
    default_type application/octet-stream;

    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';

    access_log /var/log/nginx/access.log main;

    sendfile on;

    keepalive_timeout 65;

    server {
        listen 80;
        listen [::]:80;
        return 301 https://$host$request_uri;
    }

    server {
        listen 127.0.0.1:20000 ssl proxy_protocol;
    http2 on;
        set_real_ip_from 127.0.0.1;
        real_ip_header proxy_protocol;

        ssl_certificate ../fullchain.cer;
        ssl_certificate_key ../mydomain.com.key;

        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_prefer_server_ciphers on;
        ssl_ciphers TLS13_AES_128_GCM_SHA256:TLS13_AES_256_GCM_SHA384:TLS13_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305;
        ssl_ecdh_curve secp521r1:secp384r1:secp256r1:x25519;

        location / {
            add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $http_host;
            proxy_redirect off;
            proxy_pass http://127.0.0.1:30000; #some_backend
        }
    }

xray: This config works

{
  "log": {
    "loglevel": "warning",
    "error": "/var/log/xray/error.log",
    "access": "/var/log/xray/access.log"
  },
  "inbounds": [
    {
      "listen": "127.0.0.1",
      "port": 10000,
      "protocol": "vless",
      "settings": {
        "clients": [
          {
            "id": "532f45bd-7229-425e-8831-97a463eba428",
            "flow": "xtls-rprx-vision"
          }
        ],
        "decryption": "none"
      },
      "streamSettings": {
        "network": "tcp",
        "security": "reality",
        "realitySettings": {
          "show": false,
          "dest": 20000,
          "xver": 2,
          "serverNames": [
            "xx.mydomain.com"
          ],
          "privateKey": "...",
          "shortIds": [
            "123456789"
          ]
        },
        "tcpSettings": {
          "acceptProxyProtocol": true
        }
      },
      "sniffing": {
        "enabled": true,
        "destOverride": [
          "http",
          "tls"
        ]
      }
    }
],
"outbounds": [
    {
      "protocol": "freedom",
      "settings": {}
    },
    {
      "tag": "blocked",
      "protocol": "blackhole",
      "settings": {}
    }
  ]
}

sing-box: does not work

{
  "log": {
    "level": "info"
  },
  "inbounds": [
    {
      "type": "vless",
      "tag": "vless-in",
      "listen": "::",
      "listen_port": 10000,
      "proxy_protocol": true,
      "sniff": true,
      "sniff_override_destination": true,
      "users": [
        {
          "uuid": "532f45bd-7229-425e-8831-97a463eba428",
          "flow": "xtls-rprx-vision"
        }
      ],
      "tls": {
        "enabled": true,
        "server_name": "xx.mydomain.com",
        "reality": {
          "enabled": true,
          "handshake": {
            "server": "127.0.0.1",
            "server_port": 20000
          },
          "private_key": "...",
          "short_id": [
            "123456789"
          ]
        }
      }
    }
],
"outbounds": [
    {
      "type": "direct",
      "tag": "direct"
    },
    {
      "type": "block",
      "tag": "block"
    }
  ]
}

sing-box Logs

ERROR[0043] [3919413922 1ms] inbound/vless[vless-in]: process connection from IP_Address: REALITY: processed invalid connection
ERROR[0046] [709892396 0ms] inbound/vless[vless-in]: process connection from IP_Address: REALITY: processed invalid connection
ERROR[0048] [2314159538 0ms] inbound/vless[vless-in]: process connection from IP_Address: REALITY: processed invalid connection
ERROR[0049] [2780479593 0ms] inbound/vless[vless-in]: process connection from IP_Address: REALITY: processed invalid connection
ERROR[0049] [962535195 0ms] inbound/vless[vless-in]: process connection from IP_Address: REALITY: processed invalid connection
ERROR[0050] [3203139053 1ms] inbound/vless[vless-in]: process connection from IP_Address: REALITY: processed invalid connection
ERROR[0050] [1510738249 0ms] inbound/vless[vless-in]: process connection from IP_Address: REALITY: processed invalid connection
ERROR[0052] [2292211462 0ms] inbound/vless[vless-in]: process connection from IP_Address: REALITY: processed invalid connection
chika0801 commented 12 months ago

I don't use the NGINX front SNI shunt approach myself. Here is my experienced guess as to why, you need to test it yourself.

Because you used the proxy_protocol; parameter in the nginx configuration to pass the IP to access both.

listen 127.0.0.1:20000 ssl proxy_protocol;

      "streamSettings": {
        "network": "tcp",
        "security": "reality",
        "realitySettings": {
          "show": false,
          "dest": 20000,
          "xver": 2,
          "serverNames": [
            "xx.mydomain.com"
          ],
          "privateKey": "...",
          "shortIds": [
            "123456789"
          ]
        },
        "tcpSettings": {
          "acceptProxyProtocol": true
        }
      },

This parameter is being used incorrectly.

https://xtls.github.io/Xray-docs-next/config/transport.html#streamsettingsobject

        "tcpSettings": {
          "acceptProxyProtocol": true

"xver": 2, I'm not sure if I should use 0 here, or if I should use 1 or 2

  "streamSettings": {
    "network": "tcp",
    "security": "reality",
    "realitySettings": {
      "show": false,
      "dest": 20000,
      "xver": 1, // test
      "serverNames": [
        "xx.mydomain.com"
      ],
      "privateKey": "...",
      "shortIds": [
        "123456789"
      ]
    },
    "sockopt": {
      "acceptProxyProtocol": true // test
    }
  },
chika0801 commented 12 months ago

The way the sing-box configuration is written I think is correct, it doesn't work because the parameters that sing-box passes back to nginx don't belong to the proxy_protocol. So in your nginx configuration either don't use listen 127.0.0.1:20000 ssl proxy_protocol; remove proxy_protoco.

Since I haven't experimented with them, you'll need to test the above yourself.

  "inbounds": [
    {
      "type": "vless",
      "tag": "vless-in",
      "listen": "::",
      "listen_port": 10000,
      "proxy_protocol": true,
      "sniff": true,
      "sniff_override_destination": true,
      "users": [
        {
          "uuid": "532f45bd-7229-425e-8831-97a463eba428",
          "flow": "xtls-rprx-vision"
        }
      ],
      "tls": {
        "enabled": true,
        "server_name": "xx.mydomain.com",
        "reality": {
          "enabled": true,
          "handshake": {
            "server": "127.0.0.1",
            "server_port": 20000
          },
          "private_key": "...",
          "short_id": [
            "123456789"
          ]
        }
      }
    }
],
chika0801 commented 12 months ago

https://github.com/chika0801/sing-box-examples/blob/main/VMess/WebSocket_nginx.conf#L74

I saw this configuration and it occurred to me that this one possibility NGINX front passes the port to the back when listen 127.0.0.1:20000 ssl proxy_protocol;, proxy_protocol This parameter should be added, but in the xray parameter of the back

"sockopt": {
      "acceptProxyProtocol": false// test

Then you can test whether xver uses 0 or 1 and it works.

Since I didn't even test it, you need to test it yourself.

chika0801 commented 12 months ago

https://github.com/lxhao61/integrated-examples/tree/main/Xray(M%2BF%2BB%2BG%2BA)%2BNginx

I learned it by referring to the example linked above. And I tested it.

chika0801 commented 12 months ago

This is the configuration I used after testing it myself, you can refer to it.

user nginx;
worker_processes auto;

error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
}

stream {
    map $ssl_preread_server_name $name {
        www.lovelive-chika.top     backend;
    }

    upstream backend {
        server 127.0.0.1:8004;
    }

    server {
        listen         443;
        proxy_pass     $name;
        proxy_protocol on;
        ssl_preread    on;
    }
}

http {
    log_format main '[$time_local] $proxy_protocol_addr "$http_referer" "$http_user_agent"';
    access_log /var/log/nginx/access.log main;

    map $http_upgrade $connection_upgrade {
        default upgrade;
        ""      close;
    }

    map $proxy_protocol_addr $proxy_forwarded_elem {
        ~^[0-9.]+$        "for=$proxy_protocol_addr";
        ~^[0-9A-Fa-f:.]+$ "for=\"[$proxy_protocol_addr]\"";
        default           "for=unknown";
    }

    map $http_forwarded $proxy_add_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";
        default "$proxy_forwarded_elem";
    }

    server {
        listen 80;
        return 301 https://$host$request_uri;
    }

    server {
# http2 on; 这条指令出现在1.25.1版本中 https://nginx.org/en/docs/http/ngx_http_v2_module.html
#       listen                  127.0.0.1:8005 ssl proxy_protocol;
#       http2                   on;

        listen                  127.0.0.1:8005 ssl http2 proxy_protocol;

        set_real_ip_from        127.0.0.1;
        real_ip_header          proxy_protocol;

        ssl_certificate         /etc/ssl/private/fullchain.cer;
        ssl_certificate_key     /etc/ssl/private/private.key;

        ssl_protocols           TLSv1.2 TLSv1.3;
        ssl_ciphers             TLS13_AES_128_GCM_SHA256:TLS13_AES_256_GCM_SHA384:TLS13_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305;

        ssl_session_timeout     1d;
        ssl_session_cache       shared:SSL:10m;
        ssl_session_tickets     off;

        ssl_stapling            on;
        ssl_stapling_verify     on;
        ssl_trusted_certificate /etc/ssl/private/fullchain.cer;
        resolver                1.1.1.1 valid=60s;
        resolver_timeout        2s;

# 使用 https://www.digitalocean.com/community/tools/nginx 生成的反向代理配置
        location / {
            sub_filter                         $proxy_host $host;
            sub_filter_once                    off;

            set $website                       www.lovelive-anime.jp; # 反向代理的网站
            proxy_pass                         https://$website;
            resolver 1.1.1.1;

            proxy_set_header Host              $proxy_host;

            proxy_http_version                 1.1;
            proxy_cache_bypass                 $http_upgrade;

            proxy_ssl_server_name              on;

            proxy_set_header Upgrade           $http_upgrade;
            proxy_set_header Connection        $connection_upgrade;
            proxy_set_header X-Real-IP         $proxy_protocol_addr;
            proxy_set_header Forwarded         $proxy_add_forwarded;
            proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Forwarded-Host  $host;
            proxy_set_header X-Forwarded-Port  $server_port;

            proxy_connect_timeout              60s;
            proxy_send_timeout                 60s;
            proxy_read_timeout                 60s;
        }
    }
}
chika0801 commented 12 months ago
{
    "log": {
        "loglevel": "warning"
    },
    "routing": {
        "domainStrategy": "IPIfNonMatch",
        "rules": [
            {
                "type": "field",
                "ip": [
                    "geoip:cn",
                    "geoip:private"
                ],
                "outboundTag": "block"
            }
        ]
    },
    "inbounds": [
        {
            "listen": "127.0.0.1",
            "port": 8004,
            "protocol": "vless",
            "settings": {
                "clients": [
                    {
                        "id": "chika", // 执行 xray uuid 生成,或 1-30 字节的字符串
                        "flow": "xtls-rprx-vision"
                    }
                ],
                "decryption": "none"
            },
            "streamSettings": {
                "network": "tcp",
                "security": "reality",
                "realitySettings": {
                    "show": false, // 若为 true,输出调试信息
                    "dest": "8005", // 即 "127.0.0.1:8005"
                    "xver": 1, // 发送 PROXY protocol
                    "serverNames": [ // 客户端可用的 serverName 列表,暂不支持 * 通配符,建议填由 Nginx 加载的 SSL 证书中包含的域名,建议将此域名指向服务端的 IP
                        "www.lovelive-chika.top" // 也可填任意网址,建议是国外网站
                    ],
                    "privateKey": "2KZ4uouMKgI8nR-LDJNP1_MHisCJOmKGj9jUjZLncVU", // 执行 xray x25519 生成,填 "Private key" 的值
                    "shortIds": [ // 客户端可用的 shortId 列表,可用于区分不同的客户端
                        "6ba85179e30d4fc2" // 0 到 f,长度为 2 的倍数,长度上限为 16,可留空,或执行 openssl rand -hex 8 生成
                    ]
                },
                "sockopt": {
                    "acceptProxyProtocol": false
                }
            },
            "sniffing": {
                "enabled": true,
                "destOverride": [
                    "http",
                    "tls",
                    "quic"
                ]
            }
        }
    ],
    "outbounds": [
        {
            "protocol": "freedom",
            "tag": "direct"
        },
        {
            "protocol": "blackhole",
            "tag": "block"
        }
    ],
    "policy": {
        "levels": {
            "0": {
                "handshake": 2,
                "connIdle": 120
            }
        }
    }
}
chika0801 commented 12 months ago

If you change xray to sing-box, I looked at your Sing-box config and you added "proxy_protocol": true, which is correct. But I've tested sing-box when using the steal yourself form, sing-box doesn't have the xver:1 parameter inside xray. So if you use sing-box as a server, be careful to remove listen 127.0.0.1:8005 ssl http2; remove proxy_protocol from nginx configuration.

msshn commented 11 months ago

Sorry for late reply, been out of town for a couple days removing "proxy_protocol" from nginx did it! and it's working with sing-box server now. I was ready to pull my hair off, never suspected nginx is the issue, thank you so much!