mirror of
https://github.com/netbirdio/netbird.git
synced 2026-07-16 11:39:57 +00:00
Compare commits
3 Commits
poc/prepro
...
bind-ipv6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d4ddd53f3 | ||
|
|
4cff2a09a3 | ||
|
|
90c5244a40 |
153
client/iface/bind/dual_stack_conn.go
Normal file
153
client/iface/bind/dual_stack_conn.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package bind
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
|
||||
nberrors "github.com/netbirdio/netbird/client/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
errNoIPv4Conn = errors.New("no IPv4 connection available")
|
||||
errNoIPv6Conn = errors.New("no IPv6 connection available")
|
||||
errInvalidAddr = errors.New("invalid address type")
|
||||
)
|
||||
|
||||
// DualStackPacketConn is a composite PacketConn that can handle both IPv4 and IPv6
|
||||
type DualStackPacketConn struct {
|
||||
ipv4Conn net.PacketConn
|
||||
ipv6Conn net.PacketConn
|
||||
}
|
||||
|
||||
// NewDualStackPacketConn creates a new dual-stack packet connection
|
||||
func NewDualStackPacketConn(ipv4Conn, ipv6Conn net.PacketConn) *DualStackPacketConn {
|
||||
return &DualStackPacketConn{
|
||||
ipv4Conn: ipv4Conn,
|
||||
ipv6Conn: ipv6Conn,
|
||||
}
|
||||
}
|
||||
|
||||
// ReadFrom reads from both IPv4 and IPv6 connections
|
||||
func (d *DualStackPacketConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
|
||||
// Prefer IPv4 if available
|
||||
if d.ipv4Conn != nil {
|
||||
return d.ipv4Conn.ReadFrom(b)
|
||||
}
|
||||
if d.ipv6Conn != nil {
|
||||
return d.ipv6Conn.ReadFrom(b)
|
||||
}
|
||||
return 0, nil, net.ErrClosed
|
||||
}
|
||||
|
||||
// WriteTo writes to the appropriate connection based on the address type
|
||||
func (d *DualStackPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
||||
udpAddr, ok := addr.(*net.UDPAddr)
|
||||
if !ok {
|
||||
return 0, &net.OpError{
|
||||
Op: "write",
|
||||
Net: "udp",
|
||||
Addr: addr,
|
||||
Err: errInvalidAddr,
|
||||
}
|
||||
}
|
||||
|
||||
if udpAddr.IP.To4() == nil {
|
||||
if d.ipv6Conn != nil {
|
||||
return d.ipv6Conn.WriteTo(b, addr)
|
||||
}
|
||||
return 0, &net.OpError{
|
||||
Op: "write",
|
||||
Net: "udp6",
|
||||
Addr: addr,
|
||||
Err: errNoIPv6Conn,
|
||||
}
|
||||
}
|
||||
|
||||
if d.ipv4Conn != nil {
|
||||
return d.ipv4Conn.WriteTo(b, addr)
|
||||
}
|
||||
return 0, &net.OpError{
|
||||
Op: "write",
|
||||
Net: "udp4",
|
||||
Addr: addr,
|
||||
Err: errNoIPv4Conn,
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes both connections
|
||||
func (d *DualStackPacketConn) Close() error {
|
||||
var result *multierror.Error
|
||||
if d.ipv4Conn != nil {
|
||||
if err := d.ipv4Conn.Close(); err != nil {
|
||||
result = multierror.Append(result, err)
|
||||
}
|
||||
}
|
||||
if d.ipv6Conn != nil {
|
||||
if err := d.ipv6Conn.Close(); err != nil {
|
||||
result = multierror.Append(result, err)
|
||||
}
|
||||
}
|
||||
return nberrors.FormatErrorOrNil(result)
|
||||
}
|
||||
|
||||
// LocalAddr returns the local address of the IPv4 connection (for compatibility)
|
||||
func (d *DualStackPacketConn) LocalAddr() net.Addr {
|
||||
if d.ipv4Conn != nil {
|
||||
return d.ipv4Conn.LocalAddr()
|
||||
}
|
||||
if d.ipv6Conn != nil {
|
||||
return d.ipv6Conn.LocalAddr()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetDeadline sets the deadline for both connections
|
||||
func (d *DualStackPacketConn) SetDeadline(t time.Time) error {
|
||||
var result *multierror.Error
|
||||
if d.ipv4Conn != nil {
|
||||
if err := d.ipv4Conn.SetDeadline(t); err != nil {
|
||||
result = multierror.Append(result, err)
|
||||
}
|
||||
}
|
||||
if d.ipv6Conn != nil {
|
||||
if err := d.ipv6Conn.SetDeadline(t); err != nil {
|
||||
result = multierror.Append(result, err)
|
||||
}
|
||||
}
|
||||
return nberrors.FormatErrorOrNil(result)
|
||||
}
|
||||
|
||||
// SetReadDeadline sets the read deadline for both connections
|
||||
func (d *DualStackPacketConn) SetReadDeadline(t time.Time) error {
|
||||
var result *multierror.Error
|
||||
if d.ipv4Conn != nil {
|
||||
if err := d.ipv4Conn.SetReadDeadline(t); err != nil {
|
||||
result = multierror.Append(result, err)
|
||||
}
|
||||
}
|
||||
if d.ipv6Conn != nil {
|
||||
if err := d.ipv6Conn.SetReadDeadline(t); err != nil {
|
||||
result = multierror.Append(result, err)
|
||||
}
|
||||
}
|
||||
return nberrors.FormatErrorOrNil(result)
|
||||
}
|
||||
|
||||
// SetWriteDeadline sets the write deadline for both connections
|
||||
func (d *DualStackPacketConn) SetWriteDeadline(t time.Time) error {
|
||||
var result *multierror.Error
|
||||
if d.ipv4Conn != nil {
|
||||
if err := d.ipv4Conn.SetWriteDeadline(t); err != nil {
|
||||
result = multierror.Append(result, err)
|
||||
}
|
||||
}
|
||||
if d.ipv6Conn != nil {
|
||||
if err := d.ipv6Conn.SetWriteDeadline(t); err != nil {
|
||||
result = multierror.Append(result, err)
|
||||
}
|
||||
}
|
||||
return nberrors.FormatErrorOrNil(result)
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package bind
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"errors"
|
||||
"net"
|
||||
"net/netip"
|
||||
"runtime"
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"github.com/pion/stun/v2"
|
||||
"github.com/pion/transport/v3"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/net/ipv6"
|
||||
wgConn "golang.zx2c4.com/wireguard/conn"
|
||||
|
||||
@@ -27,8 +26,8 @@ type receiverCreator struct {
|
||||
iceBind *ICEBind
|
||||
}
|
||||
|
||||
func (rc receiverCreator) CreateIPv4ReceiverFn(pc *ipv4.PacketConn, conn *net.UDPConn, rxOffload bool, msgPool *sync.Pool) wgConn.ReceiveFunc {
|
||||
return rc.iceBind.createIPv4ReceiverFn(pc, conn, rxOffload, msgPool)
|
||||
func (rc receiverCreator) CreateReceiverFn(pc wgConn.BatchReader, conn *net.UDPConn, rxOffload bool, msgPool *sync.Pool) wgConn.ReceiveFunc {
|
||||
return rc.iceBind.createReceiverFn(pc, conn, rxOffload, msgPool)
|
||||
}
|
||||
|
||||
// ICEBind is a bind implementation with two main features:
|
||||
@@ -54,6 +53,8 @@ type ICEBind struct {
|
||||
|
||||
muUDPMux sync.Mutex
|
||||
udpMux *UniversalUDPMuxDefault
|
||||
ipv4Conn *net.UDPConn
|
||||
ipv6Conn *net.UDPConn
|
||||
address wgaddr.Address
|
||||
activityRecorder *ActivityRecorder
|
||||
}
|
||||
@@ -111,11 +112,11 @@ func (s *ICEBind) ActivityRecorder() *ActivityRecorder {
|
||||
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
|
||||
if s.udpMux != nil {
|
||||
return s.udpMux, nil
|
||||
}
|
||||
return nil, errors.New("ICEBind has not been initialized yet")
|
||||
}
|
||||
|
||||
func (b *ICEBind) SetEndpoint(fakeIP netip.Addr, conn net.Conn) {
|
||||
@@ -147,18 +148,46 @@ func (b *ICEBind) Send(bufs [][]byte, ep wgConn.Endpoint) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ICEBind) createIPv4ReceiverFn(pc *ipv4.PacketConn, conn *net.UDPConn, rxOffload bool, msgsPool *sync.Pool) wgConn.ReceiveFunc {
|
||||
func (s *ICEBind) createReceiverFn(pc wgConn.BatchReader, conn *net.UDPConn, rxOffload bool, msgsPool *sync.Pool) wgConn.ReceiveFunc {
|
||||
s.muUDPMux.Lock()
|
||||
defer s.muUDPMux.Unlock()
|
||||
|
||||
s.udpMux = NewUniversalUDPMuxDefault(
|
||||
UniversalUDPMuxParams{
|
||||
UDPConn: conn,
|
||||
Net: s.transportNet,
|
||||
FilterFn: s.filterFn,
|
||||
WGAddress: s.address,
|
||||
},
|
||||
)
|
||||
localAddr, ok := conn.LocalAddr().(*net.UDPAddr)
|
||||
if !ok {
|
||||
log.Errorf("ICEBind: unexpected address type: %T", conn.LocalAddr())
|
||||
return nil
|
||||
}
|
||||
isIPv6 := localAddr.IP.To4() == nil
|
||||
|
||||
if isIPv6 {
|
||||
s.ipv6Conn = conn
|
||||
} else {
|
||||
s.ipv4Conn = conn
|
||||
}
|
||||
|
||||
needsNewMux := s.udpMux == nil && (s.ipv4Conn != nil || s.ipv6Conn != nil)
|
||||
needsUpgrade := s.udpMux != nil && s.ipv4Conn != nil && s.ipv6Conn != nil
|
||||
|
||||
if needsNewMux || needsUpgrade {
|
||||
var iceMuxConn net.PacketConn
|
||||
switch {
|
||||
case s.ipv4Conn != nil && s.ipv6Conn != nil:
|
||||
iceMuxConn = NewDualStackPacketConn(s.ipv4Conn, s.ipv6Conn)
|
||||
case s.ipv4Conn != nil:
|
||||
iceMuxConn = s.ipv4Conn
|
||||
default:
|
||||
iceMuxConn = s.ipv6Conn
|
||||
}
|
||||
|
||||
s.udpMux = NewUniversalUDPMuxDefault(
|
||||
UniversalUDPMuxParams{
|
||||
UDPConn: iceMuxConn,
|
||||
Net: s.transportNet,
|
||||
FilterFn: s.filterFn,
|
||||
WGAddress: s.address,
|
||||
},
|
||||
)
|
||||
}
|
||||
return func(bufs [][]byte, sizes []int, eps []wgConn.Endpoint) (n int, err error) {
|
||||
msgs := getMessages(msgsPool)
|
||||
for i := range bufs {
|
||||
@@ -205,7 +234,12 @@ func (s *ICEBind) createIPv4ReceiverFn(pc *ipv4.PacketConn, conn *net.UDPConn, r
|
||||
if sizes[i] == 0 {
|
||||
continue
|
||||
}
|
||||
addrPort := msg.Addr.(*net.UDPAddr).AddrPort()
|
||||
udpAddr, ok := msg.Addr.(*net.UDPAddr)
|
||||
if !ok {
|
||||
log.Errorf("ICEBind: unexpected address type: %T", msg.Addr)
|
||||
continue
|
||||
}
|
||||
addrPort := udpAddr.AddrPort()
|
||||
|
||||
if isTransportPkg(msg.Buffers, msg.N) {
|
||||
s.activityRecorder.record(addrPort)
|
||||
@@ -231,9 +265,10 @@ func (s *ICEBind) filterOutStunMessages(buffers [][]byte, n int, addr net.Addr)
|
||||
return true, err
|
||||
}
|
||||
|
||||
muxErr := s.udpMux.HandleSTUNMessage(msg, addr)
|
||||
if muxErr != nil {
|
||||
log.Warnf("failed to handle STUN packet")
|
||||
if s.udpMux != nil {
|
||||
if err := s.udpMux.HandleSTUNMessage(msg, addr); err != nil {
|
||||
log.Warnf("failed to handle STUN packet: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
buffers[i] = []byte{}
|
||||
|
||||
@@ -342,6 +342,9 @@ func (m *UDPMuxDefault) Close() error {
|
||||
}
|
||||
|
||||
func (m *UDPMuxDefault) writeTo(buf []byte, rAddr net.Addr) (n int, err error) {
|
||||
if dualStackConn, ok := m.params.UDPConn.(*DualStackPacketConn); ok {
|
||||
return dualStackConn.WriteTo(buf, rAddr)
|
||||
}
|
||||
return m.params.UDPConn.WriteTo(buf, rAddr)
|
||||
}
|
||||
|
||||
|
||||
@@ -126,6 +126,11 @@ type udpConn struct {
|
||||
}
|
||||
|
||||
func (u *udpConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||
// Check if this is a dual-stack connection and handle IPv6 addresses properly
|
||||
if dualStackConn, ok := u.PacketConn.(*DualStackPacketConn); ok {
|
||||
return dualStackConn.WriteTo(b, addr)
|
||||
}
|
||||
|
||||
if u.filterFn == nil {
|
||||
return u.PacketConn.WriteTo(b, addr)
|
||||
}
|
||||
@@ -141,6 +146,11 @@ func (u *udpConn) handleCachedAddress(isRouted bool, b []byte, addr net.Addr) (i
|
||||
if isRouted {
|
||||
return 0, fmt.Errorf("address %s is part of a routed network, refusing to write", addr)
|
||||
}
|
||||
|
||||
if dualStackConn, ok := u.PacketConn.(*DualStackPacketConn); ok {
|
||||
return dualStackConn.WriteTo(b, addr)
|
||||
}
|
||||
|
||||
return u.PacketConn.WriteTo(b, addr)
|
||||
}
|
||||
|
||||
@@ -148,6 +158,11 @@ func (u *udpConn) handleUncachedAddress(b []byte, addr net.Addr) (int, error) {
|
||||
if err := u.performFilterCheck(addr); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if dualStackConn, ok := u.PacketConn.(*DualStackPacketConn); ok {
|
||||
return dualStackConn.WriteTo(b, addr)
|
||||
}
|
||||
|
||||
return u.PacketConn.WriteTo(b, addr)
|
||||
}
|
||||
|
||||
|
||||
@@ -146,8 +146,8 @@ func (w *WorkerICE) OnNewOffer(remoteOfferAnswer *OfferAnswer) {
|
||||
RosenpassAddr: remoteOfferAnswer.RosenpassAddr,
|
||||
LocalIceCandidateType: pair.Local.Type().String(),
|
||||
RemoteIceCandidateType: pair.Remote.Type().String(),
|
||||
LocalIceCandidateEndpoint: fmt.Sprintf("%s:%d", pair.Local.Address(), pair.Local.Port()),
|
||||
RemoteIceCandidateEndpoint: fmt.Sprintf("%s:%d", pair.Remote.Address(), pair.Remote.Port()),
|
||||
LocalIceCandidateEndpoint: formatEndpoint(pair.Local.Address(), pair.Local.Port()),
|
||||
RemoteIceCandidateEndpoint: formatEndpoint(pair.Remote.Address(), pair.Remote.Port()),
|
||||
Relayed: isRelayed(pair),
|
||||
RelayedOnLocal: isRelayCandidate(pair.Local),
|
||||
}
|
||||
@@ -405,3 +405,12 @@ func selectedPriority(pair *ice.CandidatePair) conntype.ConnPriority {
|
||||
return conntype.ICEP2P
|
||||
}
|
||||
}
|
||||
|
||||
// formatEndpoint formats an IP address and port for display, adding brackets around IPv6 addresses
|
||||
func formatEndpoint(addr string, port int) string {
|
||||
parsed, err := netip.ParseAddr(addr)
|
||||
if err == nil && parsed.Is6() {
|
||||
return fmt.Sprintf("[%s]:%d", addr, port)
|
||||
}
|
||||
return fmt.Sprintf("%s:%d", addr, port)
|
||||
}
|
||||
|
||||
7
go.mod
7
go.mod
@@ -110,7 +110,7 @@ require (
|
||||
gorm.io/driver/mysql v1.5.7
|
||||
gorm.io/driver/postgres v1.5.7
|
||||
gorm.io/driver/sqlite v1.5.7
|
||||
gorm.io/gorm v1.30.0
|
||||
gorm.io/gorm v1.25.12
|
||||
gvisor.dev/gvisor v0.0.0-20231020174304-b8a429915ff1
|
||||
)
|
||||
|
||||
@@ -180,7 +180,7 @@ require (
|
||||
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/pgx/v5 v5.5.5 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||
github.com/jeandeaual/go-locale v0.0.0-20240223122105-ce5225dcaa49 // indirect
|
||||
@@ -247,14 +247,13 @@ require (
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gorm.io/datatypes v1.2.6 // indirect
|
||||
)
|
||||
|
||||
replace github.com/kardianos/service => github.com/netbirdio/service v0.0.0-20240911161631-f62744f42502
|
||||
|
||||
replace github.com/getlantern/systray => github.com/netbirdio/systray v0.0.0-20231030152038-ef1ed2a27949
|
||||
|
||||
replace golang.zx2c4.com/wireguard => github.com/netbirdio/wireguard-go v0.0.0-20241230120307-6a676aebaaf6
|
||||
replace golang.zx2c4.com/wireguard => github.com/netbirdio/wireguard-go v0.0.0-20250709101833-3247b6066880
|
||||
|
||||
replace github.com/cloudflare/circl => github.com/cunicu/circl v0.0.0-20230801113412-fec58fc7b5f6
|
||||
|
||||
|
||||
10
go.sum
10
go.sum
@@ -395,8 +395,6 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA=
|
||||
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
|
||||
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
|
||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||
@@ -511,8 +509,8 @@ github.com/netbirdio/service v0.0.0-20240911161631-f62744f42502 h1:3tHlFmhTdX9ax
|
||||
github.com/netbirdio/service v0.0.0-20240911161631-f62744f42502/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
|
||||
github.com/netbirdio/signal-dispatcher/dispatcher v0.0.0-20250514131221-a464fd5f30cb h1:Cr6age+ePALqlSvtp7wc6lYY97XN7rkD1K4XEDmY+TU=
|
||||
github.com/netbirdio/signal-dispatcher/dispatcher v0.0.0-20250514131221-a464fd5f30cb/go.mod h1:5/sjFmLb8O96B5737VCqhHyGRzNFIaN/Bu7ZodXc3qQ=
|
||||
github.com/netbirdio/wireguard-go v0.0.0-20241230120307-6a676aebaaf6 h1:X5h5QgP7uHAv78FWgHV8+WYLjHxK9v3ilkVXT1cpCrQ=
|
||||
github.com/netbirdio/wireguard-go v0.0.0-20241230120307-6a676aebaaf6/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
||||
github.com/netbirdio/wireguard-go v0.0.0-20250709101833-3247b6066880 h1:s4y7B+jGbOSCnBAyh+APS8QSe8Liq+akU4enLHs8Efo=
|
||||
github.com/netbirdio/wireguard-go v0.0.0-20250709101833-3247b6066880/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
@@ -1197,8 +1195,6 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/datatypes v1.2.6 h1:KafLdXvFUhzNeL2ncm03Gl3eTLONQfNKZ+wJ+9Y4Nck=
|
||||
gorm.io/datatypes v1.2.6/go.mod h1:M2iO+6S3hhi4nAyYe444Pcb0dcIiOMJ7QHaUXxyiNZY=
|
||||
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
|
||||
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
|
||||
gorm.io/driver/postgres v1.5.7 h1:8ptbNJTDbEmhdr62uReG5BGkdQyeasu/FZHxI0IMGnM=
|
||||
@@ -1208,8 +1204,6 @@ gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDa
|
||||
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
|
||||
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||
gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY=
|
||||
gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||
gvisor.dev/gvisor v0.0.0-20231020174304-b8a429915ff1 h1:qDCwdCWECGnwQSQC01Dpnp09fRHxJs9PbktotUqG+hs=
|
||||
|
||||
@@ -33,10 +33,6 @@ import (
|
||||
"github.com/netbirdio/netbird/management/server/status"
|
||||
)
|
||||
|
||||
// Declare sqlStore and ok at the top so they are in scope for all usages
|
||||
var sqlStore *store.SqlStore
|
||||
var ok bool
|
||||
|
||||
// GetPeers returns a list of peers under the given account filtering out peers that do not belong to a user if
|
||||
// the current user is not an admin.
|
||||
func (am *DefaultAccountManager) GetPeers(ctx context.Context, accountID, userID, nameFilter, ipFilter string) ([]*nbpeer.Peer, error) {
|
||||
@@ -411,24 +407,6 @@ func (am *DefaultAccountManager) GetNetworkMap(ctx context.Context, peerID strin
|
||||
return nil, status.Errorf(status.NotFound, "peer with ID %s not found", peerID)
|
||||
}
|
||||
|
||||
// Try to serve precomputed network map from DB if up-to-date
|
||||
sqlStore, ok = am.Store.(*store.SqlStore)
|
||||
if ok {
|
||||
db := sqlStore.GetDB()
|
||||
var record *types.NetworkMapRecord
|
||||
var err error
|
||||
record, err = types.GetNetworkMapRecord(db, peer.ID)
|
||||
if err == nil && record.Serial == account.Network.CurrentSerial() {
|
||||
var nm *types.NetworkMap
|
||||
nm, err = types.DeserializeNetworkMap(record.MapJSON)
|
||||
if err == nil {
|
||||
log.WithContext(ctx).Debugf("serving precomputed network map for peer %s from DB", peer.ID)
|
||||
return nm, nil
|
||||
}
|
||||
log.WithContext(ctx).Warnf("failed to deserialize precomputed network map for peer %s: %v", peer.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
groups := make(map[string][]string)
|
||||
for groupID, group := range account.Groups {
|
||||
groups[groupID] = group.Peers
|
||||
@@ -446,34 +424,13 @@ func (am *DefaultAccountManager) GetNetworkMap(ctx context.Context, peerID strin
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var proxyNetworkMap *types.NetworkMap
|
||||
networkMap := account.GetPeerNetworkMap(ctx, peerID, customZone, validatedPeers, account.GetResourcePoliciesMap(), account.GetResourceRoutersMap(), nil)
|
||||
proxyNetworkMap, ok = proxyNetworkMaps[peerID]
|
||||
networkMap := account.GetPeerNetworkMap(ctx, peer.ID, customZone, validatedPeers, account.GetResourcePoliciesMap(), account.GetResourceRoutersMap(), nil)
|
||||
|
||||
proxyNetworkMap, ok := proxyNetworkMaps[peer.ID]
|
||||
if ok {
|
||||
networkMap.Merge(proxyNetworkMap)
|
||||
}
|
||||
|
||||
// After generating the network map, store it as a precomputed blob in the DB
|
||||
sqlStore, ok = am.Store.(*store.SqlStore)
|
||||
if ok {
|
||||
db := sqlStore.GetDB()
|
||||
data, err := types.SerializeNetworkMap(networkMap)
|
||||
if err == nil {
|
||||
record := &types.NetworkMapRecord{
|
||||
PeerID: peer.ID,
|
||||
AccountID: account.Id,
|
||||
MapJSON: data,
|
||||
Serial: networkMap.Network.CurrentSerial(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err = types.SaveNetworkMapRecord(db, record)
|
||||
if err != nil {
|
||||
log.WithContext(ctx).Warnf("failed to store precomputed network map for peer %s: %v", peer.ID, err)
|
||||
}
|
||||
} else {
|
||||
log.WithContext(ctx).Warnf("failed to serialize network map for peer %s: %v", peer.ID, err)
|
||||
}
|
||||
}
|
||||
return networkMap, nil
|
||||
}
|
||||
|
||||
@@ -1096,47 +1053,13 @@ func (am *DefaultAccountManager) getValidatedPeerWithMap(ctx context.Context, is
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
var proxyNetworkMap *types.NetworkMap
|
||||
networkMap := account.GetPeerNetworkMap(ctx, peer.ID, customZone, approvedPeersMap, account.GetResourcePoliciesMap(), account.GetResourceRoutersMap(), nil)
|
||||
proxyNetworkMap, ok = proxyNetworkMaps[peer.ID]
|
||||
networkMap := account.GetPeerNetworkMap(ctx, peer.ID, customZone, approvedPeersMap, account.GetResourcePoliciesMap(), account.GetResourceRoutersMap(), am.metrics.AccountManagerMetrics())
|
||||
|
||||
proxyNetworkMap, ok := proxyNetworkMaps[peer.ID]
|
||||
if ok {
|
||||
networkMap.Merge(proxyNetworkMap)
|
||||
}
|
||||
|
||||
// After generating the network map, store it as a precomputed blob in the DB
|
||||
sqlStore, ok = am.Store.(*store.SqlStore)
|
||||
if ok {
|
||||
db := sqlStore.GetDB()
|
||||
data, err := types.SerializeNetworkMap(networkMap)
|
||||
if err == nil {
|
||||
record := &types.NetworkMapRecord{
|
||||
PeerID: peer.ID,
|
||||
AccountID: account.Id,
|
||||
MapJSON: data,
|
||||
Serial: networkMap.Network.CurrentSerial(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err = types.SaveNetworkMapRecord(db, record)
|
||||
if err != nil {
|
||||
log.WithContext(ctx).Warnf("failed to store precomputed network map for peer %s: %v", peer.ID, err)
|
||||
}
|
||||
} else {
|
||||
log.WithContext(ctx).Warnf("failed to serialize network map for peer %s: %v", peer.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
extraSetting, err := am.settingsManager.GetExtraSettings(ctx, accountID)
|
||||
if err != nil {
|
||||
log.WithContext(ctx).Errorf("failed to get flow enabled status: %v", err)
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
start = time.Now()
|
||||
update := toSyncResponse(ctx, nil, peer, nil, nil, networkMap, am.GetDNSDomain(account.Settings), postureChecks, nil, account.Settings, extraSetting)
|
||||
am.metrics.UpdateChannelMetrics().CountToSyncResponseDuration(time.Since(start))
|
||||
|
||||
am.peersUpdateManager.SendUpdate(ctx, peer.ID, &UpdateMessage{Update: update, NetworkMap: networkMap})
|
||||
|
||||
return peer, networkMap, postureChecks, nil
|
||||
}
|
||||
|
||||
@@ -1316,35 +1239,12 @@ func (am *DefaultAccountManager) UpdateAccountPeers(ctx context.Context, account
|
||||
am.metrics.UpdateChannelMetrics().CountCalcPeerNetworkMapDuration(time.Since(start))
|
||||
start = time.Now()
|
||||
|
||||
var proxyNetworkMap *types.NetworkMap
|
||||
proxyNetworkMap, ok = proxyNetworkMaps[p.ID]
|
||||
proxyNetworkMap, ok := proxyNetworkMaps[p.ID]
|
||||
if ok {
|
||||
remotePeerNetworkMap.Merge(proxyNetworkMap)
|
||||
}
|
||||
am.metrics.UpdateChannelMetrics().CountMergeNetworkMapDuration(time.Since(start))
|
||||
|
||||
// Store the precomputed network map in the DB
|
||||
sqlStore, ok = am.Store.(*store.SqlStore)
|
||||
if ok {
|
||||
db := sqlStore.GetDB()
|
||||
data, err := types.SerializeNetworkMap(remotePeerNetworkMap)
|
||||
if err == nil {
|
||||
record := &types.NetworkMapRecord{
|
||||
PeerID: p.ID,
|
||||
AccountID: account.Id,
|
||||
MapJSON: data,
|
||||
Serial: remotePeerNetworkMap.Network.CurrentSerial(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err = types.SaveNetworkMapRecord(db, record)
|
||||
if err != nil {
|
||||
log.WithContext(ctx).Warnf("failed to store precomputed network map for peer %s: %v", p.ID, err)
|
||||
}
|
||||
} else {
|
||||
log.WithContext(ctx).Warnf("failed to serialize network map for peer %s: %v", p.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
extraSetting, err := am.settingsManager.GetExtraSettings(ctx, accountID)
|
||||
if err != nil {
|
||||
log.WithContext(ctx).Errorf("failed to get flow enabled status: %v", err)
|
||||
@@ -1359,6 +1259,8 @@ func (am *DefaultAccountManager) UpdateAccountPeers(ctx context.Context, account
|
||||
}(peer)
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
wg.Wait()
|
||||
if am.metrics != nil {
|
||||
am.metrics.AccountManagerMetrics().CountUpdateAccountPeersDuration(time.Since(globalStart))
|
||||
@@ -1424,43 +1326,21 @@ func (am *DefaultAccountManager) UpdateAccountPeer(ctx context.Context, accountI
|
||||
return
|
||||
}
|
||||
|
||||
var proxyNetworkMap *types.NetworkMap
|
||||
proxyNetworkMap, ok = proxyNetworkMaps[peer.ID]
|
||||
remotePeerNetworkMap := account.GetPeerNetworkMap(ctx, peerId, customZone, approvedPeersMap, resourcePolicies, routers, am.metrics.AccountManagerMetrics())
|
||||
|
||||
proxyNetworkMap, ok := proxyNetworkMaps[peer.ID]
|
||||
if ok {
|
||||
remotePeerNetworkMap := account.GetPeerNetworkMap(ctx, peerId, customZone, approvedPeersMap, resourcePolicies, routers, am.metrics.AccountManagerMetrics())
|
||||
remotePeerNetworkMap.Merge(proxyNetworkMap)
|
||||
|
||||
// Store the precomputed network map in the DB
|
||||
sqlStore, ok = am.Store.(*store.SqlStore)
|
||||
if ok {
|
||||
db := sqlStore.GetDB()
|
||||
data, err := types.SerializeNetworkMap(remotePeerNetworkMap)
|
||||
if err == nil {
|
||||
record := &types.NetworkMapRecord{
|
||||
PeerID: peer.ID,
|
||||
AccountID: account.Id,
|
||||
MapJSON: data,
|
||||
Serial: remotePeerNetworkMap.Network.CurrentSerial(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err = types.SaveNetworkMapRecord(db, record)
|
||||
if err != nil {
|
||||
log.WithContext(ctx).Warnf("failed to store precomputed network map for peer %s: %v", peer.ID, err)
|
||||
}
|
||||
} else {
|
||||
log.WithContext(ctx).Warnf("failed to serialize network map for peer %s: %v", peer.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
extraSettings, err := am.settingsManager.GetExtraSettings(ctx, peer.AccountID)
|
||||
if err != nil {
|
||||
log.WithContext(ctx).Errorf("failed to get extra settings: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
update := toSyncResponse(ctx, nil, peer, nil, nil, remotePeerNetworkMap, dnsDomain, postureChecks, dnsCache, account.Settings, extraSettings)
|
||||
am.peersUpdateManager.SendUpdate(ctx, peer.ID, &UpdateMessage{Update: update, NetworkMap: remotePeerNetworkMap})
|
||||
}
|
||||
|
||||
extraSettings, err := am.settingsManager.GetExtraSettings(ctx, peer.AccountID)
|
||||
if err != nil {
|
||||
log.WithContext(ctx).Errorf("failed to get extra settings: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
update := toSyncResponse(ctx, nil, peer, nil, nil, remotePeerNetworkMap, dnsDomain, postureChecks, dnsCache, account.Settings, extraSettings)
|
||||
am.peersUpdateManager.SendUpdate(ctx, peer.ID, &UpdateMessage{Update: update, NetworkMap: remotePeerNetworkMap})
|
||||
}
|
||||
|
||||
// getNextPeerExpiration returns the minimum duration in which the next peer of the account will expire if it was found.
|
||||
|
||||
@@ -100,7 +100,6 @@ func NewSqlStore(ctx context.Context, db *gorm.DB, storeEngine types.Engine, met
|
||||
&types.Account{}, &types.Policy{}, &types.PolicyRule{}, &route.Route{}, &nbdns.NameServerGroup{},
|
||||
&installation{}, &types.ExtraSettings{}, &posture.Checks{}, &nbpeer.NetworkAddress{},
|
||||
&networkTypes.Network{}, &routerTypes.NetworkRouter{}, &resourceTypes.NetworkResource{}, &types.AccountOnboarding{},
|
||||
&types.NetworkMapRecord{}, // <-- Added for precomputed network maps
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("auto migratePreAuto: %w", err)
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// SerializeNetworkMap serializes a NetworkMap to JSON
|
||||
func SerializeNetworkMap(nm *NetworkMap) ([]byte, error) {
|
||||
return json.Marshal(nm)
|
||||
}
|
||||
|
||||
// DeserializeNetworkMap deserializes JSON data into a NetworkMap
|
||||
func DeserializeNetworkMap(data []byte) (*NetworkMap, error) {
|
||||
var nm NetworkMap
|
||||
err := json.Unmarshal(data, &nm)
|
||||
return &nm, err
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/datatypes"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// NetworkMapRecord stores a precomputed network map for a peer
|
||||
// MapJSON is stored as jsonb (Postgres), json (MySQL), or text (SQLite)
|
||||
type NetworkMapRecord struct {
|
||||
PeerID string `gorm:"primaryKey"`
|
||||
AccountID string `gorm:"index"`
|
||||
MapJSON datatypes.JSON `gorm:"type:jsonb"` // GORM will use the right type for your DB
|
||||
Serial uint64
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
// TableName sets the table name for GORM
|
||||
// This ensures the table is named consistently across all supported databases.
|
||||
func (NetworkMapRecord) TableName() string {
|
||||
return "network_map_records"
|
||||
}
|
||||
|
||||
// SaveNetworkMapRecord stores or updates a NetworkMapRecord in the database
|
||||
func SaveNetworkMapRecord(db *gorm.DB, record *NetworkMapRecord) error {
|
||||
return db.Save(record).Error
|
||||
}
|
||||
|
||||
// GetNetworkMapRecord retrieves a NetworkMapRecord by peer ID
|
||||
func GetNetworkMapRecord(db *gorm.DB, peerID string) (*NetworkMapRecord, error) {
|
||||
var record NetworkMapRecord
|
||||
err := db.First(&record, "peer_id = ?", peerID).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &record, nil
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func TestNetworkMapRecordCRUD(t *testing.T) {
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, db.AutoMigrate(&NetworkMapRecord{}))
|
||||
|
||||
record := &NetworkMapRecord{
|
||||
PeerID: "peer1",
|
||||
AccountID: "account1",
|
||||
MapJSON: []byte(`{"Peers":[],"Network":null}`),
|
||||
Serial: 1,
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
require.NoError(t, SaveNetworkMapRecord(db, record))
|
||||
|
||||
fetched, err := GetNetworkMapRecord(db, "peer1")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, record.PeerID, fetched.PeerID)
|
||||
require.Equal(t, record.AccountID, fetched.AccountID)
|
||||
require.Equal(t, record.Serial, fetched.Serial)
|
||||
require.Equal(t, record.MapJSON, fetched.MapJSON)
|
||||
}
|
||||
|
||||
// Simulate a normalized structure for comparison
|
||||
// In a real scenario, this would be split across multiple tables
|
||||
// Here, we just use a struct for benchmarking
|
||||
|
||||
type NormalizedPeer struct {
|
||||
ID string
|
||||
AccountID string
|
||||
Name string
|
||||
IP string
|
||||
}
|
||||
|
||||
type NormalizedNetworkMap struct {
|
||||
PeerID string
|
||||
Peers []NormalizedPeer
|
||||
Serial uint64
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
func BenchmarkNetworkMapRecord_StoreAndRetrieve_JSON(b *testing.B) {
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
db.AutoMigrate(&NetworkMapRecord{})
|
||||
|
||||
record := &NetworkMapRecord{
|
||||
PeerID: "peer1",
|
||||
AccountID: "account1",
|
||||
MapJSON: []byte(`{"Peers":[{"ID":"p1","AccountID":"account1","Name":"peer1","IP":"10.0.0.1"}],"Network":null}`),
|
||||
Serial: 1,
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
record.Serial = uint64(i)
|
||||
record.UpdatedAt = time.Now()
|
||||
if err := SaveNetworkMapRecord(db, record); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
_, err := GetNetworkMapRecord(db, "peer1")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNetworkMapRecord_StoreAndRetrieve_Normalized(b *testing.B) {
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
db.AutoMigrate(&NormalizedPeer{})
|
||||
|
||||
peers := []NormalizedPeer{{ID: "p1", AccountID: "account1", Name: "peer1", IP: "10.0.0.1"}}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, peer := range peers {
|
||||
if err := db.Save(&peer).Error; err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
var fetched []NormalizedPeer
|
||||
if err := db.Find(&fetched, "account_id = ?", "account1").Error; err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,9 +146,26 @@ func (s *SharedSocket) updateRouter() {
|
||||
}
|
||||
}
|
||||
|
||||
// LocalAddr returns an IPv4 address using the supplied port
|
||||
// LocalAddr returns the local address that can handle both IPv4 and IPv6 connections
|
||||
func (s *SharedSocket) LocalAddr() net.Addr {
|
||||
// todo check impact on ipv6 discovery
|
||||
if s.conn4 != nil && s.conn6 != nil {
|
||||
return &net.UDPAddr{
|
||||
IP: net.IPv6unspecified,
|
||||
Port: s.port,
|
||||
}
|
||||
}
|
||||
if s.conn4 != nil {
|
||||
return &net.UDPAddr{
|
||||
IP: net.IPv4zero,
|
||||
Port: s.port,
|
||||
}
|
||||
}
|
||||
if s.conn6 != nil {
|
||||
return &net.UDPAddr{
|
||||
IP: net.IPv6unspecified,
|
||||
Port: s.port,
|
||||
}
|
||||
}
|
||||
return &net.UDPAddr{
|
||||
IP: net.IPv4zero,
|
||||
Port: s.port,
|
||||
|
||||
Reference in New Issue
Block a user