diff --git a/client/internal/engine.go b/client/internal/engine.go index 3011495db..7255cf49e 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -19,7 +19,6 @@ import ( "github.com/netbirdio/netbird/client/internal/dns" "github.com/netbirdio/netbird/client/internal/peer" - "github.com/netbirdio/netbird/client/internal/proxy" "github.com/netbirdio/netbird/client/internal/routemanager" nbssh "github.com/netbirdio/netbird/client/ssh" nbdns "github.com/netbirdio/netbird/dns" @@ -247,7 +246,7 @@ func (e *Engine) modifyPeers(peersUpdate []*mgmProto.RemotePeerConfig) error { for _, p := range peersUpdate { peerPubKey := p.GetWgPubKey() if peerConn, ok := e.peerConns[peerPubKey]; ok { - if peerConn.GetConf().ProxyConfig.AllowedIps != strings.Join(p.AllowedIps, ",") { + if peerConn.WgConfig().AllowedIps != strings.Join(p.AllowedIps, ",") { modified = append(modified, p) continue } @@ -758,9 +757,7 @@ func (e *Engine) connWorker(conn *peer.Conn, peerKey string) { // we might have received new STUN and TURN servers meanwhile, so update them e.syncMsgMux.Lock() - conf := conn.GetConf() - conf.StunTurn = append(e.STUNs, e.TURNs...) - conn.UpdateConf(conf) + conn.UpdateStunTurn(append(e.STUNs, e.TURNs...)) e.syncMsgMux.Unlock() err := conn.Open() @@ -789,9 +786,9 @@ func (e Engine) createPeerConn(pubKey string, allowedIPs string) (*peer.Conn, er stunTurn = append(stunTurn, e.STUNs...) stunTurn = append(stunTurn, e.TURNs...) - proxyConfig := proxy.Config{ + wgConfig := peer.WgConfig{ RemoteKey: pubKey, - WgListenAddr: fmt.Sprintf("127.0.0.1:%d", e.config.WgPort), + WgListenPort: e.config.WgPort, WgInterface: e.wgInterface, AllowedIps: allowedIPs, PreSharedKey: e.config.PreSharedKey, @@ -808,7 +805,7 @@ func (e Engine) createPeerConn(pubKey string, allowedIPs string) (*peer.Conn, er Timeout: timeout, UDPMux: e.udpMux.UDPMuxDefault, UDPMuxSrflx: e.udpMux, - ProxyConfig: proxyConfig, + WgConfig: wgConfig, LocalWgPort: e.config.WgPort, NATExternalIPs: e.parseNATExternalIPMappings(), UserspaceBind: e.wgInterface.IsUserspaceBind(), diff --git a/client/internal/peer/conn.go b/client/internal/peer/conn.go index 088010ef3..a968c83e4 100644 --- a/client/internal/peer/conn.go +++ b/client/internal/peer/conn.go @@ -12,8 +12,8 @@ import ( "github.com/pion/ice/v2" log "github.com/sirupsen/logrus" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" - "github.com/netbirdio/netbird/client/internal/proxy" "github.com/netbirdio/netbird/client/internal/stdnet" "github.com/netbirdio/netbird/iface" "github.com/netbirdio/netbird/iface/bind" @@ -28,8 +28,18 @@ const ( iceKeepAliveDefault = 4 * time.Second iceDisconnectedTimeoutDefault = 6 * time.Second + + defaultWgKeepAlive = 25 * time.Second ) +type WgConfig struct { + WgListenPort int + RemoteKey string + WgInterface *iface.WGIface + AllowedIps string + PreSharedKey *wgtypes.Key +} + // ConnConfig is a peer Connection configuration type ConnConfig struct { @@ -48,7 +58,7 @@ type ConnConfig struct { Timeout time.Duration - ProxyConfig proxy.Config + WgConfig WgConfig UDPMux ice.UDPMux UDPMuxSrflx ice.UniversalUDPMux @@ -103,7 +113,7 @@ type Conn struct { statusRecorder *Status - proxy proxy.Proxy + proxy *WireGuardProxy remoteModeCh chan ModeMessage meta meta @@ -127,9 +137,14 @@ func (conn *Conn) GetConf() ConnConfig { return conn.config } -// UpdateConf updates the connection config -func (conn *Conn) UpdateConf(conf ConnConfig) { - conn.config = conf +// WgConfig returns the WireGuard config +func (conn *Conn) WgConfig() WgConfig { + return conn.config.WgConfig +} + +// UpdateStunTurn update the turn and stun addresses +func (conn *Conn) UpdateStunTurn(turnStun []*ice.URL) { + conn.config.StunTurn = turnStun } // NewConn creates a new not opened Conn to the remote peer. @@ -240,12 +255,12 @@ func readICEAgentConfigProperties() (time.Duration, time.Duration) { func (conn *Conn) Open() error { log.Debugf("trying to connect to peer %s", conn.config.Key) - peerState := State{PubKey: conn.config.Key} - - peerState.IP = strings.Split(conn.config.ProxyConfig.AllowedIps, "/")[0] - peerState.ConnStatusUpdate = time.Now() - peerState.ConnStatus = conn.status - + peerState := State{ + PubKey: conn.config.Key, + IP: strings.Split(conn.config.WgConfig.AllowedIps, "/")[0], + ConnStatusUpdate: time.Now(), + ConnStatus: conn.status, + } err := conn.statusRecorder.UpdatePeerState(peerState) if err != nil { log.Warnf("erro while updating the state of peer %s,err: %v", conn.config.Key, err) @@ -300,10 +315,11 @@ func (conn *Conn) Open() error { defer conn.notifyDisconnected() conn.mu.Unlock() - peerState = State{PubKey: conn.config.Key} - - peerState.ConnStatus = conn.status - peerState.ConnStatusUpdate = time.Now() + peerState = State{ + PubKey: conn.config.Key, + ConnStatus: conn.status, + ConnStatusUpdate: time.Now(), + } err = conn.statusRecorder.UpdatePeerState(peerState) if err != nil { log.Warnf("erro while updating the state of peer %s,err: %v", conn.config.Key, err) @@ -334,19 +350,12 @@ func (conn *Conn) Open() error { remoteWgPort = remoteOfferAnswer.WgListenPort } // the ice connection has been established successfully so we are ready to start the proxy - err = conn.startProxy(remoteConn, remoteWgPort) + remoteAddr, err := conn.configureConnection(remoteConn, remoteWgPort) if err != nil { return err } - if conn.proxy.Type() == proxy.TypeDirectNoProxy { - host, _, _ := net.SplitHostPort(remoteConn.LocalAddr().String()) - rhost, _, _ := net.SplitHostPort(remoteConn.RemoteAddr().String()) - // direct Wireguard connection - log.Infof("directly connected to peer %s [laddr <-> raddr] [%s:%d <-> %s:%d]", conn.config.Key, host, conn.config.LocalWgPort, rhost, remoteWgPort) - } else { - log.Infof("connected to peer %s [laddr <-> raddr] [%s <-> %s]", conn.config.Key, remoteConn.LocalAddr().String(), remoteConn.RemoteAddr().String()) - } + log.Infof("connected to peer %s, proxy: %v, remote address: %s", conn.config.Key, conn.proxy != nil, remoteAddr.String()) // wait until connection disconnected or has been closed externally (upper layer, e.g. engine) select { @@ -363,54 +372,58 @@ func isRelayCandidate(candidate ice.Candidate) bool { return candidate.Type() == ice.CandidateTypeRelay } -// startProxy starts proxying traffic from/to local Wireguard and sets connection status to StatusConnected -func (conn *Conn) startProxy(remoteConn net.Conn, remoteWgPort int) error { +// configureConnection starts proxying traffic from/to local Wireguard and sets connection status to StatusConnected +func (conn *Conn) configureConnection(remoteConn net.Conn, remoteWgPort int) (net.Addr, error) { conn.mu.Lock() defer conn.mu.Unlock() - var pair *ice.CandidatePair pair, err := conn.agent.GetSelectedCandidatePair() if err != nil { - return err + return nil, err } - peerState := State{PubKey: conn.config.Key} - p := conn.getProxy(pair, remoteWgPort) - conn.proxy = p - err = p.Start(remoteConn) + var endpoint net.Addr + if isRelayCandidate(pair.Local) { + conn.proxy = NewWireGuardProxy(conn.config.WgConfig.WgListenPort, conn.config.WgConfig.RemoteKey, remoteConn) + endpoint, err = conn.proxy.Start() + if err != nil { + conn.proxy = nil + return nil, err + } + } else { + // To support old version's with direct mode we attempt to punch an additional role with the remote wireguard port + go conn.punchRemoteWGPort(pair, remoteWgPort) + endpoint = remoteConn.RemoteAddr() + } + + err = conn.config.WgConfig.WgInterface.UpdatePeer(conn.config.WgConfig.RemoteKey, conn.config.WgConfig.AllowedIps, defaultWgKeepAlive, endpoint, conn.config.WgConfig.PreSharedKey) if err != nil { - return err + if conn.proxy != nil { + _ = conn.proxy.Close() + } + return nil, err } conn.status = StatusConnected - peerState.ConnStatus = conn.status - peerState.ConnStatusUpdate = time.Now() - peerState.LocalIceCandidateType = pair.Local.Type().String() - peerState.RemoteIceCandidateType = pair.Remote.Type().String() + peerState := State{ + PubKey: conn.config.Key, + ConnStatus: conn.status, + ConnStatusUpdate: time.Now(), + LocalIceCandidateType: pair.Local.Type().String(), + RemoteIceCandidateType: pair.Remote.Type().String(), + Direct: conn.proxy == nil, + } if pair.Local.Type() == ice.CandidateTypeRelay || pair.Remote.Type() == ice.CandidateTypeRelay { peerState.Relayed = true } - peerState.Direct = p.Type() == proxy.TypeDirectNoProxy || p.Type() == proxy.TypeNoProxy err = conn.statusRecorder.UpdatePeerState(peerState) if err != nil { log.Warnf("unable to save peer's state, got error: %v", err) } - return nil -} - -// todo rename this method and the proxy package to something more appropriate -func (conn *Conn) getProxy(pair *ice.CandidatePair, remoteWgPort int) proxy.Proxy { - if isRelayCandidate(pair.Local) { - return proxy.NewWireGuardProxy(conn.config.ProxyConfig) - } - - // To support old version's with direct mode we attempt to punch an additional role with the remote wireguard port - go conn.punchRemoteWGPort(pair, remoteWgPort) - - return proxy.NewNoProxy(conn.config.ProxyConfig) + return endpoint, nil } func (conn *Conn) punchRemoteWGPort(pair *ice.CandidatePair, remoteWgPort int) { @@ -439,20 +452,22 @@ func (conn *Conn) cleanup() error { conn.mu.Lock() defer conn.mu.Unlock() + var err1, err2, err3 error if conn.agent != nil { - err := conn.agent.Close() - if err != nil { - return err + err1 = conn.agent.Close() + if err1 == nil { + conn.agent = nil } - conn.agent = nil } + // todo: is it problem if we try to remove a peer what is never existed? + err2 = conn.config.WgConfig.WgInterface.RemovePeer(conn.config.WgConfig.RemoteKey) + if conn.proxy != nil { - err := conn.proxy.Close() - if err != nil { - return err + err3 = conn.proxy.Close() + if err3 != nil { + conn.proxy = nil } - conn.proxy = nil } if conn.notifyDisconnected != nil { @@ -462,10 +477,11 @@ func (conn *Conn) cleanup() error { conn.status = StatusDisconnected - peerState := State{PubKey: conn.config.Key} - peerState.ConnStatus = conn.status - peerState.ConnStatusUpdate = time.Now() - + peerState := State{ + PubKey: conn.config.Key, + ConnStatus: conn.status, + ConnStatusUpdate: time.Now(), + } err := conn.statusRecorder.UpdatePeerState(peerState) if err != nil { // pretty common error because by that time Engine can already remove the peer and status won't be available. @@ -474,8 +490,13 @@ func (conn *Conn) cleanup() error { } log.Debugf("cleaned up connection to peer %s", conn.config.Key) - - return nil + if err1 != nil { + return err1 + } + if err2 != nil { + return err2 + } + return err3 } // SetSignalOffer sets a handler function to be triggered by Conn when a new connection offer has to be signalled to the remote peer diff --git a/client/internal/proxy/wireguard.go b/client/internal/peer/wgproxy.go similarity index 55% rename from client/internal/proxy/wireguard.go rename to client/internal/peer/wgproxy.go index ec3c6a730..86143727c 100644 --- a/client/internal/proxy/wireguard.go +++ b/client/internal/peer/wgproxy.go @@ -1,7 +1,8 @@ -package proxy +package peer import ( "context" + "fmt" log "github.com/sirupsen/logrus" "net" ) @@ -11,67 +12,45 @@ type WireGuardProxy struct { ctx context.Context cancel context.CancelFunc - config Config + wgListenPort int + remoteKey string remoteConn net.Conn localConn net.Conn } -func NewWireGuardProxy(config Config) *WireGuardProxy { - p := &WireGuardProxy{config: config} +func NewWireGuardProxy(wgListenPort int, remoteKey string, remoteConn net.Conn) *WireGuardProxy { + p := &WireGuardProxy{ + wgListenPort: wgListenPort, + remoteKey: remoteKey, + remoteConn: remoteConn, + } p.ctx, p.cancel = context.WithCancel(context.Background()) return p } -func (p *WireGuardProxy) updateEndpoint() error { - udpAddr, err := net.ResolveUDPAddr(p.localConn.LocalAddr().Network(), p.localConn.LocalAddr().String()) - if err != nil { - return err - } - // add local proxy connection as a Wireguard peer - err = p.config.WgInterface.UpdatePeer(p.config.RemoteKey, p.config.AllowedIps, DefaultWgKeepAlive, - udpAddr, p.config.PreSharedKey) - if err != nil { - return err - } - - return nil -} - -func (p *WireGuardProxy) Start(remoteConn net.Conn) error { - p.remoteConn = remoteConn - - var err error - p.localConn, err = net.Dial("udp", p.config.WgListenAddr) +func (p *WireGuardProxy) Start() (net.Addr, error) { + lConn, err := net.Dial("udp", fmt.Sprintf("127.0.0.1:%d", p.wgListenPort)) if err != nil { log.Errorf("failed dialing to local Wireguard port %s", err) - return err - } - - err = p.updateEndpoint() - if err != nil { - log.Errorf("error while updating Wireguard peer endpoint [%s] %v", p.config.RemoteKey, err) - return err + return nil, err } + p.localConn = lConn go p.proxyToRemote() go p.proxyToLocal() - return nil + return lConn.LocalAddr(), nil } func (p *WireGuardProxy) Close() error { p.cancel() - if c := p.localConn; c != nil { + if p.localConn != nil { err := p.localConn.Close() if err != nil { return err } } - err := p.config.WgInterface.RemovePeer(p.config.RemoteKey) - if err != nil { - return err - } return nil } @@ -83,7 +62,7 @@ func (p *WireGuardProxy) proxyToRemote() { for { select { case <-p.ctx.Done(): - log.Debugf("stopped proxying to remote peer %s due to closed connection", p.config.RemoteKey) + log.Debugf("stopped proxying to remote peer %s due to closed connection", p.remoteKey) return default: n, err := p.localConn.Read(buf) @@ -107,7 +86,7 @@ func (p *WireGuardProxy) proxyToLocal() { for { select { case <-p.ctx.Done(): - log.Debugf("stopped proxying from remote peer %s due to closed connection", p.config.RemoteKey) + log.Debugf("stopped proxying from remote peer %s due to closed connection", p.remoteKey) return default: n, err := p.remoteConn.Read(buf) @@ -122,7 +101,3 @@ func (p *WireGuardProxy) proxyToLocal() { } } } - -func (p *WireGuardProxy) Type() Type { - return TypeWireGuard -} diff --git a/client/internal/proxy/noproxy.go b/client/internal/proxy/noproxy.go deleted file mode 100644 index dcfe182fd..000000000 --- a/client/internal/proxy/noproxy.go +++ /dev/null @@ -1,42 +0,0 @@ -package proxy - -import ( - log "github.com/sirupsen/logrus" - "net" -) - -// NoProxy is used just to configure WireGuard without any local proxy in between. -// Used when the WireGuard interface is userspace and uses bind.ICEBind -type NoProxy struct { - config Config -} - -// NewNoProxy creates a new NoProxy with a provided config -func NewNoProxy(config Config) *NoProxy { - return &NoProxy{config: config} -} - -// Close removes peer from the WireGuard interface -func (p *NoProxy) Close() error { - err := p.config.WgInterface.RemovePeer(p.config.RemoteKey) - if err != nil { - return err - } - return nil -} - -// Start just updates WireGuard peer with the remote address -func (p *NoProxy) Start(remoteConn net.Conn) error { - - log.Debugf("using NoProxy to connect to peer %s at %s", p.config.RemoteKey, remoteConn.RemoteAddr().String()) - addr, err := net.ResolveUDPAddr("udp", remoteConn.RemoteAddr().String()) - if err != nil { - return err - } - return p.config.WgInterface.UpdatePeer(p.config.RemoteKey, p.config.AllowedIps, DefaultWgKeepAlive, - addr, p.config.PreSharedKey) -} - -func (p *NoProxy) Type() Type { - return TypeNoProxy -} diff --git a/client/internal/proxy/proxy.go b/client/internal/proxy/proxy.go deleted file mode 100644 index a0b9e98a1..000000000 --- a/client/internal/proxy/proxy.go +++ /dev/null @@ -1,35 +0,0 @@ -package proxy - -import ( - "github.com/netbirdio/netbird/iface" - "golang.zx2c4.com/wireguard/wgctrl/wgtypes" - "io" - "net" - "time" -) - -const DefaultWgKeepAlive = 25 * time.Second - -type Type string - -const ( - TypeDirectNoProxy Type = "DirectNoProxy" - TypeWireGuard Type = "WireGuard" - TypeDummy Type = "Dummy" - TypeNoProxy Type = "NoProxy" -) - -type Config struct { - WgListenAddr string - RemoteKey string - WgInterface *iface.WGIface - AllowedIps string - PreSharedKey *wgtypes.Key -} - -type Proxy interface { - io.Closer - // Start creates a local remoteConn and starts proxying data from/to remoteConn - Start(remoteConn net.Conn) error - Type() Type -} diff --git a/iface/iface.go b/iface/iface.go index 788a180b8..1cc363564 100644 --- a/iface/iface.go +++ b/iface/iface.go @@ -77,12 +77,17 @@ func (w *WGIface) UpdateAddr(newAddr string) error { // UpdatePeer updates existing Wireguard Peer or creates a new one if doesn't exist // Endpoint is optional -func (w *WGIface) UpdatePeer(peerKey string, allowedIps string, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error { +func (w *WGIface) UpdatePeer(peerKey string, allowedIps string, keepAlive time.Duration, endpoint net.Addr, preSharedKey *wgtypes.Key) error { w.mu.Lock() defer w.mu.Unlock() - log.Debugf("updating interface %s peer %s: endpoint %s ", w.tun.DeviceName(), peerKey, endpoint) - return w.configurer.updatePeer(peerKey, allowedIps, keepAlive, endpoint, preSharedKey) + rAddr, err := net.ResolveUDPAddr(endpoint.Network(), endpoint.String()) + if err != nil { + return err + } + + log.Debugf("updating interface %s peer %s, endpoint %s ", w.tun.DeviceName(), peerKey, endpoint) + return w.configurer.updatePeer(peerKey, allowedIps, keepAlive, rAddr, preSharedKey) } // RemovePeer removes a Wireguard Peer from the interface iface