Skip to content

Commit

Permalink
Merge pull request #1470 from anyproto/go-3908-p2p-sort-remote-ips
Browse files Browse the repository at this point in the history
GO-3908 p2p sort remote IPs
  • Loading branch information
requilence authored Aug 28, 2024
2 parents 2c035d3 + aac63f7 commit 509ef15
Show file tree
Hide file tree
Showing 7 changed files with 397 additions and 85 deletions.
305 changes: 257 additions & 48 deletions net/addrs/common.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
package addrs

import (
"cmp"
"fmt"
"net"
"regexp"
"runtime"
"strconv"
"strings"

"golang.org/x/exp/slices"

"github.com/anyproto/anytype-heart/pkg/lib/logging"
"github.com/anyproto/anytype-heart/util/slice"
)

var log = logging.Logger("anytype-net")

type Interface struct {
net.Interface
Addrs []InterfaceAddr
Expand All @@ -20,9 +27,54 @@ type InterfaceAddr struct {
Prefix int
}

type NetInterfaceWithAddrCache struct {
net.Interface
cachedAddrs []net.Addr // ipv4 addresses
cachedErr error
}
type InterfacesAddrs struct {
Interfaces []net.Interface
Addrs []net.Addr
Interfaces []NetInterfaceWithAddrCache
Addrs []net.Addr // addrs without attachment to specific interface. Used as a fallback mechanism
}

func WrapInterface(iface net.Interface) NetInterfaceWithAddrCache {
return NetInterfaceWithAddrCache{
Interface: iface,
}
}

func WrapInterfaces(ifaces []net.Interface) []NetInterfaceWithAddrCache {
var m = make([]NetInterfaceWithAddrCache, 0, len(ifaces))
for i := range ifaces {
m = append(m, NetInterfaceWithAddrCache{
Interface: ifaces[i],
})
}
return m
}

// GetAddr returns ipv4 only addresses for interface or cached one if set
func (i NetInterfaceWithAddrCache) GetAddr() []net.Addr {
if i.cachedAddrs != nil {
return i.cachedAddrs
}
if i.cachedErr != nil {
return nil
}
i.cachedAddrs, i.cachedErr = i.Interface.Addrs()
if i.cachedErr != nil {
log.Warn("interface GetAddr error: %v", i.cachedErr)
}
// filter-out ipv6
i.cachedAddrs = slice.Filter(i.cachedAddrs, func(addr net.Addr) bool {
if ip, ok := addr.(*net.IPNet); ok {
if ip.IP.To4() == nil {
return false
}
}
return true
})
return i.cachedAddrs
}

func (i InterfacesAddrs) Equal(other InterfacesAddrs) bool {
Expand All @@ -34,78 +86,173 @@ func (i InterfacesAddrs) Equal(other InterfacesAddrs) bool {
}
myStr := getStrings(i)
otherStr := getStrings(other)
return slices.Equal(myStr, otherStr)
// compare slices without order
if !slices.Equal(myStr, otherStr) {
log.Debug(fmt.Sprintf("addrs compare: strings mismatch: %v != %v", myStr, otherStr))
return false
}
return true
}

var (
ifaceRe = regexp.MustCompile(`^([a-z]*?)([0-9]+)$`)
ifaceRe = regexp.MustCompile(`^([a-z]*?)([0-9]+)$`)
ifaceWindowsRe = regexp.MustCompile(`^(.*?)([0-9]*)$`)

// ifaceReBusSlot used for prefixBusSlot naming schema used in newer linux distros https://cgit.freedesktop.org/systemd/systemd/tree/src/udev/udev-builtin-net_id.c#n20
ifaceReBusSlot = regexp.MustCompile(`^([a-z]*?)p([0-9]+)s([0-9a-f]+)$`)
ifaceReBusSlot = regexp.MustCompile(`^(?P<type>enp|eno|ens|enx|wlp|wlx)(?P<bus>[0-9a-fA-F]*)s?(?P<slot>[0-9a-fA-F]*)?$`)
)

func parseInterfaceName(name string) (prefix string, bus int, num int64) {
func cleanInterfaceName(name string) (clean string, namingType NamingType) {
if strings.HasPrefix(name, "en") ||
strings.HasPrefix(name, "wl") ||
strings.HasPrefix(name, "eth") {

lastSymbol := name[len(name)-1]
switch NamingType(lastSymbol) {
case NamingTypeBusSlot, NamingTypeHotplug, NamingTypeMac, NamingTypeOnboard:
return name[0 : len(name)-1], NamingType(lastSymbol)
}
}

return name, NamingTypeOld
}

type NamingType string

const (
NamingTypeOld NamingType = ""
NamingTypeOnboard NamingType = "o"
NamingTypeBusSlot NamingType = "p"
NamingTypeMac NamingType = "x"
NamingTypeHotplug NamingType = "s"
)

func (n NamingType) Priority() int {
switch n {
case NamingTypeOld:
return 0
case NamingTypeOnboard:
return 1
case NamingTypeBusSlot:
return 2
case NamingTypeMac:
return 3
case NamingTypeHotplug:
return 4
default:
return 5
}
}

// parseInterfaceName parses interface name and returns prefix, naming type, bus number and slot number
// e.g. enp0s3 -> en, NamingTypeBusSlot, 0, 3
// bus and slot are interpreted as hex numbers
// bus is also used for mac address
// in case of enx001122334455 -> en, NamingTypeMac, 0x001122334455, 0
func parseInterfaceName(name string) (iface string, namingType NamingType, busNum int64, num int64) {
if runtime.GOOS == "windows" {
name, num = parseInterfaceWindowsName(name)
return
}
// try new-style naming schema first (enp0s3, wlp2s0, ...)
res := ifaceReBusSlot.FindStringSubmatch(name)
if len(res) > 0 {
if len(res) > 1 {
prefix = res[1]
}
if len(res) > 2 {
bus, _ = strconv.Atoi(res[2])
}
if len(res) > 3 {
numHex := res[3]
num, _ = strconv.ParseInt(numHex, 16, 32)

for i, subName := range ifaceReBusSlot.SubexpNames() {
if i > 0 && res[i] != "" {
switch subName {
case "type":
iface, namingType = cleanInterfaceName(res[i])
case "bus":
busNum, _ = strconv.ParseInt(res[i], 16, 64)
case "slot": // or mac
num, _ = strconv.ParseInt(res[i], 16, 64)
}
}
}
return
}
// try old-style naming schema (eth0, wlan0, ...)
res = ifaceRe.FindStringSubmatch(name)
if len(res) > 1 {
prefix = res[1]
iface = res[1]
}
if len(res) > 2 {
num, _ = strconv.ParseInt(res[2], 10, 32)
}
if iface == "" {

}
return
}

func (i InterfacesAddrs) SortWithPriority(priority []string) {
less := func(a, b net.Interface) bool {
aPrefix, aBus, aNum := parseInterfaceName(a.Name)
bPrefix, bBus, bNum := parseInterfaceName(b.Name)

aPrioirity := slice.FindPos(priority, aPrefix)
bPrioirity := slice.FindPos(priority, bPrefix)

if aPrefix == bPrefix {
return aNum < bNum
} else if aPrioirity == -1 && bPrioirity == -1 {
// sort alphabetically
return aPrefix < bPrefix
} else if aPrioirity != -1 && bPrioirity != -1 {
// in case we have [eth, wlan]
if aPrioirity == bPrioirity {
// prioritize eth0 over wlan0
return aPrioirity < bPrioirity
}
// prioritise wlan1 over eth8
if aBus != bBus {
return aBus < bBus
func parseInterfaceWindowsName(name string) (iface string, num int64) {
res := ifaceWindowsRe.FindStringSubmatch(name)
if len(res) > 1 {
iface = res[1]
}
if len(res) > 2 {
num, _ = strconv.ParseInt(res[2], 10, 32)
}
return
}

type interfaceComparer struct {
priority []string
}

func (i interfaceComparer) Compare(a, b string) int {
aPrefix, aType, aBus, aNum := parseInterfaceName(a)
bPrefix, bType, bBus, bNum := parseInterfaceName(b)

aPrioirity := slice.FindPos(i.priority, aPrefix)
bPrioirity := slice.FindPos(i.priority, bPrefix)

if aPrioirity != -1 && bPrioirity != -1 || aPrioirity == -1 && bPrioirity == -1 {
if aPrefix != bPrefix {
if aPrioirity != -1 && bPrioirity != -1 {
// prioritize by priority
return cmp.Compare(aPrioirity, bPrioirity)
} else {
// prioritize by prefix
return cmp.Compare(aPrefix, bPrefix)
}
return aNum < bNum
} else if aPrioirity != -1 {
return true
} else {
return false
}
}
slices.SortFunc(i.Interfaces, func(a, b net.Interface) int {
if less(a, b) {
return -1
if aType != bType {
return cmp.Compare(aType.Priority(), bType.Priority())
}
if aBus != bBus {
return cmp.Compare(aBus, bBus)
}
if aNum != bNum {
return cmp.Compare(aNum, bNum)
}
// shouldn't be a case
return cmp.Compare(a, b)
}

if aPrioirity == -1 {
return 1
})
} else {
return -1
}
}

func (i InterfacesAddrs) SortInterfacesWithPriority(priority []string) {
sorter := interfaceComparer{priority: priority}

compare := func(a, b NetInterfaceWithAddrCache) int {
return sorter.Compare(a.Name, b.Name)
}
slices.SortFunc(i.Interfaces, compare)
}

func (i InterfacesAddrs) NetInterfaces() []net.Interface {
var s = make([]net.Interface, 0, len(i.Interfaces))
for _, iface := range i.Interfaces {
s = append(s, iface.Interface)
}
return s
}

func getStrings(i InterfacesAddrs) (allStrings []string) {
Expand All @@ -118,3 +265,65 @@ func getStrings(i InterfacesAddrs) (allStrings []string) {
slices.Sort(allStrings)
return
}

func (i InterfacesAddrs) GetInterfaceByAddr(addr net.Addr) (net.Interface, bool) {
for _, iface := range i.Interfaces {
for _, addrInIface := range iface.GetAddr() {
if addr.String() == addrInIface.String() {
return iface.Interface, true
}
}
}
return net.Interface{}, false
}

// SortIPsLikeInterfaces sort IPs in a way they match sorted interface addresses(via mask matching)
// e.g. we have interfaces
// - en0: 192.168.1.10/24
// - lo0: 127.0.0.1/8
// we pass IPs: 10.124.22.1, 127.0.0.1, 192.168.1.25
// we will get: 192.168.1.25, 127.0.0.1, 10.124.22.1
// 10.124.22.1 does not match any interface, so it will be at the end
func (i InterfacesAddrs) SortIPsLikeInterfaces(ips []net.IP) {
slices.SortFunc(ips, func(a, b net.IP) int {
posA, _ := i.findInterfacePosByIP(a)
posB, _ := i.findInterfacePosByIP(b)

if posA == -1 && posB != -1 {
return 1
}
if posA != -1 && posB == -1 {
return -1
}
if posA < posB {
return -1
} else if posA > posB {
return 1
}
return 0
})
}

func (i InterfacesAddrs) findInterfacePosByIP(ip net.IP) (pos int, equal bool) {
for position, iface := range i.Interfaces {
for _, addr := range iface.GetAddr() {
if ni, ok := addr.(*net.IPNet); ok {
if ni.Contains(ip) {
return position, ni.IP.Equal(ip)
}
}
}
}
return -1, false
}

func filterInterfaces(ifaces []NetInterfaceWithAddrCache) []NetInterfaceWithAddrCache {
return slice.Filter(ifaces, func(iface NetInterfaceWithAddrCache) bool {
if iface.Flags&net.FlagUp != 0 && iface.Flags&net.FlagMulticast != 0 && iface.Flags&net.FlagLoopback == 0 {
if len(iface.GetAddr()) > 0 {
return true
}
}
return false
})
}
Loading

0 comments on commit 509ef15

Please sign in to comment.