Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

TUN `mixed` stack: TCP never re-injected into tun0 on Linux kernel 7.1 (UDP works, `gvisor` works)

Open
#98 0 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
1-2 days
Newbie friendliness
45/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Active
Tech stack
go, linux
Domain
networking

Research direction

Start by reproducing the report with the supplied config on Linux using the mixed TUN stack, then compare it with gvisor. The report points to sing-tun's stack_system.go, especially processIPv4TCP and the tun.Write error path; inspect those paths and add focused logging to locate where the SYN stops. Done means TCP connections are reinjected into tun0 and work with the mixed stack.

Written by the indexing model from the issue text.

Description

Operating system

Linux

System version

Kali Linux, kernel 7.1.5+kali-amd64

Installation type

Vendor-bundled binary (sing-box 1.13.8, revision d5adb54, tags below). Cannot test the official release on this machine right now; happy to do so on request.

Version
sing-box version 1.13.8
Environment: go1.25.9 linux/amd64
Tags: with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_naive_outbound,badlinkname,tfogo_checklinkname0,with_purego
Revision: d5adb54bc6c6b2c21ab6f748276c4ec62d9bb650
CGO: disabled
Describe the bug

With "stack": "mixed" the TUN inbound handles UDP fine (DNS hijack and QUIC go through the vless outbound and get replies), but no TCP connection ever reaches the router: inbound connection lines never appear, only inbound packet connection. From the application side every TCP connect times out.

tcpdump on tun0 shows only the kernel's SYNs retransmitting and nothing written back by sing-box: no rewritten 10.0.0.2 → 10.0.0.1:<nat_port> packets at all. The system-stack TCP listener does exist:

$ sudo ss -4tlnp | grep sing-box
LISTEN 0 4096 10.0.0.1:35657 0.0.0.0:* users:(("sing-box",pid=36135,fd=10))

Changing only "stack" to "gvisor" in the same config fixes it immediately (log below).

Config (same file, secrets removed)
{
  "log": { "level": "info", "timestamp": true },
  "dns": {
    "client_subnet": "0.0.0.0/0",
    "final": "dns_proxy",
    "independent_cache": true,
    "rules": [ { "action": "route", "domain": ["hh.ru"], "server": "dns_direct" } ],
    "servers": [
      { "detour": "proxy", "server": "1.1.1.1", "tag": "dns_proxy", "type": "tcp" },
      { "server": "1.0.0.1", "tag": "dns_direct", "type": "udp" }
    ],
    "strategy": "ipv4_only"
  },
  "inbounds": [
    {
      "address": ["10.0.0.1/30"],
      "auto_route": true,
      "mtu": 1500,
      "stack": "mixed",
      "strict_route": true,
      "tag": "tun-in",
      "type": "tun"
    }
  ],
  "route": {
    "auto_detect_interface": true,
    "default_domain_resolver": { "server": "dns_proxy", "strategy": "ipv4_only" },
    "final": "proxy",
    "rule_set": [
      { "format": "binary", "path": "/home/user/rule_sets/geoip.srs", "tag": "geoip", "type": "local" },
      { "format": "binary", "path": "/home/user/rule_sets/geosite.srs", "tag": "geosite", "type": "local" }
    ],
    "rules": [
      { "action": "sniff" },
      { "action": "hijack-dns", "mode": "or", "rules": [ { "protocol": "dns" }, { "port": 53 } ], "type": "logical" },
      { "ip_is_private": true, "outbound": "direct" },
      { "ip_cidr": ["217.217.x.x/32"], "outbound": "direct" },
      { "domain": "ip.accessly.app", "outbound": "proxy" },
      { "domain": ["hh.ru"], "outbound": "direct" },
      { "outbound": "direct", "rule_set": "geoip" },
      { "outbound": "direct", "rule_set": "geosite" }
    ]
  },
  "outbounds": [
    { "tag": "proxy", "type": "vless", "server": "217.217.x.x", "server_port": 443 },
    { "tag": "direct", "type": "direct" }
  ]
}
Routing state while connected (mixed)
$ ip route get 1.1.1.1
1.1.1.1 via 10.0.0.2 dev tun0 table 2022 src 10.0.0.1 uid 1000

$ ip rule
0:      from all lookup local
9000:   from all to 10.0.0.0/30 lookup 2022
9001:   from all lookup 2022 suppress_prefixlength 0
9002:   not from all dport 53 lookup main suppress_prefixlength 0
9002:   from all iif tun0 goto 9010
9003:   not from all iif lo lookup 2022
9003:   from 0.0.0.0 iif lo lookup 2022
9003:   from 10.0.0.0/30 iif lo lookup 2022
9010:   from all nop
32766:  from all lookup main
32767:  from all lookup default

$ ip route show table 2022
default via 10.0.0.2 dev tun0
tcpdump on tun0 during curl https://www.google.com (mixed)
$ sudo tcpdump -ni tun0 -c 30 'tcp port 443 and not host 34.160.111.145'
14:31:39.945205 IP 10.0.0.1.34742 > 104.21.74.183.443: Flags [S], seq 1558434216, win 64240, options [mss 1460,sackOK,TS val 433816482 ecr 0,nop,wscale 10], length 0
14:31:40.966373 IP 10.0.0.1.34742 > 104.21.74.183.443: Flags [S], seq 1558434216, ...
14:31:41.986544 IP 10.0.0.1.34742 > 104.21.74.183.443: Flags [S], seq 1558434216, ...
14:31:44.450464 IP 10.0.0.1.48668 > 142.251.140.106.443: Flags [S], seq 1110277888, ...
14:31:48.540053 IP 10.0.0.1.48110 > 108.177.96.188.443: Flags [S], seq 182472403, ...
14:31:49.570472 IP 10.0.0.1.48110 > 108.177.96.188.443: Flags [S], seq 182472403, ...
14:31:52.642360 IP 10.0.0.1.48110 > 108.177.96.188.443: Flags [S], seq 182472403, ...
14:31:59.814299 IP 10.0.0.1.48110 > 108.177.96.188.443: Flags [S], seq 182472403, ...
14:32:22.092929 IP 10.0.0.1.53822 > 34.117.59.81.443: Flags [S], seq 385209085, ...
30 packets captured, 0 packets dropped by kernel

All 30 packets are outbound SYNs from 10.0.0.1. Not a single packet from sing-box back into tun0 (expected 10.0.0.2.N > 10.0.0.1.35657 [S]).

sing-box log (mixed) during the same test

Only UDP flows are logged; the TCP SYNs above never produce an inbound connection line:

INFO [1388067991 0ms] inbound/tun[tun-in]: inbound packet connection from 10.0.0.1:55084
INFO [1388067991 0ms] inbound/tun[tun-in]: inbound packet connection to 192.168.x.x:53
INFO [1388067991 0ms] outbound/vless[proxy]: outbound connection to 1.1.1.1:53
INFO [1388067991 206ms] dns: exchanged A ip.accessly.app. 197 IN A 104.21.74.183
INFO [2521444636 0ms] inbound/tun[tun-in]: inbound packet connection from 10.0.0.1:57000
INFO [2521444636 0ms] inbound/tun[tun-in]: inbound packet connection to 172.217.171.67:443
INFO [2521444636 0ms] outbound/vless[proxy]: outbound packet connection to 172.217.171.67:443
WARN inbound/tun[tun-in]: link icmp connection from 10.0.0.1 to 1.1.1.1: icmp is not supported by default outbound: proxy
Same config with "stack": "gvisor", debug log
INFO network: updated default interface wlan0, index 2
INFO inbound/tun[tun-in]: started at tun0
INFO sing-box started (0.01s)
INFO [908698353 0ms] inbound/tun[tun-in]: inbound connection from 10.0.0.1:35540
INFO [908698353 0ms] inbound/tun[tun-in]: inbound connection to 74.125.128.188:5228
DEBUG [908698353 0ms] router: match[0] => sniff
DEBUG [908698353 13ms] router: sniffed protocol: tls, domain: mtalk.google.com
INFO [908698353 13ms] outbound/vless[proxy]: outbound connection to 74.125.128.188:5228
INFO [908698353 177ms] outbound/vless[proxy]: outbound connection to 74.125.128.188:5228
INFO [2426132354 0ms] inbound/tun[tun-in]: inbound connection from 10.0.0.1:55000
INFO [2426132354 0ms] inbound/tun[tun-in]: inbound connection to 34.149.226.178:443
DEBUG [2426132354 0ms] router: sniffed protocol: tls, domain: content-signature-2.cdn.mozilla.net
INFO [2426132354 0ms] outbound/vless[proxy]: outbound connection to 34.149.226.178:443
INFO [2426132354 160ms] outbound/vless[proxy]: outbound connection to 34.149.226.178:443
DEBUG [908698353 1m33s] connection: connection upload finished

curl https://ipinfo.io/json then returns the exit server's country (SE), browser works.

Ruled out
  • Docker: stopped docker.service and docker.socket, no change.
  • firewalld (present, inet firewalld table with input hook) and leftover iptables chains from another VPN client: all rule counters stay at 0 during the test.
  • rp_filter: reverse-path lookup for src 10.0.0.2 arriving on tun0 resolves to tun0 via rule 9000, so strict mode would not drop it.
  • Conflicting VPN daemons: a second sing-box-based client was stopped before the capture.
  • nstat shows no increments in IPReversePathFilter, InAddrErrors, InNoRoutes, InDiscards, ListenDrops during the test.
Expected behavior

With mixed/system stack, TCP SYNs read from tun should be rewritten and written back to tun so the kernel delivers them to the system-stack listener on 10.0.0.1.

Additional
  • Not yet tested on a 6.x kernel; will update.
  • Happy to run a debug build or add logging in sing-tun stack_system.go (processIPv4TCP / tun.Write error path) if that helps.
Dominant language
Go
Stars
241
Forks
243
PR merge metrics
No merged PRs in 30d

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from SagerNet/sing-tun

All issues in SagerNet/sing-tun

Similar issues

More Go issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.