diff --git a/client/iface/bind/bind.go b/client/iface/bind/bind.go deleted file mode 100644 index ba6153cb7..000000000 --- a/client/iface/bind/bind.go +++ /dev/null @@ -1,142 +0,0 @@ -package bind - -import ( - "fmt" - "net" - "runtime" - "sync" - - "github.com/pion/stun/v2" - "github.com/pion/transport/v3" - log "github.com/sirupsen/logrus" - "golang.org/x/net/ipv4" - wgConn "golang.zx2c4.com/wireguard/conn" -) - -type receiverCreator struct { - iceBind *ICEBind -} - -func (rc receiverCreator) CreateIPv4ReceiverFn(msgPool *sync.Pool, pc *ipv4.PacketConn, conn *net.UDPConn) wgConn.ReceiveFunc { - return rc.iceBind.createIPv4ReceiverFn(msgPool, pc, conn) -} - -type ICEBind struct { - *wgConn.StdNetBind - - muUDPMux sync.Mutex - - transportNet transport.Net - udpMux *UniversalUDPMuxDefault - - filterFn FilterFn -} - -func NewICEBind(transportNet transport.Net, filterFn FilterFn) *ICEBind { - ib := &ICEBind{ - transportNet: transportNet, - filterFn: filterFn, - } - - rc := receiverCreator{ - ib, - } - ib.StdNetBind = wgConn.NewStdNetBindWithReceiverCreator(rc) - return ib -} - -// GetICEMux returns the ICE UDPMux that was created and used by ICEBind -func (s *ICEBind) GetICEMux() (*UniversalUDPMuxDefault, error) { - s.muUDPMux.Lock() - defer s.muUDPMux.Unlock() - if s.udpMux == nil { - return nil, fmt.Errorf("ICEBind has not been initialized yet") - } - - return s.udpMux, nil -} - -func (s *ICEBind) createIPv4ReceiverFn(ipv4MsgsPool *sync.Pool, pc *ipv4.PacketConn, conn *net.UDPConn) wgConn.ReceiveFunc { - s.muUDPMux.Lock() - defer s.muUDPMux.Unlock() - - s.udpMux = NewUniversalUDPMuxDefault( - UniversalUDPMuxParams{ - UDPConn: conn, - Net: s.transportNet, - FilterFn: s.filterFn, - }, - ) - return func(bufs [][]byte, sizes []int, eps []wgConn.Endpoint) (n int, err error) { - msgs := ipv4MsgsPool.Get().(*[]ipv4.Message) - defer ipv4MsgsPool.Put(msgs) - for i := range bufs { - (*msgs)[i].Buffers[0] = bufs[i] - } - var numMsgs int - if runtime.GOOS == "linux" { - numMsgs, err = pc.ReadBatch(*msgs, 0) - if err != nil { - return 0, err - } - } else { - msg := &(*msgs)[0] - msg.N, msg.NN, _, msg.Addr, err = conn.ReadMsgUDP(msg.Buffers[0], msg.OOB) - if err != nil { - return 0, err - } - numMsgs = 1 - } - for i := 0; i < numMsgs; i++ { - msg := &(*msgs)[i] - - // todo: handle err - ok, _ := s.filterOutStunMessages(msg.Buffers, msg.N, msg.Addr) - if ok { - sizes[i] = 0 - } else { - sizes[i] = msg.N - } - - addrPort := msg.Addr.(*net.UDPAddr).AddrPort() - ep := &wgConn.StdNetEndpoint{AddrPort: addrPort} // TODO: remove allocation - wgConn.GetSrcFromControl(msg.OOB[:msg.NN], ep) - eps[i] = ep - } - return numMsgs, nil - } -} - -func (s *ICEBind) filterOutStunMessages(buffers [][]byte, n int, addr net.Addr) (bool, error) { - for i := range buffers { - if !stun.IsMessage(buffers[i]) { - continue - } - - msg, err := s.parseSTUNMessage(buffers[i][:n]) - if err != nil { - buffers[i] = []byte{} - return true, err - } - - muxErr := s.udpMux.HandleSTUNMessage(msg, addr) - if muxErr != nil { - log.Warnf("failed to handle STUN packet") - } - - buffers[i] = []byte{} - return true, nil - } - return false, nil -} - -func (s *ICEBind) parseSTUNMessage(raw []byte) (*stun.Message, error) { - msg := &stun.Message{ - Raw: raw, - } - if err := msg.Decode(); err != nil { - return nil, err - } - - return msg, nil -} diff --git a/client/iface/bind/endpoint.go b/client/iface/bind/endpoint.go new file mode 100644 index 000000000..1926ff88f --- /dev/null +++ b/client/iface/bind/endpoint.go @@ -0,0 +1,5 @@ +package bind + +import wgConn "golang.zx2c4.com/wireguard/conn" + +type Endpoint = wgConn.StdNetEndpoint diff --git a/client/iface/bind/ice_bind.go b/client/iface/bind/ice_bind.go new file mode 100644 index 000000000..ccdcc2cda --- /dev/null +++ b/client/iface/bind/ice_bind.go @@ -0,0 +1,276 @@ +package bind + +import ( + "fmt" + "net" + "net/netip" + "runtime" + "strings" + "sync" + + "github.com/pion/stun/v2" + "github.com/pion/transport/v3" + log "github.com/sirupsen/logrus" + "golang.org/x/net/ipv4" + wgConn "golang.zx2c4.com/wireguard/conn" +) + +type RecvMessage struct { + Endpoint *Endpoint + Buffer []byte +} + +type receiverCreator struct { + iceBind *ICEBind +} + +func (rc receiverCreator) CreateIPv4ReceiverFn(msgPool *sync.Pool, pc *ipv4.PacketConn, conn *net.UDPConn) wgConn.ReceiveFunc { + return rc.iceBind.createIPv4ReceiverFn(msgPool, pc, conn) +} + +// ICEBind is a bind implementation with two main features: +// 1. filter out STUN messages and handle them +// 2. forward the received packets to the WireGuard interface from the relayed connection +// +// ICEBind.endpoints var is a map that stores the connection for each relayed peer. Fake address is just an IP address +// without port, in the format of 127.1.x.x where x.x is the last two octets of the peer address. We try to avoid to +// use the port because in the Send function the wgConn.Endpoint the port info is not exported. +type ICEBind struct { + *wgConn.StdNetBind + RecvChan chan RecvMessage + + transportNet transport.Net + filterFn FilterFn + endpoints map[netip.Addr]net.Conn + endpointsMu sync.Mutex + // every time when Close() is called (i.e. BindUpdate()) we need to close exit from the receiveRelayed and create a + // new closed channel. With the closedChanMu we can safely close the channel and create a new one + closedChan chan struct{} + closedChanMu sync.RWMutex // protect the closeChan recreation from reading from it. + closed bool + + muUDPMux sync.Mutex + udpMux *UniversalUDPMuxDefault +} + +func NewICEBind(transportNet transport.Net, filterFn FilterFn) *ICEBind { + b, _ := wgConn.NewStdNetBind().(*wgConn.StdNetBind) + ib := &ICEBind{ + StdNetBind: b, + RecvChan: make(chan RecvMessage, 1), + transportNet: transportNet, + filterFn: filterFn, + endpoints: make(map[netip.Addr]net.Conn), + closedChan: make(chan struct{}), + closed: true, + } + + rc := receiverCreator{ + ib, + } + ib.StdNetBind = wgConn.NewStdNetBindWithReceiverCreator(rc) + return ib +} + +func (s *ICEBind) Open(uport uint16) ([]wgConn.ReceiveFunc, uint16, error) { + s.closed = false + s.closedChanMu.Lock() + s.closedChan = make(chan struct{}) + s.closedChanMu.Unlock() + fns, port, err := s.StdNetBind.Open(uport) + if err != nil { + return nil, 0, err + } + fns = append(fns, s.receiveRelayed) + return fns, port, nil +} + +func (s *ICEBind) Close() error { + if s.closed { + return nil + } + s.closed = true + + close(s.closedChan) + + return s.StdNetBind.Close() +} + +// GetICEMux returns the ICE UDPMux that was created and used by ICEBind +func (s *ICEBind) GetICEMux() (*UniversalUDPMuxDefault, error) { + s.muUDPMux.Lock() + defer s.muUDPMux.Unlock() + if s.udpMux == nil { + return nil, fmt.Errorf("ICEBind has not been initialized yet") + } + + return s.udpMux, nil +} + +func (b *ICEBind) SetEndpoint(peerAddress *net.UDPAddr, conn net.Conn) (*net.UDPAddr, error) { + 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.endpoints[fakeAddr] = conn + b.endpointsMu.Unlock() + + return fakeUDPAddr, nil +} + +func (b *ICEBind) RemoveEndpoint(fakeUDPAddr *net.UDPAddr) { + fakeAddr, ok := netip.AddrFromSlice(fakeUDPAddr.IP.To4()) + if !ok { + log.Warnf("failed to convert IP to netip.Addr") + return + } + + b.endpointsMu.Lock() + defer b.endpointsMu.Unlock() + delete(b.endpoints, fakeAddr) +} + +func (b *ICEBind) Send(bufs [][]byte, ep wgConn.Endpoint) error { + b.endpointsMu.Lock() + conn, ok := b.endpoints[ep.DstIP()] + b.endpointsMu.Unlock() + if !ok { + log.Infof("failed to find endpoint for %s", ep.DstIP()) + return b.StdNetBind.Send(bufs, ep) + } + + for _, buf := range bufs { + if _, err := conn.Write(buf); err != nil { + return err + } + } + return nil +} + +func (s *ICEBind) createIPv4ReceiverFn(ipv4MsgsPool *sync.Pool, pc *ipv4.PacketConn, conn *net.UDPConn) wgConn.ReceiveFunc { + s.muUDPMux.Lock() + defer s.muUDPMux.Unlock() + + s.udpMux = NewUniversalUDPMuxDefault( + UniversalUDPMuxParams{ + UDPConn: conn, + Net: s.transportNet, + FilterFn: s.filterFn, + }, + ) + return func(bufs [][]byte, sizes []int, eps []wgConn.Endpoint) (n int, err error) { + msgs := ipv4MsgsPool.Get().(*[]ipv4.Message) + defer ipv4MsgsPool.Put(msgs) + for i := range bufs { + (*msgs)[i].Buffers[0] = bufs[i] + } + var numMsgs int + if runtime.GOOS == "linux" { + numMsgs, err = pc.ReadBatch(*msgs, 0) + if err != nil { + return 0, err + } + } else { + msg := &(*msgs)[0] + msg.N, msg.NN, _, msg.Addr, err = conn.ReadMsgUDP(msg.Buffers[0], msg.OOB) + if err != nil { + return 0, err + } + numMsgs = 1 + } + for i := 0; i < numMsgs; i++ { + msg := &(*msgs)[i] + + // todo: handle err + ok, _ := s.filterOutStunMessages(msg.Buffers, msg.N, msg.Addr) + if ok { + sizes[i] = 0 + } else { + sizes[i] = msg.N + } + + addrPort := msg.Addr.(*net.UDPAddr).AddrPort() + ep := &wgConn.StdNetEndpoint{AddrPort: addrPort} // TODO: remove allocation + wgConn.GetSrcFromControl(msg.OOB[:msg.NN], ep) + eps[i] = ep + } + return numMsgs, nil + } +} + +func (s *ICEBind) filterOutStunMessages(buffers [][]byte, n int, addr net.Addr) (bool, error) { + for i := range buffers { + if !stun.IsMessage(buffers[i]) { + continue + } + + msg, err := s.parseSTUNMessage(buffers[i][:n]) + if err != nil { + buffers[i] = []byte{} + return true, err + } + + muxErr := s.udpMux.HandleSTUNMessage(msg, addr) + if muxErr != nil { + log.Warnf("failed to handle STUN packet") + } + + buffers[i] = []byte{} + return true, nil + } + return false, nil +} + +func (s *ICEBind) parseSTUNMessage(raw []byte) (*stun.Message, error) { + msg := &stun.Message{ + Raw: raw, + } + if err := msg.Decode(); err != nil { + return nil, err + } + + return msg, nil +} + +// receiveRelayed is a receive function that is used to receive packets from the relayed connection and forward to the +// WireGuard. Critical part is do not block if the Closed() has been called. +func (c *ICEBind) receiveRelayed(buffs [][]byte, sizes []int, eps []wgConn.Endpoint) (int, error) { + c.closedChanMu.RLock() + defer c.closedChanMu.RUnlock() + + select { + case <-c.closedChan: + return 0, net.ErrClosed + case msg, ok := <-c.RecvChan: + if !ok { + return 0, net.ErrClosed + } + copy(buffs[0], msg.Buffer) + sizes[0] = len(msg.Buffer) + eps[0] = wgConn.Endpoint(msg.Endpoint) + return 1, nil + } +} + +// 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 +} diff --git a/client/iface/device/device_android.go b/client/iface/device/device_android.go index 29e3f409d..fac2ba63d 100644 --- a/client/iface/device/device_android.go +++ b/client/iface/device/device_android.go @@ -5,7 +5,6 @@ package device import ( "strings" - "github.com/pion/transport/v3" log "github.com/sirupsen/logrus" "golang.org/x/sys/unix" "golang.zx2c4.com/wireguard/device" @@ -31,13 +30,13 @@ type WGTunDevice struct { configurer WGConfigurer } -func NewTunDevice(address WGAddress, port int, key string, mtu int, transportNet transport.Net, tunAdapter TunAdapter, filterFn bind.FilterFn) *WGTunDevice { +func NewTunDevice(address WGAddress, port int, key string, mtu int, iceBind *bind.ICEBind, tunAdapter TunAdapter) *WGTunDevice { return &WGTunDevice{ address: address, port: port, key: key, mtu: mtu, - iceBind: bind.NewICEBind(transportNet, filterFn), + iceBind: iceBind, tunAdapter: tunAdapter, } } diff --git a/client/iface/device/device_darwin.go b/client/iface/device/device_darwin.go index 03e85a7f1..b5a128bc1 100644 --- a/client/iface/device/device_darwin.go +++ b/client/iface/device/device_darwin.go @@ -6,7 +6,6 @@ import ( "fmt" "os/exec" - "github.com/pion/transport/v3" log "github.com/sirupsen/logrus" "golang.zx2c4.com/wireguard/device" "golang.zx2c4.com/wireguard/tun" @@ -29,14 +28,14 @@ type TunDevice struct { configurer WGConfigurer } -func NewTunDevice(name string, address WGAddress, port int, key string, mtu int, transportNet transport.Net, filterFn bind.FilterFn) *TunDevice { +func NewTunDevice(name string, address WGAddress, port int, key string, mtu int, iceBind *bind.ICEBind) *TunDevice { return &TunDevice{ name: name, address: address, port: port, key: key, mtu: mtu, - iceBind: bind.NewICEBind(transportNet, filterFn), + iceBind: iceBind, } } diff --git a/client/iface/device/device_ios.go b/client/iface/device/device_ios.go index 226e8a2e0..b9591e0b8 100644 --- a/client/iface/device/device_ios.go +++ b/client/iface/device/device_ios.go @@ -6,7 +6,6 @@ package device import ( "os" - "github.com/pion/transport/v3" log "github.com/sirupsen/logrus" "golang.org/x/sys/unix" "golang.zx2c4.com/wireguard/device" @@ -30,13 +29,13 @@ type TunDevice struct { configurer WGConfigurer } -func NewTunDevice(name string, address WGAddress, port int, key string, transportNet transport.Net, tunFd int, filterFn bind.FilterFn) *TunDevice { +func NewTunDevice(name string, address WGAddress, port int, key string, iceBind *bind.ICEBind, tunFd int) *TunDevice { return &TunDevice{ name: name, address: address, port: port, key: key, - iceBind: bind.NewICEBind(transportNet, filterFn), + iceBind: iceBind, tunFd: tunFd, } } diff --git a/client/iface/device/device_netstack.go b/client/iface/device/device_netstack.go index 440a1ca19..f5d39e9e0 100644 --- a/client/iface/device/device_netstack.go +++ b/client/iface/device/device_netstack.go @@ -6,7 +6,6 @@ package device import ( "fmt" - "github.com/pion/transport/v3" log "github.com/sirupsen/logrus" "golang.zx2c4.com/wireguard/device" @@ -31,7 +30,7 @@ type TunNetstackDevice struct { configurer WGConfigurer } -func NewNetstackDevice(name string, address WGAddress, wgPort int, key string, mtu int, transportNet transport.Net, listenAddress string, filterFn bind.FilterFn) *TunNetstackDevice { +func NewNetstackDevice(name string, address WGAddress, wgPort int, key string, mtu int, iceBind *bind.ICEBind, listenAddress string) *TunNetstackDevice { return &TunNetstackDevice{ name: name, address: address, @@ -39,7 +38,7 @@ func NewNetstackDevice(name string, address WGAddress, wgPort int, key string, m key: key, mtu: mtu, listenAddress: listenAddress, - iceBind: bind.NewICEBind(transportNet, filterFn), + iceBind: iceBind, } } diff --git a/client/iface/device/device_usp_unix.go b/client/iface/device/device_usp_unix.go index 4175f6556..643d77565 100644 --- a/client/iface/device/device_usp_unix.go +++ b/client/iface/device/device_usp_unix.go @@ -7,7 +7,6 @@ import ( "os" "runtime" - "github.com/pion/transport/v3" log "github.com/sirupsen/logrus" "golang.zx2c4.com/wireguard/device" "golang.zx2c4.com/wireguard/tun" @@ -30,7 +29,7 @@ type USPDevice struct { configurer WGConfigurer } -func NewUSPDevice(name string, address WGAddress, port int, key string, mtu int, transportNet transport.Net, filterFn bind.FilterFn) *USPDevice { +func NewUSPDevice(name string, address WGAddress, port int, key string, mtu int, iceBind *bind.ICEBind) *USPDevice { log.Infof("using userspace bind mode") checkUser() @@ -41,7 +40,8 @@ func NewUSPDevice(name string, address WGAddress, port int, key string, mtu int, port: port, key: key, mtu: mtu, - iceBind: bind.NewICEBind(transportNet, filterFn)} + iceBind: iceBind, + } } func (t *USPDevice) Create() (WGConfigurer, error) { diff --git a/client/iface/device/device_windows.go b/client/iface/device/device_windows.go index f3e216ccd..86968d06d 100644 --- a/client/iface/device/device_windows.go +++ b/client/iface/device/device_windows.go @@ -4,7 +4,6 @@ import ( "fmt" "net/netip" - "github.com/pion/transport/v3" log "github.com/sirupsen/logrus" "golang.org/x/sys/windows" "golang.zx2c4.com/wireguard/device" @@ -32,14 +31,14 @@ type TunDevice struct { configurer WGConfigurer } -func NewTunDevice(name string, address WGAddress, port int, key string, mtu int, transportNet transport.Net, filterFn bind.FilterFn) *TunDevice { +func NewTunDevice(name string, address WGAddress, port int, key string, mtu int, iceBind *bind.ICEBind) *TunDevice { return &TunDevice{ name: name, address: address, port: port, key: key, mtu: mtu, - iceBind: bind.NewICEBind(transportNet, filterFn), + iceBind: iceBind, } } diff --git a/client/iface/iface.go b/client/iface/iface.go index accf5ce0a..1fb9c2691 100644 --- a/client/iface/iface.go +++ b/client/iface/iface.go @@ -6,12 +6,16 @@ import ( "sync" "time" + "github.com/hashicorp/go-multierror" + "github.com/pion/transport/v3" log "github.com/sirupsen/logrus" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" + "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/device" + "github.com/netbirdio/netbird/client/iface/wgproxy" ) const ( @@ -22,14 +26,35 @@ const ( type WGAddress = device.WGAddress +type wgProxyFactory interface { + GetProxy() wgproxy.Proxy + Free() error +} + +type WGIFaceOpts struct { + IFaceName string + Address string + WGPort int + WGPrivKey string + MTU int + MobileArgs *device.MobileIFaceArguments + TransportNet transport.Net + FilterFn bind.FilterFn +} + // WGIface represents an interface instance type WGIface struct { tun WGTunDevice userspaceBind bool mu sync.Mutex - configurer device.WGConfigurer - filter device.PacketFilter + configurer device.WGConfigurer + filter device.PacketFilter + wgProxyFactory wgProxyFactory +} + +func (w *WGIface) GetProxy() wgproxy.Proxy { + return w.wgProxyFactory.GetProxy() } // IsUserspaceBind indicates whether this interfaces is userspace with bind.ICEBind @@ -124,22 +149,26 @@ func (w *WGIface) Close() error { w.mu.Lock() defer w.mu.Unlock() - err := w.tun.Close() - if err != nil { - return fmt.Errorf("failed to close wireguard interface %s: %w", w.Name(), err) + var result *multierror.Error + + if err := w.wgProxyFactory.Free(); err != nil { + result = multierror.Append(result, fmt.Errorf("failed to free WireGuard proxy: %w", err)) } - err = w.waitUntilRemoved() - if err != nil { + if err := w.tun.Close(); err != nil { + result = multierror.Append(result, fmt.Errorf("failed to close wireguard interface %s: %w", w.Name(), err)) + } + + if err := w.waitUntilRemoved(); err != nil { log.Warnf("failed to remove WireGuard interface %s: %v", w.Name(), err) - err = w.Destroy() - if err != nil { - return fmt.Errorf("failed to remove WireGuard interface %s: %w", w.Name(), err) + if err := w.Destroy(); err != nil { + result = multierror.Append(result, fmt.Errorf("failed to remove WireGuard interface %s: %w", w.Name(), err)) + return errors.FormatErrorOrNil(result) } log.Infof("interface %s successfully removed", w.Name()) } - return nil + return errors.FormatErrorOrNil(result) } // SetFilter sets packet filters for the userspace implementation diff --git a/client/iface/iface_android.go b/client/iface/iface_android.go deleted file mode 100644 index 5ed476e70..000000000 --- a/client/iface/iface_android.go +++ /dev/null @@ -1,43 +0,0 @@ -package iface - -import ( - "fmt" - - "github.com/pion/transport/v3" - - "github.com/netbirdio/netbird/client/iface/bind" - "github.com/netbirdio/netbird/client/iface/device" -) - -// NewWGIFace Creates a new WireGuard interface instance -func NewWGIFace(iFaceName string, address string, wgPort int, wgPrivKey string, mtu int, transportNet transport.Net, args *device.MobileIFaceArguments, filterFn bind.FilterFn) (*WGIface, error) { - wgAddress, err := device.ParseWGAddress(address) - if err != nil { - return nil, err - } - - wgIFace := &WGIface{ - tun: device.NewTunDevice(wgAddress, wgPort, wgPrivKey, mtu, transportNet, args.TunAdapter, filterFn), - userspaceBind: true, - } - return wgIFace, nil -} - -// CreateOnAndroid creates a new Wireguard interface, sets a given IP and brings it up. -// Will reuse an existing one. -func (w *WGIface) CreateOnAndroid(routes []string, dns string, searchDomains []string) error { - w.mu.Lock() - defer w.mu.Unlock() - - cfgr, err := w.tun.Create(routes, dns, searchDomains) - if err != nil { - return err - } - w.configurer = cfgr - return nil -} - -// Create this function make sense on mobile only -func (w *WGIface) Create() error { - return fmt.Errorf("this function has not implemented on this platform") -} diff --git a/client/iface/iface_create.go b/client/iface/iface_create.go index f389019ed..5e17c6d41 100644 --- a/client/iface/iface_create.go +++ b/client/iface/iface_create.go @@ -2,6 +2,8 @@ package iface +import "fmt" + // Create creates a new Wireguard interface, sets a given IP and brings it up. // Will reuse an existing one. // this function is different on Android @@ -17,3 +19,8 @@ func (w *WGIface) Create() error { w.configurer = cfgr return nil } + +// CreateOnAndroid this function make sense on mobile only +func (w *WGIface) CreateOnAndroid([]string, string, []string) error { + return fmt.Errorf("this function has not implemented on non mobile") +} diff --git a/client/iface/iface_create_android.go b/client/iface/iface_create_android.go new file mode 100644 index 000000000..373a9c95a --- /dev/null +++ b/client/iface/iface_create_android.go @@ -0,0 +1,24 @@ +package iface + +import ( + "fmt" +) + +// CreateOnAndroid creates a new Wireguard interface, sets a given IP and brings it up. +// Will reuse an existing one. +func (w *WGIface) CreateOnAndroid(routes []string, dns string, searchDomains []string) error { + w.mu.Lock() + defer w.mu.Unlock() + + cfgr, err := w.tun.Create(routes, dns, searchDomains) + if err != nil { + return err + } + w.configurer = cfgr + return nil +} + +// Create this function make sense on mobile only +func (w *WGIface) Create() error { + return fmt.Errorf("this function has not implemented on this platform") +} diff --git a/client/iface/iface_darwin.go b/client/iface/iface_create_darwin.go similarity index 50% rename from client/iface/iface_darwin.go rename to client/iface/iface_create_darwin.go index b46ea0f80..1d91bce54 100644 --- a/client/iface/iface_darwin.go +++ b/client/iface/iface_create_darwin.go @@ -7,39 +7,8 @@ import ( "time" "github.com/cenkalti/backoff/v4" - "github.com/pion/transport/v3" - - "github.com/netbirdio/netbird/client/iface/bind" - "github.com/netbirdio/netbird/client/iface/device" - "github.com/netbirdio/netbird/client/iface/netstack" ) -// NewWGIFace Creates a new WireGuard interface instance -func NewWGIFace(iFaceName string, address string, wgPort int, wgPrivKey string, mtu int, transportNet transport.Net, _ *device.MobileIFaceArguments, filterFn bind.FilterFn) (*WGIface, error) { - wgAddress, err := device.ParseWGAddress(address) - if err != nil { - return nil, err - } - - wgIFace := &WGIface{ - userspaceBind: true, - } - - if netstack.IsEnabled() { - wgIFace.tun = device.NewNetstackDevice(iFaceName, wgAddress, wgPort, wgPrivKey, mtu, transportNet, netstack.ListenAddr(), filterFn) - return wgIFace, nil - } - - wgIFace.tun = device.NewTunDevice(iFaceName, wgAddress, wgPort, wgPrivKey, mtu, transportNet, filterFn) - - return wgIFace, nil -} - -// CreateOnAndroid this function make sense on mobile only -func (w *WGIface) CreateOnAndroid([]string, string, []string) error { - return fmt.Errorf("this function has not implemented on this platform") -} - // Create creates a new Wireguard interface, sets a given IP and brings it up. // Will reuse an existing one. // this function is different on Android @@ -65,3 +34,8 @@ func (w *WGIface) Create() error { return backoff.Retry(operation, backOff) } + +// CreateOnAndroid this function make sense on mobile only +func (w *WGIface) CreateOnAndroid([]string, string, []string) error { + return fmt.Errorf("this function has not implemented on this platform") +} diff --git a/client/iface/iface_guid_windows.go b/client/iface/iface_guid_windows.go new file mode 100644 index 000000000..49492fd3d --- /dev/null +++ b/client/iface/iface_guid_windows.go @@ -0,0 +1,10 @@ +package iface + +import ( + "github.com/netbirdio/netbird/client/iface/device" +) + +// GetInterfaceGUIDString returns an interface GUID. This is useful on Windows only +func (w *WGIface) GetInterfaceGUIDString() (string, error) { + return w.tun.(*device.TunDevice).GetInterfaceGUIDString() +} diff --git a/client/iface/iface_ios.go b/client/iface/iface_ios.go deleted file mode 100644 index fc0214748..000000000 --- a/client/iface/iface_ios.go +++ /dev/null @@ -1,31 +0,0 @@ -//go:build ios - -package iface - -import ( - "fmt" - - "github.com/pion/transport/v3" - - "github.com/netbirdio/netbird/client/iface/bind" - "github.com/netbirdio/netbird/client/iface/device" -) - -// NewWGIFace Creates a new WireGuard interface instance -func NewWGIFace(iFaceName string, address string, wgPort int, wgPrivKey string, mtu int, transportNet transport.Net, args *device.MobileIFaceArguments, filterFn bind.FilterFn) (*WGIface, error) { - wgAddress, err := device.ParseWGAddress(address) - if err != nil { - return nil, err - } - wgIFace := &WGIface{ - tun: device.NewTunDevice(iFaceName, wgAddress, wgPort, wgPrivKey, transportNet, args.TunFd, filterFn), - userspaceBind: true, - } - return wgIFace, nil -} - -// CreateOnAndroid creates a new Wireguard interface, sets a given IP and brings it up. -// Will reuse an existing one. -func (w *WGIface) CreateOnAndroid([]string, string, []string) error { - return fmt.Errorf("this function has not implemented on this platform") -} diff --git a/client/iface/iface_moc.go b/client/iface/iface_moc.go index 703da9ce0..d91a7224f 100644 --- a/client/iface/iface_moc.go +++ b/client/iface/iface_moc.go @@ -9,6 +9,7 @@ import ( "github.com/netbirdio/netbird/client/iface/bind" "github.com/netbirdio/netbird/client/iface/configurer" "github.com/netbirdio/netbird/client/iface/device" + "github.com/netbirdio/netbird/client/iface/wgproxy" ) type MockWGIface struct { @@ -30,6 +31,7 @@ type MockWGIface struct { GetDeviceFunc func() *device.FilteredDevice GetStatsFunc func(peerKey string) (configurer.WGStats, error) GetInterfaceGUIDStringFunc func() (string, error) + GetProxyFunc func() wgproxy.Proxy } func (m *MockWGIface) GetInterfaceGUIDString() (string, error) { @@ -103,3 +105,8 @@ func (m *MockWGIface) GetDevice() *device.FilteredDevice { func (m *MockWGIface) GetStats(peerKey string) (configurer.WGStats, error) { return m.GetStatsFunc(peerKey) } + +func (m *MockWGIface) GetProxy() wgproxy.Proxy { + //TODO implement me + panic("implement me") +} diff --git a/client/iface/iface_new_android.go b/client/iface/iface_new_android.go new file mode 100644 index 000000000..69a8d1fd4 --- /dev/null +++ b/client/iface/iface_new_android.go @@ -0,0 +1,24 @@ +package iface + +import ( + "github.com/netbirdio/netbird/client/iface/bind" + "github.com/netbirdio/netbird/client/iface/device" + "github.com/netbirdio/netbird/client/iface/wgproxy" +) + +// NewWGIFace Creates a new WireGuard interface instance +func NewWGIFace(opts WGIFaceOpts) (*WGIface, error) { + wgAddress, err := device.ParseWGAddress(opts.Address) + if err != nil { + return nil, err + } + + iceBind := bind.NewICEBind(opts.TransportNet, opts.FilterFn) + + wgIFace := &WGIface{ + userspaceBind: true, + tun: device.NewTunDevice(wgAddress, opts.WGPort, opts.WGPrivKey, opts.MTU, iceBind, opts.MobileArgs.TunAdapter), + wgProxyFactory: wgproxy.NewUSPFactory(iceBind), + } + return wgIFace, nil +} diff --git a/client/iface/iface_new_darwin.go b/client/iface/iface_new_darwin.go new file mode 100644 index 000000000..a92d74e0f --- /dev/null +++ b/client/iface/iface_new_darwin.go @@ -0,0 +1,34 @@ +//go:build !ios + +package iface + +import ( + "github.com/netbirdio/netbird/client/iface/bind" + "github.com/netbirdio/netbird/client/iface/device" + "github.com/netbirdio/netbird/client/iface/netstack" + "github.com/netbirdio/netbird/client/iface/wgproxy" +) + +// NewWGIFace Creates a new WireGuard interface instance +func NewWGIFace(opts WGIFaceOpts) (*WGIface, error) { + wgAddress, err := device.ParseWGAddress(opts.Address) + if err != nil { + return nil, err + } + + iceBind := bind.NewICEBind(opts.TransportNet, opts.FilterFn) + + var tun WGTunDevice + if netstack.IsEnabled() { + tun = device.NewNetstackDevice(opts.IFaceName, wgAddress, opts.WGPort, opts.WGPrivKey, opts.MTU, iceBind, netstack.ListenAddr()) + } else { + tun = device.NewTunDevice(opts.IFaceName, wgAddress, opts.WGPort, opts.WGPrivKey, opts.MTU, iceBind) + } + + wgIFace := &WGIface{ + userspaceBind: true, + tun: tun, + wgProxyFactory: wgproxy.NewUSPFactory(iceBind), + } + return wgIFace, nil +} diff --git a/client/iface/iface_new_ios.go b/client/iface/iface_new_ios.go new file mode 100644 index 000000000..363f95e11 --- /dev/null +++ b/client/iface/iface_new_ios.go @@ -0,0 +1,26 @@ +//go:build ios + +package iface + +import ( + "github.com/netbirdio/netbird/client/iface/bind" + "github.com/netbirdio/netbird/client/iface/device" + "github.com/netbirdio/netbird/client/iface/wgproxy" +) + +// NewWGIFace Creates a new WireGuard interface instance +func NewWGIFace(opts WGIFaceOpts) (*WGIface, error) { + wgAddress, err := device.ParseWGAddress(opts.Address) + if err != nil { + return nil, err + } + + iceBind := bind.NewICEBind(opts.TransportNet, opts.FilterFn) + + wgIFace := &WGIface{ + tun: device.NewTunDevice(opts.IFaceName, wgAddress, opts.WGPort, opts.WGPrivKey, iceBind, opts.MobileArgs.TunFd), + userspaceBind: true, + wgProxyFactory: wgproxy.NewUSPFactory(iceBind), + } + return wgIFace, nil +} diff --git a/client/iface/iface_new_unix.go b/client/iface/iface_new_unix.go new file mode 100644 index 000000000..f10b17c9a --- /dev/null +++ b/client/iface/iface_new_unix.go @@ -0,0 +1,45 @@ +//go:build (linux && !android) || freebsd + +package iface + +import ( + "fmt" + + "github.com/netbirdio/netbird/client/iface/bind" + "github.com/netbirdio/netbird/client/iface/device" + "github.com/netbirdio/netbird/client/iface/netstack" + "github.com/netbirdio/netbird/client/iface/wgproxy" +) + +// NewWGIFace Creates a new WireGuard interface instance +func NewWGIFace(opts WGIFaceOpts) (*WGIface, error) { + wgAddress, err := device.ParseWGAddress(opts.Address) + if err != nil { + return nil, err + } + + wgIFace := &WGIface{} + + if netstack.IsEnabled() { + iceBind := bind.NewICEBind(opts.TransportNet, opts.FilterFn) + wgIFace.tun = device.NewNetstackDevice(opts.IFaceName, wgAddress, opts.WGPort, opts.WGPrivKey, opts.MTU, iceBind, netstack.ListenAddr()) + wgIFace.userspaceBind = true + wgIFace.wgProxyFactory = wgproxy.NewUSPFactory(iceBind) + return wgIFace, nil + } + + if device.WireGuardModuleIsLoaded() { + wgIFace.tun = device.NewKernelDevice(opts.IFaceName, wgAddress, opts.WGPort, opts.WGPrivKey, opts.MTU, opts.TransportNet) + wgIFace.wgProxyFactory = wgproxy.NewKernelFactory(opts.WGPort) + return wgIFace, nil + } + if device.ModuleTunIsLoaded() { + iceBind := bind.NewICEBind(opts.TransportNet, opts.FilterFn) + wgIFace.tun = device.NewUSPDevice(opts.IFaceName, wgAddress, opts.WGPort, opts.WGPrivKey, opts.MTU, iceBind) + wgIFace.userspaceBind = true + wgIFace.wgProxyFactory = wgproxy.NewUSPFactory(iceBind) + return wgIFace, nil + } + + return nil, fmt.Errorf("couldn't check or load tun module") +} diff --git a/client/iface/iface_new_windows.go b/client/iface/iface_new_windows.go new file mode 100644 index 000000000..2e6355496 --- /dev/null +++ b/client/iface/iface_new_windows.go @@ -0,0 +1,32 @@ +package iface + +import ( + "github.com/netbirdio/netbird/client/iface/bind" + "github.com/netbirdio/netbird/client/iface/device" + "github.com/netbirdio/netbird/client/iface/netstack" + "github.com/netbirdio/netbird/client/iface/wgproxy" +) + +// NewWGIFace Creates a new WireGuard interface instance +func NewWGIFace(opts WGIFaceOpts) (*WGIface, error) { + wgAddress, err := device.ParseWGAddress(opts.Address) + if err != nil { + return nil, err + } + iceBind := bind.NewICEBind(opts.TransportNet, opts.FilterFn) + + var tun WGTunDevice + if netstack.IsEnabled() { + tun = device.NewNetstackDevice(opts.IFaceName, wgAddress, opts.WGPort, opts.WGPrivKey, opts.MTU, iceBind, netstack.ListenAddr()) + } else { + tun = device.NewTunDevice(opts.IFaceName, wgAddress, opts.WGPort, opts.WGPrivKey, opts.MTU, iceBind) + } + + wgIFace := &WGIface{ + userspaceBind: true, + tun: tun, + wgProxyFactory: wgproxy.NewUSPFactory(iceBind), + } + return wgIFace, nil + +} diff --git a/client/iface/iface_test.go b/client/iface/iface_test.go index 87a68addb..85db9cacb 100644 --- a/client/iface/iface_test.go +++ b/client/iface/iface_test.go @@ -45,7 +45,16 @@ func TestWGIface_UpdateAddr(t *testing.T) { t.Fatal(err) } - iface, err := NewWGIFace(ifaceName, addr, wgPort, key, DefaultMTU, newNet, nil, nil) + opts := WGIFaceOpts{ + IFaceName: ifaceName, + Address: addr, + WGPort: wgPort, + WGPrivKey: key, + MTU: DefaultMTU, + TransportNet: newNet, + } + + iface, err := NewWGIFace(opts) if err != nil { t.Fatal(err) } @@ -118,7 +127,16 @@ func Test_CreateInterface(t *testing.T) { if err != nil { t.Fatal(err) } - iface, err := NewWGIFace(ifaceName, wgIP, 33100, key, DefaultMTU, newNet, nil, nil) + opts := WGIFaceOpts{ + IFaceName: ifaceName, + Address: wgIP, + WGPort: 33100, + WGPrivKey: key, + MTU: DefaultMTU, + TransportNet: newNet, + } + + iface, err := NewWGIFace(opts) if err != nil { t.Fatal(err) } @@ -153,7 +171,16 @@ func Test_Close(t *testing.T) { t.Fatal(err) } - iface, err := NewWGIFace(ifaceName, wgIP, wgPort, key, DefaultMTU, newNet, nil, nil) + opts := WGIFaceOpts{ + IFaceName: ifaceName, + Address: wgIP, + WGPort: wgPort, + WGPrivKey: key, + MTU: DefaultMTU, + TransportNet: newNet, + } + + iface, err := NewWGIFace(opts) if err != nil { t.Fatal(err) } @@ -189,7 +216,16 @@ func TestRecreation(t *testing.T) { t.Fatal(err) } - iface, err := NewWGIFace(ifaceName, wgIP, wgPort, key, DefaultMTU, newNet, nil, nil) + opts := WGIFaceOpts{ + IFaceName: ifaceName, + Address: wgIP, + WGPort: wgPort, + WGPrivKey: key, + MTU: DefaultMTU, + TransportNet: newNet, + } + + iface, err := NewWGIFace(opts) if err != nil { t.Fatal(err) } @@ -252,7 +288,15 @@ func Test_ConfigureInterface(t *testing.T) { if err != nil { t.Fatal(err) } - iface, err := NewWGIFace(ifaceName, wgIP, wgPort, key, DefaultMTU, newNet, nil, nil) + opts := WGIFaceOpts{ + IFaceName: ifaceName, + Address: wgIP, + WGPort: wgPort, + WGPrivKey: key, + MTU: DefaultMTU, + TransportNet: newNet, + } + iface, err := NewWGIFace(opts) if err != nil { t.Fatal(err) } @@ -300,7 +344,16 @@ func Test_UpdatePeer(t *testing.T) { t.Fatal(err) } - iface, err := NewWGIFace(ifaceName, wgIP, 33100, key, DefaultMTU, newNet, nil, nil) + opts := WGIFaceOpts{ + IFaceName: ifaceName, + Address: wgIP, + WGPort: 33100, + WGPrivKey: key, + MTU: DefaultMTU, + TransportNet: newNet, + } + + iface, err := NewWGIFace(opts) if err != nil { t.Fatal(err) } @@ -361,7 +414,16 @@ func Test_RemovePeer(t *testing.T) { t.Fatal(err) } - iface, err := NewWGIFace(ifaceName, wgIP, 33100, key, DefaultMTU, newNet, nil, nil) + opts := WGIFaceOpts{ + IFaceName: ifaceName, + Address: wgIP, + WGPort: 33100, + WGPrivKey: key, + MTU: DefaultMTU, + TransportNet: newNet, + } + + iface, err := NewWGIFace(opts) if err != nil { t.Fatal(err) } @@ -418,7 +480,15 @@ func Test_ConnectPeers(t *testing.T) { guid := fmt.Sprintf("{%s}", uuid.New().String()) device.CustomWindowsGUIDString = strings.ToLower(guid) - iface1, err := NewWGIFace(peer1ifaceName, peer1wgIP, peer1wgPort, peer1Key.String(), DefaultMTU, newNet, nil, nil) + optsPeer1 := WGIFaceOpts{ + IFaceName: peer1ifaceName, + Address: peer1wgIP, + WGPort: peer1wgPort, + WGPrivKey: peer1Key.String(), + MTU: DefaultMTU, + TransportNet: newNet, + } + iface1, err := NewWGIFace(optsPeer1) if err != nil { t.Fatal(err) } @@ -432,7 +502,12 @@ func Test_ConnectPeers(t *testing.T) { t.Fatal(err) } - peer1endpoint, err := net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", peer1wgPort)) + localIP, err := getLocalIP() + if err != nil { + t.Fatal(err) + } + + peer1endpoint, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", localIP, peer1wgPort)) if err != nil { t.Fatal(err) } @@ -444,7 +519,17 @@ func Test_ConnectPeers(t *testing.T) { if err != nil { t.Fatal(err) } - iface2, err := NewWGIFace(peer2ifaceName, peer2wgIP, peer2wgPort, peer2Key.String(), DefaultMTU, newNet, nil, nil) + + optsPeer2 := WGIFaceOpts{ + IFaceName: peer2ifaceName, + Address: peer2wgIP, + WGPort: peer2wgPort, + WGPrivKey: peer2Key.String(), + MTU: DefaultMTU, + TransportNet: newNet, + } + + iface2, err := NewWGIFace(optsPeer2) if err != nil { t.Fatal(err) } @@ -458,7 +543,7 @@ func Test_ConnectPeers(t *testing.T) { t.Fatal(err) } - peer2endpoint, err := net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", peer2wgPort)) + peer2endpoint, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", localIP, peer2wgPort)) if err != nil { t.Fatal(err) } @@ -527,3 +612,28 @@ func getPeer(ifaceName, peerPubKey string) (wgtypes.Peer, error) { } return wgtypes.Peer{}, fmt.Errorf("peer not found") } + +func getLocalIP() (string, error) { + // Get all interfaces + addrs, err := net.InterfaceAddrs() + if err != nil { + return "", err + } + + for _, addr := range addrs { + ipNet, ok := addr.(*net.IPNet) + if !ok { + continue + } + if ipNet.IP.IsLoopback() { + continue + } + + if ipNet.IP.To4() == nil { + continue + } + return ipNet.IP.String(), nil + } + + return "", fmt.Errorf("no local IP found") +} diff --git a/client/iface/iface_unix.go b/client/iface/iface_unix.go deleted file mode 100644 index 09dbb2c1f..000000000 --- a/client/iface/iface_unix.go +++ /dev/null @@ -1,49 +0,0 @@ -//go:build (linux && !android) || freebsd - -package iface - -import ( - "fmt" - "runtime" - - "github.com/pion/transport/v3" - - "github.com/netbirdio/netbird/client/iface/bind" - "github.com/netbirdio/netbird/client/iface/device" - "github.com/netbirdio/netbird/client/iface/netstack" -) - -// NewWGIFace Creates a new WireGuard interface instance -func NewWGIFace(iFaceName string, address string, wgPort int, wgPrivKey string, mtu int, transportNet transport.Net, args *device.MobileIFaceArguments, filterFn bind.FilterFn) (*WGIface, error) { - wgAddress, err := device.ParseWGAddress(address) - if err != nil { - return nil, err - } - - wgIFace := &WGIface{} - - // move the kernel/usp/netstack preference evaluation to upper layer - if netstack.IsEnabled() { - wgIFace.tun = device.NewNetstackDevice(iFaceName, wgAddress, wgPort, wgPrivKey, mtu, transportNet, netstack.ListenAddr(), filterFn) - wgIFace.userspaceBind = true - return wgIFace, nil - } - - if device.WireGuardModuleIsLoaded() { - wgIFace.tun = device.NewKernelDevice(iFaceName, wgAddress, wgPort, wgPrivKey, mtu, transportNet) - wgIFace.userspaceBind = false - return wgIFace, nil - } - - if !device.ModuleTunIsLoaded() { - return nil, fmt.Errorf("couldn't check or load tun module") - } - wgIFace.tun = device.NewUSPDevice(iFaceName, wgAddress, wgPort, wgPrivKey, mtu, transportNet, nil) - wgIFace.userspaceBind = true - return wgIFace, nil -} - -// CreateOnAndroid this function make sense on mobile only -func (w *WGIface) CreateOnAndroid([]string, string, []string) error { - return fmt.Errorf("CreateOnAndroid function has not implemented on %s platform", runtime.GOOS) -} diff --git a/client/iface/iface_windows.go b/client/iface/iface_windows.go deleted file mode 100644 index 6845ef3dd..000000000 --- a/client/iface/iface_windows.go +++ /dev/null @@ -1,41 +0,0 @@ -package iface - -import ( - "fmt" - - "github.com/pion/transport/v3" - - "github.com/netbirdio/netbird/client/iface/bind" - "github.com/netbirdio/netbird/client/iface/device" - "github.com/netbirdio/netbird/client/iface/netstack" -) - -// NewWGIFace Creates a new WireGuard interface instance -func NewWGIFace(iFaceName string, address string, wgPort int, wgPrivKey string, mtu int, transportNet transport.Net, args *device.MobileIFaceArguments, filterFn bind.FilterFn) (*WGIface, error) { - wgAddress, err := device.ParseWGAddress(address) - if err != nil { - return nil, err - } - - wgIFace := &WGIface{ - userspaceBind: true, - } - - if netstack.IsEnabled() { - wgIFace.tun = device.NewNetstackDevice(iFaceName, wgAddress, wgPort, wgPrivKey, mtu, transportNet, netstack.ListenAddr(), filterFn) - return wgIFace, nil - } - - wgIFace.tun = device.NewTunDevice(iFaceName, wgAddress, wgPort, wgPrivKey, mtu, transportNet, filterFn) - return wgIFace, nil -} - -// CreateOnAndroid this function make sense on mobile only -func (w *WGIface) CreateOnAndroid([]string, string, []string) error { - return fmt.Errorf("this function has not implemented on non mobile") -} - -// GetInterfaceGUIDString returns an interface GUID. This is useful on Windows only -func (w *WGIface) GetInterfaceGUIDString() (string, error) { - return w.tun.(*device.TunDevice).GetInterfaceGUIDString() -} diff --git a/client/iface/iwginterface.go b/client/iface/iwginterface.go index cb6d7ccd9..f5ab29539 100644 --- a/client/iface/iwginterface.go +++ b/client/iface/iwginterface.go @@ -11,6 +11,7 @@ import ( "github.com/netbirdio/netbird/client/iface/bind" "github.com/netbirdio/netbird/client/iface/configurer" "github.com/netbirdio/netbird/client/iface/device" + "github.com/netbirdio/netbird/client/iface/wgproxy" ) type IWGIface interface { @@ -22,6 +23,7 @@ type IWGIface interface { ToInterface() *net.Interface Up() (*bind.UniversalUDPMuxDefault, error) UpdateAddr(newAddr string) error + GetProxy() wgproxy.Proxy UpdatePeer(peerKey string, allowedIps string, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error RemovePeer(peerKey string) error AddAllowedIP(peerKey string, allowedIP string) error diff --git a/client/iface/iwginterface_windows.go b/client/iface/iwginterface_windows.go index 6baeb66ae..96eec52a5 100644 --- a/client/iface/iwginterface_windows.go +++ b/client/iface/iwginterface_windows.go @@ -9,6 +9,7 @@ import ( "github.com/netbirdio/netbird/client/iface/bind" "github.com/netbirdio/netbird/client/iface/configurer" "github.com/netbirdio/netbird/client/iface/device" + "github.com/netbirdio/netbird/client/iface/wgproxy" ) type IWGIface interface { @@ -20,6 +21,7 @@ type IWGIface interface { ToInterface() *net.Interface Up() (*bind.UniversalUDPMuxDefault, error) UpdateAddr(newAddr string) error + GetProxy() wgproxy.Proxy UpdatePeer(peerKey string, allowedIps string, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error RemovePeer(peerKey string) error AddAllowedIP(peerKey string, allowedIP string) error diff --git a/client/iface/wgproxy/bind/proxy.go b/client/iface/wgproxy/bind/proxy.go new file mode 100644 index 000000000..e986d6d7b --- /dev/null +++ b/client/iface/wgproxy/bind/proxy.go @@ -0,0 +1,137 @@ +package bind + +import ( + "context" + "fmt" + "net" + "net/netip" + "sync" + + log "github.com/sirupsen/logrus" + + "github.com/netbirdio/netbird/client/iface/bind" +) + +type ProxyBind struct { + Bind *bind.ICEBind + + wgAddr *net.UDPAddr + wgEndpoint *bind.Endpoint + remoteConn net.Conn + ctx context.Context + cancel context.CancelFunc + closeMu sync.Mutex + closed bool + + pausedMu sync.Mutex + paused bool + isStarted bool +} + +// AddTurnConn adds a new connection to the bind. +// endpoint is the NetBird address of the remote peer. The SetEndpoint return with the address what will be used in the +// WireGuard configuration. +func (p *ProxyBind) AddTurnConn(ctx context.Context, nbAddr *net.UDPAddr, remoteConn net.Conn) error { + addr, err := p.Bind.SetEndpoint(nbAddr, remoteConn) + if err != nil { + return err + } + + p.wgAddr = addr + p.wgEndpoint = addrToEndpoint(addr) + p.remoteConn = remoteConn + p.ctx, p.cancel = context.WithCancel(ctx) + return err + +} +func (p *ProxyBind) EndpointAddr() *net.UDPAddr { + return p.wgAddr +} + +func (p *ProxyBind) Work() { + if p.remoteConn == nil { + return + } + + p.pausedMu.Lock() + p.paused = false + p.pausedMu.Unlock() + + // Start the proxy only once + if !p.isStarted { + p.isStarted = true + go p.proxyToLocal(p.ctx) + } +} + +func (p *ProxyBind) Pause() { + if p.remoteConn == nil { + return + } + + p.pausedMu.Lock() + p.paused = true + p.pausedMu.Unlock() +} + +func (p *ProxyBind) CloseConn() error { + if p.cancel == nil { + return fmt.Errorf("proxy not started") + } + return p.close() +} + +func (p *ProxyBind) close() error { + p.closeMu.Lock() + defer p.closeMu.Unlock() + + if p.closed { + return nil + } + p.closed = true + + p.cancel() + + p.Bind.RemoveEndpoint(p.wgAddr) + + return p.remoteConn.Close() +} + +func (p *ProxyBind) proxyToLocal(ctx context.Context) { + defer func() { + if err := p.close(); err != nil { + log.Warnf("failed to close remote conn: %s", err) + } + }() + + buf := make([]byte, 1500) + for { + n, err := p.remoteConn.Read(buf) + if err != nil { + if ctx.Err() != nil { + return + } + log.Errorf("failed to read from remote conn: %s, %s", p.remoteConn.RemoteAddr(), err) + return + } + + p.pausedMu.Lock() + if p.paused { + p.pausedMu.Unlock() + continue + } + + msg := bind.RecvMessage{ + Endpoint: p.wgEndpoint, + Buffer: buf[:n], + } + p.Bind.RecvChan <- msg + p.pausedMu.Unlock() + } +} + +func addrToEndpoint(addr *net.UDPAddr) *bind.Endpoint { + ip, _ := netip.AddrFromSlice(addr.IP.To4()) + addrPort := netip.AddrPortFrom(ip, uint16(addr.Port)) + return &bind.Endpoint{AddrPort: addrPort} +} diff --git a/client/internal/wgproxy/ebpf/portlookup.go b/client/iface/wgproxy/ebpf/portlookup.go similarity index 100% rename from client/internal/wgproxy/ebpf/portlookup.go rename to client/iface/wgproxy/ebpf/portlookup.go diff --git a/client/internal/wgproxy/ebpf/portlookup_test.go b/client/iface/wgproxy/ebpf/portlookup_test.go similarity index 100% rename from client/internal/wgproxy/ebpf/portlookup_test.go rename to client/iface/wgproxy/ebpf/portlookup_test.go diff --git a/client/internal/wgproxy/ebpf/proxy.go b/client/iface/wgproxy/ebpf/proxy.go similarity index 99% rename from client/internal/wgproxy/ebpf/proxy.go rename to client/iface/wgproxy/ebpf/proxy.go index e850f4533..e21fc35d4 100644 --- a/client/internal/wgproxy/ebpf/proxy.go +++ b/client/iface/wgproxy/ebpf/proxy.go @@ -119,7 +119,7 @@ func (p *WGEBPFProxy) Free() error { p.ctxCancel() var result *multierror.Error - if p.conn != nil { // p.conn will be nil if we have failed to listen + if p.conn != nil { if err := p.conn.Close(); err != nil { result = multierror.Append(result, err) } diff --git a/client/internal/wgproxy/ebpf/proxy_test.go b/client/iface/wgproxy/ebpf/proxy_test.go similarity index 100% rename from client/internal/wgproxy/ebpf/proxy_test.go rename to client/iface/wgproxy/ebpf/proxy_test.go diff --git a/client/internal/wgproxy/ebpf/wrapper.go b/client/iface/wgproxy/ebpf/wrapper.go similarity index 95% rename from client/internal/wgproxy/ebpf/wrapper.go rename to client/iface/wgproxy/ebpf/wrapper.go index b6a8ac452..efd5fd946 100644 --- a/client/internal/wgproxy/ebpf/wrapper.go +++ b/client/iface/wgproxy/ebpf/wrapper.go @@ -28,7 +28,7 @@ type ProxyWrapper struct { isStarted bool } -func (p *ProxyWrapper) AddTurnConn(ctx context.Context, remoteConn net.Conn) error { +func (p *ProxyWrapper) AddTurnConn(ctx context.Context, endpoint *net.UDPAddr, remoteConn net.Conn) error { addr, err := p.WgeBPFProxy.AddTurnConn(remoteConn) if err != nil { return fmt.Errorf("add turn conn: %w", err) diff --git a/client/iface/wgproxy/factory_kernel.go b/client/iface/wgproxy/factory_kernel.go new file mode 100644 index 000000000..32e96e34f --- /dev/null +++ b/client/iface/wgproxy/factory_kernel.go @@ -0,0 +1,47 @@ +//go:build linux && !android + +package wgproxy + +import ( + log "github.com/sirupsen/logrus" + + "github.com/netbirdio/netbird/client/iface/wgproxy/ebpf" + udpProxy "github.com/netbirdio/netbird/client/iface/wgproxy/udp" +) + +type KernelFactory struct { + wgPort int + + ebpfProxy *ebpf.WGEBPFProxy +} + +func NewKernelFactory(wgPort int) *KernelFactory { + f := &KernelFactory{ + wgPort: wgPort, + } + + ebpfProxy := ebpf.NewWGEBPFProxy(wgPort) + if err := ebpfProxy.Listen(); err != nil { + log.Warnf("failed to initialize ebpf proxy, fallback to user space proxy: %s", err) + return f + } + f.ebpfProxy = ebpfProxy + return f +} + +func (w *KernelFactory) GetProxy() Proxy { + if w.ebpfProxy == nil { + return udpProxy.NewWGUDPProxy(w.wgPort) + } + + return &ebpf.ProxyWrapper{ + WgeBPFProxy: w.ebpfProxy, + } +} + +func (w *KernelFactory) Free() error { + if w.ebpfProxy == nil { + return nil + } + return w.ebpfProxy.Free() +} diff --git a/client/iface/wgproxy/factory_kernel_freebsd.go b/client/iface/wgproxy/factory_kernel_freebsd.go new file mode 100644 index 000000000..7ac2f99a8 --- /dev/null +++ b/client/iface/wgproxy/factory_kernel_freebsd.go @@ -0,0 +1,26 @@ +package wgproxy + +import ( + udpProxy "github.com/netbirdio/netbird/client/iface/wgproxy/udp" +) + +// KernelFactory todo: check eBPF support on FreeBSD +type KernelFactory struct { + wgPort int +} + +func NewKernelFactory(wgPort int) *KernelFactory { + f := &KernelFactory{ + wgPort: wgPort, + } + + return f +} + +func (w *KernelFactory) GetProxy() Proxy { + return udpProxy.NewWGUDPProxy(w.wgPort) +} + +func (w *KernelFactory) Free() error { + return nil +} diff --git a/client/iface/wgproxy/factory_usp.go b/client/iface/wgproxy/factory_usp.go new file mode 100644 index 000000000..99f5ada01 --- /dev/null +++ b/client/iface/wgproxy/factory_usp.go @@ -0,0 +1,27 @@ +package wgproxy + +import ( + "github.com/netbirdio/netbird/client/iface/bind" + proxyBind "github.com/netbirdio/netbird/client/iface/wgproxy/bind" +) + +type USPFactory struct { + bind *bind.ICEBind +} + +func NewUSPFactory(iceBind *bind.ICEBind) *USPFactory { + f := &USPFactory{ + bind: iceBind, + } + return f +} + +func (w *USPFactory) GetProxy() Proxy { + return &proxyBind.ProxyBind{ + Bind: w.bind, + } +} + +func (w *USPFactory) Free() error { + return nil +} diff --git a/client/iface/wgproxy/proxy.go b/client/iface/wgproxy/proxy.go new file mode 100644 index 000000000..243aa2bd2 --- /dev/null +++ b/client/iface/wgproxy/proxy.go @@ -0,0 +1,15 @@ +package wgproxy + +import ( + "context" + "net" +) + +// Proxy is a transfer layer between the relayed connection and the WireGuard +type Proxy interface { + AddTurnConn(ctx context.Context, endpoint *net.UDPAddr, remoteConn net.Conn) error + EndpointAddr() *net.UDPAddr // EndpointAddr returns the address of the WireGuard peer endpoint + Work() // Work start or resume the proxy + Pause() // Pause to forward the packages from remote connection to WireGuard. The opposite way still works. + CloseConn() error +} diff --git a/client/iface/wgproxy/proxy_linux_test.go b/client/iface/wgproxy/proxy_linux_test.go new file mode 100644 index 000000000..298c98cc0 --- /dev/null +++ b/client/iface/wgproxy/proxy_linux_test.go @@ -0,0 +1,56 @@ +//go:build linux && !android + +package wgproxy + +import ( + "context" + "os" + "testing" + + "github.com/netbirdio/netbird/client/iface/wgproxy/ebpf" +) + +func TestProxyCloseByRemoteConnEBPF(t *testing.T) { + if os.Getenv("GITHUB_ACTIONS") != "true" { + t.Skip("Skipping test as it requires root privileges") + } + ctx := context.Background() + + ebpfProxy := ebpf.NewWGEBPFProxy(51831) + if err := ebpfProxy.Listen(); err != nil { + t.Fatalf("failed to initialize ebpf proxy: %s", err) + } + + defer func() { + if err := ebpfProxy.Free(); err != nil { + t.Errorf("failed to free ebpf proxy: %s", err) + } + }() + + tests := []struct { + name string + proxy Proxy + }{ + { + name: "ebpf proxy", + proxy: &ebpf.ProxyWrapper{ + WgeBPFProxy: ebpfProxy, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + relayedConn := newMockConn() + err := tt.proxy.AddTurnConn(ctx, nil, relayedConn) + if err != nil { + t.Errorf("error: %v", err) + } + + _ = relayedConn.Close() + if err := tt.proxy.CloseConn(); err != nil { + t.Errorf("error: %v", err) + } + }) + } +} diff --git a/client/internal/wgproxy/proxy_test.go b/client/iface/wgproxy/proxy_test.go similarity index 90% rename from client/internal/wgproxy/proxy_test.go rename to client/iface/wgproxy/proxy_test.go index b88ff3f83..64b617621 100644 --- a/client/internal/wgproxy/proxy_test.go +++ b/client/iface/wgproxy/proxy_test.go @@ -11,8 +11,8 @@ import ( "testing" "time" - "github.com/netbirdio/netbird/client/internal/wgproxy/ebpf" - "github.com/netbirdio/netbird/client/internal/wgproxy/usp" + "github.com/netbirdio/netbird/client/iface/wgproxy/ebpf" + udpProxy "github.com/netbirdio/netbird/client/iface/wgproxy/udp" "github.com/netbirdio/netbird/util" ) @@ -84,7 +84,7 @@ func TestProxyCloseByRemoteConn(t *testing.T) { }{ { name: "userspace proxy", - proxy: usp.NewWGUserSpaceProxy(51830), + proxy: udpProxy.NewWGUDPProxy(51830), }, } @@ -114,7 +114,7 @@ func TestProxyCloseByRemoteConn(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { relayedConn := newMockConn() - err := tt.proxy.AddTurnConn(ctx, relayedConn) + err := tt.proxy.AddTurnConn(ctx, nil, relayedConn) if err != nil { t.Errorf("error: %v", err) } diff --git a/client/internal/wgproxy/usp/proxy.go b/client/iface/wgproxy/udp/proxy.go similarity index 83% rename from client/internal/wgproxy/usp/proxy.go rename to client/iface/wgproxy/udp/proxy.go index f73500717..8bee09901 100644 --- a/client/internal/wgproxy/usp/proxy.go +++ b/client/iface/wgproxy/udp/proxy.go @@ -1,4 +1,4 @@ -package usp +package udp import ( "context" @@ -12,8 +12,8 @@ import ( "github.com/netbirdio/netbird/client/errors" ) -// WGUserSpaceProxy proxies -type WGUserSpaceProxy struct { +// WGUDPProxy proxies +type WGUDPProxy struct { localWGListenPort int remoteConn net.Conn @@ -28,10 +28,10 @@ type WGUserSpaceProxy struct { isStarted bool } -// NewWGUserSpaceProxy instantiate a user space WireGuard proxy. This is not a thread safe implementation -func NewWGUserSpaceProxy(wgPort int) *WGUserSpaceProxy { +// NewWGUDPProxy instantiate a UDP based WireGuard proxy. This is not a thread safe implementation +func NewWGUDPProxy(wgPort int) *WGUDPProxy { log.Debugf("Initializing new user space proxy with port %d", wgPort) - p := &WGUserSpaceProxy{ + p := &WGUDPProxy{ localWGListenPort: wgPort, } return p @@ -42,7 +42,7 @@ func NewWGUserSpaceProxy(wgPort int) *WGUserSpaceProxy { // the connection is complete, an error is returned. Once successfully // connected, any expiration of the context will not affect the // connection. -func (p *WGUserSpaceProxy) AddTurnConn(ctx context.Context, remoteConn net.Conn) error { +func (p *WGUDPProxy) AddTurnConn(ctx context.Context, endpoint *net.UDPAddr, remoteConn net.Conn) error { dialer := net.Dialer{} localConn, err := dialer.DialContext(ctx, "udp", fmt.Sprintf(":%d", p.localWGListenPort)) if err != nil { @@ -57,7 +57,7 @@ func (p *WGUserSpaceProxy) AddTurnConn(ctx context.Context, remoteConn net.Conn) return err } -func (p *WGUserSpaceProxy) EndpointAddr() *net.UDPAddr { +func (p *WGUDPProxy) EndpointAddr() *net.UDPAddr { if p.localConn == nil { return nil } @@ -66,7 +66,7 @@ func (p *WGUserSpaceProxy) EndpointAddr() *net.UDPAddr { } // Work starts the proxy or resumes it if it was paused -func (p *WGUserSpaceProxy) Work() { +func (p *WGUDPProxy) Work() { if p.remoteConn == nil { return } @@ -83,7 +83,7 @@ func (p *WGUserSpaceProxy) Work() { } // Pause pauses the proxy from receiving data from the remote peer -func (p *WGUserSpaceProxy) Pause() { +func (p *WGUDPProxy) Pause() { if p.remoteConn == nil { return } @@ -94,14 +94,14 @@ func (p *WGUserSpaceProxy) Pause() { } // CloseConn close the localConn -func (p *WGUserSpaceProxy) CloseConn() error { +func (p *WGUDPProxy) CloseConn() error { if p.cancel == nil { return fmt.Errorf("proxy not started") } return p.close() } -func (p *WGUserSpaceProxy) close() error { +func (p *WGUDPProxy) close() error { p.closeMu.Lock() defer p.closeMu.Unlock() @@ -125,7 +125,7 @@ func (p *WGUserSpaceProxy) close() error { } // proxyToRemote proxies from Wireguard to the RemoteKey -func (p *WGUserSpaceProxy) proxyToRemote(ctx context.Context) { +func (p *WGUDPProxy) proxyToRemote(ctx context.Context) { defer func() { if err := p.close(); err != nil { log.Warnf("error in proxy to remote loop: %s", err) @@ -157,7 +157,7 @@ func (p *WGUserSpaceProxy) proxyToRemote(ctx context.Context) { // proxyToLocal proxies from the Remote peer to local WireGuard // if the proxy is paused it will drain the remote conn and drop the packets -func (p *WGUserSpaceProxy) proxyToLocal(ctx context.Context) { +func (p *WGUDPProxy) proxyToLocal(ctx context.Context) { defer func() { if err := p.close(); err != nil { log.Warnf("error in proxy to local loop: %s", err) diff --git a/client/internal/dns/server_test.go b/client/internal/dns/server_test.go index 53d18a678..4a5aff3ea 100644 --- a/client/internal/dns/server_test.go +++ b/client/internal/dns/server_test.go @@ -267,7 +267,17 @@ func TestUpdateDNSServer(t *testing.T) { if err != nil { t.Fatal(err) } - wgIface, err := iface.NewWGIFace(fmt.Sprintf("utun230%d", n), fmt.Sprintf("100.66.100.%d/32", n+1), 33100, privKey.String(), iface.DefaultMTU, newNet, nil, nil) + + opts := iface.WGIFaceOpts{ + IFaceName: fmt.Sprintf("utun230%d", n), + Address: fmt.Sprintf("100.66.100.%d/32", n+1), + WGPort: 33100, + WGPrivKey: privKey.String(), + MTU: iface.DefaultMTU, + TransportNet: newNet, + } + + wgIface, err := iface.NewWGIFace(opts) if err != nil { t.Fatal(err) } @@ -345,7 +355,15 @@ func TestDNSFakeResolverHandleUpdates(t *testing.T) { } privKey, _ := wgtypes.GeneratePrivateKey() - wgIface, err := iface.NewWGIFace("utun2301", "100.66.100.1/32", 33100, privKey.String(), iface.DefaultMTU, newNet, nil, nil) + opts := iface.WGIFaceOpts{ + IFaceName: "utun2301", + Address: "100.66.100.1/32", + WGPort: 33100, + WGPrivKey: privKey.String(), + MTU: iface.DefaultMTU, + TransportNet: newNet, + } + wgIface, err := iface.NewWGIFace(opts) if err != nil { t.Errorf("build interface wireguard: %v", err) return @@ -803,7 +821,17 @@ func createWgInterfaceWithBind(t *testing.T) (*iface.WGIface, error) { } privKey, _ := wgtypes.GeneratePrivateKey() - wgIface, err := iface.NewWGIFace("utun2301", "100.66.100.2/24", 33100, privKey.String(), iface.DefaultMTU, newNet, nil, nil) + + opts := iface.WGIFaceOpts{ + IFaceName: "utun2301", + Address: "100.66.100.2/24", + WGPort: 33100, + WGPrivKey: privKey.String(), + MTU: iface.DefaultMTU, + TransportNet: newNet, + } + + wgIface, err := iface.NewWGIFace(opts) if err != nil { t.Fatalf("build interface wireguard: %v", err) return nil, err diff --git a/client/internal/engine.go b/client/internal/engine.go index eac8ec098..459518de1 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -35,7 +35,6 @@ import ( "github.com/netbirdio/netbird/client/internal/rosenpass" "github.com/netbirdio/netbird/client/internal/routemanager" "github.com/netbirdio/netbird/client/internal/routemanager/systemops" - "github.com/netbirdio/netbird/client/internal/wgproxy" nbssh "github.com/netbirdio/netbird/client/ssh" "github.com/netbirdio/netbird/client/system" nbdns "github.com/netbirdio/netbird/dns" @@ -141,8 +140,7 @@ type Engine struct { ctx context.Context cancel context.CancelFunc - wgInterface iface.IWGIface - wgProxyFactory *wgproxy.Factory + wgInterface iface.IWGIface udpMux *bind.UniversalUDPMuxDefault @@ -299,9 +297,6 @@ func (e *Engine) Start() error { } e.wgInterface = wgIface - userspace := e.wgInterface.IsUserspaceBind() - e.wgProxyFactory = wgproxy.NewFactory(userspace, e.config.WgPort) - if e.config.RosenpassEnabled { log.Infof("rosenpass is enabled") if e.config.RosenpassPermissive { @@ -966,7 +961,7 @@ func (e *Engine) createPeerConn(pubKey string, allowedIPs string) (*peer.Conn, e }, } - peerConn, err := peer.NewConn(e.ctx, config, e.statusRecorder, e.wgProxyFactory, e.signaler, e.mobileDep.IFaceDiscover, e.relayManager) + peerConn, err := peer.NewConn(e.ctx, config, e.statusRecorder, e.signaler, e.mobileDep.IFaceDiscover, e.relayManager) if err != nil { return nil, err } @@ -1117,12 +1112,6 @@ func (e *Engine) parseNATExternalIPMappings() []string { } func (e *Engine) close() { - if e.wgProxyFactory != nil { - if err := e.wgProxyFactory.Free(); err != nil { - log.Errorf("failed closing ebpf proxy: %s", err) - } - } - log.Debugf("removing Netbird interface %s", e.config.WgIfaceName) if e.wgInterface != nil { if err := e.wgInterface.Close(); err != nil { @@ -1167,21 +1156,29 @@ func (e *Engine) newWgIface() (*iface.WGIface, error) { log.Errorf("failed to create pion's stdnet: %s", err) } - var mArgs *device.MobileIFaceArguments + opts := iface.WGIFaceOpts{ + IFaceName: e.config.WgIfaceName, + Address: e.config.WgAddr, + WGPort: e.config.WgPort, + WGPrivKey: e.config.WgPrivateKey.String(), + MTU: iface.DefaultMTU, + TransportNet: transportNet, + FilterFn: e.addrViaRoutes, + } + switch runtime.GOOS { case "android": - mArgs = &device.MobileIFaceArguments{ + opts.MobileArgs = &device.MobileIFaceArguments{ TunAdapter: e.mobileDep.TunAdapter, TunFd: int(e.mobileDep.FileDescriptor), } case "ios": - mArgs = &device.MobileIFaceArguments{ + opts.MobileArgs = &device.MobileIFaceArguments{ TunFd: int(e.mobileDep.FileDescriptor), } - default: } - return iface.NewWGIFace(e.config.WgIfaceName, e.config.WgAddr, e.config.WgPort, e.config.WgPrivateKey.String(), iface.DefaultMTU, transportNet, mArgs, e.addrViaRoutes) + return iface.NewWGIFace(opts) } func (e *Engine) wgInterfaceCreate() (err error) { diff --git a/client/internal/engine_test.go b/client/internal/engine_test.go index 74b10ee44..d0ba1fffc 100644 --- a/client/internal/engine_test.go +++ b/client/internal/engine_test.go @@ -602,7 +602,16 @@ func TestEngine_UpdateNetworkMapWithRoutes(t *testing.T) { if err != nil { t.Fatal(err) } - engine.wgInterface, err = iface.NewWGIFace(wgIfaceName, wgAddr, engine.config.WgPort, key.String(), iface.DefaultMTU, newNet, nil, nil) + + opts := iface.WGIFaceOpts{ + IFaceName: wgIfaceName, + Address: wgAddr, + WGPort: engine.config.WgPort, + WGPrivKey: key.String(), + MTU: iface.DefaultMTU, + TransportNet: newNet, + } + engine.wgInterface, err = iface.NewWGIFace(opts) assert.NoError(t, err, "shouldn't return error") input := struct { inputSerial uint64 @@ -774,7 +783,15 @@ func TestEngine_UpdateNetworkMapWithDNSUpdate(t *testing.T) { if err != nil { t.Fatal(err) } - engine.wgInterface, err = iface.NewWGIFace(wgIfaceName, wgAddr, 33100, key.String(), iface.DefaultMTU, newNet, nil, nil) + opts := iface.WGIFaceOpts{ + IFaceName: wgIfaceName, + Address: wgAddr, + WGPort: 33100, + WGPrivKey: key.String(), + MTU: iface.DefaultMTU, + TransportNet: newNet, + } + engine.wgInterface, err = iface.NewWGIFace(opts) assert.NoError(t, err, "shouldn't return error") mockRouteManager := &routemanager.MockManager{ diff --git a/client/internal/peer/conn.go b/client/internal/peer/conn.go index 1b740388d..99acfde31 100644 --- a/client/internal/peer/conn.go +++ b/client/internal/peer/conn.go @@ -17,8 +17,8 @@ import ( "github.com/netbirdio/netbird/client/iface" "github.com/netbirdio/netbird/client/iface/configurer" + "github.com/netbirdio/netbird/client/iface/wgproxy" "github.com/netbirdio/netbird/client/internal/stdnet" - "github.com/netbirdio/netbird/client/internal/wgproxy" relayClient "github.com/netbirdio/netbird/relay/client" "github.com/netbirdio/netbird/route" nbnet "github.com/netbirdio/netbird/util/net" @@ -81,11 +81,10 @@ type Conn struct { ctxCancel context.CancelFunc config ConnConfig statusRecorder *Status - wgProxyFactory *wgproxy.Factory signaler *Signaler - iFaceDiscover stdnet.ExternalIFaceDiscover relayManager *relayClient.Manager - allowedIPsIP string + allowedIP net.IP + allowedNet string handshaker *Handshaker onConnected func(remoteWireGuardKey string, remoteRosenpassPubKey []byte, wireGuardIP string, remoteRosenpassAddr string) @@ -116,8 +115,8 @@ type Conn struct { // NewConn creates a new not opened Conn to the remote peer. // To establish a connection run Conn.Open -func NewConn(engineCtx context.Context, config ConnConfig, statusRecorder *Status, wgProxyFactory *wgproxy.Factory, signaler *Signaler, iFaceDiscover stdnet.ExternalIFaceDiscover, relayManager *relayClient.Manager) (*Conn, error) { - _, allowedIPsIP, err := net.ParseCIDR(config.WgConfig.AllowedIps) +func NewConn(engineCtx context.Context, config ConnConfig, statusRecorder *Status, signaler *Signaler, iFaceDiscover stdnet.ExternalIFaceDiscover, relayManager *relayClient.Manager) (*Conn, error) { + allowedIP, allowedNet, err := net.ParseCIDR(config.WgConfig.AllowedIps) if err != nil { log.Errorf("failed to parse allowedIPS: %v", err) return nil, err @@ -127,19 +126,17 @@ func NewConn(engineCtx context.Context, config ConnConfig, statusRecorder *Statu connLog := log.WithField("peer", config.Key) var conn = &Conn{ - log: connLog, - ctx: ctx, - ctxCancel: ctxCancel, - config: config, - statusRecorder: statusRecorder, - wgProxyFactory: wgProxyFactory, - signaler: signaler, - iFaceDiscover: iFaceDiscover, - relayManager: relayManager, - allowedIPsIP: allowedIPsIP.String(), - statusRelay: NewAtomicConnStatus(), - statusICE: NewAtomicConnStatus(), - + log: connLog, + ctx: ctx, + ctxCancel: ctxCancel, + config: config, + statusRecorder: statusRecorder, + signaler: signaler, + relayManager: relayManager, + allowedIP: allowedIP, + allowedNet: allowedNet.String(), + statusRelay: NewAtomicConnStatus(), + statusICE: NewAtomicConnStatus(), iCEDisconnected: make(chan bool, 1), relayDisconnected: make(chan bool, 1), } @@ -692,7 +689,7 @@ func (conn *Conn) doOnConnected(remoteRosenpassPubKey []byte, remoteRosenpassAdd } if conn.onConnected != nil { - conn.onConnected(conn.config.Key, remoteRosenpassPubKey, conn.allowedIPsIP, remoteRosenpassAddr) + conn.onConnected(conn.config.Key, remoteRosenpassPubKey, conn.allowedNet, remoteRosenpassAddr) } } @@ -783,8 +780,13 @@ func (conn *Conn) freeUpConnID() { func (conn *Conn) newProxy(remoteConn net.Conn) (wgproxy.Proxy, error) { conn.log.Debugf("setup proxied WireGuard connection") - wgProxy := conn.wgProxyFactory.GetProxy() - if err := wgProxy.AddTurnConn(conn.ctx, remoteConn); err != nil { + udpAddr := &net.UDPAddr{ + IP: conn.allowedIP, + Port: conn.config.WgConfig.WgListenPort, + } + + wgProxy := conn.config.WgConfig.WgInterface.GetProxy() + if err := wgProxy.AddTurnConn(conn.ctx, udpAddr, remoteConn); err != nil { conn.log.Errorf("failed to add turn net.Conn to local proxy: %v", err) return nil, err } diff --git a/client/internal/peer/conn_test.go b/client/internal/peer/conn_test.go index b4926a9d2..e68861c5f 100644 --- a/client/internal/peer/conn_test.go +++ b/client/internal/peer/conn_test.go @@ -11,7 +11,6 @@ import ( "github.com/netbirdio/netbird/client/iface" "github.com/netbirdio/netbird/client/internal/stdnet" - "github.com/netbirdio/netbird/client/internal/wgproxy" "github.com/netbirdio/netbird/util" ) @@ -44,11 +43,7 @@ func TestNewConn_interfaceFilter(t *testing.T) { } func TestConn_GetKey(t *testing.T) { - wgProxyFactory := wgproxy.NewFactory(false, connConf.LocalWgPort) - defer func() { - _ = wgProxyFactory.Free() - }() - conn, err := NewConn(context.Background(), connConf, nil, wgProxyFactory, nil, nil, nil) + conn, err := NewConn(context.Background(), connConf, nil, nil, nil, nil) if err != nil { return } @@ -59,11 +54,7 @@ func TestConn_GetKey(t *testing.T) { } func TestConn_OnRemoteOffer(t *testing.T) { - wgProxyFactory := wgproxy.NewFactory(false, connConf.LocalWgPort) - defer func() { - _ = wgProxyFactory.Free() - }() - conn, err := NewConn(context.Background(), connConf, NewRecorder("https://mgm"), wgProxyFactory, nil, nil, nil) + conn, err := NewConn(context.Background(), connConf, NewRecorder("https://mgm"), nil, nil, nil) if err != nil { return } @@ -96,11 +87,7 @@ func TestConn_OnRemoteOffer(t *testing.T) { } func TestConn_OnRemoteAnswer(t *testing.T) { - wgProxyFactory := wgproxy.NewFactory(false, connConf.LocalWgPort) - defer func() { - _ = wgProxyFactory.Free() - }() - conn, err := NewConn(context.Background(), connConf, NewRecorder("https://mgm"), wgProxyFactory, nil, nil, nil) + conn, err := NewConn(context.Background(), connConf, NewRecorder("https://mgm"), nil, nil, nil) if err != nil { return } @@ -132,11 +119,7 @@ func TestConn_OnRemoteAnswer(t *testing.T) { wg.Wait() } func TestConn_Status(t *testing.T) { - wgProxyFactory := wgproxy.NewFactory(false, connConf.LocalWgPort) - defer func() { - _ = wgProxyFactory.Free() - }() - conn, err := NewConn(context.Background(), connConf, NewRecorder("https://mgm"), wgProxyFactory, nil, nil, nil) + conn, err := NewConn(context.Background(), connConf, NewRecorder("https://mgm"), nil, nil, nil) if err != nil { return } diff --git a/client/internal/routemanager/manager_test.go b/client/internal/routemanager/manager_test.go index 2f26f7a5e..044a996c7 100644 --- a/client/internal/routemanager/manager_test.go +++ b/client/internal/routemanager/manager_test.go @@ -407,7 +407,15 @@ func TestManagerUpdateRoutes(t *testing.T) { if err != nil { t.Fatal(err) } - wgInterface, err := iface.NewWGIFace(fmt.Sprintf("utun43%d", n), "100.65.65.2/24", 33100, peerPrivateKey.String(), iface.DefaultMTU, newNet, nil, nil) + opts := iface.WGIFaceOpts{ + IFaceName: fmt.Sprintf("utun43%d", n), + Address: "100.65.65.2/24", + WGPort: 33100, + WGPrivKey: peerPrivateKey.String(), + MTU: iface.DefaultMTU, + TransportNet: newNet, + } + wgInterface, err := iface.NewWGIFace(opts) require.NoError(t, err, "should create testing WGIface interface") defer wgInterface.Close() diff --git a/client/internal/routemanager/systemops/systemops_generic_test.go b/client/internal/routemanager/systemops/systemops_generic_test.go index 238225807..ce5b6b843 100644 --- a/client/internal/routemanager/systemops/systemops_generic_test.go +++ b/client/internal/routemanager/systemops/systemops_generic_test.go @@ -61,7 +61,14 @@ func TestAddRemoveRoutes(t *testing.T) { if err != nil { t.Fatal(err) } - wgInterface, err := iface.NewWGIFace(fmt.Sprintf("utun53%d", n), "100.65.75.2/24", 33100, peerPrivateKey.String(), iface.DefaultMTU, newNet, nil, nil) + opts := iface.WGIFaceOpts{ + IFaceName: fmt.Sprintf("utun53%d", n), + Address: "100.65.75.2/24", + WGPrivKey: peerPrivateKey.String(), + MTU: iface.DefaultMTU, + TransportNet: newNet, + } + wgInterface, err := iface.NewWGIFace(opts) require.NoError(t, err, "should create testing WGIface interface") defer wgInterface.Close() @@ -213,7 +220,15 @@ func TestAddExistAndRemoveRoute(t *testing.T) { if err != nil { t.Fatal(err) } - wgInterface, err := iface.NewWGIFace(fmt.Sprintf("utun53%d", n), "100.65.75.2/24", 33100, peerPrivateKey.String(), iface.DefaultMTU, newNet, nil, nil) + opts := iface.WGIFaceOpts{ + IFaceName: fmt.Sprintf("utun53%d", n), + Address: "100.65.75.2/24", + WGPort: 33100, + WGPrivKey: peerPrivateKey.String(), + MTU: iface.DefaultMTU, + TransportNet: newNet, + } + wgInterface, err := iface.NewWGIFace(opts) require.NoError(t, err, "should create testing WGIface interface") defer wgInterface.Close() @@ -345,7 +360,15 @@ func createWGInterface(t *testing.T, interfaceName, ipAddressCIDR string, listen newNet, err := stdnet.NewNet() require.NoError(t, err) - wgInterface, err := iface.NewWGIFace(interfaceName, ipAddressCIDR, listenPort, peerPrivateKey.String(), iface.DefaultMTU, newNet, nil, nil) + opts := iface.WGIFaceOpts{ + IFaceName: interfaceName, + Address: ipAddressCIDR, + WGPrivKey: peerPrivateKey.String(), + WGPort: listenPort, + MTU: iface.DefaultMTU, + TransportNet: newNet, + } + wgInterface, err := iface.NewWGIFace(opts) require.NoError(t, err, "should create testing WireGuard interface") err = wgInterface.Create() diff --git a/client/internal/wgproxy/factory_linux.go b/client/internal/wgproxy/factory_linux.go deleted file mode 100644 index 369ba99db..000000000 --- a/client/internal/wgproxy/factory_linux.go +++ /dev/null @@ -1,50 +0,0 @@ -//go:build !android - -package wgproxy - -import ( - log "github.com/sirupsen/logrus" - - "github.com/netbirdio/netbird/client/internal/wgproxy/ebpf" - "github.com/netbirdio/netbird/client/internal/wgproxy/usp" -) - -type Factory struct { - wgPort int - ebpfProxy *ebpf.WGEBPFProxy -} - -func NewFactory(userspace bool, wgPort int) *Factory { - f := &Factory{wgPort: wgPort} - - if userspace { - return f - } - - ebpfProxy := ebpf.NewWGEBPFProxy(wgPort) - err := ebpfProxy.Listen() - if err != nil { - log.Warnf("failed to initialize ebpf proxy, fallback to user space proxy: %s", err) - return f - } - - f.ebpfProxy = ebpfProxy - return f -} - -func (w *Factory) GetProxy() Proxy { - if w.ebpfProxy != nil { - p := &ebpf.ProxyWrapper{ - WgeBPFProxy: w.ebpfProxy, - } - return p - } - return usp.NewWGUserSpaceProxy(w.wgPort) -} - -func (w *Factory) Free() error { - if w.ebpfProxy == nil { - return nil - } - return w.ebpfProxy.Free() -} diff --git a/client/internal/wgproxy/factory_nonlinux.go b/client/internal/wgproxy/factory_nonlinux.go deleted file mode 100644 index f930b09b3..000000000 --- a/client/internal/wgproxy/factory_nonlinux.go +++ /dev/null @@ -1,21 +0,0 @@ -//go:build !linux || android - -package wgproxy - -import "github.com/netbirdio/netbird/client/internal/wgproxy/usp" - -type Factory struct { - wgPort int -} - -func NewFactory(_ bool, wgPort int) *Factory { - return &Factory{wgPort: wgPort} -} - -func (w *Factory) GetProxy() Proxy { - return usp.NewWGUserSpaceProxy(w.wgPort) -} - -func (w *Factory) Free() error { - return nil -} diff --git a/client/internal/wgproxy/proxy.go b/client/internal/wgproxy/proxy.go deleted file mode 100644 index 558121cdd..000000000 --- a/client/internal/wgproxy/proxy.go +++ /dev/null @@ -1,15 +0,0 @@ -package wgproxy - -import ( - "context" - "net" -) - -// Proxy is a transfer layer between the relayed connection and the WireGuard -type Proxy interface { - AddTurnConn(ctx context.Context, turnConn net.Conn) error - EndpointAddr() *net.UDPAddr - Work() - Pause() - CloseConn() error -}