From e426455309597857d687678e0fc8dbd044e7fa7b Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Fri, 9 Oct 2015 14:54:10 +0300 Subject: [PATCH 1/2] guests/darwin: Configure network following the MAC addresses matching Currently `configure_networks` guest cap configures NICs following the device order and fails when the device order is mixed. We should detect the appropriate NIC by its MAC address. --- .../guests/darwin/cap/configure_networks.rb | 108 ++++++++++++++---- 1 file changed, 86 insertions(+), 22 deletions(-) diff --git a/plugins/guests/darwin/cap/configure_networks.rb b/plugins/guests/darwin/cap/configure_networks.rb index fefd0a2e2..1f43acd6a 100644 --- a/plugins/guests/darwin/cap/configure_networks.rb +++ b/plugins/guests/darwin/cap/configure_networks.rb @@ -6,45 +6,109 @@ module VagrantPlugins module GuestDarwin module Cap class ConfigureNetworks + @@logger = Log4r::Logger.new("vagrant::guest::darwin::configure_networks") + include Vagrant::Util def self.configure_networks(machine, networks) - # Slightly different than other plugins, using the template to build commands - # rather than templating the files. + if !machine.provider.capability?(:nic_mac_addresses) + raise Errors::CantReadMACAddresses, + provider: machine.provider_name.to_s + end - machine.communicate.sudo("networksetup -detectnewhardware") - machine.communicate.sudo("networksetup -listnetworkserviceorder > /tmp/vagrant.interfaces") - tmpints = File.join(Dir.tmpdir, File.basename("#{machine.id}.interfaces")) - machine.communicate.download("/tmp/vagrant.interfaces",tmpints) + nic_mac_addresses = machine.provider.capability(:nic_mac_addresses) + @@logger.debug("mac addresses: #{nic_mac_addresses.inspect}") - devlist = [] - ints = ::IO.read(tmpints) + mac_service_map = create_mac_service_map(machine) + + networks.each do |network| + mac_address = nic_mac_addresses[network[:interface]+1] + if mac_address.nil? + @@logger.warn("Could not find mac address for network #{network.inspect}") + next + end + + service_name = mac_service_map[mac_address] + if service_name.nil? + @@logger.warn("Could not find network service for mac address #{mac_address}") + next + end + + network_type = network[:type].to_sym + if network_type == :static + command = "networksetup -setmanual \"#{service_name}\" #{network[:ip]} #{network[:netmask]}" + elsif network_type == :dhcp + command = "networksetup -setdhcp \"#{service_name}\"" + else + raise "#{network_type} network type is not supported, try static or dhcp" + end + + machine.communicate.sudo(command) + end + end + + # Creates a hash mapping MAC addresses to network service name + # Example: { "00C100A1B2C3" => "Thunderbolt Ethernet" } + def self.create_mac_service_map(machine) + tmp_ints = File.join(Dir.tmpdir, File.basename("#{machine.id}.interfaces")) + tmp_hw = File.join(Dir.tmpdir, File.basename("#{machine.id}.hardware")) + + machine.communicate.tap do |comm| + comm.sudo("networksetup -detectnewhardware") + comm.sudo("networksetup -listnetworkserviceorder > /tmp/vagrant.interfaces") + comm.sudo("networksetup -listallhardwareports > /tmp/vagrant.hardware") + comm.download("/tmp/vagrant.interfaces", tmp_ints) + comm.download("/tmp/vagrant.hardware", tmp_hw) + end + + interface_map = {} + ints = ::IO.read(tmp_ints) ints.split(/\n\n/m).each do |i| - if i.match(/Hardware/) and not i.match(/Ethernet/).nil? - devmap = {} + if i.match(/Hardware/) && i.match(/Ethernet/) # Ethernet, should be 2 lines, # (3) Thunderbolt Ethernet # (Hardware Port: Thunderbolt Ethernet, Device: en1) # multiline, should match "Thunderbolt Ethernet", "en1" devicearry = i.match(/\([0-9]+\) (.+)\n.*Device: (.+)\)/m) - devmap[:interface] = devicearry[2] - devmap[:service] = devicearry[1] - devlist << devmap + service = devicearry[1] + interface = devicearry[2] + + # Should map interface to service { "en1" => "Thunderbolt Ethernet" } + interface_map[interface] = service end end - File.delete(tmpints) + File.delete(tmp_ints) - networks.each do |network| - service_name = devlist[network[:interface]][:service] - if network[:type].to_sym == :static - command = "networksetup -setmanual \"#{service_name}\" #{network[:ip]} #{network[:netmask]}" - elsif network[:type].to_sym == :dhcp - command = "networksetup -setdhcp \"#{service_name}\"" + mac_service_map = {} + macs = ::IO.read(tmp_hw) + macs.split(/\n\n/m).each do |i| + if i.match(/Hardware/) && i.match(/Ethernet/) + # Ethernet, should be 3 lines, + # Hardware Port: Thunderbolt 1 + # Device: en1 + # Ethernet Address: a1:b2:c3:d4:e5:f6 + + # multiline, should match "en1", "00:c1:00:a1:b2:c3" + devicearry = i.match(/Device: (.+)\nEthernet Address: (.+)/m) + interface = devicearry[1] + naked_mac = devicearry[2].gsub(':','').upcase + + # Skip hardware ports without MAC (bridges, bluetooth, etc.) + next if naked_mac == "N/A" + + if !interface_map[interface] + @@logger.warn("Could not find network service for interface #{interface}") + next + end + + # Should map MAC to service, { "00C100A1B2C3" => "Thunderbolt Ethernet" } + mac_service_map[naked_mac] = interface_map[interface] end - - machine.communicate.sudo(command) end + File.delete(tmp_hw) + + mac_service_map end end end From f930fa94af0b339b6f4498844a0ba429703074e7 Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Fri, 9 Oct 2015 14:56:18 +0300 Subject: [PATCH 2/2] Move "cant_read_mac_addresses" error to the global space Now it is used not only by Windows, but by Darwin guests as well. --- lib/vagrant/errors.rb | 4 ++++ plugins/guests/darwin/cap/configure_networks.rb | 2 +- plugins/guests/windows/cap/configure_networks.rb | 2 +- plugins/guests/windows/errors.rb | 4 ---- templates/locales/en.yml | 8 ++++++++ templates/locales/guest_windows.yml | 8 -------- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index 906b8d9bc..82f8a5e24 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -248,6 +248,10 @@ module Vagrant error_key(:bundler_error) end + class CantReadMACAddresses < VagrantError + error_key(:cant_read_mac_addresses) + end + class CapabilityHostExplicitNotDetected < VagrantError error_key(:capability_host_explicit_not_detected) end diff --git a/plugins/guests/darwin/cap/configure_networks.rb b/plugins/guests/darwin/cap/configure_networks.rb index 1f43acd6a..1be71c82f 100644 --- a/plugins/guests/darwin/cap/configure_networks.rb +++ b/plugins/guests/darwin/cap/configure_networks.rb @@ -12,7 +12,7 @@ module VagrantPlugins def self.configure_networks(machine, networks) if !machine.provider.capability?(:nic_mac_addresses) - raise Errors::CantReadMACAddresses, + raise Vagrant::Errors::CantReadMACAddresses, provider: machine.provider_name.to_s end diff --git a/plugins/guests/windows/cap/configure_networks.rb b/plugins/guests/windows/cap/configure_networks.rb index 8e766a319..a4f4685d9 100644 --- a/plugins/guests/windows/cap/configure_networks.rb +++ b/plugins/guests/windows/cap/configure_networks.rb @@ -53,7 +53,7 @@ module VagrantPlugins def self.create_vm_interface_map(machine, guest_network) if !machine.provider.capability?(:nic_mac_addresses) - raise Errors::CantReadMACAddresses, + raise Vagrant::Errors::CantReadMACAddresses, provider: machine.provider_name.to_s end diff --git a/plugins/guests/windows/errors.rb b/plugins/guests/windows/errors.rb index d5be14ce6..e76753646 100644 --- a/plugins/guests/windows/errors.rb +++ b/plugins/guests/windows/errors.rb @@ -6,10 +6,6 @@ module VagrantPlugins error_namespace("vagrant_windows.errors") end - class CantReadMACAddresses < WindowsError - error_key(:cant_read_mac_addresses) - end - class NetworkWinRMRequired < WindowsError error_key(:network_winrm_required) end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 9e8c680aa..94f47371d 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -606,6 +606,14 @@ en: issues. The error from Bundler is: %{message} + cant_read_mac_addresses: |- + The provider you are using ('%{provider}') doesn't support the + "nic_mac_addresses" provider capability which is required + for advanced networking to work with this guest OS. 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. capability_host_explicit_not_detected: |- The explicit capability host specified of '%{value}' could not be found. diff --git a/templates/locales/guest_windows.yml b/templates/locales/guest_windows.yml index a648f5a09..f9a3bf997 100644 --- a/templates/locales/guest_windows.yml +++ b/templates/locales/guest_windows.yml @@ -1,14 +1,6 @@ en: vagrant_windows: 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. network_winrm_required: |- Configuring networks on Windows requires the communicator to be set to WinRM. To do this, add the following to your Vagrantfile: