@@ -323,6 +323,9 @@ type Driver interface {
323323 // HostIP retrieves the host IP address for the virtual machine based on the state.
324324 HostIP (multistep.StateBag ) (string , error )
325325
326+ // GetGuestIPAddress retrieves the guest IP address for the virtual machine using VMware Tools.
327+ GetGuestIPAddress (string ) (string , error )
328+
326329 // Export exports a virtual machine using the provided arguments.
327330 Export ([]string ) error
328331
@@ -473,17 +476,16 @@ func readCustomDeviceName(vmxData map[string]string) (string, error) {
473476
474477// VmwareDriver is a struct that provides methods and paths needed for virtual machine management.
475478type VmwareDriver struct {
476- // These methods define paths that are utilized by the driver
477- // A driver must overload these in order to point to the correct
478- // files so that the address detection (ip and ethernet) machinery
479- // works.
479+ // These methods define paths that are used by the driver.
480480 DhcpLeasesPath func (string ) string
481481 DhcpConfPath func (string ) string
482482 VmnetnatConfPath func (string ) string
483483
484- // This method returns an object with the NetworkNameMapper interface
485- // that maps network to device and vice versa.
484+ // This method returns an object with the NetworkNameMapper interface that maps network to device and vice versa.
486485 NetworkMapper func () (NetworkNameMapper , error )
486+
487+ // GetHostIPForDevice returns the IP address for a given device.
488+ GetHostIPForDevice func (device string ) (string , error )
487489}
488490
489491// GuestAddress retrieves the MAC address of a guest virtual machine from the .vmx configuration.
@@ -533,12 +535,12 @@ func (d *VmwareDriver) PotentialGuestIP(state multistep.StateBag) ([]string, err
533535 return nil , err
534536 }
535537
536- // Search the desktop hypervisor for DHCP leases.
538+ // Search for DHCP leases.
537539 if addrs := d .getDhcpLeasesHypervisor (devices , hwaddr ); len (addrs ) > 0 {
538540 return addrs , nil
539541 }
540542
541- // If VMware Fusion on macOS , search the Apple DHCP leases as a fallback .
543+ // Fallback: For VMware Fusion, search the Apple DHCP leases. .
542544 if runtime .GOOS == osMacOS {
543545 if addrs := d .getDhcpLeasesMacos (hwaddr ); len (addrs ) > 0 {
544546 return addrs , nil
@@ -551,23 +553,22 @@ func (d *VmwareDriver) PotentialGuestIP(state multistep.StateBag) ([]string, err
551553// HostAddress retrieves the host's hardware address linked to the network device specified in the state.
552554func (d * VmwareDriver ) HostAddress (state multistep.StateBag ) (string , error ) {
553555
554- // grab mapper for converting network<-> device
556+ // Grab mapper for converting a network to a device.
555557 netmap , err := d .NetworkMapper ()
556558 if err != nil {
557559 return "" , err
558560 }
559561
560- // convert network to name
562+ // Convert network to name.
561563 network := state .Get ("vmnetwork" ).(string )
562564 devices , err := netmap .NameIntoDevices (network )
563565
564- // log them to see what was detected
566+ // Log discovered devices.
565567 for _ , device := range devices {
566568 log .Printf ("[INFO] Discovered device matching %s: %s" , network , device )
567569 }
568570
569- // we were unable to find the device, maybe it's a custom one...
570- // so, check to see if it's in the .vmx configuration
571+ // If unable to find the device, it may be custom.
571572 if err != nil || network == "custom" {
572573 vmxPath := state .Get ("vmx_path" ).(string )
573574 vmxData , err := readVMXConfig (vmxPath )
@@ -586,7 +587,6 @@ func (d *VmwareDriver) HostAddress(state multistep.StateBag) (string, error) {
586587
587588 var lastError error
588589 for _ , device := range devices {
589- // parse dhcpd configuration
590590 pathDhcpConfig := d .DhcpConfPath (device )
591591 if _ , err := os .Stat (pathDhcpConfig ); err != nil {
592592 return "" , fmt .Errorf ("unable to find vmnetdhcp conf file: %s" , pathDhcpConfig )
@@ -598,22 +598,22 @@ func (d *VmwareDriver) HostAddress(state multistep.StateBag) (string, error) {
598598 continue
599599 }
600600
601- // find the entry configured in the dhcpd
601+ // Read the DHCP configuration file.
602602 interfaceConfig , err := config .HostByName (device )
603603 if err != nil {
604604 lastError = err
605605 continue
606606 }
607607
608- // finally grab the hardware address
608+ // Find the entry configured in the dhcpd.
609609 address , err := interfaceConfig .Hardware ()
610610 if err == nil {
611611 return address .String (), nil
612612 }
613613
614- // we didn't find it, so search through our interfaces for the device name
614+ // If not found, search through the interfaces for the device name.
615615 interfaceList , err := net .Interfaces ()
616- if err = = nil {
616+ if err ! = nil {
617617 return "" , err
618618 }
619619
@@ -632,23 +632,22 @@ func (d *VmwareDriver) HostAddress(state multistep.StateBag) (string, error) {
632632// HostIP retrieves the host machine's IP address associated with the specific network device defined in the state.
633633func (d * VmwareDriver ) HostIP (state multistep.StateBag ) (string , error ) {
634634
635- // grab mapper for converting network<-> device
635+ // Grab mapper for converting a network to a device.
636636 netmap , err := d .NetworkMapper ()
637637 if err != nil {
638638 return "" , err
639639 }
640640
641- // convert network to name
641+ // Convert network to name.
642642 network := state .Get ("vmnetwork" ).(string )
643643 devices , err := netmap .NameIntoDevices (network )
644644
645- // log them to see what was detected
645+ // Log discovered devices.
646646 for _ , device := range devices {
647647 log .Printf ("[INFO] Discovered device matching %s: %s" , network , device )
648648 }
649649
650- // we were unable to find the device, maybe it's a custom one...
651- // so, check to see if it's in the .vmx configuration
650+ // If unable to find the device, it may be custom.
652651 if err != nil || network == "custom" {
653652 vmxPath := state .Get ("vmx_path" ).(string )
654653 vmxData , err := readVMXConfig (vmxPath )
@@ -662,38 +661,119 @@ func (d *VmwareDriver) HostIP(state multistep.StateBag) (string, error) {
662661 if err != nil {
663662 return "" , err
664663 }
665- log .Printf ("[INFO] Discovered custom device matching %s: %s" , network , device )
664+ log .Printf ("[INFO] Discovered a custom device matching %s: %s" , network , device )
666665 }
667666
668667 var lastError error
669668 for _ , device := range devices {
670- // parse dhcpd configuration
669+ networkName , err := netmap .DeviceIntoName (device )
670+
671+ // Check if this is a bridged network device.
672+ isBridged := err == nil && strings .EqualFold (networkName , "bridged" )
673+
674+ if isBridged {
675+ // Bridged networks connect to the physical network.
676+ // Find the host's IP address on the physical network interface.
677+ log .Printf ("[INFO] Attempting to discover host IP address for bridged network on device %s." , device )
678+
679+ var address string
680+ var err error
681+ if d .GetHostIPForDevice != nil {
682+ // Get the host IP address using the device. This allows for mocking in tests.
683+ address , err = d .GetHostIPForDevice (device )
684+ } else {
685+ // Get the host IP address using the bridged network interface default route.
686+ address , err = getHostIPForBridgedNetwork ()
687+ }
688+ if err != nil {
689+ lastError = err
690+ continue
691+ }
692+ return address , nil
693+ }
694+
695+ // For non-bridged networks, use the DHCP leases path.
671696 pathDhcpConfig := d .DhcpConfPath (device )
672697 if _ , err := os .Stat (pathDhcpConfig ); err != nil {
673698 return "" , fmt .Errorf ("unable to find vmnetdhcp conf file: %s" , pathDhcpConfig )
674699 }
700+
701+ // Read the DHCP configuration file.
675702 config , err := ReadDhcpConfig (pathDhcpConfig )
676703 if err != nil {
677704 lastError = err
678705 continue
679706 }
680707
681- // find the entry configured in the dhcpd
708+ // Find the entry configured in the dhcpd.
682709 interfaceConfig , err := config .HostByName (device )
683710 if err != nil {
684711 lastError = err
685712 continue
686713 }
687714
715+ // Retrieve the IP address.
688716 address , err := interfaceConfig .IP4 ()
689717 if err != nil {
690718 lastError = err
691719 continue
692720 }
693721
722+ // Return the IP address.
694723 return address .String (), nil
695724 }
696- return "" , fmt .Errorf ("unable to find host IP from devices %v, last error: %s" , devices , lastError )
725+ return "" , fmt .Errorf ("unable to retrieve host IP address from devices %v, last error: %s" , devices , lastError )
726+ }
727+
728+ // getHostIPForBridgedNetwork retrieves the host's IP address for a bridged network.
729+ func getHostIPForBridgedNetwork () (string , error ) {
730+ // Determine the source IP used for default-route communications.
731+ if conn , err := net .Dial ("udp4" , "1.1.1.1:53" ); err == nil {
732+ defer conn .Close ()
733+ if addr , ok := conn .LocalAddr ().(* net.UDPAddr ); ok {
734+ log .Printf ("[INFO] Discovered host IP address for bridged network using default route: %s" , addr .IP )
735+ return addr .IP .String (), nil
736+ }
737+ }
738+
739+ // Fallback: Walk all interfaces and return the first non-loopback IPv4 address that doesn't have a virtual/tunnel prefix.
740+ virtualPrefixes := []string {"docker" , "br-" , "veth" , "podman" , "virbr" , "vmnet" , "vboxnet" , "utun" , "tun" , "tap" }
741+
742+ ifaces , err := net .Interfaces ()
743+ if err != nil {
744+ return "" , fmt .Errorf ("unable to enumerate network interfaces: %s" , err )
745+ }
746+
747+ for _ , iface := range ifaces {
748+ if iface .Flags & net .FlagLoopback != 0 || iface .Flags & net .FlagUp == 0 {
749+ continue
750+ }
751+ lower := strings .ToLower (iface .Name )
752+ virtual := false
753+ for _ , prefix := range virtualPrefixes {
754+ if strings .HasPrefix (lower , prefix ) {
755+ virtual = true
756+ break
757+ }
758+ }
759+ if virtual {
760+ continue
761+ }
762+ addrs , err := iface .Addrs ()
763+ if err != nil {
764+ continue
765+ }
766+ for _ , addr := range addrs {
767+ if ipnet , ok := addr .(* net.IPNet ); ok {
768+ if ipv4 := ipnet .IP .To4 (); ipv4 != nil {
769+ log .Printf ("[INFO] Discovered host IP address for bridged network: %s on interface %s" , ipv4 , iface .Name )
770+ return ipv4 .String (), nil
771+ }
772+ }
773+ }
774+ }
775+
776+ return "" , fmt .Errorf ("unable to find a non-loopback IPv4 address for bridged networking" )
697777}
698778
699779// GetDhcpLeasesPaths returns a copy of the DHCP leases paths.
0 commit comments