You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
WireGuard proxy socket opened before startTun is never protected and loops into the app's own tun
#852
A wireguard proxy's UDP socket is created before the tun is up, so it is never protected with VpnService.protect(). Its packets then come back into the app's own tun, and the rules send them into that same WireGuard proxy. The tunnel handshakes once and then dies. Most cold starts end up like this.
The code path, at 3cc102a:
The app always puts itself inside its own VPN, in every access-control mode: TunService.kt#L157-L168 (+ packageName for AcceptSelected, - packageName for DenySelected). Every core socket therefore has to be protected.
Protection goes through dialer.DefaultSocketHook → app.MarkSocket (delegate/init.go#L50). Until the tun starts, markSocketImpl is a no-op (app/tun.go#L36-L50). The real markSocket is only installed in startTun (native/tun.go#L84).
The profile loads before startTun. Its health checks (fallback groups) and its DNS through a proxy (nameserver: tcp://10.x.0.1:53#group) dial the WireGuard proxy right away. The WireGuard device binds its UDP socket at that moment, about 5 ms before the tun exists (log below: handshake initiation at .465, TUN: fd = 121 at .470). The socket lives for the whole session, so it stays unprotected.
Once the tun is up, every packet from that socket is routed into the tun. The core sees it as app traffic and matches it by rules:
[UDP] 192.168.1.4:34424 --> 203.0.113.10:443 match Match using wgfleet[server-1-awg]
The first handshake completes, because it left before the tun existed. After that nothing reaches the server: no packets from that source port reach the home router (no conntrack entry) or the server (tcpdump). After 15 s wireguard-go logs Retrying handshake because we stopped hearing back after 15 seconds, and the proxy stays dead until the next restart. TCP proxies (VLESS) are not affected: they dial per connection, after the tun is up.
Which side wins the race varies. Over 4 cold starts on the same device and profile, WireGuard came up once.
To Reproduce
A profile with a wireguard proxy (AmneziaWG options present here, but the protect path does not depend on them) inside a fallback group with url/interval. Optionally, dns.nameserver through that group.
Rules end in MATCH,<group containing the wireguard proxy>.
Force-stop the app and start the VPN (cold start).
Within ~20 s the log shows a [UDP] <device>:<port> --> <wg server>:<wg port> match Match using <group>[<wg proxy>] line, the health check for the WireGuard proxy fails, and stopped hearing back repeats.
Workaround: put a rule ahead of everything that sends the proxy's own endpoint DIRECT, so the looped packets go out again on a protected socket:
With it, 8 of 9 cold starts were clean (the loop still happens, but the rule catches it).
Expected: WireGuard sockets are protected regardless of whether the profile or the tun comes first. For example, install the protect hook before the profile is loaded, or delay outbound dials until startTun.
Device Info
Device: Samsung Galaxy S26 Ultra (SM-S948B)
ROM: One UI, build BP4A.251205.006.S948BXXS4AZHL
Android Version: 16
Application Info
Version: 2.11.34.Meta (core Alpha_ab405ba_260914)
APK filename: arm64-v8a release from GitHub Releases
Distribution Channel: GitHub Releases (sideloaded)
Configure File
Trimmed to the parts that matter; keys and addresses replaced.
Access control: AcceptSelected (the app adds itself, see point 1).
Logs
Cold start, debug level (addresses replaced):
12:54:57.465 [WG](server-1-awg) UDP bind has been updated
12:54:57.465 [WG](server-1-awg) - Sending handshake initiation
12:54:57.470 TUN: fd = 121, stack = system, gateway = 172.19.0.1/30, portal = 172.19.0.2, dns = 0.0.0.0
12:54:57.470 {"enable":true,"device":"Meta","stack":"System",...,"auto-route":false,"auto-detect-interface":false,...}
12:54:57.495 [WG](server-1-awg) - Received handshake response
12:54:57.496 [UDP] 192.168.1.4:34424 --> 203.0.113.10:443 match Match using wgfleet[server-1-awg]
12:55:02.465 Health Checked, proxy: server-1-awg, alive: false, delay: 65535 ms
12:55:12.598 [WG](server-1-awg) - Retrying handshake because we stopped hearing back after 15 seconds
...
Possibly related: mihomo hot reload leaving the old WireGuard socket open (MetaCubeX/mihomo#3255). On this device a profile update also left two sockets to the same server.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Describe the bug
A
wireguardproxy's UDP socket is created before the tun is up, so it is never protected withVpnService.protect(). Its packets then come back into the app's own tun, and the rules send them into that same WireGuard proxy. The tunnel handshakes once and then dies. Most cold starts end up like this.The code path, at
3cc102a:TunService.kt#L157-L168(+ packageNamefor AcceptSelected,- packageNamefor DenySelected). Every core socket therefore has to be protected.dialer.DefaultSocketHook→app.MarkSocket(delegate/init.go#L50). Until the tun starts,markSocketImplis a no-op (app/tun.go#L36-L50). The realmarkSocketis only installed instartTun(native/tun.go#L84).startTun. Its health checks (fallbackgroups) and its DNS through a proxy (nameserver: tcp://10.x.0.1:53#group) dial the WireGuard proxy right away. The WireGuard device binds its UDP socket at that moment, about 5 ms before the tun exists (log below: handshake initiation at.465,TUN: fd = 121at.470). The socket lives for the whole session, so it stays unprotected.Once the tun is up, every packet from that socket is routed into the tun. The core sees it as app traffic and matches it by rules:
The first handshake completes, because it left before the tun existed. After that nothing reaches the server: no packets from that source port reach the home router (no conntrack entry) or the server (tcpdump). After 15 s wireguard-go logs
Retrying handshake because we stopped hearing back after 15 seconds, and the proxy stays dead until the next restart. TCP proxies (VLESS) are not affected: they dial per connection, after the tun is up.Which side wins the race varies. Over 4 cold starts on the same device and profile, WireGuard came up once.
To Reproduce
wireguardproxy (AmneziaWG options present here, but the protect path does not depend on them) inside afallbackgroup withurl/interval. Optionally,dns.nameserverthrough that group.MATCH,<group containing the wireguard proxy>.[UDP] <device>:<port> --> <wg server>:<wg port> match Match using <group>[<wg proxy>]line, the health check for the WireGuard proxy fails, andstopped hearing backrepeats.Workaround: put a rule ahead of everything that sends the proxy's own endpoint
DIRECT, so the looped packets go out again on a protected socket:With it, 8 of 9 cold starts were clean (the loop still happens, but the rule catches it).
Expected: WireGuard sockets are protected regardless of whether the profile or the tun comes first. For example, install the protect hook before the profile is loaded, or delay outbound dials until
startTun.Device Info
Application Info
Alpha_ab405ba_260914)Configure File
Trimmed to the parts that matter; keys and addresses replaced.
Access control: AcceptSelected (the app adds itself, see point 1).
Logs
Cold start, debug level (addresses replaced):
Possibly related: mihomo hot reload leaving the old WireGuard socket open (MetaCubeX/mihomo#3255). On this device a profile update also left two sockets to the same server.
All reactions