From e426455309597857d687678e0fc8dbd044e7fa7b Mon Sep 17 00:00:00 2001 From: Mikhail Zholobov Date: Fri, 9 Oct 2015 14:54:10 +0300 Subject: [PATCH] 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