This commit is contained in:
HueCodes 2026-02-22 14:59:44 -08:00 committed by GitHub
commit 2cbcc1912f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 235 additions and 1 deletions

View File

@ -410,7 +410,14 @@ func determineICMPv4Src(userDefinedSrc string, logger *zerolog.Logger) (netip.Ad
return netip.Addr{}, fmt.Errorf("expect IPv4, but %s is IPv6", userDefinedSrc) return netip.Addr{}, fmt.Errorf("expect IPv4, but %s is IPv6", userDefinedSrc)
} }
addr, err := findLocalAddr(net.ParseIP("192.168.0.1"), 53) // First try to find an IP from a preferred physical interface,
// avoiding virtual/bridge interfaces (Docker, etc.)
if addr := findPreferredIP(true, logger); addr.IsValid() {
return addr, nil
}
// Fall back to dialing a public IP to determine the default route interface
addr, err := findLocalAddr(net.ParseIP("8.8.8.8"), 53)
if err != nil { if err != nil {
addr = netip.IPv4Unspecified() addr = netip.IPv4Unspecified()
logger.Debug().Err(err).Msgf("Failed to determine the IPv4 for this machine. It will use %s to send/listen for ICMPv4 echo", addr) logger.Debug().Err(err).Msgf("Failed to determine the IPv4 for this machine. It will use %s to send/listen for ICMPv4 echo", addr)
@ -423,6 +430,125 @@ type interfaceIP struct {
ip net.IP ip net.IP
} }
// virtualInterfacePrefixes are prefixes for virtual/bridge interfaces that should be deprioritized
var virtualInterfacePrefixes = []string{
"br-", // Docker bridge
"docker", // Docker
"veth", // Virtual ethernet (containers)
"virbr", // libvirt bridge
"vboxnet", // VirtualBox
"vmnet", // VMware
"lxcbr", // LXC bridge
"lxdbr", // LXD bridge
"cni", // Kubernetes CNI
"flannel", // Flannel overlay
"cali", // Calico
"weave", // Weave
"podman", // Podman
}
// physicalInterfacePrefixes are prefixes for physical interfaces that should be prioritized
var physicalInterfacePrefixes = []string{
"eth", // Traditional ethernet (Linux)
"enp", // Systemd predictable naming (PCI)
"ens", // Systemd predictable naming (slot)
"eno", // Systemd predictable naming (onboard)
"wlan", // Traditional wireless (Linux)
"wlp", // Systemd predictable naming (wireless PCI)
"en", // macOS/BSD ethernet and wireless
}
// isVirtualInterface returns true if the interface name matches a known virtual/bridge interface pattern
func isVirtualInterface(name string) bool {
for _, prefix := range virtualInterfacePrefixes {
if strings.HasPrefix(name, prefix) {
return true
}
}
return false
}
// isPhysicalInterface returns true if the interface name matches a known physical interface pattern
func isPhysicalInterface(name string) bool {
for _, prefix := range physicalInterfacePrefixes {
if strings.HasPrefix(name, prefix) {
return true
}
}
return false
}
// findPreferredIP returns an IP address from a preferred physical interface.
// It prioritizes interfaces matching physical patterns and excludes virtual/bridge interfaces.
// Returns zero value if no suitable interface is found.
func findPreferredIP(wantIPv4 bool, logger *zerolog.Logger) netip.Addr {
interfaces, err := net.Interfaces()
if err != nil {
return netip.Addr{}
}
var fallbackIP netip.Addr
for _, iface := range interfaces {
// Skip interfaces that are down or loopback
if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
continue
}
addrs, err := iface.Addrs()
if err != nil {
continue
}
for _, addr := range addrs {
ipnet, ok := addr.(*net.IPNet)
if !ok {
continue
}
ip := ipnet.IP
parsedIP, err := netip.ParseAddr(ip.String())
if err != nil {
continue
}
// Check IP version match
if wantIPv4 && !parsedIP.Is4() {
continue
}
if !wantIPv4 && !parsedIP.Is6() {
continue
}
// Skip link-local addresses for IPv4
if wantIPv4 && ip.IsLinkLocalUnicast() {
continue
}
// For IPv6, skip if it's link-local and we're looking for a routable address
// (link-local is fine as a fallback)
isLinkLocal := ip.IsLinkLocalUnicast()
// Skip virtual interfaces
if isVirtualInterface(iface.Name) {
continue
}
// Prefer physical interfaces
if isPhysicalInterface(iface.Name) && !isLinkLocal {
logger.Debug().Msgf("Selected %s from physical interface %s for ICMP proxy", parsedIP, iface.Name)
return parsedIP
}
// Store as fallback if we haven't found one yet
if !fallbackIP.IsValid() && !isLinkLocal {
fallbackIP = parsedIP
}
}
}
return fallbackIP
}
func determineICMPv6Src(userDefinedSrc string, logger *zerolog.Logger, ipv4Src netip.Addr) (addr netip.Addr, zone string, err error) { func determineICMPv6Src(userDefinedSrc string, logger *zerolog.Logger, ipv4Src netip.Addr) (addr netip.Addr, zone string, err error) {
if userDefinedSrc != "" { if userDefinedSrc != "" {
addr, err := netip.ParseAddr(userDefinedSrc) addr, err := netip.ParseAddr(userDefinedSrc)
@ -447,6 +573,11 @@ func determineICMPv6Src(userDefinedSrc string, logger *zerolog.Logger, ipv4Src n
interfacesWithIPv6 := make([]interfaceIP, 0) interfacesWithIPv6 := make([]interfaceIP, 0)
for _, interf := range interfaces { for _, interf := range interfaces {
// Skip virtual/bridge interfaces
if isVirtualInterface(interf.Name) {
continue
}
interfaceAddrs, err := interf.Addrs() interfaceAddrs, err := interf.Addrs()
if err != nil { if err != nil {
continue continue
@ -483,6 +614,17 @@ func determineICMPv6Src(userDefinedSrc string, logger *zerolog.Logger, ipv4Src n
} }
} }
// Prefer physical interfaces when selecting from available IPv6 interfaces
for _, interf := range interfacesWithIPv6 {
if isPhysicalInterface(interf.name) {
addr, err := netip.ParseAddr(interf.ip.String())
if err == nil {
return addr, interf.name, nil
}
}
}
// Fall back to any non-virtual interface with IPv6
for _, interf := range interfacesWithIPv6 { for _, interf := range interfacesWithIPv6 {
addr, err := netip.ParseAddr(interf.ip.String()) addr, err := netip.ParseAddr(interf.ip.String())
if err == nil { if err == nil {

View File

@ -0,0 +1,92 @@
package tunnel
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestIsVirtualInterface(t *testing.T) {
tests := []struct {
name string
expected bool
}{
// Virtual interfaces that should be filtered
{"br-1744e4cf9e20", true},
{"docker0", true},
{"docker1", true},
{"veth1234abc", true},
{"virbr0", true},
{"vboxnet0", true},
{"vmnet1", true},
{"lxcbr0", true},
{"lxdbr0", true},
{"cni0", true},
{"flannel.1", true},
{"cali1234", true},
{"weave", true},
{"podman0", true},
// Physical interfaces that should not be filtered
{"eth0", false},
{"enp6s0", false},
{"ens192", false},
{"eno1", false},
{"wlan0", false},
{"wlp3s0", false},
{"lo", false},
{"bond0", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := isVirtualInterface(tt.name)
assert.Equal(t, tt.expected, result, "isVirtualInterface(%q)", tt.name)
})
}
}
func TestIsPhysicalInterface(t *testing.T) {
tests := []struct {
name string
expected bool
}{
// Physical interfaces that should be prioritized (Linux)
{"eth0", true},
{"eth1", true},
{"enp6s0", true},
{"enp0s25", true},
{"ens192", true},
{"ens33", true},
{"eno1", true},
{"eno2", true},
{"wlan0", true},
{"wlan1", true},
{"wlp3s0", true},
{"wlp2s0", true},
// Physical interfaces (macOS)
{"en0", true},
{"en1", true},
{"en5", true},
// Non-physical interfaces
{"lo", false},
{"lo0", false},
{"docker0", false},
{"br-abc123", false},
{"veth1234", false},
{"bond0", false},
{"tun0", false},
{"tap0", false},
{"utun0", false},
{"bridge0", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := isPhysicalInterface(tt.name)
assert.Equal(t, tt.expected, result, "isPhysicalInterface(%q)", tt.name)
})
}
}