Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions internal/daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ func (d *Daemon) Run() error {
} else {
log.Info().Str("component", "daemon").Msg("Relay session closed")
}
// Wait for the relay's USB goroutines to exit before the watcher
// releases the device handle, otherwise transfers can race with
// the close and crash inside cgo.
relay.Stop()
case <-watcher.Context().Done():
log.Info().Str("component", "daemon").Msg("Daemon shutting down — stopping active relay...")
relay.Stop()
Expand Down
49 changes: 35 additions & 14 deletions internal/daemon/relay.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ type Relay struct {
phoneMAC []byte
OnDHCP func(gateway, client string)

// wg tracks the USB-facing worker goroutines so Stop can wait for them
// before the device handle is released.
wg sync.WaitGroup

// Dynamic state set after DHCP
mu sync.Mutex
clientIP net.IP
Expand Down Expand Up @@ -62,15 +66,6 @@ func NewRelay(ctx context.Context, dev *usb.Device, tun tun.Interface, phoneMAC
func (r *Relay) Start() error {
errChan := make(chan error, 3)

// Break blocking reads when context is cancelled (Shutdown)
go func() {
<-r.ctx.Done()
// Force close device interfaces to break syscall.Read/usbIn.Read
if r.dev != nil {
_ = r.dev.Close()
}
}()

// Mac -> Phone (Tunnel -> USB)
go func() {
buf := make([]byte, 2048)
Expand Down Expand Up @@ -116,8 +111,11 @@ func (r *Relay) Start() error {

// Wrap in RNDIS and send
pkt := rndis.EncapsulatePacket(eth)
_, err = r.usbOut.Write(pkt)
_, err = r.usbOut.WriteContext(r.ctx, pkt)
if err != nil {
if r.ctx.Err() != nil {
return
}
errChan <- fmt.Errorf("relay: usb write error: %w", err)
return
}
Expand All @@ -130,16 +128,27 @@ func (r *Relay) Start() error {
}()

// Phone -> Mac (USB -> Tunnel)
r.wg.Add(1)
go func() {
defer r.wg.Done()
buf := make([]byte, 16384)
usbErrors := 0
for {
select {
case <-r.ctx.Done():
return
default:
n, err := r.usbIn.Read(buf)
n, err := r.usbIn.ReadContext(r.ctx, buf)
if err != nil {
if r.ctx.Err() != nil {
return
}
// Permanent errors mean the phone is gone; exit so the
// watcher can re-attach when it comes back.
if err == gousb.ErrorNoDevice || err == gousb.ErrorNotFound || err == gousb.TransferNoDevice {
errChan <- fmt.Errorf("relay: USB device disconnected: %w", err)
return
}
usbErrors++
if usbErrors <= 5 || usbErrors%100 == 0 {
log.Warn().Str("component", "relay").Err(err).Int("count", usbErrors).Msg("USB IN read error (retrying)")
Expand Down Expand Up @@ -203,7 +212,9 @@ func (r *Relay) Start() error {
}()

// DHCP + KeepAlive goroutine
r.wg.Add(1)
go func() {
defer r.wg.Done()
ticker := time.NewTicker(4 * time.Second)
defer ticker.Stop()

Expand All @@ -215,14 +226,14 @@ func (r *Relay) Start() error {
time.Sleep(500 * time.Millisecond)
dhcpPkt := r.buildDHCPDiscover()
log.Info().Str("component", "relay").Msg("Sending DHCP Discover...")
_, _ = r.usbOut.Write(rndis.EncapsulatePacket(dhcpPkt))
_, _ = r.usbOut.WriteContext(r.ctx, rndis.EncapsulatePacket(dhcpPkt))

for {
select {
case <-r.ctx.Done():
return
case <-ticker.C:
_, _ = r.usbOut.Write(dummyPkt)
_, _ = r.usbOut.WriteContext(r.ctx, dummyPkt)

// Log traffic stats every 5s
r.mu.Lock()
Expand Down Expand Up @@ -535,7 +546,17 @@ func (r *Relay) inspectPacket(data []byte, direction string) {
log.Trace().Str("component", "relay").Str("dir", direction).Str("src", src).Str("dst", dst).Msg("packet")
}

// Stop shuts down the relay.
// Stop shuts down the relay and waits for its USB-facing worker goroutines to
// finish, so no transfer is in flight when the device handle is released.
func (r *Relay) Stop() {
r.cancel()
done := make(chan struct{})
go func() {
r.wg.Wait()
close(done)
}()
select {
case <-done:
case <-time.After(2 * time.Second):
}
}