hashicorp / vagrant

Vagrant is a tool for building and distributing development environments.
https://www.vagrantup.com
Other
26.33k stars 4.44k forks source link

VMWare Workstation Secondary Network Adapters on Windows #5000

Closed uchagani closed 1 year ago

uchagani commented 9 years ago

When a secondary NIC is configured for VMWare Vagrant issues the following error:

==> default: Configuring secondary network adapters through VMware
==> default: on Windows is not yet supported. You will need to manually
==> default: configure the network adapter.

However, I can still configure a secondary NIC by doing:

  config.vm.provider "vmware_workstation" do |vmware|
    vmware.vmx["ethernet1.connectionType"] = "bridged"
    vmware.vmx["ethernet1.present"] = "TRUE"
    vmware.vmx["ethernet1.virtualDev"] = "e1000e"     
  end  

Why doesn't Vagrant configure this by default when these same values are present in the vmx file?

Base Box VMX File
ethernet0.addresstype = "generated"
ethernet0.bsdname = "en0"
ethernet0.connectiontype = "nat"
ethernet0.displayname = "Ethernet"
ethernet0.linkstatepropagation.enable = "FALSE"
ethernet0.pcislotnumber = "33"
ethernet0.present = "TRUE"
ethernet0.virtualdev = "e1000"
ethernet0.wakeonpcktrcv = "FALSE"
ethernet1.connectiontype = "bridged"
ethernet1.present = "true"
ethernet1.virtualdev = "e1000e"
StefanScherer commented 9 years ago

To set the IP address for a private network, maybe this could help: http://professionalvmware.com/2008/12/vmware-vix-changing-ips-of-a-guest-vm/

vmrun -gu [Guest User] -gp [Guest Password] runProgramInGuest “[Path] to/the.vmx” c:\windows\system32\netsh int ip set address “Local Area Connection” static 192.168.15.25 255.255.255.0 192.168.15.1
StefanScherer commented 9 years ago

@mitchellh I just made a test from my Mac spinning up a Windows 10 VM in VMware Fusion with an additional private network with a static ip 192.168.33.15 defined in the Vagrantfile:

# -*- mode: ruby -*-
# vi: set ft=ruby :

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "windows_10"
  config.vm.network :forwarded_port, guest: 5985, host: 5985, id: "winrm", auto_correct: true
  config.vm.network :forwarded_port, guest: 3389, host: 3389, id: "rdp", auto_correct: true

  config.vm.network :private_network, ip: "192.168.33.15", gateway: "192.168.33.1"

  config.vm.communicator = "winrm"

  config.winrm.username = "vagrant"
  config.winrm.password = "vagrant"

  config.vm.guest = :windows
  config.windows.halt_timeout = 15

  ["vmware_fusion", "vmware_workstation"].each do |provider|
    config.vm.provider provider do |v, override|
      v.gui = true
    end
  end
end

Spinning up the Vagrant VM shows the warning for that second network card:

$ vagrant up --provider vmware_fusion
Bringing machine 'default' up with 'vmware_fusion' provider...
==> default: Cloning VMware VM: 'windows_10'. This can take some time...
==> default: Verifying vmnet devices are healthy...
==> default: Preparing network adapters...
==> default: Starting the VMware VM...
==> default: Waiting for machine to boot. This may take a few minutes...
    default: WinRM address: 192.168.254.134:5985
    default: WinRM username: vagrant
    default: WinRM transport: plaintext
==> default: Machine booted and ready!
==> default: Forwarding ports...
    default: -- 3389 => 3389
    default: -- 22 => 2222
    default: -- 5985 => 5985
==> default: Configuring network adapters within the VM...
==> default: Configuring secondary network adapters through VMware 
==> default: on Windows is not yet supported. You will need to manually
==> default: configure the network adapter.
==> default: Enabling and configuring shared folders...
    default: -- /Users/stefan/code/win10: /vagrant

Inside the Windows 10 VM the network addresses are configured as follows:

C:\Users\vagrant>netsh int ip show addresses

Configuration for interface "Ethernet 3"
    DHCP enabled:                         Yes
    IP Address:                           192.168.254.134
    Subnet Prefix:                        192.168.254.0/24 (mask 255.255.255.0)
    Default Gateway:                      192.168.254.2
    Gateway Metric:                       1
    InterfaceMetric:                      5

Configuration for interface "Ethernet 2"
    DHCP enabled:                         Yes
    IP Address:                           192.168.33.129
    Subnet Prefix:                        192.168.33.0/24 (mask 255.255.255.0)
    InterfaceMetric:                      10

Configuration for interface "Loopback Pseudo-Interface 1"
    DHCP enabled:                         No
    IP Address:                           127.0.0.1
    Subnet Prefix:                        127.0.0.0/8 (mask 255.0.0.0)
    InterfaceMetric:                      50

The second network has DHCP enabled and has to be changed to the fixed IP address given in the Vagrantfile. I tried the vmrun command like above to do this and run this command from my MBP:

$ "/Applications/VMware Fusion.app/Contents/Library/vmrun" -gu vagrant -gp vagrant runProgramInGuest .vagrant/machines/default/vmware_fusion/d254df82-2549-48df-a691-36b2c5f56598/packer-vmware-iso.vmx netsh.exe int ip set address "Ethernet 2" static 192.168.33.15 255.255.255.0 192.168.33.1

Afterwards the networks in the Windows 10 VM shows the correct settings:

C:\Users\vagrant>netsh int ip show addresses

Configuration for interface "Ethernet 3"
    DHCP enabled:                         Yes
    IP Address:                           192.168.254.134
    Subnet Prefix:                        192.168.254.0/24 (mask 255.255.255.0)
    Default Gateway:                      192.168.254.2
    Gateway Metric:                       1
    InterfaceMetric:                      5

Configuration for interface "Ethernet 2"
    DHCP enabled:                         No
    IP Address:                           192.168.33.15
    Subnet Prefix:                        192.168.33.0/24 (mask 255.255.255.0)
    Default Gateway:                      192.168.33.1
    Gateway Metric:                       1
    InterfaceMetric:                      10

Configuration for interface "Loopback Pseudo-Interface 1"
    DHCP enabled:                         No
    IP Address:                           127.0.0.1
    Subnet Prefix:                        127.0.0.0/8 (mask 255.0.0.0)
    InterfaceMetric:                      50

The only difficulty with this approach is to get the interface name "Ethernet 2" from the host as it seems that vmrun doesn't show the stdout of the program in the VM.

But this could also be done with the winrm communicator to run the netsh.exe int ip addresses command.

I've tried it in a small provision script and came up to this workaround:

# -*- mode: ruby -*-
# vi: set ft=ruby :

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "windows_10"
  config.vm.network :forwarded_port, guest: 5985, host: 5985, id: "winrm", auto_correct: true
  config.vm.network :forwarded_port, guest: 3389, host: 3389, id: "rdp", auto_correct: true

  config.vm.network :private_network, ip: "192.168.33.15", gateway: "192.168.33.1"

  config.vm.communicator = "winrm"

  config.winrm.username = "vagrant"
  config.winrm.password = "vagrant"

  config.vm.guest = :windows
  config.windows.halt_timeout = 15

  config.vm.provision "shell", inline: <<-SHELL
    netsh.exe int ip show addresses
    netsh.exe int ip set address "Ethernet 2" static 192.168.33.15 255.255.255.0 192.168.33.1
  SHELL

  ["vmware_fusion", "vmware_workstation"].each do |provider|
    config.vm.provider provider do |v, override|
      v.gui = true
    end
  end
end

Can this small part be done by the vagrant-vmware-fusion as well? So for a user of Vagrant it just behaves like the virtualbox provider then.

defn commented 8 years ago

Is the blocker here the lack of nic_mac_addresses capability in the vmware provider?

bsuh commented 8 years ago

This should be relabeled guest/windows from host/windows.

It's disappointing that paid products like the vmware providers are less featured than their free open-source counterpart. And the problem has been reported...over a year ago...with no response.

The whole experience of using open-source vagrant with closed-source vmware providers is frustrating. You find a bug in vagrant and want to fix it? Ok but good luck getting your self-compiled vagrant to work with your vmware provider because of vagrant's "DRM". I feel like a less empowered second-rate citizen as a paying customer than people who are using only the free open-source parts of vagrant.

bsuh commented 8 years ago
diff --git a/plugins/guests/windows/cap/configure_networks.rb b/plugins/guests/windows/cap/configure_networks.rb
index a4f4685..d628f99 100644
--- a/plugins/guests/windows/cap/configure_networks.rb
+++ b/plugins/guests/windows/cap/configure_networks.rb
@@ -16,33 +16,27 @@ module VagrantPlugins
           @@logger.debug("Networks: #{networks.inspect}")

           guest_network = GuestNetwork.new(machine.communicate)
-          if machine.provider_name.to_s.start_with?("vmware")
-            machine.ui.warn("Configuring secondary network adapters through VMware ")
-            machine.ui.warn("on Windows is not yet supported. You will need to manually")
-            machine.ui.warn("configure the network adapter.")
-          else
-            vm_interface_map = create_vm_interface_map(machine, guest_network)
-            networks.each do |network|
-              interface = vm_interface_map[network[:interface]+1]
-              if interface.nil?
-                @@logger.warn("Could not find interface for network #{network.inspect}")
-                next
-              end
+          vm_interface_map = create_vm_interface_map(machine, guest_network)
+          networks.each do |network|
+            interface = vm_interface_map[network[:interface]+1]
+            if interface.nil?
+              @@logger.warn("Could not find interface for network #{network.inspect}")
+              next
+            end

-              network_type = network[:type].to_sym
-              if network_type == :static
-                guest_network.configure_static_interface(
-                  interface[:index],
-                  interface[:net_connection_id],
-                  network[:ip],
-                  network[:netmask])
-              elsif network_type == :dhcp
-                guest_network.configure_dhcp_interface(
-                  interface[:index],
-                  interface[:net_connection_id])
-              else
-                raise "#{network_type} network type is not supported, try static or dhcp"
-              end
+            network_type = network[:type].to_sym
+            if network_type == :static
+              guest_network.configure_static_interface(
+                interface[:index],
+                interface[:net_connection_id],
+                network[:ip],
+                network[:netmask])
+            elsif network_type == :dhcp
+              guest_network.configure_dhcp_interface(
+                interface[:index],
+                interface[:net_connection_id])
+            else
+              raise "#{network_type} network type is not supported, try static or dhcp"
             end
           end

diff --git a/plugins/guests/windows/cap/nic_mac_addresses.rb b/plugins/guests/windows/cap/nic_mac_addresses.rb
new file mode 100644
index 0000000..d866ba2
--- /dev/null
+++ b/plugins/guests/windows/cap/nic_mac_addresses.rb
@@ -0,0 +1,41 @@
+require "log4r"
+
+module VagrantPlugins
+  module GuestWindows
+    module Cap
+      class NicMacAddresses
+        @@logger = Log4r::Logger.new("vagrant::guest::windows::cap::nic_mac_addresses")
+
+        def self.read_variable(machine, variable)
+          @@logger.debug("driver methods: #{machine.provider.driver.methods}")
+          result = machine.provider.driver.send(:vmrun, "readVariable", machine.id, "runtimeConfig", variable, retryable: true)
+          @@logger.debug("vmrun returned: #{result.exit_code} #{result.stdout} #{result.stderr}")
+          result.stdout.strip
+        end
+
+        def self.clean_mac(mac_address)
+          mac_address.gsub(':', '').upcase
+        end
+
+        def self.nic_mac_addresses(machine)
+          macs = []
+          i = 0
+          while true do
+            type = read_variable(machine, "ethernet#{i}.addressType")
+            case type
+            when "static"
+              macs.push(clean_mac(read_variable(machine, "ethernet#{i}.address")))
+            when "generated"
+              macs.push(clean_mac(read_variable(machine, "ethernet#{i}.generatedAddress")))
+            else
+              break
+            end
+            i += 1
+          end
+
+          Hash[macs.map.with_index{ |mac, index| [index+1, mac] }]
+        end
+      end
+    end
+  end
+end
diff --git a/plugins/guests/windows/plugin.rb b/plugins/guests/windows/plugin.rb
index d22dc27..d3d1d35 100644
--- a/plugins/guests/windows/plugin.rb
+++ b/plugins/guests/windows/plugin.rb
@@ -74,6 +74,11 @@ module VagrantPlugins
         Cap::RSync
       end

+      provider_capability(:vmware_fusion, :nic_mac_addresses) do
+        require_relative "cap/nic_mac_addresses"
+        Cap::NicMacAddresses
+      end
+
       protected

       def self.init!

Well after a few hours of tinkering, I was able to add the :nic_mac_addresses capability to the vmware_fusion provider by hackily patching stuff up. This diff can be applied to /opt/vagrant/embedded/gems/gems/vagrant-1.8.1/. But now it seems that configuring network adapters messes up the WinRM connection, but vagrant doesn't detect anything is out of order, so it just hangs waiting for the script output.

StefanScherer commented 8 years ago

@bsuh Wow, great approach. Might be some inspiration for the Hashicorp team :pray: I have another issue that holds me from updating to 1.8.1 (again) but then I'll have a look at it.

bsuh commented 8 years ago
diff --git a/plugins/communicators/winrm/communicator.rb b/plugins/communicators/winrm/communicator.rb
index e22a4f9..c39fb6b 100644
--- a/plugins/communicators/winrm/communicator.rb
+++ b/plugins/communicators/winrm/communicator.rb
@@ -102,7 +102,7 @@ module VagrantPlugins
         @logger.info("Checking whether WinRM is ready...")

         result = Timeout.timeout(@machine.config.winrm.timeout) do
-          shell(true).powershell("hostname")
+          shell().powershell("hostname")
         end

         @logger.info("WinRM is ready!")

Fixed hanging while configuring network adapters inside the VM. At first, vagrant tries to connect to WinRM on the IP address of the first ethernet interface, but later tries to connect to WinRM on the IP address of the second ethernet interface to set the static ip address. Setting the static ip address of the interface that is holding the WinRM connection destroys the WinRM connection causing it to hang. Instead of always requesting the WinRM address to connect to using :winrm_info, I made it reuse previous WinRM shell.

akappel commented 8 years ago

+1 for getting this enhancement into Vagrant. I've attempted to add

config.vm.provider "vmware_workstation" do |vmware|
    vmware.vmx["ethernet1.connectionType"] = "bridged"
    vmware.vmx["ethernet1.present"] = "TRUE"
    vmware.vmx["ethernet1.virtualDev"] = "e1000e"     
  end

and receive the same message. RDPing into the machine shows that it has not received an extra, DHCP'd address as expected. Manually configuring bridged mode after a vagrant up seems like a hassle in the face of the ease of performing this action with VirtualBox.

StefanScherer commented 8 years ago

To patch the IP address of a second network card I wrote a small PowerShell script

    cfg.vm.network :private_network, ip: "192.168.33.2", gateway: "192.168.33.1"
    ["vmware_fusion", "vmware_workstation"].each do |provider|
      cfg.vm.provision "shell", path: "scripts/fix-second-network.ps1", privileged: false, args: "192.168.33.2"
    end

and the script looks like this. It searches for the network interface with the given subnet and sets the fixed IP address. This script might be a basis for a PR to get this into Vagrants core.

param ([String] $ip)

$subnet = $ip -replace "\.\d+$", ""

$name = (Get-NetIPAddress -AddressFamily IPv4 `
   | Where-Object -FilterScript { ($_.IPAddress).StartsWith($subnet) } `
   ).InterfaceAlias

if ($name) {
  Write-Host "Set IP address to $ip of interface $name"
  & netsh.exe int ip set address "$name" static $ip 255.255.255.0 "$subnet.1"
}
lmayorga1980 commented 7 years ago

@StefanScherer I am trying to do the same thing as the remote vmware ESXI provider with a Windows Guest. Couldn't find the right Autounattend.xml syntax to set a static ip so now trying with netsh.

goodwinb99 commented 6 years ago

Any of the examples here for using a provisioning script to set the interface configuration didn't work for me - it causes the provisioning step to hang. This is what I ended up doing as the last step. Note that even calling Start-ScheduledTask would cause the hang because the network config would be set while WinRM was connected. This step must be last for the same reason. If you need the IP to set for provisioning to continue, I suspect you could set the schedule task to start in 5 seconds, sleep for 10, and then continue. Hope it helps!

    config.vm.provision "shell", privileged: false, name: "Schedule setting secondary interface", inline: <<-SHELL.gsub(/^ +/, '')
      UnRegister-ScheduledTask SetIPConfig -EA SilentlyContinue
      $A = New-ScheduledTaskAction -Execute "netsh.exe" -Argument "int ip set address Ethernet1 static 192.168.198.10 255.255.255.0"
      $T = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(1)
      $D = New-ScheduledTask -Action $A -Trigger $T
      Register-ScheduledTask SetIPConfig -InputObject $D
    SHELL