@ -18,6 +18,7 @@ import (
"strings"
"strings"
"sync"
"sync"
"sync/atomic"
"sync/atomic"
"syscall"
"time"
"time"
"github.com/tailscale/wireguard-go/conn"
"github.com/tailscale/wireguard-go/conn"
@ -305,6 +306,10 @@ type Conn struct {
// getPeerByKey optionally specifies a function to look up a peer's
// getPeerByKey optionally specifies a function to look up a peer's
// wireguard state by its public key. If nil, it's not used.
// wireguard state by its public key. If nil, it's not used.
getPeerByKey func ( key . NodePublic ) ( _ wgint . Peer , ok bool )
getPeerByKey func ( key . NodePublic ) ( _ wgint . Peer , ok bool )
// lastEPERMRebind tracks the last time a rebind was performed
// after experiencing a syscall.EPERM.
lastEPERMRebind syncs . AtomicValue [ time . Time ]
}
}
// SetDebugLoggingEnabled controls whether spammy debug logging is enabled.
// SetDebugLoggingEnabled controls whether spammy debug logging is enabled.
@ -1047,6 +1052,8 @@ func (c *Conn) sendUDPBatch(addr netip.AddrPort, buffs [][]byte) (sent bool, err
if errors . As ( err , & errGSO ) {
if errors . As ( err , & errGSO ) {
c . logf ( "magicsock: %s" , errGSO . Error ( ) )
c . logf ( "magicsock: %s" , errGSO . Error ( ) )
err = errGSO . RetryErr
err = errGSO . RetryErr
} else {
_ = c . maybeRebindOnError ( runtime . GOOS , err )
}
}
}
}
return err == nil , err
return err == nil , err
@ -1061,6 +1068,7 @@ func (c *Conn) sendUDP(ipp netip.AddrPort, b []byte) (sent bool, err error) {
sent , err = c . sendUDPStd ( ipp , b )
sent , err = c . sendUDPStd ( ipp , b )
if err != nil {
if err != nil {
metricSendUDPError . Add ( 1 )
metricSendUDPError . Add ( 1 )
_ = c . maybeRebindOnError ( runtime . GOOS , err )
} else {
} else {
if sent {
if sent {
metricSendUDP . Add ( 1 )
metricSendUDP . Add ( 1 )
@ -1069,6 +1077,34 @@ func (c *Conn) sendUDP(ipp netip.AddrPort, b []byte) (sent bool, err error) {
return
return
}
}
// maybeRebindOnError performs a rebind and restun if the error is defined and
// any conditionals are met.
func ( c * Conn ) maybeRebindOnError ( os string , err error ) bool {
switch err {
case syscall . EPERM :
why := "operation-not-permitted-rebind"
switch os {
// We currently will only rebind and restun on a syscall.EPERM if it is experienced
// on a client running darwin.
// TODO(charlotte, raggi): expand os options if required.
case "darwin" :
// TODO(charlotte): implement a backoff, so we don't end up in a rebind loop for persistent
// EPERMs.
if c . lastEPERMRebind . Load ( ) . Before ( time . Now ( ) . Add ( - 5 * time . Second ) ) {
c . logf ( "magicsock: performing %q" , why )
c . lastEPERMRebind . Store ( time . Now ( ) )
c . Rebind ( )
go c . ReSTUN ( why )
return true
}
default :
c . logf ( "magicsock: not performing %q" , why )
return false
}
}
return false
}
// sendUDP sends UDP packet b to addr.
// sendUDP sends UDP packet b to addr.
// See sendAddr's docs on the return value meanings.
// See sendAddr's docs on the return value meanings.
func ( c * Conn ) sendUDPStd ( addr netip . AddrPort , b [ ] byte ) ( sent bool , err error ) {
func ( c * Conn ) sendUDPStd ( addr netip . AddrPort , b [ ] byte ) ( sent bool , err error ) {