[client] Fix TURN-Relay switch (#3456)

- When a peer is connected with TURN and a Relay connection is established, do not force switching to Relay. Keep using TURN until disconnection.

-In the proxy preparation phase, the Bind Proxy does not set the remote conn as a fake address for Bind. When running the Work() function, the proper proxy instance updates the conn inside the Bind.
This commit is contained in:
Zoltan Papp 2025-03-07 12:00:25 +01:00 committed by GitHub
parent 062d1ec76f
commit 4b76d93cec
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 45 additions and 61 deletions

View File

@ -5,7 +5,6 @@ import (
"net" "net"
"net/netip" "net/netip"
"runtime" "runtime"
"strings"
"sync" "sync"
"github.com/pion/stun/v2" "github.com/pion/stun/v2"
@ -108,35 +107,17 @@ func (s *ICEBind) GetICEMux() (*UniversalUDPMuxDefault, error) {
return s.udpMux, nil return s.udpMux, nil
} }
func (b *ICEBind) SetEndpoint(peerAddress *net.UDPAddr, conn net.Conn) (*net.UDPAddr, error) { func (b *ICEBind) SetEndpoint(fakeIP netip.Addr, conn net.Conn) {
fakeUDPAddr, err := fakeAddress(peerAddress)
if err != nil {
return nil, err
}
// force IPv4
fakeAddr, ok := netip.AddrFromSlice(fakeUDPAddr.IP.To4())
if !ok {
return nil, fmt.Errorf("failed to convert IP to netip.Addr")
}
b.endpointsMu.Lock() b.endpointsMu.Lock()
b.endpoints[fakeAddr] = conn b.endpoints[fakeIP] = conn
b.endpointsMu.Unlock() b.endpointsMu.Unlock()
return fakeUDPAddr, nil
} }
func (b *ICEBind) RemoveEndpoint(fakeUDPAddr *net.UDPAddr) { func (b *ICEBind) RemoveEndpoint(fakeIP netip.Addr) {
fakeAddr, ok := netip.AddrFromSlice(fakeUDPAddr.IP.To4())
if !ok {
log.Warnf("failed to convert IP to netip.Addr")
return
}
b.endpointsMu.Lock() b.endpointsMu.Lock()
defer b.endpointsMu.Unlock() defer b.endpointsMu.Unlock()
delete(b.endpoints, fakeAddr)
delete(b.endpoints, fakeIP)
} }
func (b *ICEBind) Send(bufs [][]byte, ep wgConn.Endpoint) error { func (b *ICEBind) Send(bufs [][]byte, ep wgConn.Endpoint) error {
@ -275,21 +256,6 @@ func (c *ICEBind) receiveRelayed(buffs [][]byte, sizes []int, eps []wgConn.Endpo
} }
} }
// fakeAddress returns a fake address that is used to as an identifier for the peer.
// The fake address is in the format of 127.1.x.x where x.x is the last two octets of the peer address.
func fakeAddress(peerAddress *net.UDPAddr) (*net.UDPAddr, error) {
octets := strings.Split(peerAddress.IP.String(), ".")
if len(octets) != 4 {
return nil, fmt.Errorf("invalid IP format")
}
newAddr := &net.UDPAddr{
IP: net.ParseIP(fmt.Sprintf("127.1.%s.%s", octets[2], octets[3])),
Port: peerAddress.Port,
}
return newAddr, nil
}
func getMessages(msgsPool *sync.Pool) *[]ipv6.Message { func getMessages(msgsPool *sync.Pool) *[]ipv6.Message {
return msgsPool.Get().(*[]ipv6.Message) return msgsPool.Get().(*[]ipv6.Message)
} }

View File

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"net" "net"
"net/netip" "net/netip"
"strings"
"sync" "sync"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@ -16,8 +17,8 @@ import (
type ProxyBind struct { type ProxyBind struct {
Bind *bind.ICEBind Bind *bind.ICEBind
wgAddr *net.UDPAddr fakeNetIP *netip.AddrPort
wgEndpoint *bind.Endpoint wgBindEndpoint *bind.Endpoint
remoteConn net.Conn remoteConn net.Conn
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
@ -33,20 +34,24 @@ type ProxyBind struct {
// endpoint is the NetBird address of the remote peer. The SetEndpoint return with the address what will be used in the // endpoint is the NetBird address of the remote peer. The SetEndpoint return with the address what will be used in the
// WireGuard configuration. // WireGuard configuration.
func (p *ProxyBind) AddTurnConn(ctx context.Context, nbAddr *net.UDPAddr, remoteConn net.Conn) error { func (p *ProxyBind) AddTurnConn(ctx context.Context, nbAddr *net.UDPAddr, remoteConn net.Conn) error {
addr, err := p.Bind.SetEndpoint(nbAddr, remoteConn) fakeNetIP, err := fakeAddress(nbAddr)
if err != nil { if err != nil {
return err return err
} }
p.wgAddr = addr p.fakeNetIP = fakeNetIP
p.wgEndpoint = addrToEndpoint(addr) p.wgBindEndpoint = &bind.Endpoint{AddrPort: *fakeNetIP}
p.remoteConn = remoteConn p.remoteConn = remoteConn
p.ctx, p.cancel = context.WithCancel(ctx) p.ctx, p.cancel = context.WithCancel(ctx)
return err return nil
} }
func (p *ProxyBind) EndpointAddr() *net.UDPAddr { func (p *ProxyBind) EndpointAddr() *net.UDPAddr {
return p.wgAddr return &net.UDPAddr{
IP: p.fakeNetIP.Addr().AsSlice(),
Port: int(p.fakeNetIP.Port()),
Zone: p.fakeNetIP.Addr().Zone(),
}
} }
func (p *ProxyBind) Work() { func (p *ProxyBind) Work() {
@ -54,6 +59,8 @@ func (p *ProxyBind) Work() {
return return
} }
p.Bind.SetEndpoint(p.fakeNetIP.Addr(), p.remoteConn)
p.pausedMu.Lock() p.pausedMu.Lock()
p.paused = false p.paused = false
p.pausedMu.Unlock() p.pausedMu.Unlock()
@ -93,7 +100,7 @@ func (p *ProxyBind) close() error {
p.cancel() p.cancel()
p.Bind.RemoveEndpoint(p.wgAddr) p.Bind.RemoveEndpoint(p.fakeNetIP.Addr())
if rErr := p.remoteConn.Close(); rErr != nil && !errors.Is(rErr, net.ErrClosed) { if rErr := p.remoteConn.Close(); rErr != nil && !errors.Is(rErr, net.ErrClosed) {
return rErr return rErr
@ -126,7 +133,7 @@ func (p *ProxyBind) proxyToLocal(ctx context.Context) {
} }
msg := bind.RecvMessage{ msg := bind.RecvMessage{
Endpoint: p.wgEndpoint, Endpoint: p.wgBindEndpoint,
Buffer: buf[:n], Buffer: buf[:n],
} }
p.Bind.RecvChan <- msg p.Bind.RecvChan <- msg
@ -134,8 +141,19 @@ func (p *ProxyBind) proxyToLocal(ctx context.Context) {
} }
} }
func addrToEndpoint(addr *net.UDPAddr) *bind.Endpoint { // fakeAddress returns a fake address that is used to as an identifier for the peer.
ip, _ := netip.AddrFromSlice(addr.IP.To4()) // The fake address is in the format of 127.1.x.x where x.x is the last two octets of the peer address.
addrPort := netip.AddrPortFrom(ip, uint16(addr.Port)) func fakeAddress(peerAddress *net.UDPAddr) (*netip.AddrPort, error) {
return &bind.Endpoint{AddrPort: addrPort} octets := strings.Split(peerAddress.IP.String(), ".")
if len(octets) != 4 {
return nil, fmt.Errorf("invalid IP format")
}
fakeIP, err := netip.ParseAddr(fmt.Sprintf("127.1.%s.%s", octets[2], octets[3]))
if err != nil {
return nil, fmt.Errorf("failed to parse new IP: %w", err)
}
netipAddr := netip.AddrPortFrom(fakeIP, uint16(peerAddress.Port))
return &netipAddr, nil
} }

View File

@ -442,8 +442,8 @@ func (conn *Conn) onRelayConnectionIsReady(rci RelayConnInfo) {
conn.log.Infof("created new wgProxy for relay connection: %s", wgProxy.EndpointAddr().String()) conn.log.Infof("created new wgProxy for relay connection: %s", wgProxy.EndpointAddr().String())
if conn.iceP2PIsActive() { if conn.isICEActive() {
conn.log.Debugf("do not switch to relay because current priority is: %s", conn.currentConnPriority.String()) conn.log.Infof("do not switch to relay because current priority is: %s", conn.currentConnPriority.String())
conn.setRelayedProxy(wgProxy) conn.setRelayedProxy(wgProxy)
conn.statusRelay.Set(StatusConnected) conn.statusRelay.Set(StatusConnected)
conn.updateRelayStatus(rci.relayedConn.RemoteAddr().String(), rci.rosenpassPubKey) conn.updateRelayStatus(rci.relayedConn.RemoteAddr().String(), rci.rosenpassPubKey)
@ -711,8 +711,8 @@ func (conn *Conn) isReadyToUpgrade() bool {
return conn.wgProxyRelay != nil && conn.currentConnPriority != connPriorityRelay return conn.wgProxyRelay != nil && conn.currentConnPriority != connPriorityRelay
} }
func (conn *Conn) iceP2PIsActive() bool { func (conn *Conn) isICEActive() bool {
return conn.currentConnPriority == connPriorityICEP2P && conn.statusICE.Get() == StatusConnected return (conn.currentConnPriority == connPriorityICEP2P || conn.currentConnPriority == connPriorityICETurn) && conn.statusICE.Get() == StatusConnected
} }
func (conn *Conn) removeWgPeer() error { func (conn *Conn) removeWgPeer() error {