@@ -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,124 @@ 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
460+ "enp" , // Systemd predictable naming (PCI)
461+ "ens" , // Systemd predictable naming (slot)
462+ "eno" , // Systemd predictable naming (onboard)
463+ "wlan" , // Traditional wireless
464+ "wlp" , // Systemd predictable naming (wireless PCI)
465+ }
466+
467+ // isVirtualInterface returns true if the interface name matches a known virtual/bridge interface pattern
468+ func isVirtualInterface (name string ) bool {
469+ for _ , prefix := range virtualInterfacePrefixes {
470+ if strings .HasPrefix (name , prefix ) {
471+ return true
472+ }
473+ }
474+ return false
475+ }
476+
477+ // isPhysicalInterface returns true if the interface name matches a known physical interface pattern
478+ func isPhysicalInterface (name string ) bool {
479+ for _ , prefix := range physicalInterfacePrefixes {
480+ if strings .HasPrefix (name , prefix ) {
481+ return true
482+ }
483+ }
484+ return false
485+ }
486+
487+ // findPreferredIP returns an IP address from a preferred physical interface.
488+ // It prioritizes interfaces matching physical patterns and excludes virtual/bridge interfaces.
489+ // Returns zero value if no suitable interface is found.
490+ func findPreferredIP (wantIPv4 bool , logger * zerolog.Logger ) netip.Addr {
491+ interfaces , err := net .Interfaces ()
492+ if err != nil {
493+ return netip.Addr {}
494+ }
495+
496+ var fallbackIP netip.Addr
497+ for _ , iface := range interfaces {
498+ // Skip interfaces that are down or loopback
499+ if iface .Flags & net .FlagUp == 0 || iface .Flags & net .FlagLoopback != 0 {
500+ continue
501+ }
502+
503+ addrs , err := iface .Addrs ()
504+ if err != nil {
505+ continue
506+ }
507+
508+ for _ , addr := range addrs {
509+ ipnet , ok := addr .(* net.IPNet )
510+ if ! ok {
511+ continue
512+ }
513+
514+ ip := ipnet .IP
515+ parsedIP , err := netip .ParseAddr (ip .String ())
516+ if err != nil {
517+ continue
518+ }
519+
520+ // Check IP version match
521+ if wantIPv4 && ! parsedIP .Is4 () {
522+ continue
523+ }
524+ if ! wantIPv4 && ! parsedIP .Is6 () {
525+ continue
526+ }
527+
528+ // Skip link-local addresses for IPv4
529+ if wantIPv4 && ip .IsLinkLocalUnicast () {
530+ continue
531+ }
532+
533+ // For IPv6, skip if it's link-local and we're looking for a routable address
534+ // (link-local is fine as a fallback)
535+ isLinkLocal := ip .IsLinkLocalUnicast ()
536+
537+ // Skip virtual interfaces
538+ if isVirtualInterface (iface .Name ) {
539+ continue
540+ }
541+
542+ // Prefer physical interfaces
543+ if isPhysicalInterface (iface .Name ) && ! isLinkLocal {
544+ logger .Debug ().Msgf ("Selected %s from physical interface %s for ICMP proxy" , parsedIP , iface .Name )
545+ return parsedIP
546+ }
547+
548+ // Store as fallback if we haven't found one yet
549+ if ! fallbackIP .IsValid () && ! isLinkLocal {
550+ fallbackIP = parsedIP
551+ }
552+ }
553+ }
554+
555+ return fallbackIP
556+ }
557+
433558func determineICMPv6Src (userDefinedSrc string , logger * zerolog.Logger , ipv4Src netip.Addr ) (addr netip.Addr , zone string , err error ) {
434559 if userDefinedSrc != "" {
435560 addr , err := netip .ParseAddr (userDefinedSrc )
@@ -454,6 +579,11 @@ func determineICMPv6Src(userDefinedSrc string, logger *zerolog.Logger, ipv4Src n
454579
455580 interfacesWithIPv6 := make ([]interfaceIP , 0 )
456581 for _ , interf := range interfaces {
582+ // Skip virtual/bridge interfaces
583+ if isVirtualInterface (interf .Name ) {
584+ continue
585+ }
586+
457587 interfaceAddrs , err := interf .Addrs ()
458588 if err != nil {
459589 continue
@@ -490,6 +620,17 @@ func determineICMPv6Src(userDefinedSrc string, logger *zerolog.Logger, ipv4Src n
490620 }
491621 }
492622
623+ // Prefer physical interfaces when selecting from available IPv6 interfaces
624+ for _ , interf := range interfacesWithIPv6 {
625+ if isPhysicalInterface (interf .name ) {
626+ addr , err := netip .ParseAddr (interf .ip .String ())
627+ if err == nil {
628+ return addr , interf .name , nil
629+ }
630+ }
631+ }
632+
633+ // Fall back to any non-virtual interface with IPv6
493634 for _ , interf := range interfacesWithIPv6 {
494635 addr , err := netip .ParseAddr (interf .ip .String ())
495636 if err == nil {
0 commit comments