From d600e83e6d5f698a389dbfe6516736cde157bfb1 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 14 Mar 2014 11:23:07 -0700 Subject: [PATCH] guests/windows: configure networks --- .../guests/windows/cap/configure_networks.rb | 42 +++--- plugins/guests/windows/errors.rb | 14 ++ plugins/guests/windows/guest_network.rb | 125 ++++++++++++++++++ plugins/guests/windows/plugin.rb | 13 ++ .../windows/scripts/set_work_network.ps1 | 6 + .../windows/scripts/winrs_v3_get_adapters.ps1 | 11 ++ plugins/providers/virtualbox/cap.rb | 7 + plugins/providers/virtualbox/plugin.rb | 5 + templates/locales/guest_windows.yml | 11 ++ 9 files changed, 216 insertions(+), 18 deletions(-) create mode 100644 plugins/guests/windows/errors.rb create mode 100644 plugins/guests/windows/guest_network.rb create mode 100644 plugins/guests/windows/scripts/set_work_network.ps1 create mode 100644 plugins/guests/windows/scripts/winrs_v3_get_adapters.ps1 create mode 100644 templates/locales/guest_windows.yml diff --git a/plugins/guests/windows/cap/configure_networks.rb b/plugins/guests/windows/cap/configure_networks.rb index 7188d7c55..e8c12e7df 100644 --- a/plugins/guests/windows/cap/configure_networks.rb +++ b/plugins/guests/windows/cap/configure_networks.rb @@ -1,10 +1,6 @@ require "log4r" -require_relative '../../communication/guestnetwork' -require_relative '../../communication/winrmshell' -require_relative '../../errors' -require_relative '../../helper' -require_relative '../../windows_machine' +require_relative "../guest_network" module VagrantPlugins module GuestWindows @@ -15,19 +11,20 @@ module VagrantPlugins def self.configure_networks(machine, networks) @@logger.debug("Networks: #{networks.inspect}") - windows_machine = VagrantWindows::WindowsMachine.new(machine) - guest_network = VagrantWindows::Communication::GuestNetwork.new(windows_machine.winrmshell) - if windows_machine.is_vmware?() - @@logger.warn('Configuring secondary network adapters through VMware is not yet supported.') - @@logger.warn('You will need to manually configure the network adapter.') + guest_network = GuestNetwork.new(machine.communicate.shell) + 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(windows_machine, guest_network) + 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( @@ -40,19 +37,27 @@ module VagrantPlugins interface[:index], interface[:net_connection_id]) else - raise WindowsError, "#{network_type} network type is not supported, try static or dhcp" + raise "#{network_type} network type is not supported, try static or dhcp" end end end - guest_network.set_all_networks_to_work() if windows_machine.windows_config.set_work_network + + if machine.config.windows.set_work_network + guest_network.set_all_networks_to_work + end end - #{1=>{:name=>"Local Area Connection", :mac_address=>"0800275FAC5B", :interface_index=>"11", :index=>"7"}} - def self.create_vm_interface_map(windows_machine, guest_network) - vm_interface_map = {} - driver_mac_address = windows_machine.read_mac_addresses.invert + def self.create_vm_interface_map(machine, guest_network) + if !machine.provider.capability?(:nic_mac_addresses) + raise Errors::CantReadMACAddresses, + provider: machine.provider_name.to_s + end + + driver_mac_address = machine.provider.capability(:nic_mac_addresses) @@logger.debug("mac addresses: #{driver_mac_address.inspect}") - guest_network.network_adapters().each do |nic| + + vm_interface_map = {} + guest_network.network_adapters.each do |nic| @@logger.debug("nic: #{nic.inspect}") naked_mac = nic[:mac_address].gsub(':','') if driver_mac_address[naked_mac] @@ -63,6 +68,7 @@ module VagrantPlugins :index => nic[:index] } end end + @@logger.debug("vm_interface_map: #{vm_interface_map.inspect}") vm_interface_map end diff --git a/plugins/guests/windows/errors.rb b/plugins/guests/windows/errors.rb new file mode 100644 index 000000000..4b6b3c716 --- /dev/null +++ b/plugins/guests/windows/errors.rb @@ -0,0 +1,14 @@ +module VagrantPlugins + module GuestWindows + module Errors + # A convenient superclass for all our errors. + class WindowsError < Vagrant::Errors::VagrantError + error_namespace("vagrant_windows.errors") + end + + class CantReadMACAddresses < WindowsError + error_key(:cant_read_mac_addresses) + end + end + end +end diff --git a/plugins/guests/windows/guest_network.rb b/plugins/guests/windows/guest_network.rb new file mode 100644 index 000000000..6f4b22134 --- /dev/null +++ b/plugins/guests/windows/guest_network.rb @@ -0,0 +1,125 @@ +require "log4r" + +module VagrantPlugins + module GuestWindows + # Manages the remote Windows guest network. + class GuestNetwork + PS_GET_WSMAN_VER = '((test-wsman).productversion.split(" ") | select -last 1).split("\.")[0]' + WQL_NET_ADAPTERS_V2 = 'SELECT * FROM Win32_NetworkAdapter WHERE MACAddress IS NOT NULL' + + def initialize(winrmshell) + @logger = Log4r::Logger.new("vagrant::windows::guestnetwork") + @winrmshell = winrmshell + end + + # Returns an array of all NICs on the guest. Each array entry is a + # Hash of the NICs properties. + # + # @return [Array] + def network_adapters + wsman_version == 2? network_adapters_v2_winrm : network_adapters_v3_winrm + end + + # Checks to see if the specified NIC is currently configured for DHCP. + # + # @return [Boolean] + def is_dhcp_enabled(nic_index) + has_dhcp_enabled = false + cmd = "Get-WmiObject -Class Win32_NetworkAdapterConfiguration -Filter \"Index=#{nic_index} and DHCPEnabled=True\"" + @winrmshell.powershell(cmd) do |type, line| + has_dhcp_enabled = !line.nil? + end + @logger.debug("NIC #{nic_index} has DHCP enabled: #{has_dhcp_enabled}") + has_dhcp_enabled + end + + # Configures the specified interface for DHCP + # + # @param [Integer] The interface index. + # @param [String] The unique name of the NIC, such as 'Local Area Connection'. + def configure_dhcp_interface(nic_index, net_connection_id) + @logger.info("Configuring NIC #{net_connection_id} for DHCP") + if !is_dhcp_enabled(nic_index) + netsh = "netsh interface ip set address \"#{net_connection_id}\" dhcp" + @winrmshell.powershell(netsh) + end + end + + # Configures the specified interface using a static address + # + # @param [Integer] The interface index. + # @param [String] The unique name of the NIC, such as 'Local Area Connection'. + # @param [String] The static IP address to assign to the specified NIC. + # @param [String] The network mask to use with the static IP. + def configure_static_interface(nic_index, net_connection_id, ip, netmask) + @logger.info("Configuring NIC #{net_connection_id} using static ip #{ip}") + #netsh interface ip set address "Local Area Connection 2" static 192.168.33.10 255.255.255.0 + netsh = "netsh interface ip set address \"#{net_connection_id}\" static #{ip} #{netmask}" + @winrmshell.powershell(netsh) + end + + # Sets all networks on the guest to 'Work Network' mode. This is + # to allow guest access from the host via a private IP on Win7 + # https://github.com/WinRb/vagrant-windows/issues/63 + def set_all_networks_to_work + @logger.info("Setting all networks to 'Work Network'") + command = File.read(File.expand_path("../scripts/set_work_network.ps1")) + @winrmshell.powershell(command) + end + + protected + + # Checks the WinRS version on the guest. Usually 2 on Windows 7/2008 + # and 3 on Windows 8/2012. + # + # @return [Integer] + def wsman_version + @logger.debug("querying WSMan version") + version = '' + @winrmshell.powershell(PS_GET_WSMAN_VER) do |type, line| + version = version + "#{line}" if type == :stdout && !line.nil? + end + @logger.debug("wsman version: #{version}") + Integer(version) + end + + # Returns an array of all NICs on the guest. Each array entry is a + # Hash of the NICs properties. This method should only be used on + # guests that have WinRS version 2. + # + # @return [Array] + def network_adapters_v2_winrm + @logger.debug("querying network adapters") + + # Get all NICs that have a MAC address + # http://msdn.microsoft.com/en-us/library/windows/desktop/aa394216(v=vs.85).aspx + adapters = @winrmshell.wql(WQL_NET_ADAPTERS_V2)[:win32_network_adapter] + @logger.debug("#{adapters.inspect}") + return adapters + end + + # Returns an array of all NICs on the guest. Each array entry is a + # Hash of the NICs properties. This method should only be used on + # guests that have WinRS version 3. + # + # This method is a workaround until the WinRM gem supports WinRS version 3. + # + # @return [Array] + def network_adapters_v3_winrm + command = File.read(File.expand_path("../scripts/winrs_v3_get_adapters.ps1")) + output = "" + @winrmshell.powershell(command) do |type, line| + output = output + "#{line}" if type == :stdout && !line.nil? + end + + adapters = [] + JSON.parse(output).each do |nic| + adapters << nic.inject({}){ |memo,(k,v)| memo[k.to_sym] = v; memo } + end + + @logger.debug("#{adapters.inspect}") + return adapters + end + end + end +end diff --git a/plugins/guests/windows/plugin.rb b/plugins/guests/windows/plugin.rb index 50f6df34e..eed04d872 100644 --- a/plugins/guests/windows/plugin.rb +++ b/plugins/guests/windows/plugin.rb @@ -2,6 +2,8 @@ require "vagrant" module VagrantPlugins module GuestWindows + autoload :Errors, File.expand_path("../errors", __FILE__) + class Plugin < Vagrant.plugin("2") name "Windows guest." description "Windows guest support." @@ -13,6 +15,7 @@ module VagrantPlugins guest("windows") do require_relative "guest" + init! Guest end @@ -45,6 +48,16 @@ module VagrantPlugins require_relative "cap/mount_shared_folder" Cap::MountSharedFolder end + + protected + + def self.init! + return if defined?(@_init) + I18n.load_path << File.expand_path( + "templates/locales/guest_windows.yml", Vagrant.source_root) + I18n.reload! + @_init = true + end end end end diff --git a/plugins/guests/windows/scripts/set_work_network.ps1 b/plugins/guests/windows/scripts/set_work_network.ps1 new file mode 100644 index 000000000..c184454bb --- /dev/null +++ b/plugins/guests/windows/scripts/set_work_network.ps1 @@ -0,0 +1,6 @@ +# Get network connections +$networkListManager = [Activator]::CreateInstance([Type]::GetTypeFromCLSID([Guid]"{DCB00C01-570F-4A9B-8D69-199FDBA5723B}")) +$connections = $networkListManager.GetNetworkConnections() + +# Set network location to Private for all networks +$connections | % {$_.GetNetwork().SetCategory(1)} diff --git a/plugins/guests/windows/scripts/winrs_v3_get_adapters.ps1 b/plugins/guests/windows/scripts/winrs_v3_get_adapters.ps1 new file mode 100644 index 000000000..4a2796479 --- /dev/null +++ b/plugins/guests/windows/scripts/winrs_v3_get_adapters.ps1 @@ -0,0 +1,11 @@ +$adapters = get-ciminstance win32_networkadapter -filter "macaddress is not null" +$processed = @() +foreach ($adapter in $adapters) { + $Processed += new-object PSObject -Property @{ + mac_address = $adapter.macaddress + net_connection_id = $adapter.netconnectionid + interface_index = $adapter.interfaceindex + index = $adapter.index + } +} +convertto-json -inputobject $processed diff --git a/plugins/providers/virtualbox/cap.rb b/plugins/providers/virtualbox/cap.rb index 315c02c01..e459c97bf 100644 --- a/plugins/providers/virtualbox/cap.rb +++ b/plugins/providers/virtualbox/cap.rb @@ -15,6 +15,13 @@ module VagrantPlugins end end end + + # Reads the network interface card MAC addresses and returns them. + # + # @return [Hash] Adapter => MAC address + def self.nic_mac_addresses(machine) + machine.provider.driver.read_mac_addresses + end end end end diff --git a/plugins/providers/virtualbox/plugin.rb b/plugins/providers/virtualbox/plugin.rb index 66781f440..125807a87 100644 --- a/plugins/providers/virtualbox/plugin.rb +++ b/plugins/providers/virtualbox/plugin.rb @@ -28,6 +28,11 @@ module VagrantPlugins require_relative "cap" Cap end + + provider_capability(:virtualbox, :nic_mac_addresses) do + require_relative "cap" + Cap + end end autoload :Action, File.expand_path("../action", __FILE__) diff --git a/templates/locales/guest_windows.yml b/templates/locales/guest_windows.yml new file mode 100644 index 000000000..dbdb2d7ce --- /dev/null +++ b/templates/locales/guest_windows.yml @@ -0,0 +1,11 @@ +en: + vagrant_winrm: + errors: + cant_read_mac_addresses: |- + The provider being used to start Windows ('%{provider}') + doesn't support the "nic_mac_addresses" capability which is required + for advanced networking to work with Windows guests. Please inform + the author of the provider to add this feature. + + Until then, you must remove any networking configurations other + than forwarded ports from your Vagrantfile for Vagrant to continue.