mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-22 10:16:38 +00:00
[client] Fix/v4 stun routing (#4430)
Deduplicate STUN package sending. Originally, because every peer shared the same UDP address, the library could not distinguish which STUN message was associated with which candidate. As a result, the Pion library responded from all candidates for every STUN message.
This commit is contained in:
@@ -15,6 +15,7 @@ import (
|
|||||||
"golang.org/x/net/ipv6"
|
"golang.org/x/net/ipv6"
|
||||||
wgConn "golang.zx2c4.com/wireguard/conn"
|
wgConn "golang.zx2c4.com/wireguard/conn"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/iface/udpmux"
|
||||||
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
||||||
nbnet "github.com/netbirdio/netbird/util/net"
|
nbnet "github.com/netbirdio/netbird/util/net"
|
||||||
)
|
)
|
||||||
@@ -44,7 +45,7 @@ type ICEBind struct {
|
|||||||
RecvChan chan RecvMessage
|
RecvChan chan RecvMessage
|
||||||
|
|
||||||
transportNet transport.Net
|
transportNet transport.Net
|
||||||
filterFn FilterFn
|
filterFn udpmux.FilterFn
|
||||||
endpoints map[netip.Addr]net.Conn
|
endpoints map[netip.Addr]net.Conn
|
||||||
endpointsMu sync.Mutex
|
endpointsMu sync.Mutex
|
||||||
// every time when Close() is called (i.e. BindUpdate()) we need to close exit from the receiveRelayed and create a
|
// every time when Close() is called (i.e. BindUpdate()) we need to close exit from the receiveRelayed and create a
|
||||||
@@ -54,13 +55,13 @@ type ICEBind struct {
|
|||||||
closed bool
|
closed bool
|
||||||
|
|
||||||
muUDPMux sync.Mutex
|
muUDPMux sync.Mutex
|
||||||
udpMux *UniversalUDPMuxDefault
|
udpMux *udpmux.UniversalUDPMuxDefault
|
||||||
address wgaddr.Address
|
address wgaddr.Address
|
||||||
mtu uint16
|
mtu uint16
|
||||||
activityRecorder *ActivityRecorder
|
activityRecorder *ActivityRecorder
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewICEBind(transportNet transport.Net, filterFn FilterFn, address wgaddr.Address, mtu uint16) *ICEBind {
|
func NewICEBind(transportNet transport.Net, filterFn udpmux.FilterFn, address wgaddr.Address, mtu uint16) *ICEBind {
|
||||||
b, _ := wgConn.NewStdNetBind().(*wgConn.StdNetBind)
|
b, _ := wgConn.NewStdNetBind().(*wgConn.StdNetBind)
|
||||||
ib := &ICEBind{
|
ib := &ICEBind{
|
||||||
StdNetBind: b,
|
StdNetBind: b,
|
||||||
@@ -115,7 +116,7 @@ func (s *ICEBind) ActivityRecorder() *ActivityRecorder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetICEMux returns the ICE UDPMux that was created and used by ICEBind
|
// GetICEMux returns the ICE UDPMux that was created and used by ICEBind
|
||||||
func (s *ICEBind) GetICEMux() (*UniversalUDPMuxDefault, error) {
|
func (s *ICEBind) GetICEMux() (*udpmux.UniversalUDPMuxDefault, error) {
|
||||||
s.muUDPMux.Lock()
|
s.muUDPMux.Lock()
|
||||||
defer s.muUDPMux.Unlock()
|
defer s.muUDPMux.Unlock()
|
||||||
if s.udpMux == nil {
|
if s.udpMux == nil {
|
||||||
@@ -158,8 +159,8 @@ func (s *ICEBind) createIPv4ReceiverFn(pc *ipv4.PacketConn, conn *net.UDPConn, r
|
|||||||
s.muUDPMux.Lock()
|
s.muUDPMux.Lock()
|
||||||
defer s.muUDPMux.Unlock()
|
defer s.muUDPMux.Unlock()
|
||||||
|
|
||||||
s.udpMux = NewUniversalUDPMuxDefault(
|
s.udpMux = udpmux.NewUniversalUDPMuxDefault(
|
||||||
UniversalUDPMuxParams{
|
udpmux.UniversalUDPMuxParams{
|
||||||
UDPConn: nbnet.WrapPacketConn(conn),
|
UDPConn: nbnet.WrapPacketConn(conn),
|
||||||
Net: s.transportNet,
|
Net: s.transportNet,
|
||||||
FilterFn: s.filterFn,
|
FilterFn: s.filterFn,
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
//go:build ios
|
|
||||||
|
|
||||||
package bind
|
|
||||||
|
|
||||||
func (m *UDPMuxDefault) notifyAddressRemoval(addr string) {
|
|
||||||
// iOS doesn't support nbnet hooks, so this is a no-op
|
|
||||||
}
|
|
||||||
@@ -7,14 +7,14 @@ import (
|
|||||||
|
|
||||||
wgdevice "golang.zx2c4.com/wireguard/device"
|
wgdevice "golang.zx2c4.com/wireguard/device"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/iface/bind"
|
|
||||||
"github.com/netbirdio/netbird/client/iface/device"
|
"github.com/netbirdio/netbird/client/iface/device"
|
||||||
|
"github.com/netbirdio/netbird/client/iface/udpmux"
|
||||||
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WGTunDevice interface {
|
type WGTunDevice interface {
|
||||||
Create() (device.WGConfigurer, error)
|
Create() (device.WGConfigurer, error)
|
||||||
Up() (*bind.UniversalUDPMuxDefault, error)
|
Up() (*udpmux.UniversalUDPMuxDefault, error)
|
||||||
UpdateAddr(address wgaddr.Address) error
|
UpdateAddr(address wgaddr.Address) error
|
||||||
WgAddress() wgaddr.Address
|
WgAddress() wgaddr.Address
|
||||||
MTU() uint16
|
MTU() uint16
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
|
|
||||||
"github.com/netbirdio/netbird/client/iface/bind"
|
"github.com/netbirdio/netbird/client/iface/bind"
|
||||||
"github.com/netbirdio/netbird/client/iface/configurer"
|
"github.com/netbirdio/netbird/client/iface/configurer"
|
||||||
|
"github.com/netbirdio/netbird/client/iface/udpmux"
|
||||||
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -29,7 +30,7 @@ type WGTunDevice struct {
|
|||||||
name string
|
name string
|
||||||
device *device.Device
|
device *device.Device
|
||||||
filteredDevice *FilteredDevice
|
filteredDevice *FilteredDevice
|
||||||
udpMux *bind.UniversalUDPMuxDefault
|
udpMux *udpmux.UniversalUDPMuxDefault
|
||||||
configurer WGConfigurer
|
configurer WGConfigurer
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +89,7 @@ func (t *WGTunDevice) Create(routes []string, dns string, searchDomains []string
|
|||||||
}
|
}
|
||||||
return t.configurer, nil
|
return t.configurer, nil
|
||||||
}
|
}
|
||||||
func (t *WGTunDevice) Up() (*bind.UniversalUDPMuxDefault, error) {
|
func (t *WGTunDevice) Up() (*udpmux.UniversalUDPMuxDefault, error) {
|
||||||
err := t.device.Up()
|
err := t.device.Up()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
|
|
||||||
"github.com/netbirdio/netbird/client/iface/bind"
|
"github.com/netbirdio/netbird/client/iface/bind"
|
||||||
"github.com/netbirdio/netbird/client/iface/configurer"
|
"github.com/netbirdio/netbird/client/iface/configurer"
|
||||||
|
"github.com/netbirdio/netbird/client/iface/udpmux"
|
||||||
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,7 +27,7 @@ type TunDevice struct {
|
|||||||
|
|
||||||
device *device.Device
|
device *device.Device
|
||||||
filteredDevice *FilteredDevice
|
filteredDevice *FilteredDevice
|
||||||
udpMux *bind.UniversalUDPMuxDefault
|
udpMux *udpmux.UniversalUDPMuxDefault
|
||||||
configurer WGConfigurer
|
configurer WGConfigurer
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +72,7 @@ func (t *TunDevice) Create() (WGConfigurer, error) {
|
|||||||
return t.configurer, nil
|
return t.configurer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TunDevice) Up() (*bind.UniversalUDPMuxDefault, error) {
|
func (t *TunDevice) Up() (*udpmux.UniversalUDPMuxDefault, error) {
|
||||||
err := t.device.Up()
|
err := t.device.Up()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
|
|
||||||
"github.com/netbirdio/netbird/client/iface/bind"
|
"github.com/netbirdio/netbird/client/iface/bind"
|
||||||
"github.com/netbirdio/netbird/client/iface/configurer"
|
"github.com/netbirdio/netbird/client/iface/configurer"
|
||||||
|
"github.com/netbirdio/netbird/client/iface/udpmux"
|
||||||
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,7 +29,7 @@ type TunDevice struct {
|
|||||||
|
|
||||||
device *device.Device
|
device *device.Device
|
||||||
filteredDevice *FilteredDevice
|
filteredDevice *FilteredDevice
|
||||||
udpMux *bind.UniversalUDPMuxDefault
|
udpMux *udpmux.UniversalUDPMuxDefault
|
||||||
configurer WGConfigurer
|
configurer WGConfigurer
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +84,7 @@ func (t *TunDevice) Create() (WGConfigurer, error) {
|
|||||||
return t.configurer, nil
|
return t.configurer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TunDevice) Up() (*bind.UniversalUDPMuxDefault, error) {
|
func (t *TunDevice) Up() (*udpmux.UniversalUDPMuxDefault, error) {
|
||||||
err := t.device.Up()
|
err := t.device.Up()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import (
|
|||||||
"golang.zx2c4.com/wireguard/device"
|
"golang.zx2c4.com/wireguard/device"
|
||||||
"golang.zx2c4.com/wireguard/tun/netstack"
|
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/iface/bind"
|
|
||||||
"github.com/netbirdio/netbird/client/iface/configurer"
|
"github.com/netbirdio/netbird/client/iface/configurer"
|
||||||
|
"github.com/netbirdio/netbird/client/iface/udpmux"
|
||||||
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
||||||
"github.com/netbirdio/netbird/sharedsock"
|
"github.com/netbirdio/netbird/sharedsock"
|
||||||
nbnet "github.com/netbirdio/netbird/util/net"
|
nbnet "github.com/netbirdio/netbird/util/net"
|
||||||
@@ -31,9 +31,9 @@ type TunKernelDevice struct {
|
|||||||
|
|
||||||
link *wgLink
|
link *wgLink
|
||||||
udpMuxConn net.PacketConn
|
udpMuxConn net.PacketConn
|
||||||
udpMux *bind.UniversalUDPMuxDefault
|
udpMux *udpmux.UniversalUDPMuxDefault
|
||||||
|
|
||||||
filterFn bind.FilterFn
|
filterFn udpmux.FilterFn
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewKernelDevice(name string, address wgaddr.Address, wgPort int, key string, mtu uint16, transportNet transport.Net) *TunKernelDevice {
|
func NewKernelDevice(name string, address wgaddr.Address, wgPort int, key string, mtu uint16, transportNet transport.Net) *TunKernelDevice {
|
||||||
@@ -79,7 +79,7 @@ func (t *TunKernelDevice) Create() (WGConfigurer, error) {
|
|||||||
return configurer, nil
|
return configurer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TunKernelDevice) Up() (*bind.UniversalUDPMuxDefault, error) {
|
func (t *TunKernelDevice) Up() (*udpmux.UniversalUDPMuxDefault, error) {
|
||||||
if t.udpMux != nil {
|
if t.udpMux != nil {
|
||||||
return t.udpMux, nil
|
return t.udpMux, nil
|
||||||
}
|
}
|
||||||
@@ -106,14 +106,14 @@ func (t *TunKernelDevice) Up() (*bind.UniversalUDPMuxDefault, error) {
|
|||||||
udpConn = nbnet.WrapPacketConn(rawSock)
|
udpConn = nbnet.WrapPacketConn(rawSock)
|
||||||
}
|
}
|
||||||
|
|
||||||
bindParams := bind.UniversalUDPMuxParams{
|
bindParams := udpmux.UniversalUDPMuxParams{
|
||||||
UDPConn: udpConn,
|
UDPConn: udpConn,
|
||||||
Net: t.transportNet,
|
Net: t.transportNet,
|
||||||
FilterFn: t.filterFn,
|
FilterFn: t.filterFn,
|
||||||
WGAddress: t.address,
|
WGAddress: t.address,
|
||||||
MTU: t.mtu,
|
MTU: t.mtu,
|
||||||
}
|
}
|
||||||
mux := bind.NewUniversalUDPMuxDefault(bindParams)
|
mux := udpmux.NewUniversalUDPMuxDefault(bindParams)
|
||||||
go mux.ReadFromConn(t.ctx)
|
go mux.ReadFromConn(t.ctx)
|
||||||
t.udpMuxConn = rawSock
|
t.udpMuxConn = rawSock
|
||||||
t.udpMux = mux
|
t.udpMux = mux
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/iface/bind"
|
"github.com/netbirdio/netbird/client/iface/bind"
|
||||||
"github.com/netbirdio/netbird/client/iface/configurer"
|
"github.com/netbirdio/netbird/client/iface/configurer"
|
||||||
nbnetstack "github.com/netbirdio/netbird/client/iface/netstack"
|
nbnetstack "github.com/netbirdio/netbird/client/iface/netstack"
|
||||||
|
"github.com/netbirdio/netbird/client/iface/udpmux"
|
||||||
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
||||||
nbnet "github.com/netbirdio/netbird/util/net"
|
nbnet "github.com/netbirdio/netbird/util/net"
|
||||||
)
|
)
|
||||||
@@ -26,7 +27,7 @@ type TunNetstackDevice struct {
|
|||||||
device *device.Device
|
device *device.Device
|
||||||
filteredDevice *FilteredDevice
|
filteredDevice *FilteredDevice
|
||||||
nsTun *nbnetstack.NetStackTun
|
nsTun *nbnetstack.NetStackTun
|
||||||
udpMux *bind.UniversalUDPMuxDefault
|
udpMux *udpmux.UniversalUDPMuxDefault
|
||||||
configurer WGConfigurer
|
configurer WGConfigurer
|
||||||
|
|
||||||
net *netstack.Net
|
net *netstack.Net
|
||||||
@@ -80,7 +81,7 @@ func (t *TunNetstackDevice) create() (WGConfigurer, error) {
|
|||||||
return t.configurer, nil
|
return t.configurer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TunNetstackDevice) Up() (*bind.UniversalUDPMuxDefault, error) {
|
func (t *TunNetstackDevice) Up() (*udpmux.UniversalUDPMuxDefault, error) {
|
||||||
if t.device == nil {
|
if t.device == nil {
|
||||||
return nil, fmt.Errorf("device is not ready yet")
|
return nil, fmt.Errorf("device is not ready yet")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/netbirdio/netbird/client/iface/bind"
|
"github.com/netbirdio/netbird/client/iface/bind"
|
||||||
"github.com/netbirdio/netbird/client/iface/configurer"
|
"github.com/netbirdio/netbird/client/iface/configurer"
|
||||||
|
"github.com/netbirdio/netbird/client/iface/udpmux"
|
||||||
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -25,7 +26,7 @@ type USPDevice struct {
|
|||||||
|
|
||||||
device *device.Device
|
device *device.Device
|
||||||
filteredDevice *FilteredDevice
|
filteredDevice *FilteredDevice
|
||||||
udpMux *bind.UniversalUDPMuxDefault
|
udpMux *udpmux.UniversalUDPMuxDefault
|
||||||
configurer WGConfigurer
|
configurer WGConfigurer
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +75,7 @@ func (t *USPDevice) Create() (WGConfigurer, error) {
|
|||||||
return t.configurer, nil
|
return t.configurer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *USPDevice) Up() (*bind.UniversalUDPMuxDefault, error) {
|
func (t *USPDevice) Up() (*udpmux.UniversalUDPMuxDefault, error) {
|
||||||
if t.device == nil {
|
if t.device == nil {
|
||||||
return nil, fmt.Errorf("device is not ready yet")
|
return nil, fmt.Errorf("device is not ready yet")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
|
|
||||||
"github.com/netbirdio/netbird/client/iface/bind"
|
"github.com/netbirdio/netbird/client/iface/bind"
|
||||||
"github.com/netbirdio/netbird/client/iface/configurer"
|
"github.com/netbirdio/netbird/client/iface/configurer"
|
||||||
|
"github.com/netbirdio/netbird/client/iface/udpmux"
|
||||||
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -29,7 +30,7 @@ type TunDevice struct {
|
|||||||
device *device.Device
|
device *device.Device
|
||||||
nativeTunDevice *tun.NativeTun
|
nativeTunDevice *tun.NativeTun
|
||||||
filteredDevice *FilteredDevice
|
filteredDevice *FilteredDevice
|
||||||
udpMux *bind.UniversalUDPMuxDefault
|
udpMux *udpmux.UniversalUDPMuxDefault
|
||||||
configurer WGConfigurer
|
configurer WGConfigurer
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +105,7 @@ func (t *TunDevice) Create() (WGConfigurer, error) {
|
|||||||
return t.configurer, nil
|
return t.configurer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TunDevice) Up() (*bind.UniversalUDPMuxDefault, error) {
|
func (t *TunDevice) Up() (*udpmux.UniversalUDPMuxDefault, error) {
|
||||||
err := t.device.Up()
|
err := t.device.Up()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ import (
|
|||||||
|
|
||||||
"golang.zx2c4.com/wireguard/tun/netstack"
|
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/iface/bind"
|
|
||||||
"github.com/netbirdio/netbird/client/iface/device"
|
"github.com/netbirdio/netbird/client/iface/device"
|
||||||
|
"github.com/netbirdio/netbird/client/iface/udpmux"
|
||||||
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WGTunDevice interface {
|
type WGTunDevice interface {
|
||||||
Create(routes []string, dns string, searchDomains []string) (device.WGConfigurer, error)
|
Create(routes []string, dns string, searchDomains []string) (device.WGConfigurer, error)
|
||||||
Up() (*bind.UniversalUDPMuxDefault, error)
|
Up() (*udpmux.UniversalUDPMuxDefault, error)
|
||||||
UpdateAddr(address wgaddr.Address) error
|
UpdateAddr(address wgaddr.Address) error
|
||||||
WgAddress() wgaddr.Address
|
WgAddress() wgaddr.Address
|
||||||
MTU() uint16
|
MTU() uint16
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ import (
|
|||||||
wgdevice "golang.zx2c4.com/wireguard/device"
|
wgdevice "golang.zx2c4.com/wireguard/device"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/errors"
|
"github.com/netbirdio/netbird/client/errors"
|
||||||
"github.com/netbirdio/netbird/client/iface/bind"
|
|
||||||
"github.com/netbirdio/netbird/client/iface/configurer"
|
"github.com/netbirdio/netbird/client/iface/configurer"
|
||||||
"github.com/netbirdio/netbird/client/iface/device"
|
"github.com/netbirdio/netbird/client/iface/device"
|
||||||
|
"github.com/netbirdio/netbird/client/iface/udpmux"
|
||||||
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
||||||
"github.com/netbirdio/netbird/client/iface/wgproxy"
|
"github.com/netbirdio/netbird/client/iface/wgproxy"
|
||||||
"github.com/netbirdio/netbird/monotime"
|
"github.com/netbirdio/netbird/monotime"
|
||||||
@@ -61,7 +61,7 @@ type WGIFaceOpts struct {
|
|||||||
MTU uint16
|
MTU uint16
|
||||||
MobileArgs *device.MobileIFaceArguments
|
MobileArgs *device.MobileIFaceArguments
|
||||||
TransportNet transport.Net
|
TransportNet transport.Net
|
||||||
FilterFn bind.FilterFn
|
FilterFn udpmux.FilterFn
|
||||||
DisableDNS bool
|
DisableDNS bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,7 +114,7 @@ func (r *WGIface) ToInterface() *net.Interface {
|
|||||||
|
|
||||||
// Up configures a Wireguard interface
|
// Up configures a Wireguard interface
|
||||||
// The interface must exist before calling this method (e.g. call interface.Create() before)
|
// The interface must exist before calling this method (e.g. call interface.Create() before)
|
||||||
func (w *WGIface) Up() (*bind.UniversalUDPMuxDefault, error) {
|
func (w *WGIface) Up() (*udpmux.UniversalUDPMuxDefault, error) {
|
||||||
w.mu.Lock()
|
w.mu.Lock()
|
||||||
defer w.mu.Unlock()
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package bind
|
package udpmux
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Most of this code was copied from https://github.com/pion/ice and modified to fulfill NetBird's requirements
|
Most of this code was copied from https://github.com/pion/ice and modified to fulfill NetBird's requirements
|
||||||
@@ -16,11 +16,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type udpMuxedConnParams struct {
|
type udpMuxedConnParams struct {
|
||||||
Mux *UDPMuxDefault
|
Mux *SingleSocketUDPMux
|
||||||
AddrPool *sync.Pool
|
AddrPool *sync.Pool
|
||||||
Key string
|
Key string
|
||||||
LocalAddr net.Addr
|
LocalAddr net.Addr
|
||||||
Logger logging.LeveledLogger
|
Logger logging.LeveledLogger
|
||||||
|
CandidateID string
|
||||||
}
|
}
|
||||||
|
|
||||||
// udpMuxedConn represents a logical packet conn for a single remote as identified by ufrag
|
// udpMuxedConn represents a logical packet conn for a single remote as identified by ufrag
|
||||||
@@ -119,6 +120,10 @@ func (c *udpMuxedConn) Close() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *udpMuxedConn) GetCandidateID() string {
|
||||||
|
return c.params.CandidateID
|
||||||
|
}
|
||||||
|
|
||||||
func (c *udpMuxedConn) isClosed() bool {
|
func (c *udpMuxedConn) isClosed() bool {
|
||||||
select {
|
select {
|
||||||
case <-c.closedChan:
|
case <-c.closedChan:
|
||||||
64
client/iface/udpmux/doc.go
Normal file
64
client/iface/udpmux/doc.go
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
// Package udpmux provides a custom implementation of a UDP multiplexer
|
||||||
|
// that allows multiple logical ICE connections to share a single underlying
|
||||||
|
// UDP socket. This is based on Pion's ICE library, with modifications for
|
||||||
|
// NetBird's requirements.
|
||||||
|
//
|
||||||
|
// # Background
|
||||||
|
//
|
||||||
|
// In WebRTC and NAT traversal scenarios, ICE (Interactive Connectivity
|
||||||
|
// Establishment) is responsible for discovering candidate network paths
|
||||||
|
// and maintaining connectivity between peers. Each ICE connection
|
||||||
|
// normally requires a dedicated UDP socket. However, using one socket
|
||||||
|
// per candidate can be inefficient and difficult to manage.
|
||||||
|
//
|
||||||
|
// This package introduces SingleSocketUDPMux, which allows multiple ICE
|
||||||
|
// candidate connections (muxed connections) to share a single UDP socket.
|
||||||
|
// It handles demultiplexing of packets based on ICE ufrag values, STUN
|
||||||
|
// attributes, and candidate IDs.
|
||||||
|
//
|
||||||
|
// # Usage
|
||||||
|
//
|
||||||
|
// The typical flow is:
|
||||||
|
//
|
||||||
|
// 1. Create a UDP socket (net.PacketConn).
|
||||||
|
// 2. Construct Params with the socket and optional logger/net stack.
|
||||||
|
// 3. Call NewSingleSocketUDPMux(params).
|
||||||
|
// 4. For each ICE candidate ufrag, call GetConn(ufrag, addr, candidateID)
|
||||||
|
// to obtain a logical PacketConn.
|
||||||
|
// 5. Use the returned PacketConn just like a normal UDP connection.
|
||||||
|
//
|
||||||
|
// # STUN Message Routing Logic
|
||||||
|
//
|
||||||
|
// When a STUN packet arrives, the mux decides which connection should
|
||||||
|
// receive it using this routing logic:
|
||||||
|
//
|
||||||
|
// Primary Routing: Candidate Pair ID
|
||||||
|
// - Extract the candidate pair ID from the STUN message using
|
||||||
|
// ice.CandidatePairIDFromSTUN(msg)
|
||||||
|
// - The target candidate is the locally generated candidate that
|
||||||
|
// corresponds to the connection that should handle this STUN message
|
||||||
|
// - If found, use the target candidate ID to lookup the specific
|
||||||
|
// connection in candidateConnMap
|
||||||
|
// - Route the message directly to that connection
|
||||||
|
//
|
||||||
|
// Fallback Routing: Broadcasting
|
||||||
|
// When candidate pair ID is not available or lookup fails:
|
||||||
|
// - Collect connections from addressMap based on source address
|
||||||
|
// - Find connection using username attribute (ufrag) from STUN message
|
||||||
|
// - Remove duplicate connections from the list
|
||||||
|
// - Send the STUN message to all collected connections
|
||||||
|
//
|
||||||
|
// # Peer Reflexive Candidate Discovery
|
||||||
|
//
|
||||||
|
// When a remote peer sends a STUN message from an unknown source address
|
||||||
|
// (from a candidate that has not been exchanged via signal), the ICE
|
||||||
|
// library will:
|
||||||
|
// - Generate a new peer reflexive candidate for this source address
|
||||||
|
// - Extract or assign a candidate ID based on the STUN message attributes
|
||||||
|
// - Create a mapping between the new peer reflexive candidate ID and
|
||||||
|
// the appropriate local connection
|
||||||
|
//
|
||||||
|
// This discovery mechanism ensures that STUN messages from newly discovered
|
||||||
|
// peer reflexive candidates can be properly routed to the correct local
|
||||||
|
// connection without requiring fallback broadcasting.
|
||||||
|
package udpmux
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package bind
|
package udpmux
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -22,9 +22,9 @@ import (
|
|||||||
|
|
||||||
const receiveMTU = 8192
|
const receiveMTU = 8192
|
||||||
|
|
||||||
// UDPMuxDefault is an implementation of the interface
|
// SingleSocketUDPMux is an implementation of the interface
|
||||||
type UDPMuxDefault struct {
|
type SingleSocketUDPMux struct {
|
||||||
params UDPMuxParams
|
params Params
|
||||||
|
|
||||||
closedChan chan struct{}
|
closedChan chan struct{}
|
||||||
closeOnce sync.Once
|
closeOnce sync.Once
|
||||||
@@ -32,6 +32,9 @@ type UDPMuxDefault struct {
|
|||||||
// connsIPv4 and connsIPv6 are maps of all udpMuxedConn indexed by ufrag|network|candidateType
|
// connsIPv4 and connsIPv6 are maps of all udpMuxedConn indexed by ufrag|network|candidateType
|
||||||
connsIPv4, connsIPv6 map[string]*udpMuxedConn
|
connsIPv4, connsIPv6 map[string]*udpMuxedConn
|
||||||
|
|
||||||
|
// candidateConnMap maps local candidate IDs to their corresponding connection.
|
||||||
|
candidateConnMap map[string]*udpMuxedConn
|
||||||
|
|
||||||
addressMapMu sync.RWMutex
|
addressMapMu sync.RWMutex
|
||||||
addressMap map[string][]*udpMuxedConn
|
addressMap map[string][]*udpMuxedConn
|
||||||
|
|
||||||
@@ -46,8 +49,8 @@ type UDPMuxDefault struct {
|
|||||||
|
|
||||||
const maxAddrSize = 512
|
const maxAddrSize = 512
|
||||||
|
|
||||||
// UDPMuxParams are parameters for UDPMux.
|
// Params are parameters for UDPMux.
|
||||||
type UDPMuxParams struct {
|
type Params struct {
|
||||||
Logger logging.LeveledLogger
|
Logger logging.LeveledLogger
|
||||||
UDPConn net.PacketConn
|
UDPConn net.PacketConn
|
||||||
|
|
||||||
@@ -147,18 +150,19 @@ func isZeros(ip net.IP) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUDPMuxDefault creates an implementation of UDPMux
|
// NewSingleSocketUDPMux creates an implementation of UDPMux
|
||||||
func NewUDPMuxDefault(params UDPMuxParams) *UDPMuxDefault {
|
func NewSingleSocketUDPMux(params Params) *SingleSocketUDPMux {
|
||||||
if params.Logger == nil {
|
if params.Logger == nil {
|
||||||
params.Logger = getLogger()
|
params.Logger = getLogger()
|
||||||
}
|
}
|
||||||
|
|
||||||
mux := &UDPMuxDefault{
|
mux := &SingleSocketUDPMux{
|
||||||
addressMap: map[string][]*udpMuxedConn{},
|
addressMap: map[string][]*udpMuxedConn{},
|
||||||
params: params,
|
params: params,
|
||||||
connsIPv4: make(map[string]*udpMuxedConn),
|
connsIPv4: make(map[string]*udpMuxedConn),
|
||||||
connsIPv6: make(map[string]*udpMuxedConn),
|
connsIPv6: make(map[string]*udpMuxedConn),
|
||||||
closedChan: make(chan struct{}, 1),
|
candidateConnMap: make(map[string]*udpMuxedConn),
|
||||||
|
closedChan: make(chan struct{}, 1),
|
||||||
pool: &sync.Pool{
|
pool: &sync.Pool{
|
||||||
New: func() interface{} {
|
New: func() interface{} {
|
||||||
// big enough buffer to fit both packet and address
|
// big enough buffer to fit both packet and address
|
||||||
@@ -171,15 +175,15 @@ func NewUDPMuxDefault(params UDPMuxParams) *UDPMuxDefault {
|
|||||||
return mux
|
return mux
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *UDPMuxDefault) updateLocalAddresses() {
|
func (m *SingleSocketUDPMux) updateLocalAddresses() {
|
||||||
var localAddrsForUnspecified []net.Addr
|
var localAddrsForUnspecified []net.Addr
|
||||||
if addr, ok := m.params.UDPConn.LocalAddr().(*net.UDPAddr); !ok {
|
if addr, ok := m.params.UDPConn.LocalAddr().(*net.UDPAddr); !ok {
|
||||||
m.params.Logger.Errorf("LocalAddr is not a net.UDPAddr, got %T", m.params.UDPConn.LocalAddr())
|
m.params.Logger.Errorf("LocalAddr is not a net.UDPAddr, got %T", m.params.UDPConn.LocalAddr())
|
||||||
} else if ok && addr.IP.IsUnspecified() {
|
} else if ok && addr.IP.IsUnspecified() {
|
||||||
// For unspecified addresses, the correct behavior is to return errListenUnspecified, but
|
// For unspecified addresses, the correct behavior is to return errListenUnspecified, but
|
||||||
// it will break the applications that are already using unspecified UDP connection
|
// it will break the applications that are already using unspecified UDP connection
|
||||||
// with UDPMuxDefault, so print a warn log and create a local address list for mux.
|
// with SingleSocketUDPMux, so print a warn log and create a local address list for mux.
|
||||||
m.params.Logger.Warn("UDPMuxDefault should not listening on unspecified address, use NewMultiUDPMuxFromPort instead")
|
m.params.Logger.Warn("SingleSocketUDPMux should not listening on unspecified address, use NewMultiUDPMuxFromPort instead")
|
||||||
var networks []ice.NetworkType
|
var networks []ice.NetworkType
|
||||||
switch {
|
switch {
|
||||||
|
|
||||||
@@ -216,13 +220,13 @@ func (m *UDPMuxDefault) updateLocalAddresses() {
|
|||||||
m.mu.Unlock()
|
m.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// LocalAddr returns the listening address of this UDPMuxDefault
|
// LocalAddr returns the listening address of this SingleSocketUDPMux
|
||||||
func (m *UDPMuxDefault) LocalAddr() net.Addr {
|
func (m *SingleSocketUDPMux) LocalAddr() net.Addr {
|
||||||
return m.params.UDPConn.LocalAddr()
|
return m.params.UDPConn.LocalAddr()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetListenAddresses returns the list of addresses that this mux is listening on
|
// GetListenAddresses returns the list of addresses that this mux is listening on
|
||||||
func (m *UDPMuxDefault) GetListenAddresses() []net.Addr {
|
func (m *SingleSocketUDPMux) GetListenAddresses() []net.Addr {
|
||||||
m.updateLocalAddresses()
|
m.updateLocalAddresses()
|
||||||
|
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
@@ -236,7 +240,7 @@ func (m *UDPMuxDefault) GetListenAddresses() []net.Addr {
|
|||||||
|
|
||||||
// GetConn returns a PacketConn given the connection's ufrag and network address
|
// GetConn returns a PacketConn given the connection's ufrag and network address
|
||||||
// creates the connection if an existing one can't be found
|
// creates the connection if an existing one can't be found
|
||||||
func (m *UDPMuxDefault) GetConn(ufrag string, addr net.Addr) (net.PacketConn, error) {
|
func (m *SingleSocketUDPMux) GetConn(ufrag string, addr net.Addr, candidateID string) (net.PacketConn, error) {
|
||||||
// don't check addr for mux using unspecified address
|
// don't check addr for mux using unspecified address
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
lenLocalAddrs := len(m.localAddrsForUnspecified)
|
lenLocalAddrs := len(m.localAddrsForUnspecified)
|
||||||
@@ -260,12 +264,14 @@ func (m *UDPMuxDefault) GetConn(ufrag string, addr net.Addr) (net.PacketConn, er
|
|||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
c := m.createMuxedConn(ufrag)
|
c := m.createMuxedConn(ufrag, candidateID)
|
||||||
go func() {
|
go func() {
|
||||||
<-c.CloseChannel()
|
<-c.CloseChannel()
|
||||||
m.RemoveConnByUfrag(ufrag)
|
m.RemoveConnByUfrag(ufrag)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
m.candidateConnMap[candidateID] = c
|
||||||
|
|
||||||
if isIPv6 {
|
if isIPv6 {
|
||||||
m.connsIPv6[ufrag] = c
|
m.connsIPv6[ufrag] = c
|
||||||
} else {
|
} else {
|
||||||
@@ -276,7 +282,7 @@ func (m *UDPMuxDefault) GetConn(ufrag string, addr net.Addr) (net.PacketConn, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RemoveConnByUfrag stops and removes the muxed packet connection
|
// RemoveConnByUfrag stops and removes the muxed packet connection
|
||||||
func (m *UDPMuxDefault) RemoveConnByUfrag(ufrag string) {
|
func (m *SingleSocketUDPMux) RemoveConnByUfrag(ufrag string) {
|
||||||
removedConns := make([]*udpMuxedConn, 0, 2)
|
removedConns := make([]*udpMuxedConn, 0, 2)
|
||||||
|
|
||||||
// Keep lock section small to avoid deadlock with conn lock
|
// Keep lock section small to avoid deadlock with conn lock
|
||||||
@@ -284,10 +290,12 @@ func (m *UDPMuxDefault) RemoveConnByUfrag(ufrag string) {
|
|||||||
if c, ok := m.connsIPv4[ufrag]; ok {
|
if c, ok := m.connsIPv4[ufrag]; ok {
|
||||||
delete(m.connsIPv4, ufrag)
|
delete(m.connsIPv4, ufrag)
|
||||||
removedConns = append(removedConns, c)
|
removedConns = append(removedConns, c)
|
||||||
|
delete(m.candidateConnMap, c.GetCandidateID())
|
||||||
}
|
}
|
||||||
if c, ok := m.connsIPv6[ufrag]; ok {
|
if c, ok := m.connsIPv6[ufrag]; ok {
|
||||||
delete(m.connsIPv6, ufrag)
|
delete(m.connsIPv6, ufrag)
|
||||||
removedConns = append(removedConns, c)
|
removedConns = append(removedConns, c)
|
||||||
|
delete(m.candidateConnMap, c.GetCandidateID())
|
||||||
}
|
}
|
||||||
m.mu.Unlock()
|
m.mu.Unlock()
|
||||||
|
|
||||||
@@ -314,7 +322,7 @@ func (m *UDPMuxDefault) RemoveConnByUfrag(ufrag string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IsClosed returns true if the mux had been closed
|
// IsClosed returns true if the mux had been closed
|
||||||
func (m *UDPMuxDefault) IsClosed() bool {
|
func (m *SingleSocketUDPMux) IsClosed() bool {
|
||||||
select {
|
select {
|
||||||
case <-m.closedChan:
|
case <-m.closedChan:
|
||||||
return true
|
return true
|
||||||
@@ -324,7 +332,7 @@ func (m *UDPMuxDefault) IsClosed() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Close the mux, no further connections could be created
|
// Close the mux, no further connections could be created
|
||||||
func (m *UDPMuxDefault) Close() error {
|
func (m *SingleSocketUDPMux) Close() error {
|
||||||
var err error
|
var err error
|
||||||
m.closeOnce.Do(func() {
|
m.closeOnce.Do(func() {
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
@@ -347,11 +355,11 @@ func (m *UDPMuxDefault) Close() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *UDPMuxDefault) writeTo(buf []byte, rAddr net.Addr) (n int, err error) {
|
func (m *SingleSocketUDPMux) writeTo(buf []byte, rAddr net.Addr) (n int, err error) {
|
||||||
return m.params.UDPConn.WriteTo(buf, rAddr)
|
return m.params.UDPConn.WriteTo(buf, rAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *UDPMuxDefault) registerConnForAddress(conn *udpMuxedConn, addr string) {
|
func (m *SingleSocketUDPMux) registerConnForAddress(conn *udpMuxedConn, addr string) {
|
||||||
if m.IsClosed() {
|
if m.IsClosed() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -368,81 +376,109 @@ func (m *UDPMuxDefault) registerConnForAddress(conn *udpMuxedConn, addr string)
|
|||||||
log.Debugf("ICE: registered %s for %s", addr, conn.params.Key)
|
log.Debugf("ICE: registered %s for %s", addr, conn.params.Key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *UDPMuxDefault) createMuxedConn(key string) *udpMuxedConn {
|
func (m *SingleSocketUDPMux) createMuxedConn(key string, candidateID string) *udpMuxedConn {
|
||||||
c := newUDPMuxedConn(&udpMuxedConnParams{
|
c := newUDPMuxedConn(&udpMuxedConnParams{
|
||||||
Mux: m,
|
Mux: m,
|
||||||
Key: key,
|
Key: key,
|
||||||
AddrPool: m.pool,
|
AddrPool: m.pool,
|
||||||
LocalAddr: m.LocalAddr(),
|
LocalAddr: m.LocalAddr(),
|
||||||
Logger: m.params.Logger,
|
Logger: m.params.Logger,
|
||||||
|
CandidateID: candidateID,
|
||||||
})
|
})
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleSTUNMessage handles STUN packets and forwards them to underlying pion/ice library
|
// HandleSTUNMessage handles STUN packets and forwards them to underlying pion/ice library
|
||||||
func (m *UDPMuxDefault) HandleSTUNMessage(msg *stun.Message, addr net.Addr) error {
|
func (m *SingleSocketUDPMux) HandleSTUNMessage(msg *stun.Message, addr net.Addr) error {
|
||||||
|
|
||||||
remoteAddr, ok := addr.(*net.UDPAddr)
|
remoteAddr, ok := addr.(*net.UDPAddr)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("underlying PacketConn did not return a UDPAddr")
|
return fmt.Errorf("underlying PacketConn did not return a UDPAddr")
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have already seen this address dispatch to the appropriate destination
|
// Try to route to specific candidate connection first
|
||||||
// If you are using the same socket for the Host and SRFLX candidates, it might be that there are more than one
|
if conn := m.findCandidateConnection(msg); conn != nil {
|
||||||
// muxed connection - one for the SRFLX candidate and the other one for the HOST one.
|
return conn.writePacket(msg.Raw, remoteAddr)
|
||||||
// We will then forward STUN packets to each of these connections.
|
}
|
||||||
m.addressMapMu.RLock()
|
|
||||||
|
// Fallback: route to all possible connections
|
||||||
|
return m.forwardToAllConnections(msg, addr, remoteAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// findCandidateConnection attempts to find the specific connection for a STUN message
|
||||||
|
func (m *SingleSocketUDPMux) findCandidateConnection(msg *stun.Message) *udpMuxedConn {
|
||||||
|
candidatePairID, ok, err := ice.CandidatePairIDFromSTUN(msg)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
} else if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
conn, exists := m.candidateConnMap[candidatePairID.TargetCandidateID()]
|
||||||
|
if !exists {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return conn
|
||||||
|
}
|
||||||
|
|
||||||
|
// forwardToAllConnections forwards STUN message to all relevant connections
|
||||||
|
func (m *SingleSocketUDPMux) forwardToAllConnections(msg *stun.Message, addr net.Addr, remoteAddr *net.UDPAddr) error {
|
||||||
var destinationConnList []*udpMuxedConn
|
var destinationConnList []*udpMuxedConn
|
||||||
|
|
||||||
|
// Add connections from address map
|
||||||
|
m.addressMapMu.RLock()
|
||||||
if storedConns, ok := m.addressMap[addr.String()]; ok {
|
if storedConns, ok := m.addressMap[addr.String()]; ok {
|
||||||
destinationConnList = append(destinationConnList, storedConns...)
|
destinationConnList = append(destinationConnList, storedConns...)
|
||||||
}
|
}
|
||||||
m.addressMapMu.RUnlock()
|
m.addressMapMu.RUnlock()
|
||||||
|
|
||||||
var isIPv6 bool
|
if conn, ok := m.findConnectionByUsername(msg, addr); ok {
|
||||||
if udpAddr, _ := addr.(*net.UDPAddr); udpAddr != nil && udpAddr.IP.To4() == nil {
|
// If we have already seen this address dispatch to the appropriate destination
|
||||||
isIPv6 = true
|
// If you are using the same socket for the Host and SRFLX candidates, it might be that there are more than one
|
||||||
|
// muxed connection - one for the SRFLX candidate and the other one for the HOST one.
|
||||||
|
// We will then forward STUN packets to each of these connections.
|
||||||
|
if !m.connectionExists(conn, destinationConnList) {
|
||||||
|
destinationConnList = append(destinationConnList, conn)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This block is needed to discover Peer Reflexive Candidates for which we don't know the Endpoint upfront.
|
// Forward to all found connections
|
||||||
// However, we can take a username attribute from the STUN message which contains ufrag.
|
|
||||||
// We can use ufrag to identify the destination conn to route packet to.
|
|
||||||
attr, stunAttrErr := msg.Get(stun.AttrUsername)
|
|
||||||
if stunAttrErr == nil {
|
|
||||||
ufrag := strings.Split(string(attr), ":")[0]
|
|
||||||
|
|
||||||
m.mu.Lock()
|
|
||||||
destinationConn := m.connsIPv4[ufrag]
|
|
||||||
if isIPv6 {
|
|
||||||
destinationConn = m.connsIPv6[ufrag]
|
|
||||||
}
|
|
||||||
|
|
||||||
if destinationConn != nil {
|
|
||||||
exists := false
|
|
||||||
for _, conn := range destinationConnList {
|
|
||||||
if conn.params.Key == destinationConn.params.Key {
|
|
||||||
exists = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !exists {
|
|
||||||
destinationConnList = append(destinationConnList, destinationConn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forward STUN packets to each destination connections even thought the STUN packet might not belong there.
|
|
||||||
// It will be discarded by the further ICE candidate logic if so.
|
|
||||||
for _, conn := range destinationConnList {
|
for _, conn := range destinationConnList {
|
||||||
if err := conn.writePacket(msg.Raw, remoteAddr); err != nil {
|
if err := conn.writePacket(msg.Raw, remoteAddr); err != nil {
|
||||||
log.Errorf("could not write packet: %v", err)
|
log.Errorf("could not write packet: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *UDPMuxDefault) getConn(ufrag string, isIPv6 bool) (val *udpMuxedConn, ok bool) {
|
// findConnectionByUsername finds connection using username attribute from STUN message
|
||||||
|
func (m *SingleSocketUDPMux) findConnectionByUsername(msg *stun.Message, addr net.Addr) (*udpMuxedConn, bool) {
|
||||||
|
attr, err := msg.Get(stun.AttrUsername)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
ufrag := strings.Split(string(attr), ":")[0]
|
||||||
|
isIPv6 := isIPv6Address(addr)
|
||||||
|
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
return m.getConn(ufrag, isIPv6)
|
||||||
|
}
|
||||||
|
|
||||||
|
// connectionExists checks if a connection already exists in the list
|
||||||
|
func (m *SingleSocketUDPMux) connectionExists(target *udpMuxedConn, conns []*udpMuxedConn) bool {
|
||||||
|
for _, conn := range conns {
|
||||||
|
if conn.params.Key == target.params.Key {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SingleSocketUDPMux) getConn(ufrag string, isIPv6 bool) (val *udpMuxedConn, ok bool) {
|
||||||
if isIPv6 {
|
if isIPv6 {
|
||||||
val, ok = m.connsIPv6[ufrag]
|
val, ok = m.connsIPv6[ufrag]
|
||||||
} else {
|
} else {
|
||||||
@@ -451,6 +487,13 @@ func (m *UDPMuxDefault) getConn(ufrag string, isIPv6 bool) (val *udpMuxedConn, o
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isIPv6Address(addr net.Addr) bool {
|
||||||
|
if udpAddr, ok := addr.(*net.UDPAddr); ok {
|
||||||
|
return udpAddr.IP.To4() == nil
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type bufferHolder struct {
|
type bufferHolder struct {
|
||||||
buf []byte
|
buf []byte
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
//go:build !ios
|
//go:build !ios
|
||||||
|
|
||||||
package bind
|
package udpmux
|
||||||
|
|
||||||
import (
|
import (
|
||||||
nbnet "github.com/netbirdio/netbird/util/net"
|
nbnet "github.com/netbirdio/netbird/util/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (m *UDPMuxDefault) notifyAddressRemoval(addr string) {
|
func (m *SingleSocketUDPMux) notifyAddressRemoval(addr string) {
|
||||||
// Kernel mode: direct nbnet.PacketConn (SharedSocket wrapped with nbnet)
|
// Kernel mode: direct nbnet.PacketConn (SharedSocket wrapped with nbnet)
|
||||||
if conn, ok := m.params.UDPConn.(*nbnet.PacketConn); ok {
|
if conn, ok := m.params.UDPConn.(*nbnet.PacketConn); ok {
|
||||||
conn.RemoveAddress(addr)
|
conn.RemoveAddress(addr)
|
||||||
7
client/iface/udpmux/mux_ios.go
Normal file
7
client/iface/udpmux/mux_ios.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
//go:build ios
|
||||||
|
|
||||||
|
package udpmux
|
||||||
|
|
||||||
|
func (m *SingleSocketUDPMux) notifyAddressRemoval(addr string) {
|
||||||
|
// iOS doesn't support nbnet hooks, so this is a no-op
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package bind
|
package udpmux
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Most of this code was copied from https://github.com/pion/ice and modified to fulfill NetBird's requirements.
|
Most of this code was copied from https://github.com/pion/ice and modified to fulfill NetBird's requirements.
|
||||||
@@ -29,7 +29,7 @@ type FilterFn func(address netip.Addr) (bool, netip.Prefix, error)
|
|||||||
// UniversalUDPMuxDefault handles STUN and TURN servers packets by wrapping the original UDPConn
|
// UniversalUDPMuxDefault handles STUN and TURN servers packets by wrapping the original UDPConn
|
||||||
// It then passes packets to the UDPMux that does the actual connection muxing.
|
// It then passes packets to the UDPMux that does the actual connection muxing.
|
||||||
type UniversalUDPMuxDefault struct {
|
type UniversalUDPMuxDefault struct {
|
||||||
*UDPMuxDefault
|
*SingleSocketUDPMux
|
||||||
params UniversalUDPMuxParams
|
params UniversalUDPMuxParams
|
||||||
|
|
||||||
// since we have a shared socket, for srflx candidates it makes sense to have a shared mapped address across all the agents
|
// since we have a shared socket, for srflx candidates it makes sense to have a shared mapped address across all the agents
|
||||||
@@ -72,12 +72,12 @@ func NewUniversalUDPMuxDefault(params UniversalUDPMuxParams) *UniversalUDPMuxDef
|
|||||||
address: params.WGAddress,
|
address: params.WGAddress,
|
||||||
}
|
}
|
||||||
|
|
||||||
udpMuxParams := UDPMuxParams{
|
udpMuxParams := Params{
|
||||||
Logger: params.Logger,
|
Logger: params.Logger,
|
||||||
UDPConn: m.params.UDPConn,
|
UDPConn: m.params.UDPConn,
|
||||||
Net: m.params.Net,
|
Net: m.params.Net,
|
||||||
}
|
}
|
||||||
m.UDPMuxDefault = NewUDPMuxDefault(udpMuxParams)
|
m.SingleSocketUDPMux = NewSingleSocketUDPMux(udpMuxParams)
|
||||||
|
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
@@ -211,8 +211,8 @@ func (m *UniversalUDPMuxDefault) GetRelayedAddr(turnAddr net.Addr, deadline time
|
|||||||
|
|
||||||
// GetConnForURL add uniques to the muxed connection by concatenating ufrag and URL (e.g. STUN URL) to be able to support multiple STUN/TURN servers
|
// GetConnForURL add uniques to the muxed connection by concatenating ufrag and URL (e.g. STUN URL) to be able to support multiple STUN/TURN servers
|
||||||
// and return a unique connection per server.
|
// and return a unique connection per server.
|
||||||
func (m *UniversalUDPMuxDefault) GetConnForURL(ufrag string, url string, addr net.Addr) (net.PacketConn, error) {
|
func (m *UniversalUDPMuxDefault) GetConnForURL(ufrag string, url string, addr net.Addr, candidateID string) (net.PacketConn, error) {
|
||||||
return m.UDPMuxDefault.GetConn(fmt.Sprintf("%s%s", ufrag, url), addr)
|
return m.SingleSocketUDPMux.GetConn(fmt.Sprintf("%s%s", ufrag, url), addr, candidateID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleSTUNMessage discovers STUN packets that carry a XOR mapped address from a STUN server.
|
// HandleSTUNMessage discovers STUN packets that carry a XOR mapped address from a STUN server.
|
||||||
@@ -233,7 +233,7 @@ func (m *UniversalUDPMuxDefault) HandleSTUNMessage(msg *stun.Message, addr net.A
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return m.UDPMuxDefault.HandleSTUNMessage(msg, addr)
|
return m.SingleSocketUDPMux.HandleSTUNMessage(msg, addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// isXORMappedResponse indicates whether the message is a XORMappedAddress and is coming from the known STUN server.
|
// isXORMappedResponse indicates whether the message is a XORMappedAddress and is coming from the known STUN server.
|
||||||
@@ -29,9 +29,9 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/firewall"
|
"github.com/netbirdio/netbird/client/firewall"
|
||||||
firewallManager "github.com/netbirdio/netbird/client/firewall/manager"
|
firewallManager "github.com/netbirdio/netbird/client/firewall/manager"
|
||||||
"github.com/netbirdio/netbird/client/iface"
|
"github.com/netbirdio/netbird/client/iface"
|
||||||
"github.com/netbirdio/netbird/client/iface/bind"
|
|
||||||
"github.com/netbirdio/netbird/client/iface/device"
|
"github.com/netbirdio/netbird/client/iface/device"
|
||||||
nbnetstack "github.com/netbirdio/netbird/client/iface/netstack"
|
nbnetstack "github.com/netbirdio/netbird/client/iface/netstack"
|
||||||
|
"github.com/netbirdio/netbird/client/iface/udpmux"
|
||||||
"github.com/netbirdio/netbird/client/internal/acl"
|
"github.com/netbirdio/netbird/client/internal/acl"
|
||||||
"github.com/netbirdio/netbird/client/internal/dns"
|
"github.com/netbirdio/netbird/client/internal/dns"
|
||||||
dnsconfig "github.com/netbirdio/netbird/client/internal/dns/config"
|
dnsconfig "github.com/netbirdio/netbird/client/internal/dns/config"
|
||||||
@@ -166,7 +166,7 @@ type Engine struct {
|
|||||||
|
|
||||||
wgInterface WGIface
|
wgInterface WGIface
|
||||||
|
|
||||||
udpMux *bind.UniversalUDPMuxDefault
|
udpMux *udpmux.UniversalUDPMuxDefault
|
||||||
|
|
||||||
// networkSerial is the latest CurrentSerial (state ID) of the network sent by the Management service
|
// networkSerial is the latest CurrentSerial (state ID) of the network sent by the Management service
|
||||||
networkSerial uint64
|
networkSerial uint64
|
||||||
@@ -461,7 +461,7 @@ func (e *Engine) Start(netbirdConfig *mgmProto.NetbirdConfig, mgmtURL *url.URL)
|
|||||||
StunTurn: &e.stunTurn,
|
StunTurn: &e.stunTurn,
|
||||||
InterfaceBlackList: e.config.IFaceBlackList,
|
InterfaceBlackList: e.config.IFaceBlackList,
|
||||||
DisableIPv6Discovery: e.config.DisableIPv6Discovery,
|
DisableIPv6Discovery: e.config.DisableIPv6Discovery,
|
||||||
UDPMux: e.udpMux.UDPMuxDefault,
|
UDPMux: e.udpMux.SingleSocketUDPMux,
|
||||||
UDPMuxSrflx: e.udpMux,
|
UDPMuxSrflx: e.udpMux,
|
||||||
NATExternalIPs: e.parseNATExternalIPMappings(),
|
NATExternalIPs: e.parseNATExternalIPMappings(),
|
||||||
}
|
}
|
||||||
@@ -1326,7 +1326,7 @@ func (e *Engine) createPeerConn(pubKey string, allowedIPs []netip.Prefix, agentV
|
|||||||
StunTurn: &e.stunTurn,
|
StunTurn: &e.stunTurn,
|
||||||
InterfaceBlackList: e.config.IFaceBlackList,
|
InterfaceBlackList: e.config.IFaceBlackList,
|
||||||
DisableIPv6Discovery: e.config.DisableIPv6Discovery,
|
DisableIPv6Discovery: e.config.DisableIPv6Discovery,
|
||||||
UDPMux: e.udpMux.UDPMuxDefault,
|
UDPMux: e.udpMux.SingleSocketUDPMux,
|
||||||
UDPMuxSrflx: e.udpMux,
|
UDPMuxSrflx: e.udpMux,
|
||||||
NATExternalIPs: e.parseNATExternalIPMappings(),
|
NATExternalIPs: e.parseNATExternalIPMappings(),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -26,10 +26,11 @@ import (
|
|||||||
"google.golang.org/grpc/keepalive"
|
"google.golang.org/grpc/keepalive"
|
||||||
|
|
||||||
"github.com/netbirdio/management-integrations/integrations"
|
"github.com/netbirdio/management-integrations/integrations"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/iface"
|
"github.com/netbirdio/netbird/client/iface"
|
||||||
"github.com/netbirdio/netbird/client/iface/bind"
|
|
||||||
"github.com/netbirdio/netbird/client/iface/configurer"
|
"github.com/netbirdio/netbird/client/iface/configurer"
|
||||||
"github.com/netbirdio/netbird/client/iface/device"
|
"github.com/netbirdio/netbird/client/iface/device"
|
||||||
|
"github.com/netbirdio/netbird/client/iface/udpmux"
|
||||||
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
||||||
"github.com/netbirdio/netbird/client/iface/wgproxy"
|
"github.com/netbirdio/netbird/client/iface/wgproxy"
|
||||||
"github.com/netbirdio/netbird/client/internal/dns"
|
"github.com/netbirdio/netbird/client/internal/dns"
|
||||||
@@ -84,7 +85,7 @@ type MockWGIface struct {
|
|||||||
NameFunc func() string
|
NameFunc func() string
|
||||||
AddressFunc func() wgaddr.Address
|
AddressFunc func() wgaddr.Address
|
||||||
ToInterfaceFunc func() *net.Interface
|
ToInterfaceFunc func() *net.Interface
|
||||||
UpFunc func() (*bind.UniversalUDPMuxDefault, error)
|
UpFunc func() (*udpmux.UniversalUDPMuxDefault, error)
|
||||||
UpdateAddrFunc func(newAddr string) error
|
UpdateAddrFunc func(newAddr string) error
|
||||||
UpdatePeerFunc func(peerKey string, allowedIps []netip.Prefix, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error
|
UpdatePeerFunc func(peerKey string, allowedIps []netip.Prefix, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error
|
||||||
RemovePeerFunc func(peerKey string) error
|
RemovePeerFunc func(peerKey string) error
|
||||||
@@ -134,7 +135,7 @@ func (m *MockWGIface) ToInterface() *net.Interface {
|
|||||||
return m.ToInterfaceFunc()
|
return m.ToInterfaceFunc()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockWGIface) Up() (*bind.UniversalUDPMuxDefault, error) {
|
func (m *MockWGIface) Up() (*udpmux.UniversalUDPMuxDefault, error) {
|
||||||
return m.UpFunc()
|
return m.UpFunc()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,7 +414,7 @@ func TestEngine_UpdateNetworkMap(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
engine.udpMux = bind.NewUniversalUDPMuxDefault(bind.UniversalUDPMuxParams{UDPConn: conn, MTU: 1280})
|
engine.udpMux = udpmux.NewUniversalUDPMuxDefault(udpmux.UniversalUDPMuxParams{UDPConn: conn, MTU: 1280})
|
||||||
engine.ctx = ctx
|
engine.ctx = ctx
|
||||||
engine.srWatcher = guard.NewSRWatcher(nil, nil, nil, icemaker.Config{})
|
engine.srWatcher = guard.NewSRWatcher(nil, nil, nil, icemaker.Config{})
|
||||||
engine.connMgr = NewConnMgr(engine.config, engine.statusRecorder, engine.peerStore, wgIface)
|
engine.connMgr = NewConnMgr(engine.config, engine.statusRecorder, engine.peerStore, wgIface)
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import (
|
|||||||
"golang.zx2c4.com/wireguard/tun/netstack"
|
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/iface/bind"
|
|
||||||
"github.com/netbirdio/netbird/client/iface/configurer"
|
"github.com/netbirdio/netbird/client/iface/configurer"
|
||||||
"github.com/netbirdio/netbird/client/iface/device"
|
"github.com/netbirdio/netbird/client/iface/device"
|
||||||
|
"github.com/netbirdio/netbird/client/iface/udpmux"
|
||||||
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
||||||
"github.com/netbirdio/netbird/client/iface/wgproxy"
|
"github.com/netbirdio/netbird/client/iface/wgproxy"
|
||||||
"github.com/netbirdio/netbird/monotime"
|
"github.com/netbirdio/netbird/monotime"
|
||||||
@@ -24,7 +24,7 @@ type wgIfaceBase interface {
|
|||||||
Name() string
|
Name() string
|
||||||
Address() wgaddr.Address
|
Address() wgaddr.Address
|
||||||
ToInterface() *net.Interface
|
ToInterface() *net.Interface
|
||||||
Up() (*bind.UniversalUDPMuxDefault, error)
|
Up() (*udpmux.UniversalUDPMuxDefault, error)
|
||||||
UpdateAddr(newAddr string) error
|
UpdateAddr(newAddr string) error
|
||||||
GetProxy() wgproxy.Proxy
|
GetProxy() wgproxy.Proxy
|
||||||
UpdatePeer(peerKey string, allowedIps []netip.Prefix, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error
|
UpdatePeer(peerKey string, allowedIps []netip.Prefix, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error
|
||||||
|
|||||||
@@ -9,11 +9,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pion/ice/v4"
|
"github.com/pion/ice/v4"
|
||||||
"github.com/pion/stun/v2"
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/iface"
|
"github.com/netbirdio/netbird/client/iface"
|
||||||
"github.com/netbirdio/netbird/client/iface/bind"
|
"github.com/netbirdio/netbird/client/iface/udpmux"
|
||||||
"github.com/netbirdio/netbird/client/internal/peer/conntype"
|
"github.com/netbirdio/netbird/client/internal/peer/conntype"
|
||||||
icemaker "github.com/netbirdio/netbird/client/internal/peer/ice"
|
icemaker "github.com/netbirdio/netbird/client/internal/peer/ice"
|
||||||
"github.com/netbirdio/netbird/client/internal/stdnet"
|
"github.com/netbirdio/netbird/client/internal/stdnet"
|
||||||
@@ -55,10 +54,6 @@ type WorkerICE struct {
|
|||||||
sessionID ICESessionID
|
sessionID ICESessionID
|
||||||
muxAgent sync.Mutex
|
muxAgent sync.Mutex
|
||||||
|
|
||||||
StunTurn []*stun.URI
|
|
||||||
|
|
||||||
sentExtraSrflx bool
|
|
||||||
|
|
||||||
localUfrag string
|
localUfrag string
|
||||||
localPwd string
|
localPwd string
|
||||||
|
|
||||||
@@ -139,7 +134,6 @@ func (w *WorkerICE) OnNewOffer(remoteOfferAnswer *OfferAnswer) {
|
|||||||
w.muxAgent.Unlock()
|
w.muxAgent.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.sentExtraSrflx = false
|
|
||||||
w.agent = agent
|
w.agent = agent
|
||||||
w.agentDialerCancel = dialerCancel
|
w.agentDialerCancel = dialerCancel
|
||||||
w.agentConnecting = true
|
w.agentConnecting = true
|
||||||
@@ -166,6 +160,21 @@ func (w *WorkerICE) OnRemoteCandidate(candidate ice.Candidate, haRoutes route.HA
|
|||||||
w.log.Errorf("error while handling remote candidate")
|
w.log.Errorf("error while handling remote candidate")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if shouldAddExtraCandidate(candidate) {
|
||||||
|
// sends an extra server reflexive candidate to the remote peer with our related port (usually the wireguard port)
|
||||||
|
// this is useful when network has an existing port forwarding rule for the wireguard port and this peer
|
||||||
|
extraSrflx, err := extraSrflxCandidate(candidate)
|
||||||
|
if err != nil {
|
||||||
|
w.log.Errorf("failed creating extra server reflexive candidate %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := w.agent.AddRemoteCandidate(extraSrflx); err != nil {
|
||||||
|
w.log.Errorf("error while handling remote candidate")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WorkerICE) GetLocalUserCredentials() (frag string, pwd string) {
|
func (w *WorkerICE) GetLocalUserCredentials() (frag string, pwd string) {
|
||||||
@@ -327,7 +336,7 @@ func (w *WorkerICE) punchRemoteWGPort(pair *ice.CandidatePair, remoteWgPort int)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
mux, ok := w.config.ICEConfig.UDPMuxSrflx.(*bind.UniversalUDPMuxDefault)
|
mux, ok := w.config.ICEConfig.UDPMuxSrflx.(*udpmux.UniversalUDPMuxDefault)
|
||||||
if !ok {
|
if !ok {
|
||||||
w.log.Warn("invalid udp mux conversion")
|
w.log.Warn("invalid udp mux conversion")
|
||||||
return
|
return
|
||||||
@@ -354,26 +363,6 @@ func (w *WorkerICE) onICECandidate(candidate ice.Candidate) {
|
|||||||
w.log.Errorf("failed signaling candidate to the remote peer %s %s", w.config.Key, err)
|
w.log.Errorf("failed signaling candidate to the remote peer %s %s", w.config.Key, err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if !w.shouldSendExtraSrflxCandidate(candidate) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// sends an extra server reflexive candidate to the remote peer with our related port (usually the wireguard port)
|
|
||||||
// this is useful when network has an existing port forwarding rule for the wireguard port and this peer
|
|
||||||
extraSrflx, err := extraSrflxCandidate(candidate)
|
|
||||||
if err != nil {
|
|
||||||
w.log.Errorf("failed creating extra server reflexive candidate %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.sentExtraSrflx = true
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
err = w.signaler.SignalICECandidate(extraSrflx, w.config.Key)
|
|
||||||
if err != nil {
|
|
||||||
w.log.Errorf("failed signaling the extra server reflexive candidate: %s", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WorkerICE) onICESelectedCandidatePair(c1 ice.Candidate, c2 ice.Candidate) {
|
func (w *WorkerICE) onICESelectedCandidatePair(c1 ice.Candidate, c2 ice.Candidate) {
|
||||||
@@ -424,22 +413,31 @@ func (w *WorkerICE) onConnectionStateChange(agent *icemaker.ThreadSafeAgent, dia
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WorkerICE) shouldSendExtraSrflxCandidate(candidate ice.Candidate) bool {
|
|
||||||
if !w.sentExtraSrflx && candidate.Type() == ice.CandidateTypeServerReflexive && candidate.Port() != candidate.RelatedAddress().Port {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WorkerICE) turnAgentDial(ctx context.Context, agent *icemaker.ThreadSafeAgent, remoteOfferAnswer *OfferAnswer) (*ice.Conn, error) {
|
func (w *WorkerICE) turnAgentDial(ctx context.Context, agent *icemaker.ThreadSafeAgent, remoteOfferAnswer *OfferAnswer) (*ice.Conn, error) {
|
||||||
isControlling := w.config.LocalKey > w.config.Key
|
if isController(w.config) {
|
||||||
if isControlling {
|
return w.agent.Dial(ctx, remoteOfferAnswer.IceCredentials.UFrag, remoteOfferAnswer.IceCredentials.Pwd)
|
||||||
return agent.Dial(ctx, remoteOfferAnswer.IceCredentials.UFrag, remoteOfferAnswer.IceCredentials.Pwd)
|
|
||||||
} else {
|
} else {
|
||||||
return agent.Accept(ctx, remoteOfferAnswer.IceCredentials.UFrag, remoteOfferAnswer.IceCredentials.Pwd)
|
return agent.Accept(ctx, remoteOfferAnswer.IceCredentials.UFrag, remoteOfferAnswer.IceCredentials.Pwd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func shouldAddExtraCandidate(candidate ice.Candidate) bool {
|
||||||
|
if candidate.Type() != ice.CandidateTypeServerReflexive {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if candidate.Port() == candidate.RelatedAddress().Port {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// in the older version when we didn't set candidate ID extension the remote peer sent the extra candidates
|
||||||
|
// in newer version we generate locally the extra candidate
|
||||||
|
if _, ok := candidate.GetExtension(ice.ExtensionKeyCandidateID); !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func extraSrflxCandidate(candidate ice.Candidate) (*ice.CandidateServerReflexive, error) {
|
func extraSrflxCandidate(candidate ice.Candidate) (*ice.CandidateServerReflexive, error) {
|
||||||
relatedAdd := candidate.RelatedAddress()
|
relatedAdd := candidate.RelatedAddress()
|
||||||
ec, err := ice.NewCandidateServerReflexive(&ice.CandidateServerReflexiveConfig{
|
ec, err := ice.NewCandidateServerReflexive(&ice.CandidateServerReflexiveConfig{
|
||||||
@@ -455,6 +453,10 @@ func extraSrflxCandidate(candidate ice.Candidate) (*ice.CandidateServerReflexive
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, e := range candidate.Extensions() {
|
for _, e := range candidate.Extensions() {
|
||||||
|
// overwrite the original candidate ID with the new one to avoid candidate duplication
|
||||||
|
if e.Key == ice.ExtensionKeyCandidateID {
|
||||||
|
e.Value = candidate.ID()
|
||||||
|
}
|
||||||
if err := ec.AddExtension(e); err != nil {
|
if err := ec.AddExtension(e); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -261,6 +261,6 @@ replace golang.zx2c4.com/wireguard => github.com/netbirdio/wireguard-go v0.0.0-2
|
|||||||
|
|
||||||
replace github.com/cloudflare/circl => github.com/cunicu/circl v0.0.0-20230801113412-fec58fc7b5f6
|
replace github.com/cloudflare/circl => github.com/cunicu/circl v0.0.0-20230801113412-fec58fc7b5f6
|
||||||
|
|
||||||
replace github.com/pion/ice/v4 => github.com/netbirdio/ice/v4 v4.0.0-20250827161942-426799a23107
|
replace github.com/pion/ice/v4 => github.com/netbirdio/ice/v4 v4.0.0-20250908184934-6202be846b51
|
||||||
|
|
||||||
replace github.com/libp2p/go-netroute => github.com/netbirdio/go-netroute v0.0.0-20240611143515-f59b0e1d3944
|
replace github.com/libp2p/go-netroute => github.com/netbirdio/go-netroute v0.0.0-20240611143515-f59b0e1d3944
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -501,8 +501,8 @@ github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJE
|
|||||||
github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||||
github.com/netbirdio/go-netroute v0.0.0-20240611143515-f59b0e1d3944 h1:TDtJKmM6Sf8uYFx/dMeqNOL90KUoRscdfpFZ3Im89uk=
|
github.com/netbirdio/go-netroute v0.0.0-20240611143515-f59b0e1d3944 h1:TDtJKmM6Sf8uYFx/dMeqNOL90KUoRscdfpFZ3Im89uk=
|
||||||
github.com/netbirdio/go-netroute v0.0.0-20240611143515-f59b0e1d3944/go.mod h1:sHA6TRxjQ6RLbnI+3R4DZo2Eseg/iKiPRfNmcuNySVQ=
|
github.com/netbirdio/go-netroute v0.0.0-20240611143515-f59b0e1d3944/go.mod h1:sHA6TRxjQ6RLbnI+3R4DZo2Eseg/iKiPRfNmcuNySVQ=
|
||||||
github.com/netbirdio/ice/v4 v4.0.0-20250827161942-426799a23107 h1:ZJwhKexMlK15B/Ld+1T8VYE2Mt1lk1kf2DlXr46EHcw=
|
github.com/netbirdio/ice/v4 v4.0.0-20250908184934-6202be846b51 h1:Ov4qdafATOgGMB1wbSuh+0aAHcwz9hdvB6VZjh1mVMI=
|
||||||
github.com/netbirdio/ice/v4 v4.0.0-20250827161942-426799a23107/go.mod h1:ZSIbPdBn5hePO8CpF1PekH2SfpTxg1PDhEwtbqZS7R8=
|
github.com/netbirdio/ice/v4 v4.0.0-20250908184934-6202be846b51/go.mod h1:ZSIbPdBn5hePO8CpF1PekH2SfpTxg1PDhEwtbqZS7R8=
|
||||||
github.com/netbirdio/management-integrations/integrations v0.0.0-20250906095204-f87a07690ba0 h1:9BUqQHPVOGr0edk8EifUBUfTr2Ob0ypAPxtasUApBxQ=
|
github.com/netbirdio/management-integrations/integrations v0.0.0-20250906095204-f87a07690ba0 h1:9BUqQHPVOGr0edk8EifUBUfTr2Ob0ypAPxtasUApBxQ=
|
||||||
github.com/netbirdio/management-integrations/integrations v0.0.0-20250906095204-f87a07690ba0/go.mod h1:v0nUbbHbuQnqR7yKIYnKzsLBCswLtp2JctmKYmGgVhc=
|
github.com/netbirdio/management-integrations/integrations v0.0.0-20250906095204-f87a07690ba0/go.mod h1:v0nUbbHbuQnqR7yKIYnKzsLBCswLtp2JctmKYmGgVhc=
|
||||||
github.com/netbirdio/service v0.0.0-20240911161631-f62744f42502 h1:3tHlFmhTdX9axERMVN63dqyFqnvuD+EMJHzM7mNGON8=
|
github.com/netbirdio/service v0.0.0-20240911161631-f62744f42502 h1:3tHlFmhTdX9axERMVN63dqyFqnvuD+EMJHzM7mNGON8=
|
||||||
|
|||||||
Reference in New Issue
Block a user