Merge branch 'rebind' into dev

Former-commit-id: 2139aeaa85
This commit is contained in:
Owen
2026-01-18 11:37:43 -08:00
2 changed files with 107 additions and 0 deletions

View File

@@ -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",
})
}

View File

@@ -273,6 +273,11 @@ func (o *Olm) registerAPICallbacks() {
}
return nil
},
// onRebind
func() error {
logger.Info("Processing rebind request via API")
return o.RebindSocket()
},
)
}
@@ -785,6 +790,74 @@ 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")
}
// 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 (released port: %d)", currentPort)
// Create a new UDP socket
var newConn *net.UDPConn
var newPort uint16
// 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,
}
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")