Merge pull request #6386 from legal90/fix-osx-nic-order

Fix network configuration in OS X (Darwin) guests
This commit is contained in:
Mitchell Hashimoto 2015-11-18 14:33:51 -08:00
commit d69d7047b2
6 changed files with 99 additions and 35 deletions

View File

@ -240,6 +240,10 @@ module Vagrant
error_key(:bundler_error) error_key(:bundler_error)
end end
class CantReadMACAddresses < VagrantError
error_key(:cant_read_mac_addresses)
end
class CapabilityHostExplicitNotDetected < VagrantError class CapabilityHostExplicitNotDetected < VagrantError
error_key(:capability_host_explicit_not_detected) error_key(:capability_host_explicit_not_detected)
end end

View File

@ -6,45 +6,109 @@ module VagrantPlugins
module GuestDarwin module GuestDarwin
module Cap module Cap
class ConfigureNetworks class ConfigureNetworks
@@logger = Log4r::Logger.new("vagrant::guest::darwin::configure_networks")
include Vagrant::Util include Vagrant::Util
def self.configure_networks(machine, networks) def self.configure_networks(machine, networks)
# Slightly different than other plugins, using the template to build commands if !machine.provider.capability?(:nic_mac_addresses)
# rather than templating the files. raise Vagrant::Errors::CantReadMACAddresses,
provider: machine.provider_name.to_s
end
machine.communicate.sudo("networksetup -detectnewhardware") nic_mac_addresses = machine.provider.capability(:nic_mac_addresses)
machine.communicate.sudo("networksetup -listnetworkserviceorder > /tmp/vagrant.interfaces") @@logger.debug("mac addresses: #{nic_mac_addresses.inspect}")
tmpints = File.join(Dir.tmpdir, File.basename("#{machine.id}.interfaces"))
machine.communicate.download("/tmp/vagrant.interfaces",tmpints)
devlist = [] mac_service_map = create_mac_service_map(machine)
ints = ::IO.read(tmpints)
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| ints.split(/\n\n/m).each do |i|
if i.match(/Hardware/) and not i.match(/Ethernet/).nil? if i.match(/Hardware/) && i.match(/Ethernet/)
devmap = {}
# Ethernet, should be 2 lines, # Ethernet, should be 2 lines,
# (3) Thunderbolt Ethernet # (3) Thunderbolt Ethernet
# (Hardware Port: Thunderbolt Ethernet, Device: en1) # (Hardware Port: Thunderbolt Ethernet, Device: en1)
# multiline, should match "Thunderbolt Ethernet", "en1" # multiline, should match "Thunderbolt Ethernet", "en1"
devicearry = i.match(/\([0-9]+\) (.+)\n.*Device: (.+)\)/m) devicearry = i.match(/\([0-9]+\) (.+)\n.*Device: (.+)\)/m)
devmap[:interface] = devicearry[2] service = devicearry[1]
devmap[:service] = devicearry[1] interface = devicearry[2]
devlist << devmap
# Should map interface to service { "en1" => "Thunderbolt Ethernet" }
interface_map[interface] = service
end end
end end
File.delete(tmpints) File.delete(tmp_ints)
networks.each do |network| mac_service_map = {}
service_name = devlist[network[:interface]][:service] macs = ::IO.read(tmp_hw)
if network[:type].to_sym == :static macs.split(/\n\n/m).each do |i|
command = "networksetup -setmanual \"#{service_name}\" #{network[:ip]} #{network[:netmask]}" if i.match(/Hardware/) && i.match(/Ethernet/)
elsif network[:type].to_sym == :dhcp # Ethernet, should be 3 lines,
command = "networksetup -setdhcp \"#{service_name}\"" # 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 end
machine.communicate.sudo(command)
end end
File.delete(tmp_hw)
mac_service_map
end end
end end
end end

View File

@ -53,7 +53,7 @@ module VagrantPlugins
def self.create_vm_interface_map(machine, guest_network) def self.create_vm_interface_map(machine, guest_network)
if !machine.provider.capability?(:nic_mac_addresses) if !machine.provider.capability?(:nic_mac_addresses)
raise Errors::CantReadMACAddresses, raise Vagrant::Errors::CantReadMACAddresses,
provider: machine.provider_name.to_s provider: machine.provider_name.to_s
end end

View File

@ -6,10 +6,6 @@ module VagrantPlugins
error_namespace("vagrant_windows.errors") error_namespace("vagrant_windows.errors")
end end
class CantReadMACAddresses < WindowsError
error_key(:cant_read_mac_addresses)
end
class NetworkWinRMRequired < WindowsError class NetworkWinRMRequired < WindowsError
error_key(:network_winrm_required) error_key(:network_winrm_required)
end end

View File

@ -603,6 +603,14 @@ en:
issues. The error from Bundler is: issues. The error from Bundler is:
%{message} %{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: |- capability_host_explicit_not_detected: |-
The explicit capability host specified of '%{value}' could not be The explicit capability host specified of '%{value}' could not be
found. found.

View File

@ -1,14 +1,6 @@
en: en:
vagrant_windows: vagrant_windows:
errors: 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: |- network_winrm_required: |-
Configuring networks on Windows requires the communicator to be Configuring networks on Windows requires the communicator to be
set to WinRM. To do this, add the following to your Vagrantfile: set to WinRM. To do this, add the following to your Vagrantfile: