1
0
Fork 0
mirror of https://github.com/anyproto/anytype-heart.git synced 2025-06-08 05:47:07 +09:00

GO-3908 fix iface filtering and sorting

This commit is contained in:
Roman Khafizianov 2024-08-26 23:42:40 +02:00
parent ce23acf64c
commit 9d70c79fa2
No known key found for this signature in database
GPG key ID: F07A7D55A2684852
6 changed files with 264 additions and 78 deletions

View file

@ -1,16 +1,22 @@
package addrs
import (
"cmp"
"fmt"
"net"
"regexp"
"runtime"
"strconv"
"strings"
"github.com/ethereum/go-ethereum/log"
"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
@ -55,7 +61,7 @@ func (i NetInterfaceWithAddrCache) GetAddr() []net.Addr {
if i.cachedErr != nil {
return nil
}
i.cachedAddrs, i.cachedErr = i.Addrs()
i.cachedAddrs, i.cachedErr = i.Interface.Addrs()
if i.cachedErr != nil {
log.Warn("interface GetAddr error: %v", i.cachedErr)
}
@ -80,35 +86,110 @@ 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 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)
@ -116,42 +197,54 @@ func parseInterfaceName(name string) (prefix string, bus int, num int64) {
return
}
func (i InterfacesAddrs) SortWithPriority(priority []string) {
less := func(a, b NetInterfaceWithAddrCache) bool {
aPrefix, aBus, aNum := parseInterfaceName(a.Name)
bPrefix, bBus, bNum := parseInterfaceName(b.Name)
type interfaceComparer struct {
priority []string
}
aPrioirity := slice.FindPos(priority, aPrefix)
bPrioirity := slice.FindPos(priority, bPrefix)
func (i interfaceComparer) Compare(a, b string) int {
aPrefix, aType, aBus, aNum := parseInterfaceName(a)
bPrefix, bType, bBus, bNum := parseInterfaceName(b)
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
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)
}
// prioritise wlan1 over eth8
if aBus != bBus {
return aBus < bBus
}
return aNum < bNum
} else if aPrioirity != -1 {
return true
} else {
return false
}
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)
}
slices.SortFunc(i.Interfaces, func(a, b NetInterfaceWithAddrCache) int {
if less(a, b) {
return -1
}
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 {
@ -223,3 +316,14 @@ func (i InterfacesAddrs) findInterfacePosByIP(ip net.IP) (pos int, equal bool) {
}
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
})
}

View file

@ -1,6 +1,11 @@
package addrs
import "testing"
import (
"sort"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_parseInterfaceName(t *testing.T) {
type args struct {
@ -10,18 +15,27 @@ func Test_parseInterfaceName(t *testing.T) {
name string
args args
wantPrefix string
wantBus int
wantType NamingType
wantBus int64
wantNum int64
}{
{"eth0", args{"eth0"}, "eth", 0, 0},
{"eth1", args{"eth1"}, "eth", 0, 1},
{"eth10", args{"eth10"}, "eth", 0, 10},
{"enp0s10", args{"enp0s10"}, "en", 0, 16},
{"wlp0s20f3", args{"wlp0s20f3"}, "wl", 0, 8435},
{"eth0", args{"eth0"}, "eth", NamingTypeOld, 0, 0},
{"eth1", args{"eth1"}, "eth", NamingTypeOld, 0, 1},
{"eth10", args{"eth10"}, "eth", NamingTypeOld, 0, 10},
{"enp0s10", args{"enp0s10"}, "en", NamingTypeBusSlot, 0, 0x10},
{"wlp0s20f3", args{"wlp0s20f3"}, "wl", NamingTypeBusSlot, 0, 0x20f3},
{"tun0", args{"tun0"}, "tun", NamingTypeOld, 0, 0},
{"tap0", args{"tap0"}, "tap", NamingTypeOld, 0, 0},
{"lo0", args{"lo0"}, "lo", NamingTypeOld, 0, 0},
{"lo1", args{"lo1"}, "lo", NamingTypeOld, 0, 1},
{"lo10", args{"lo10"}, "lo", NamingTypeOld, 0, 10},
{"wlx001122334455", args{"wlx001122334455"}, "wl", NamingTypeMac, 0x001122334455, 0},
{"wlxffffffffffff", args{"wlxffffffffffff"}, "wl", NamingTypeMac, 0xffffffffffff, 0},
{"eno16777736", args{"eno16777736"}, "en", NamingTypeOnboard, 0x16777736, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotPrefix, gotBus, gotNum := parseInterfaceName(tt.args.name)
gotPrefix, gotType, gotBus, gotNum := parseInterfaceName(tt.args.name)
if gotPrefix != tt.wantPrefix {
t.Errorf("parseInterfaceName() gotPrefix = %v, want %v", gotPrefix, tt.wantPrefix)
}
@ -31,6 +45,54 @@ func Test_parseInterfaceName(t *testing.T) {
if gotNum != tt.wantNum {
t.Errorf("parseInterfaceName() gotNum = %v, want %v", gotNum, tt.wantNum)
}
if gotType != tt.wantType {
t.Errorf("parseInterfaceName() gotType = %v, want %v", gotType, tt.wantType)
}
})
}
}
// TestInterfaceSorterSorting tests sorting a list of interface names using the Compare method.
func TestInterfaceSorterSorting(t *testing.T) {
sorter := interfaceComparer{
priority: []string{"wl", "wlan", "en", "eth", "tun", "tap", "utun"},
}
// List of interfaces to sort
interfaces := []string{
"tap0",
"awdl0",
"eth0",
"eno1",
"wlp2s0",
"ens2",
"enp0s3",
"tun0",
"wlan0",
"enx001122334455",
"wlx001122334455",
}
// Expected order after sorting
expected := []string{
"wlp2s0", // Wireless LAN on PCI bus
"wlx001122334455", // Wireless LAN with MAC address
"wlan0", // Old-style Wireless LAN
"eno1", // Highest priority (onboard Ethernet)
"enp0s3", // PCI bus Ethernet
"enx001122334455", // Ethernet with MAC address
"ens2", // Hotplug Ethernet
"eth0", // Old-style Ethernet
"tun0", // VPN TUN interface
"tap0", // VPN TAP interface
"awdl0",
}
// Sorting the interfaces using the Compare method
sort.Slice(interfaces, func(i, j int) bool {
return sorter.Compare(interfaces[i], interfaces[j]) < 0
})
// Assert the sorted order matches the expected order
assert.Equal(t, expected, interfaces, "The interfaces should be sorted correctly according to priority.")
}

View file

@ -6,8 +6,6 @@ package addrs
import (
"net"
"slices"
"github.com/anyproto/anytype-heart/util/slice"
)
func SetInterfaceAddrsGetter(getter InterfaceAddrsGetter) {}
@ -32,11 +30,7 @@ func GetInterfacesAddrs() (iAddrs InterfacesAddrs, err error) {
if err != nil {
return
}
iAddrs.Interfaces = WrapInterfaces(ifaces)
iAddrs.Interfaces = slice.Filter(iAddrs.Interfaces, func(iface NetInterfaceWithAddrCache) bool {
return iface.Flags&net.FlagUp != 0 && iface.Flags&net.FlagMulticast != 0
})
iAddrs.Interfaces = filterInterfaces(WrapInterfaces(ifaces))
return
}

View file

@ -58,7 +58,6 @@ func GetInterfacesAddrs() (addrs InterfacesAddrs, err error) {
lock.Unlock()
for _, iface := range interfaceGetter.Interfaces() {
ifaceWrapped := WrapInterface(iface.Interface)
addrs.Interfaces = append(addrs.Interfaces, WrapInterface(iface.Interface))
unmaskedAddrs := iface.Addrs
ifaceAddrs := make([]net.Addr, 0, len(unmaskedAddrs))
for _, addr := range unmaskedAddrs {
@ -76,6 +75,9 @@ func GetInterfacesAddrs() (addrs InterfacesAddrs, err error) {
// inject cached addresses, because we can't get them from net.Interface's Addrs() on android
ifaceWrapped.cachedAddrs = ifaceAddrs
addrs.Addrs = append(addrs.Addrs, ifaceAddrs...)
addrs.Interfaces = append(addrs.Interfaces, ifaceWrapped)
}
addrs.Interfaces = filterInterfaces(addrs.Interfaces)
return
}