Merge branch 'main' into ssh-rewrite

This commit is contained in:
Viktor Liu
2025-07-04 16:53:54 +02:00
32 changed files with 1386 additions and 552 deletions
+9
View File
@@ -32,11 +32,13 @@ func ipv4Checksum(header []byte) uint16 {
var sum1, sum2 uint32
// Parallel processing - unroll and compute two sums simultaneously
sum1 += uint32(binary.BigEndian.Uint16(header[0:2]))
sum2 += uint32(binary.BigEndian.Uint16(header[2:4]))
sum1 += uint32(binary.BigEndian.Uint16(header[4:6]))
sum2 += uint32(binary.BigEndian.Uint16(header[6:8]))
sum1 += uint32(binary.BigEndian.Uint16(header[8:10]))
// Skip checksum field at [10:12]
sum2 += uint32(binary.BigEndian.Uint16(header[12:14]))
sum1 += uint32(binary.BigEndian.Uint16(header[14:16]))
sum2 += uint32(binary.BigEndian.Uint16(header[16:18]))
@@ -44,6 +46,7 @@ func ipv4Checksum(header []byte) uint16 {
sum := sum1 + sum2
// Handle remaining bytes for headers > 20 bytes
for i := 20; i < len(header)-1; i += 2 {
sum += uint32(binary.BigEndian.Uint16(header[i : i+2]))
}
@@ -52,6 +55,7 @@ func ipv4Checksum(header []byte) uint16 {
sum += uint32(header[len(header)-1]) << 8
}
// Optimized carry fold - single iteration handles most cases
sum = (sum & 0xFFFF) + (sum >> 16)
if sum > 0xFFFF {
sum++
@@ -65,6 +69,7 @@ func icmpChecksum(data []byte) uint16 {
var sum1, sum2, sum3, sum4 uint32
i := 0
// Process 16 bytes at once with 4 parallel accumulators
for i <= len(data)-16 {
sum1 += uint32(binary.BigEndian.Uint16(data[i : i+2]))
sum2 += uint32(binary.BigEndian.Uint16(data[i+2 : i+4]))
@@ -79,6 +84,7 @@ func icmpChecksum(data []byte) uint16 {
sum := sum1 + sum2 + sum3 + sum4
// Handle remaining bytes
for i < len(data)-1 {
sum += uint32(binary.BigEndian.Uint16(data[i : i+2]))
i += 2
@@ -255,6 +261,7 @@ func (m *Manager) AddInternalDNATMapping(originalAddr, translatedAddr netip.Addr
m.dnatMutex.Lock()
defer m.dnatMutex.Unlock()
// Initialize both maps together if either is nil
if m.dnatMappings == nil || m.dnatBiMap == nil {
m.dnatMappings = make(map[netip.Addr]netip.Addr)
m.dnatBiMap = newBiDNATMap()
@@ -482,12 +489,14 @@ func (m *Manager) updateICMPChecksum(packetData []byte, ipHeaderLen int) {
func incrementalUpdate(oldChecksum uint16, oldBytes, newBytes []byte) uint16 {
sum := uint32(^oldChecksum)
// Fast path for IPv4 addresses (4 bytes) - most common case
if len(oldBytes) == 4 && len(newBytes) == 4 {
sum += uint32(^binary.BigEndian.Uint16(oldBytes[0:2]))
sum += uint32(^binary.BigEndian.Uint16(oldBytes[2:4]))
sum += uint32(binary.BigEndian.Uint16(newBytes[0:2]))
sum += uint32(binary.BigEndian.Uint16(newBytes[2:4]))
} else {
// Fallback for other lengths
for i := 0; i < len(oldBytes)-1; i += 2 {
sum += uint32(^binary.BigEndian.Uint16(oldBytes[i : i+2]))
}
+1 -1
View File
@@ -156,7 +156,7 @@ func registerPeer(ctx context.Context, serverPublicKey wgtypes.Key, client *mgm.
)
loginResp, err := client.Register(serverPublicKey, validSetupKey.String(), jwtToken, info, pubSSHKey, config.DNSLabels)
if err != nil {
log.Errorf("failed registering peer %v,%s", err, validSetupKey.String())
log.Errorf("failed registering peer %v", err)
return nil, err
}
+2 -1
View File
@@ -144,6 +144,7 @@ func (m *DefaultManager) setupAndroidRoutes(config ManagerConfig) {
cr := m.initialClientRoutes(config.InitialRoutes)
routesForComparison := slices.Clone(cr)
if config.DNSFeatureFlag {
m.fakeIPManager = fakeip.NewManager()
@@ -158,7 +159,7 @@ func (m *DefaultManager) setupAndroidRoutes(config ManagerConfig) {
cr = append(cr, fakeIPRoute)
}
m.notifier.SetInitialClientRoutes(cr, routesForComparison, config.DNSFeatureFlag)
m.notifier.SetInitialClientRoutes(cr, routesForComparison)
}
func (m *DefaultManager) setupRefCounters(useNoop bool) {
@@ -1,145 +0,0 @@
package notifier
import (
"net/netip"
"runtime"
"slices"
"sort"
"strings"
"sync"
"github.com/netbirdio/netbird/client/internal/listener"
"github.com/netbirdio/netbird/route"
)
type Notifier struct {
initialRoutes []*route.Route
routesForComparison []*route.Route
dnsFeatureFlag bool
listener listener.NetworkChangeListener
listenerMux sync.Mutex
}
func NewNotifier() *Notifier {
return &Notifier{}
}
func (n *Notifier) SetListener(listener listener.NetworkChangeListener) {
n.listenerMux.Lock()
defer n.listenerMux.Unlock()
n.listener = listener
}
func (n *Notifier) SetInitialClientRoutes(allRoutes []*route.Route, routesForComparison []*route.Route, dnsFeatureFlag bool) {
n.dnsFeatureFlag = dnsFeatureFlag
n.initialRoutes = allRoutes
n.routesForComparison = routesForComparison
}
func (n *Notifier) OnNewRoutes(idMap route.HAMap) {
if runtime.GOOS != "android" {
return
}
var newRoutes []*route.Route
for _, routes := range idMap {
newRoutes = append(newRoutes, routes...)
}
if !n.hasRouteDiff(n.routesForComparison, newRoutes) {
return
}
n.routesForComparison = newRoutes
n.notify()
}
// OnNewPrefixes is called from iOS only
func (n *Notifier) OnNewPrefixes(prefixes []netip.Prefix) {
newNets := make([]string, 0)
for _, prefix := range prefixes {
newNets = append(newNets, prefix.String())
}
sort.Strings(newNets)
currentNets := n.routesToStrings(n.routesForComparison)
if slices.Equal(currentNets, newNets) {
return
}
n.notify()
}
func (n *Notifier) notify() {
n.listenerMux.Lock()
defer n.listenerMux.Unlock()
if n.listener == nil {
return
}
routeStrings := n.routesToStrings(n.routesForComparison)
sort.Strings(routeStrings)
go func(l listener.NetworkChangeListener) {
l.OnNetworkChanged(strings.Join(n.addIPv6RangeIfNeeded(routeStrings, n.routesForComparison), ","))
}(n.listener)
}
// hasRouteDiff compares two route slices for differences
func (n *Notifier) hasRouteDiff(a []*route.Route, b []*route.Route) bool {
aFiltered := n.filterRoutes(a)
bFiltered := n.filterRoutes(b)
slices.SortFunc(aFiltered, func(x, y *route.Route) int {
return strings.Compare(x.NetString(), y.NetString())
})
slices.SortFunc(bFiltered, func(x, y *route.Route) int {
return strings.Compare(x.NetString(), y.NetString())
})
return !slices.EqualFunc(aFiltered, bFiltered, func(x, y *route.Route) bool {
return x.NetString() == y.NetString()
})
}
// filterRoutes filters routes based on DNS feature flag
func (n *Notifier) filterRoutes(routes []*route.Route) []*route.Route {
filtered := make([]*route.Route, 0, len(routes))
for _, r := range routes {
if r.IsDynamic() && !n.dnsFeatureFlag {
// this kind of dynamic route is not supported on android
continue
}
filtered = append(filtered, r)
}
return filtered
}
// routesToStrings converts routes to string slice (caller should sort if needed)
func (n *Notifier) routesToStrings(routes []*route.Route) []string {
filtered := n.filterRoutes(routes)
nets := make([]string, 0, len(filtered))
for _, r := range filtered {
nets = append(nets, r.NetString())
}
return nets
}
func (n *Notifier) GetInitialRouteRanges() []string {
initialStrings := n.routesToStrings(n.initialRoutes)
sort.Strings(initialStrings)
return n.addIPv6RangeIfNeeded(initialStrings, n.initialRoutes)
}
// addIPv6RangeIfNeeded returns the input ranges with the default IPv6 range when there is an IPv4 default route.
func (n *Notifier) addIPv6RangeIfNeeded(inputRanges []string, routes []*route.Route) []string {
for _, r := range routes {
// we are intentionally adding the ipv6 default range in case of ipv4 default range
// to ensure that all traffic is managed by the tunnel interface on android
if r.Network.Addr().Is4() && r.Network.Bits() == 0 {
return append(slices.Clone(inputRanges), "::/0")
}
}
return inputRanges
}
@@ -0,0 +1,127 @@
//go:build android
package notifier
import (
"net/netip"
"slices"
"sort"
"strings"
"sync"
"github.com/netbirdio/netbird/client/internal/listener"
"github.com/netbirdio/netbird/route"
)
type Notifier struct {
initialRoutes []*route.Route
currentRoutes []*route.Route
listener listener.NetworkChangeListener
listenerMux sync.Mutex
}
func NewNotifier() *Notifier {
return &Notifier{}
}
func (n *Notifier) SetListener(listener listener.NetworkChangeListener) {
n.listenerMux.Lock()
defer n.listenerMux.Unlock()
n.listener = listener
}
func (n *Notifier) SetInitialClientRoutes(initialRoutes []*route.Route, routesForComparison []*route.Route) {
// initialRoutes contains fake IP block for interface configuration
filteredInitial := make([]*route.Route, 0)
for _, r := range initialRoutes {
if r.IsDynamic() {
continue
}
filteredInitial = append(filteredInitial, r)
}
n.initialRoutes = filteredInitial
// routesForComparison excludes fake IP block for comparison with new routes
filteredComparison := make([]*route.Route, 0)
for _, r := range routesForComparison {
if r.IsDynamic() {
continue
}
filteredComparison = append(filteredComparison, r)
}
n.currentRoutes = filteredComparison
}
func (n *Notifier) OnNewRoutes(idMap route.HAMap) {
var newRoutes []*route.Route
for _, routes := range idMap {
for _, r := range routes {
if r.IsDynamic() {
continue
}
newRoutes = append(newRoutes, r)
}
}
if !n.hasRouteDiff(n.currentRoutes, newRoutes) {
return
}
n.currentRoutes = newRoutes
n.notify()
}
func (n *Notifier) OnNewPrefixes([]netip.Prefix) {
// Not used on Android
}
func (n *Notifier) notify() {
n.listenerMux.Lock()
defer n.listenerMux.Unlock()
if n.listener == nil {
return
}
routeStrings := n.routesToStrings(n.currentRoutes)
sort.Strings(routeStrings)
go func(l listener.NetworkChangeListener) {
l.OnNetworkChanged(strings.Join(n.addIPv6RangeIfNeeded(routeStrings, n.currentRoutes), ","))
}(n.listener)
}
func (n *Notifier) routesToStrings(routes []*route.Route) []string {
nets := make([]string, 0, len(routes))
for _, r := range routes {
nets = append(nets, r.NetString())
}
return nets
}
func (n *Notifier) hasRouteDiff(a []*route.Route, b []*route.Route) bool {
slices.SortFunc(a, func(x, y *route.Route) int {
return strings.Compare(x.NetString(), y.NetString())
})
slices.SortFunc(b, func(x, y *route.Route) int {
return strings.Compare(x.NetString(), y.NetString())
})
return !slices.EqualFunc(a, b, func(x, y *route.Route) bool {
return x.NetString() == y.NetString()
})
}
func (n *Notifier) GetInitialRouteRanges() []string {
initialStrings := n.routesToStrings(n.initialRoutes)
sort.Strings(initialStrings)
return n.addIPv6RangeIfNeeded(initialStrings, n.initialRoutes)
}
func (n *Notifier) addIPv6RangeIfNeeded(inputRanges []string, routes []*route.Route) []string {
for _, r := range routes {
if r.Network.Addr().Is4() && r.Network.Bits() == 0 {
return append(slices.Clone(inputRanges), "::/0")
}
}
return inputRanges
}
@@ -0,0 +1,80 @@
//go:build ios
package notifier
import (
"net/netip"
"slices"
"sort"
"strings"
"sync"
"github.com/netbirdio/netbird/client/internal/listener"
"github.com/netbirdio/netbird/route"
)
type Notifier struct {
currentPrefixes []string
listener listener.NetworkChangeListener
listenerMux sync.Mutex
}
func NewNotifier() *Notifier {
return &Notifier{}
}
func (n *Notifier) SetListener(listener listener.NetworkChangeListener) {
n.listenerMux.Lock()
defer n.listenerMux.Unlock()
n.listener = listener
}
func (n *Notifier) SetInitialClientRoutes([]*route.Route, []*route.Route) {
// iOS doesn't care about initial routes
}
func (n *Notifier) OnNewRoutes(route.HAMap) {
// Not used on iOS
}
func (n *Notifier) OnNewPrefixes(prefixes []netip.Prefix) {
newNets := make([]string, 0)
for _, prefix := range prefixes {
newNets = append(newNets, prefix.String())
}
sort.Strings(newNets)
if slices.Equal(n.currentPrefixes, newNets) {
return
}
n.currentPrefixes = newNets
n.notify()
}
func (n *Notifier) notify() {
n.listenerMux.Lock()
defer n.listenerMux.Unlock()
if n.listener == nil {
return
}
go func(l listener.NetworkChangeListener) {
l.OnNetworkChanged(strings.Join(n.addIPv6RangeIfNeeded(n.currentPrefixes), ","))
}(n.listener)
}
func (n *Notifier) GetInitialRouteRanges() []string {
return nil
}
func (n *Notifier) addIPv6RangeIfNeeded(inputRanges []string) []string {
for _, r := range inputRanges {
if r == "0.0.0.0/0" {
return append(slices.Clone(inputRanges), "::/0")
}
}
return inputRanges
}
@@ -0,0 +1,36 @@
//go:build !android && !ios
package notifier
import (
"net/netip"
"github.com/netbirdio/netbird/client/internal/listener"
"github.com/netbirdio/netbird/route"
)
type Notifier struct{}
func NewNotifier() *Notifier {
return &Notifier{}
}
func (n *Notifier) SetListener(listener listener.NetworkChangeListener) {
// Not used on non-mobile platforms
}
func (n *Notifier) SetInitialClientRoutes([]*route.Route, []*route.Route) {
// Not used on non-mobile platforms
}
func (n *Notifier) OnNewRoutes(idMap route.HAMap) {
// Not used on non-mobile platforms
}
func (n *Notifier) OnNewPrefixes(prefixes []netip.Prefix) {
// Not used on non-mobile platforms
}
func (n *Notifier) GetInitialRouteRanges() []string {
return []string{}
}
@@ -5,6 +5,7 @@ import (
"net"
"net/netip"
"sync"
"sync/atomic"
"github.com/netbirdio/netbird/client/iface/wgaddr"
"github.com/netbirdio/netbird/client/internal/routemanager/notifier"
@@ -52,6 +53,9 @@ type SysOps struct {
mu sync.Mutex
// notifier is used to notify the system of route changes (also used on mobile)
notifier *notifier.Notifier
// seq is an atomic counter for generating unique sequence numbers for route messages
//nolint:unused // only used on BSD systems
seq atomic.Uint32
}
func NewSysOps(wgInterface wgIface, notifier *notifier.Notifier) *SysOps {
@@ -61,6 +65,11 @@ func NewSysOps(wgInterface wgIface, notifier *notifier.Notifier) *SysOps {
}
}
//nolint:unused // only used on BSD systems
func (r *SysOps) getSeq() int {
return int(r.seq.Add(1))
}
func (r *SysOps) validateRoute(prefix netip.Prefix) error {
addr := prefix.Addr()
@@ -108,7 +108,7 @@ func (r *SysOps) buildRouteMessage(action int, prefix netip.Prefix, nexthop Next
Type: action,
Flags: unix.RTF_UP,
Version: unix.RTM_VERSION,
Seq: 1,
Seq: r.getSeq(),
}
const numAddrs = unix.RTAX_NETMASK + 1