@@ -417,7 +417,14 @@ func determineICMPv4Src(userDefinedSrc string, logger *zerolog.Logger) (netip.Ad
417417 return netip.Addr {}, fmt .Errorf ("expect IPv4, but %s is IPv6" , userDefinedSrc )
418418 }
419419
420- addr , err := findLocalAddr (net .ParseIP ("192.168.0.1" ), 53 )
420+ // First try to find an IP from a preferred physical interface,
421+ // avoiding virtual/bridge interfaces (Docker, etc.)
422+ if addr := findPreferredIP (true , logger ); addr .IsValid () {
423+ return addr , nil
424+ }
425+
426+ // Fall back to dialing a public IP to determine the default route interface
427+ addr , err := findLocalAddr (net .ParseIP ("8.8.8.8" ), 53 )
421428 if err != nil {
422429 addr = netip .IPv4Unspecified ()
423430 logger .Debug ().Err (err ).Msgf ("Failed to determine the IPv4 for this machine. It will use %s to send/listen for ICMPv4 echo" , addr )
@@ -430,6 +437,125 @@ type interfaceIP struct {
430437 ip net.IP
431438}
432439
440+ // virtualInterfacePrefixes are prefixes for virtual/bridge interfaces that should be deprioritized
441+ var virtualInterfacePrefixes = []string {
442+ "br-" , // Docker bridge
443+ "docker" , // Docker
444+ "veth" , // Virtual ethernet (containers)
445+ "virbr" , // libvirt bridge
446+ "vboxnet" , // VirtualBox
447+ "vmnet" , // VMware
448+ "lxcbr" , // LXC bridge
449+ "lxdbr" , // LXD bridge
450+ "cni" , // Kubernetes CNI
451+ "flannel" , // Flannel overlay
452+ "cali" , // Calico
453+ "weave" , // Weave
454+ "podman" , // Podman
455+ }
456+
457+ // physicalInterfacePrefixes are prefixes for physical interfaces that should be prioritized
458+ var physicalInterfacePrefixes = []string {
459+ "eth" , // Traditional ethernet (Linux)
460+ "enp" , // Systemd predictable naming (PCI)
461+ "ens" , // Systemd predictable naming (slot)
462+ "eno" , // Systemd predictable naming (onboard)
463+ "wlan" , // Traditional wireless (Linux)
464+ "wlp" , // Systemd predictable naming (wireless PCI)
465+ "en" , // macOS/BSD ethernet and wireless
466+ }
467+
468+ // isVirtualInterface returns true if the interface name matches a known virtual/bridge interface pattern
469+ func isVirtualInterface (name string ) bool {
470+ for _ , prefix := range virtualInterfacePrefixes {
471+ if strings .HasPrefix (name , prefix ) {
472+ return true
473+ }
474+ }
475+ return false
476+ }
477+
478+ // isPhysicalInterface returns true if the interface name matches a known physical interface pattern
479+ func isPhysicalInterface (name string ) bool {
480+ for _ , prefix := range physicalInterfacePrefixes {
481+ if strings .HasPrefix (name , prefix ) {
482+ return true
483+ }
484+ }
485+ return false
486+ }
487+
488+ // findPreferredIP returns an IP address from a preferred physical interface.
489+ // It prioritizes interfaces matching physical patterns and excludes virtual/bridge interfaces.
490+ // Returns zero value if no suitable interface is found.
491+ func findPreferredIP (wantIPv4 bool , logger * zerolog.Logger ) netip.Addr {
492+ interfaces , err := net .Interfaces ()
493+ if err != nil {
494+ return netip.Addr {}
495+ }
496+
497+ var fallbackIP netip.Addr
498+ for _ , iface := range interfaces {
499+ // Skip interfaces that are down or loopback
500+ if iface .Flags & net .FlagUp == 0 || iface .Flags & net .FlagLoopback != 0 {
501+ continue
502+ }
503+
504+ addrs , err := iface .Addrs ()
505+ if err != nil {
506+ continue
507+ }
508+
509+ for _ , addr := range addrs {
510+ ipnet , ok := addr .(* net.IPNet )
511+ if ! ok {
512+ continue
513+ }
514+
515+ ip := ipnet .IP
516+ parsedIP , err := netip .ParseAddr (ip .String ())
517+ if err != nil {
518+ continue
519+ }
520+
521+ // Check IP version match
522+ if wantIPv4 && ! parsedIP .Is4 () {
523+ continue
524+ }
525+ if ! wantIPv4 && ! parsedIP .Is6 () {
526+ continue
527+ }
528+
529+ // Skip link-local addresses for IPv4
530+ if wantIPv4 && ip .IsLinkLocalUnicast () {
531+ continue
532+ }
533+
534+ // For IPv6, skip if it's link-local and we're looking for a routable address
535+ // (link-local is fine as a fallback)
536+ isLinkLocal := ip .IsLinkLocalUnicast ()
537+
538+ // Skip virtual interfaces
539+ if isVirtualInterface (iface .Name ) {
540+ continue
541+ }
542+
543+ // Prefer physical interfaces
544+ if isPhysicalInterface (iface .Name ) && ! isLinkLocal {
545+ logger .Debug ().Msgf ("Selected %s from physical interface %s for ICMP proxy" , parsedIP , iface .Name )
546+ return parsedIP
547+ }
548+
549+ // Store as fallback if we haven't found one yet
550+ if ! fallbackIP .IsValid () && ! isLinkLocal {
551+ fallbackIP = parsedIP
552+ }
553+ }
554+ }
555+
556+ return fallbackIP
557+ }
558+
433559func determineICMPv6Src (userDefinedSrc string , logger * zerolog.Logger , ipv4Src netip.Addr ) (addr netip.Addr , zone string , err error ) {
434560 if userDefinedSrc != "" {
435561 addr , err := netip .ParseAddr (userDefinedSrc )
@@ -454,6 +580,11 @@ func determineICMPv6Src(userDefinedSrc string, logger *zerolog.Logger, ipv4Src n
454580
455581 interfacesWithIPv6 := make ([]interfaceIP , 0 )
456582 for _ , interf := range interfaces {
583+ // Skip virtual/bridge interfaces
584+ if isVirtualInterface (interf .Name ) {
585+ continue
586+ }
587+
457588 interfaceAddrs , err := interf .Addrs ()
458589 if err != nil {
459590 continue
@@ -490,6 +621,17 @@ func determineICMPv6Src(userDefinedSrc string, logger *zerolog.Logger, ipv4Src n
490621 }
491622 }
492623
624+ // Prefer physical interfaces when selecting from available IPv6 interfaces
625+ for _ , interf := range interfacesWithIPv6 {
626+ if isPhysicalInterface (interf .name ) {
627+ addr , err := netip .ParseAddr (interf .ip .String ())
628+ if err == nil {
629+ return addr , interf .name , nil
630+ }
631+ }
632+ }
633+
634+ // Fall back to any non-virtual interface with IPv6
493635 for _ , interf := range interfacesWithIPv6 {
494636 addr , err := netip .ParseAddr (interf .ip .String ())
495637 if err == nil {
0 commit comments