UDP responses not handled when auto_redirect enabled in TUN on Alpine Linux
Operating system
Linux
System version
Alpine Linux 3.22.2 (kernel 6.12.51)
Installation type
Original sing-box Command Line
If you are using a graphical client, please provide the version of the client.
No response
Version
sing-box version 1.12.12-87eb1935
Environment: go1.25.3 linux/amd64
Tags: with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0
Revision: 87eb193522f9b1894d3abe1931327921e291ef93
CGO: enabled
Description
UDP doesn't work when using TUN inbound with "auto_redirect":true on Alpine Linux.
According to a Wireshark capture, a client->server packet is sent to tun0, successfully handled by sing-box and sent to the destination, then a server->client UDP packet is received by sing-box, appeared on the tun0 capture, but is never received by an application.
When auto_redirect is unset or set to false, UDP traffic is handled correctly, an application sends and receives any data reliably.
So maybe there's some differences in nftables defaults in Alpine compared to other distros, or something like that. I checked Void Linux musl, UDP is handled correctly with auto_redirect enabled.
Reproduction
It doesn't matter if the default outbound is a proxy or direct.
Sample config:
test.json
{
"log": {"level": "trace"},
"dns": {
"servers": [
{
"type": "udp",
"server": "1.1.1.1",
"server_port": 53
}
]
},
"route": {
"final": "direct",
"auto_detect_interface": true,
"rules": [
{
"network": "udp",
"port": 53,
"action": "hijack-dns"
}
]
},
"inbounds": [
{
"type": "tun",
"address": "172.18.0.1/30",
"auto_route": true,
"auto_redirect": true
}
],
"outbounds": [
{
"tag": "direct",
"type": "direct"
}
]
}
The issue can be reproduced on any Alpine system, you can even do this in a LiveCD environment running in QEMU:
Reproducing with QEMU
$ wget https://dl-cdn.alpinelinux.org/alpine/v3.22/releases/x86_64/alpine-virt-3.22.2-x86_64.iso
$ qemu-system-x86_64 -accel kvm -cpu host -smp cores=6,threads=2 -m 4G -boot order=d -cdrom alpine-virt-3.22.2-x86_64.iso
(adjust -smp flag for your CPU, and maybe -m in case you're testing on a machine with a little amount of RAM)
user: root
# mount -o remount,size=8G /
# setup-interfaces
(just Enter on all questions, or eth0, dhcp, n)
# ip link set eth0 up
# udhcpc
# setup-apkrepos
(c, Enter, 1, Enter -- this enables community repo and chooses the official mirror)
# apk add git go make curl iperf3
# git clone https://github.com/SagerNet/sing-box
# cd sing-box
# make
# cat >test.json
{
"log": {"level": "trace"},
"dns": {
"servers": [
{"type": "udp", "server": "1.1.1.1", "server_port": 53}
]
},
"route": {
"final": "direct",
"auto_detect_interface": true,
"rules": [
{"network": "udp", "port": 53, "action": "hijack-dns"}
]
},
"inbounds": [
{
"type": "tun",
"address": "172.18.0.1/30",
"auto_route": true,
"auto_redirect": true
}
],
"outbounds": [
{"tag": "direct", "type": "direct"}
]
}
Then Ctrl-D.
# iperf3 -c psf.lt -u
UDP iperf3 test runs successfully.
# modprobe tun
# ./sing-box run -c test.json &
# iperf3 -c psf.lt -u
iperf3 handshake times out / hangs forever.
# fg and Ctrl-C.
# sed -i 's/auto_redirect": *true/auto_redirect": false/' test.json
# ./sing-box run -c test.json &
# iperf3 -c psf.lt -u
UDP iperf3 test runs successfully after disabling auto_redirect.
# fg and Ctrl-C to stop sing-box.
Logs
INFO[0000] network: updated default interface wlan0, index 26
TRACE[0000] inbound/tun[0]: creating stack
INFO[0000] inbound/tun[0]: started at tun0
INFO[0000] sing-box started (0.11s)
INFO[0005] [1878929381 0ms] inbound/tun[0]: inbound packet connection from (my local ip):52749
INFO[0005] [1878929381 0ms] inbound/tun[0]: inbound packet connection to 172.18.0.2:53
DEBUG[0005] [1878929381 0ms] router: match[0] network=udp port=53 => hijack-dns
DEBUG[0005] [1878929381 0ms] dns: exchange psf.lt. IN AAAA
DEBUG[0005] [1878929381 0ms] dns: exchange psf.lt. IN A
DEBUG[0005] [1878929381 29ms] dns: exchanged psf.lt NOERROR 246
INFO[0005] [1878929381 29ms] dns: exchanged SOA psf.lt. 246 IN SOA ns1.psf.lt. admin.projectsegfau.lt. 2023051567 14400 3600 1209600 300
DEBUG[0005] [1878929381 29ms] dns: exchanged psf.lt NOERROR 3547
INFO[0005] [1878929381 29ms] dns: exchanged A psf.lt. 3547 IN A 45.14.112.55
INFO[0005] [639735026 0ms] inbound/tun[0]: inbound redirect connection from (my local ip):60552
INFO[0005] [639735026 0ms] inbound/tun[0]: inbound connection to 45.14.112.55:5201
INFO[0005] [639735026 0ms] outbound/direct[direct]: outbound connection to 45.14.112.55:5201
INFO[0005] [2568946660 0ms] inbound/tun[0]: inbound packet connection from (my local ip):41723
INFO[0005] [2568946660 0ms] inbound/tun[0]: inbound packet connection to 172.18.0.2:53
DEBUG[0005] [2568946660 0ms] router: match[0] network=udp port=53 => hijack-dns
DEBUG[0005] [2568946660 0ms] dns: exchange psf.lt. IN AAAA
DEBUG[0005] [2568946660 0ms] dns: cached psf.lt NOERROR 245
INFO[0005] [2568946660 0ms] dns: cached SOA psf.lt. 245 IN SOA ns1.psf.lt. admin.projectsegfau.lt. 2023051567 14400 3600 1209600 300
DEBUG[0005] [2568946660 0ms] dns: exchange psf.lt. IN A
DEBUG[0005] [2568946660 0ms] dns: cached psf.lt NOERROR 3546
INFO[0005] [2568946660 0ms] dns: cached A psf.lt. 3546 IN A 45.14.112.55
INFO[0005] [2548013780 0ms] inbound/tun[0]: inbound packet connection from (my local ip):36401
INFO[0005] [2548013780 0ms] inbound/tun[0]: inbound packet connection to 45.14.112.55:5201
INFO[0005] [2548013780 0ms] outbound/direct[direct]: outbound packet connection
TRACE[0036] [639735026 30.71s] connection: connection download closed
TRACE[0036] [639735026 30.71s] connection: connection upload closed
Supporter
- [ ] I am a sponsor
Integrity requirements
- [x] I confirm that I have read the documentation, understand the meaning of all the configuration items I wrote, and did not pile up seemingly useful options or default values.
- [x] I confirm that I have provided the server and client configuration files and process that can be reproduced locally, instead of a complicated client configuration file that has been stripped of sensitive data.
- [x] I confirm that I have provided the simplest configuration that can be used to reproduce the error I reported, instead of depending on remote servers, TUN, graphical interface clients, or other closed-source software.
- [x] I confirm that I have provided the complete configuration files and logs, rather than just providing parts I think are useful out of confidence in my own intelligence.
Looking at the definition of ZSTD_ENABLE_ASM_X86_64_BMI2,
it appears it does not imply the presence of __BMI2__.
The full test is :
#if !defined(ZSTD_DISABLE_ASM) && \
ZSTD_ASM_SUPPORTED && \
defined(__x86_64__) && \
(DYNAMIC_BMI2 || defined(__BMI2__))
So, ZSTD_ENABLE_ASM_X86_64_BMI2 can be defined to 1 if DYNAMIC_BMI2 is set to 1 too,
even if __BMI2__ is not defined.
It follows that ZSTD_ENABLE_ASM_X86_64_BMI2 && defined(__BMI2__) is not the same test as ZSTD_ENABLE_ASM_X86_64_BMI2 alone.
So the proposed change is not obvious to validate.
The context into which this macro test is invoked is also a bit complex, or at least it's not trivial to me.
I just note that, before this block, there are other blocks also triggered by similar though different macro tests,
such as #if DYNAMIC_BMI2, and # if ZSTD_ENABLE_ASM_X86_64_BMI2,
and they do something similar, such as setting loopFn function pointer.
I would look for @terrelln guidance on this code block, and in absence of such guidance, I would rather lean towards cautiousness and not validate the suggested modification.
Yep, you are right, it's not exact.
Please, ignore this one for now. Imo it should be refactored with STATIC_BMI instead
Please, ignore this one for now. Imo it should be refactored with STATIC_BMI instead
should be ok now.
linux-kernel fails with this error:
../linux/lib/zstd/compress/../common/portability_macros.h:66:11: error: "STATIC_BMI2" is not defined, evaluates to 0 [-Werror=undef]
66 | && !STATIC_BMI2
however, there is no STATIC_BMI2 on portability_macros.h:66 line
Try to rebase your PR on top of latest dev branch commit
Try to rebase your PR on top of latest
devbranch commit
done
I'm not too sure what is happening with the linux test,
but it's a significant issue.
Maybe portability_macros.h gets altered or rebuilt by some script during the linux build process.
cc @terrelln .
doesn't look like it, entire zstd repo only has a couple of mentions of it.
Code File Line Column
#include "../common/portability_macros.h" zstd\lib\compress\zstd_cwksp.h 19 21
#include "portability_macros.h" zstd\lib\common\compiler.h 16 11
#include "../common/portability_macros.h" zstd\lib\decompress\huf_decompress_amd64.S 11 21
also, it mentions some bogus line numbers in the error message:
In file included from ../linux/lib/zstd/compress/../common/compiler.h:17,
from ../linux/lib/zstd/compress/fse_compress.c:19:
../linux/lib/zstd/compress/../common/portability_macros.h:66:11: error: "STATIC_BMI2" is not defined, evaluates to 0 [-Werror=undef]
66 | && !STATIC_BMI2
| ^~~~~~~~~~~
On top of that, right before where STATIC_BMI2 is used in portability_macros.h there is a define for STATIC_BMI2.