From a06436eeab5c26be568a4fe9de13a74331d753b6 Mon Sep 17 00:00:00 2001 From: Owen Date: Sat, 17 Jan 2026 17:05:29 -0800 Subject: [PATCH 1/2] Add rebind endpoints for the shared socket Former-commit-id: 6fd0984b13954402b4598bf396710a34e2337128 --- api/api.go | 34 ++++++++++++++++++++++++++ olm/olm.go | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) diff --git a/api/api.go b/api/api.go index 442162e..e18bee7 100644 --- a/api/api.go +++ b/api/api.go @@ -78,6 +78,7 @@ type API struct { onMetadataChange func(MetadataChangeRequest) error onDisconnect func() error onExit func() error + onRebind func() error statusMu sync.RWMutex peerStatuses map[int]*PeerStatus @@ -126,11 +127,13 @@ func (s *API) SetHandlers( onMetadataChange func(MetadataChangeRequest) error, onDisconnect func() error, onExit func() error, + onRebind func() error, ) { s.onConnect = onConnect s.onSwitchOrg = onSwitchOrg s.onDisconnect = onDisconnect s.onExit = onExit + s.onRebind = onRebind } // Start starts the HTTP server @@ -147,6 +150,7 @@ func (s *API) Start() error { mux.HandleFunc("/disconnect", s.handleDisconnect) mux.HandleFunc("/exit", s.handleExit) mux.HandleFunc("/health", s.handleHealth) + mux.HandleFunc("/rebind", s.handleRebind) s.server = &http.Server{ Handler: mux, @@ -560,3 +564,33 @@ func (s *API) GetStatus() StatusResponse { NetworkSettings: network.GetSettings(), } } + +// handleRebind handles the /rebind endpoint +// This triggers a socket rebind, which is necessary when network connectivity changes +// (e.g., WiFi to cellular transition on macOS/iOS) and the old socket becomes stale. +func (s *API) handleRebind(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + logger.Info("Received rebind request via API") + + // Call the rebind handler if set + if s.onRebind != nil { + if err := s.onRebind(); err != nil { + http.Error(w, fmt.Sprintf("Rebind failed: %v", err), http.StatusInternalServerError) + return + } + } else { + http.Error(w, "Rebind handler not configured", http.StatusNotImplemented) + return + } + + // Return a success response + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(map[string]string{ + "status": "socket rebound successfully", + }) +} diff --git a/olm/olm.go b/olm/olm.go index f6e1980..26fc0e4 100644 --- a/olm/olm.go +++ b/olm/olm.go @@ -273,6 +273,11 @@ func (o *Olm) registerAPICallbacks() { } return nil }, + // onRebind + func() error { + logger.Info("Processing rebind request via API") + return o.RebindSocket() + }, ) } @@ -783,6 +788,72 @@ func (o *Olm) SetPowerMode(mode string) error { return nil } +// RebindSocket recreates the UDP socket when network connectivity changes. +// This is necessary on macOS/iOS when transitioning between WiFi and cellular, +// as the old socket becomes stale and can no longer route packets. +// Call this method when detecting a network path change. +func (o *Olm) RebindSocket() error { + if o.sharedBind == nil { + return fmt.Errorf("shared bind is not initialized") + } + + // Get the current port so we can try to reuse it + currentPort := o.sharedBind.GetPort() + + logger.Info("Rebinding UDP socket (current port: %d)", currentPort) + + // Create a new UDP socket + var newConn *net.UDPConn + var newPort uint16 + var err error + + // First try to bind to the same port + localAddr := &net.UDPAddr{ + Port: int(currentPort), + IP: net.IPv4zero, + } + + newConn, err = net.ListenUDP("udp4", localAddr) + if err != nil { + // If we can't reuse the port, find a new one + logger.Warn("Could not rebind to port %d, finding new port: %v", currentPort, err) + newPort, err = util.FindAvailableUDPPort(49152, 65535) + if err != nil { + return fmt.Errorf("failed to find available UDP port: %w", err) + } + + localAddr = &net.UDPAddr{ + Port: int(newPort), + IP: net.IPv4zero, + } + + // Use udp4 explicitly to avoid IPv6 dual-stack issues + newConn, err = net.ListenUDP("udp4", localAddr) + if err != nil { + return fmt.Errorf("failed to create new UDP socket: %w", err) + } + } else { + newPort = currentPort + } + + // Rebind the shared bind with the new connection + if err := o.sharedBind.Rebind(newConn); err != nil { + newConn.Close() + return fmt.Errorf("failed to rebind shared bind: %w", err) + } + + logger.Info("Successfully rebound UDP socket on port %d", newPort) + + // Trigger a hole punch to re-establish NAT mappings with the new socket + if o.holePunchManager != nil { + o.holePunchManager.TriggerHolePunch() + o.holePunchManager.ResetServerHolepunchInterval() + logger.Info("Triggered hole punch after socket rebind") + } + + return nil +} + func (o *Olm) AddDevice(fd uint32) error { if o.middleDev == nil { return fmt.Errorf("middle device is not initialized") From 4e4d1a39f6b980c7785cb8ed190461299484348c Mon Sep 17 00:00:00 2001 From: Owen Date: Sat, 17 Jan 2026 17:35:00 -0800 Subject: [PATCH 2/2] Try to close the socket first Former-commit-id: ed4775bd263085442907fbc3ff97db2a79c9769f --- olm/olm.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/olm/olm.go b/olm/olm.go index 26fc0e4..286db25 100644 --- a/olm/olm.go +++ b/olm/olm.go @@ -797,17 +797,19 @@ func (o *Olm) RebindSocket() error { return fmt.Errorf("shared bind is not initialized") } - // Get the current port so we can try to reuse it - currentPort := o.sharedBind.GetPort() + // Close the old socket first to release the port, then try to rebind to the same port + currentPort, err := o.sharedBind.CloseSocket() + if err != nil { + return fmt.Errorf("failed to close old socket: %w", err) + } - logger.Info("Rebinding UDP socket (current port: %d)", currentPort) + logger.Info("Rebinding UDP socket (released port: %d)", currentPort) // Create a new UDP socket var newConn *net.UDPConn var newPort uint16 - var err error - // First try to bind to the same port + // First try to bind to the same port (now available since we closed the old socket) localAddr := &net.UDPAddr{ Port: int(currentPort), IP: net.IPv4zero,