Skip to content

Commit c1559f6

Browse files
feat(network): stable DNS routing and MTU optimizations for reliable internet
1 parent 8e78bfc commit c1559f6

8 files changed

Lines changed: 68 additions & 17 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ Format:
1616

1717
## v0.7.0 — 2026-03-28
1818

19+
### 2026-03-28 18:35 — 🌐 DNS Routing and MTU Stability Fixes
20+
- What: Updated `scutil` script to use `SupplementalMatchDomains` for forced DNS resolution; lowered default `TUN.MTU` to 1400; added support for configuring MTU and filtered MacOS IPv6 broadcasts.
21+
- Why: Pinging worked but DNS failed because macOS was skipping our `utun` for lookups. Web traffic failed due to USB fragmentation limits on Android exceeding 1400 MTU.
22+
- Files: `internal/tun/utun_darwin.go`, `internal/tun/utun.go`, `internal/daemon/daemon.go`, `internal/daemon/relay.go`, `config/default.toml`
23+
- Breaking: no ✅
24+
1925
### 2026-03-28 16:45 — Instant Teardown and Panic Fix
2026
- What: Resolved a shutdown deadlock by force-closing USB handles when the daemon stops; added nil-pointer checks to the USB watcher to prevent exit panics.
2127
- Why: Ensure the daemon exits immediately and cleanly on `Ctrl+C` even while waiting for network packets.

VERSIONS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ v1.0.0 = MVP complete and working on M1/M2/M3.
1212

1313
## v0.7.0 — 2026-03-28
1414
- Milestone: DNS Automation & Graceful Shutdown
15-
- What works: Automatic DNS configuration via `scutil` (macOS). The system now uses the phone's DNS gateway immediately. Added a `WaitGroup`-based shutdown to the `Daemon` to ensure all routes and tunnel interfaces are cleanly removed when stopping.
15+
- What works: Automatic DNS configuration via `scutil` (macOS) with forced SupplementalMatchDomains. The system now uses the phone's DNS gateway immediately for all lookups. Added a `WaitGroup`-based shutdown to the `Daemon` to ensure all routes and tunnel interfaces are cleanly removed when stopping. `TUN.MTU` is automatically reduced to `1400` to support standard Android USB RNDIS fragmentation limits natively. Internet web traffic and DNS fully working.
1616
- Next: Move toward v1.0.0 after community testing.
1717

1818
## v0.6.0 — 2026-03-28

config/default.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ read_buffer_size = 65536
3838
interface_name_prefix = "droidtether"
3939

4040
# MTU for the utun interface
41-
mtu = 1500
41+
mtu = 1400
4242

4343

4444
[dhcp]

internal/daemon/daemon.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package daemon
22

33
import (
4+
"fmt"
45
"os"
56
"os/signal"
67
"sync"
@@ -78,10 +79,16 @@ func (d *Daemon) Run() error {
7879

7980
relay.OnDHCP = func(gateway, client string) {
8081
log.Info().Str("component", "daemon").Str("gateway", gateway).Str("client", client).Msg("🔥 DHCPOFFER Intercepted! Auto-configuring network...")
81-
if err := iface.Configure(client, gateway); err != nil {
82+
83+
mtuStr := fmt.Sprintf("%d", d.cfg.TUN.MTU)
84+
if d.cfg.TUN.MTU <= 0 {
85+
mtuStr = "1400" // fallback
86+
}
87+
88+
if err := iface.Configure(client, gateway, mtuStr); err != nil {
8289
log.Warn().Str("component", "daemon").Err(err).Msg("Failed to auto-configure interface IP")
8390
} else {
84-
log.Info().Str("component", "daemon").Msg("✨ Network auto-configured! Ping should now work natively!")
91+
log.Info().Str("component", "daemon").Str("mtu", mtuStr).Msg("✨ Network auto-configured! Ping should now work natively!")
8592

8693
// Inject default route if configured
8794
if d.cfg.Route.SetDefaultRoute {
@@ -90,9 +97,9 @@ func (d *Daemon) Run() error {
9097
log.Warn().Str("component", "daemon").Err(err).Msg("Failed to set default route")
9198
}
9299

93-
// Set DNS to phone gateway
94-
log.Info().Str("component", "daemon").Str("dns", gateway).Msg("Setting system DNS to phone gateway...")
95-
if err := iface.SetDNS([]string{gateway, "8.8.8.8"}); err != nil {
100+
// Set DNS to Google (Primary) and phone gateway (Secondary)
101+
log.Info().Str("component", "daemon").Msg("Setting system DNS to 8.8.8.8 (Google)...")
102+
if err := iface.SetDNS([]string{"8.8.8.8", gateway}); err != nil {
96103
log.Warn().Str("component", "daemon").Err(err).Msg("Failed to set DNS")
97104
}
98105
}

internal/daemon/relay.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ type Relay struct {
3333
// Dynamic state set after DHCP
3434
mu sync.Mutex
3535
clientIP net.IP
36+
37+
// Traffic stats
38+
sentBytes uint64
39+
recvBytes uint64
3640
}
3741

3842
// NewRelay creates a new bidirectional relay.
@@ -91,6 +95,9 @@ func (r *Relay) Start() error {
9195

9296
// Drop non-IPv4 packets (e.g. IPv6 from macOS background traffic)
9397
if rawIP[0]>>4 != 4 {
98+
if rawIP[0]>>4 == 6 {
99+
log.Trace().Str("component", "relay").Msg("Dropping IPv6 packet (unsupported)")
100+
}
94101
continue
95102
}
96103

@@ -114,6 +121,10 @@ func (r *Relay) Start() error {
114121
errChan <- fmt.Errorf("relay: usb write error: %w", err)
115122
return
116123
}
124+
125+
r.mu.Lock()
126+
r.sentBytes += uint64(len(pkt))
127+
r.mu.Unlock()
117128
}
118129
}
119130
}()
@@ -172,7 +183,12 @@ func (r *Relay) Start() error {
172183
outBuf := make([]byte, 4+len(rawIP))
173184
binary.BigEndian.PutUint32(outBuf[0:4], 2)
174185
copy(outBuf[4:], rawIP)
175-
_, _ = r.tun.Write(outBuf)
186+
if _, err := r.tun.Write(outBuf); err != nil {
187+
log.Error().Str("component", "relay").Err(err).Msg("Failed to write to utun interface")
188+
}
189+
r.mu.Lock()
190+
r.recvBytes += uint64(len(msg))
191+
r.mu.Unlock()
176192
} else if ethType == 0x0806 { // ARP
177193
r.handleARP(ethPkt)
178194
}
@@ -206,6 +222,17 @@ func (r *Relay) Start() error {
206222
return
207223
case <-ticker.C:
208224
_, _ = r.usbOut.Write(dummyPkt)
225+
226+
// Log traffic stats every 5s
227+
r.mu.Lock()
228+
s, rv := r.sentBytes, r.recvBytes
229+
r.mu.Unlock()
230+
if s > 0 || rv > 0 {
231+
log.Info().Str("component", "relay").
232+
Str("sent", fmt.Sprintf("%.2f KB", float64(s)/1024)).
233+
Str("received", fmt.Sprintf("%.2f KB", float64(rv)/1024)).
234+
Msg("🚀 Traffic Monitor")
235+
}
209236
}
210237
}
211238
}()

internal/rndis/messages.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,18 +149,18 @@ func UnmarshalSetCmplt(data []byte) (uint32, uint32, error) { // returns Request
149149

150150
// EncapsulatePacket wraps a raw Ethernet packet with an RNDIS header.
151151
func EncapsulatePacket(packet []byte) []byte {
152-
headerLen := 44
152+
headerLen := 48 // Use 48 bytes for 8-byte alignment (44 + 4 padding)
153153
totalLen := headerLen + len(packet)
154154

155155
b := make([]byte, totalLen)
156156
binary.LittleEndian.PutUint32(b[0:4], MsgPacket)
157157
binary.LittleEndian.PutUint32(b[4:8], uint32(totalLen))
158-
binary.LittleEndian.PutUint32(b[8:12], 36) // DataOffset (relative to byte 8)
158+
binary.LittleEndian.PutUint32(b[8:12], 40) // DataOffset (relative to byte 8: 8 + 40 = 48)
159159
binary.LittleEndian.PutUint32(b[12:16], uint32(len(packet)))
160-
// Bytes 16-43 are reserved/optional fields (offset/length for OOB data, info, etc.)
160+
// Bytes 16-47 are reserved/optional fields (offset/length for OOB data, info, etc.)
161161
// We leave them zeroed.
162162

163-
copy(b[44:], packet)
163+
copy(b[48:], packet)
164164
return b
165165
}
166166

internal/tun/utun.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
type Interface interface {
99
io.ReadWriteCloser
1010
Name() string
11-
Configure(localIP, remoteIP string) error
11+
Configure(localIP, remoteIP, mtu string) error
1212
SetDefaultRoute(gateway string) error
1313
SetDNS(dnsServers []string) error
1414
}

internal/tun/utun_darwin.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ func (i *utunInterface) Close() error {
4444
defer stdin.Close()
4545
fmt.Fprintln(stdin, "open")
4646
fmt.Fprintln(stdin, "remove State:/Network/Service/droidtether/DNS")
47+
fmt.Fprintln(stdin, "remove State:/Network/Global/DNS")
4748
fmt.Fprintln(stdin, "quit")
4849
}()
4950
_ = cmd.Run()
@@ -54,10 +55,10 @@ func (i *utunInterface) Name() string {
5455
return i.name
5556
}
5657

57-
// Configure sets the IP addresses for the utun interface using the 'ifconfig' command.
58-
func (i *utunInterface) Configure(localIP, remoteIP string) error {
59-
// Formula: ifconfig <name> <local> <remote> up
60-
cmd := exec.Command("ifconfig", i.name, localIP, remoteIP, "up")
58+
// Configure sets the IP addresses and MTU for the utun interface using the 'ifconfig' command.
59+
func (i *utunInterface) Configure(localIP, remoteIP, mtu string) error {
60+
// Formula: ifconfig <name> <local> <remote> mtu <val> up
61+
cmd := exec.Command("ifconfig", i.name, localIP, remoteIP, "mtu", mtu, "up")
6162
if out, err := cmd.CombinedOutput(); err != nil {
6263
return fmt.Errorf("ifconfig failed: %w (output: %s)", err, string(out))
6364
}
@@ -104,7 +105,17 @@ func (i *utunInterface) SetDNS(dnsServers []string) error {
104105
fmt.Fprintf(stdin, " %s", s)
105106
}
106107
fmt.Fprintln(stdin)
108+
// SupplementalMatchDomains set to empty string tells macOS to use these servers for ALL lookups.
109+
fmt.Fprintln(stdin, "d.add SupplementalMatchDomains * \"\"")
110+
fmt.Fprintln(stdin, "d.add SupplementalMatchOrders * 10")
107111
fmt.Fprintln(stdin, "set State:/Network/Service/droidtether/DNS")
112+
fmt.Fprintln(stdin, "set State:/Network/Global/DNS")
113+
114+
// Force Global IPv4 state to 'Online' via our utun interface
115+
fmt.Fprintln(stdin, "d.init")
116+
fmt.Fprintf(stdin, "d.add Router %s\n", dnsServers[len(dnsServers)-1]) // use phone gateway
117+
fmt.Fprintf(stdin, "d.add Interface %s\n", i.name)
118+
fmt.Fprintln(stdin, "set State:/Network/Global/IPv4")
108119
fmt.Fprintln(stdin, "quit")
109120
}()
110121

0 commit comments

Comments
 (0)