Sing-box: TUN inbound in client configuration did not work for HTTP(S) traffics

Description

  • Usual HTTP(S) and SOCKS proxies inbound does work.
  • TUN inbound did work for DNS traffics.
  • But TUN inbound did not work for HTTP(S) traffics.

Configuration

services.sing-box = {
  enable = true;
  settings = {
    "inbounds" = [
      {
        "tag" = "tun-in";
        "type" = "tun";
        "address" = [
          "172.16.0.1/30"
          "fd00::1/126"
        ];
        "mtu" = 1492;
        "auto_route" = true;
        "strict_route" = true;
        "sniff" = true;
      }
    ];
    "outbounds" = self.extraOutbounds ++ [
      {
        "tag" = "direct-out";
        "type" = "direct";
      }
      {
        "tag" = "dns-out";
        "type" = "dns";
      }
    ];
    "dns" = {
      "servers" = [
        {
          "tag" = "system";
          "address" = "local";
        }
        {
          "tag" = "alidns";
          "address" = "223.5.5.5";
        }
        {
          "tag" = "alidns-doh";
          "address" = "https://dns.alidns.com/dns-query";
          "address_resolver" = "alidns";
        }
        {
          "tag" = "opendns";
          "address" = "208.67.220.220";
        }
        {
          "tag" = "cloudflare-doh";
          "address" = "https://1.1.1.1/dns-query";
          "address_resolver" = "opendns";
        }
        {
          "tag" = "google-doh";
          "address" = "https://dns.google/dns-query";
          "address_resolver" = "opendns";
        }
      ];
      "final" = "alidns-doh";
      "rules" = [
        {
          "rule_set" = "geosite-geolocation-!cn";
          "server" = "cloudflare-doh";
        }
      ];
    };
    "route" = {
      "auto_detect_interface" = true;
      "final" = "direct-out";
      "rules" = self.extraRouteRules ++ [
        {
          "protocol" = "dns";
          "outbound" = "dns-out";
        }
      ];
      "rule_set" = [
        {
          "tag" = "geosite-geolocation-cn";
          "type" = "local";
          "format" = "binary";
          "path" = "${pkgs.sing-geosite}/share/sing-box/rule-set/geosite-geolocation-cn.srs";
        }
        {
          "tag" = "geosite-geolocation-!cn";
          "type" = "local";
          "format" = "binary";
          "path" = "${pkgs.sing-geosite}/share/sing-box/rule-set/geosite-geolocation-!cn.srs";
        }
        {
          "tag" = "geoip-cn";
          "type" = "local";
          "format" = "binary";
          "path" = "${pkgs.sing-geoip}/share/sing-box/rule-set/geoip-cn.srs";
        }
      ];
    };
  };
};

where extraOutbounds is like

[
  {
    "tag": "trojan-out",
    "type": "trojan",
    "server": "████████",
    "server_port": ████,
    "password": "████████",
    "tls": {
      "enabled": true,
      "server_name": "████████",
      "utls": {
        "enabled": true,
        "fingerprint": "firefox"
      }
    },
    "multiplex": {
      "enabled": true
    }
  }
]

and extraRouteRules is like

[
  {
    "type": "logical",
    "mode": "and",
    "rules": [
        {
            "rule_set": "geoip-cn",
            "invert": true
        },
        {
            "rule_set": "geosite-geolocation-!cn"
        }
    ],
    "outbound": "trojan-out"
  }
]

The proxy based configuration is the same, except that the inbound is

{
  "tag" = "mixed-in";
  "type" = "mixed";
  "listen" = "127.0.0.1";
  "listen_port" = proxyPort;
}

with

networking.proxy.default = "http://127.0.0.1:${toString proxyPort}";

In addition, the default DNS server for geosite-geolocation-!cn is opendns instead of cloudflare-doh. However, this should not matter, since both servers work well in both settings.

Log

When TUN was used, only DNS traffics were matched:

Dec 18 11:44:21 laptain sing-box[149424]: INFO[0358] [4230968915 0ms] inbound/tun[tun-in]: inbound packet connection from 172.16.0.1:52079
Dec 18 11:44:21 laptain sing-box[149424]: INFO[0358] [4230968915 0ms] inbound/tun[tun-in]: inbound packet connection to 192.168.110.1:53
Dec 18 11:44:21 laptain sing-box[149424]: DEBUG[0358] [4230968915 0ms] router: sniffed packet protocol: dns
Dec 18 11:44:21 laptain sing-box[149424]: DEBUG[0358] [4230968915 0ms] router: match[1] protocol=dns => dns-out
Dec 18 11:44:21 laptain sing-box[149424]: DEBUG[0358] [4230968915 0ms] dns: exchange github.com. IN A

In contrast, when proxy is used the HTTP(S) traffics are matched as expected:

Dec 18 11:44:50 laptain sing-box[153267]: INFO[0000] [4063974782 0ms] inbound/mixed[mixed-in]: inbound connection from 127.0.0.1:46548
Dec 18 11:44:50 laptain sing-box[153267]: INFO[0000] [4063974782 0ms] inbound/mixed[mixed-in]: inbound connection to detectportal.firefox.com:80
Dec 18 11:44:50 laptain sing-box[153267]: DEBUG[0000] [4063974782 0ms] router: match[0] !(rule_set=geoip-cn) && rule_set=geosite-geolocation-!cn => trojan-out
Dec 18 11:44:50 laptain sing-box[153267]: INFO[0000] [4063974782 0ms] outbound/trojan[trojan-out]: outbound multiplex connection to detectportal.firefox.com:80

I don’t know why, but setting

let
  tunName = "tun0";
in
networking.firewall.trustedInterfaces = [ tunName ];
services.sing-box.settings.inbounds = [
  {
    "tag" = "tun-in";
    "type" = "tun";
    "interface_name" = tunName;
    # ...
  }
];

solves this.