diff --git a/lib/vagrant/action/builtin/handle_forwarded_port_collisions.rb b/lib/vagrant/action/builtin/handle_forwarded_port_collisions.rb index ffa9bad67..b2157697d 100644 --- a/lib/vagrant/action/builtin/handle_forwarded_port_collisions.rb +++ b/lib/vagrant/action/builtin/handle_forwarded_port_collisions.rb @@ -243,35 +243,33 @@ module Vagrant return extra_in_use.fetch(hostport).include?(hostip) end - def ipv4_addresses - ip_addresses = [] - Socket.getifaddrs.each do |ifaddr| - if ifaddr.addr && ifaddr.addr.ipv4? - ip_addresses << ifaddr.addr.ip_address - end + def ipv4_interfaces + Socket.getifaddrs.select do |ifaddr| + ifaddr.addr && ifaddr.addr.ipv4? + end.map do |ifaddr| + [ifaddr.name, ifaddr.addr.ip_address] end - ip_addresses end def port_check(host_ip, host_port) # If no host_ip is specified, intention taken to be listen on all interfaces. test_host_ip = host_ip || "0.0.0.0" if @machine.config.vm.guest == :windows && test_host_ip == "0.0.0.0" - @logger.debug("Checking port #{host_port} on all IPv4 addresses...") - available_ips = ipv4_addresses.select do |test_host_ip| - @logger.debug("Host IP: #{test_host_ip}, port: #{host_port}") - !is_port_open?(test_host_ip, host_port) + @logger.debug("Testing port #{host_port} on all IPv4 interfaces...") + available_interfaces = ipv4_interfaces.select do |interface| + @logger.debug("Testing #{interface[0]} with IP address #{interface[1]}") + !is_port_open?(interface[1], host_port) end - if available_ips.empty? + if available_interfaces.empty? @logger.debug("Cannot forward port #{host_port} on any interfaces.") true else - @logger.debug("These IP addresses will forward to the guest: #{available_ips.join(', ')}") + @logger.debug("Port #{host_port} will forward to the guest on the following interfaces: #{available_interfaces}") false end else # Do a regular check - if test_host_ip == "0.0.0.0" || ipv4_addresses.include?(test_host_ip) + if test_host_ip == "0.0.0.0" || ipv4_interfaces.detect { |iface| iface[1] == test_host_ip } is_port_open?(test_host_ip, host_port) else raise Errors::ForwardPortHostIPNotFound diff --git a/test/unit/vagrant/action/builtin/handle_forwarded_port_collisions_test.rb b/test/unit/vagrant/action/builtin/handle_forwarded_port_collisions_test.rb index c429cf3e5..51aade9bf 100644 --- a/test/unit/vagrant/action/builtin/handle_forwarded_port_collisions_test.rb +++ b/test/unit/vagrant/action/builtin/handle_forwarded_port_collisions_test.rb @@ -148,20 +148,33 @@ describe Vagrant::Action::Builtin::HandleForwardedPortCollisions do describe "#recover" do end - describe "#ipv4_addresses" do - let(:ipv4_ifaddr) { double("ipv4_ifaddr") } - let(:ipv6_ifaddr) { double("ipv6_ifaddr") } + describe "#ipv4_interfaces" do + let(:name) { double("name") } + let(:address) { double("address") } + + let(:ipv4_ifaddr) do + double("ipv4_ifaddr").tap do |ifaddr| + allow(ifaddr).to receive(:name).and_return(name) + allow(ifaddr).to receive_message_chain(:addr, :ipv4?).and_return(true) + allow(ifaddr).to receive_message_chain(:addr, :ip_address).and_return(address) + end + end + + let(:ipv6_ifaddr) do + double("ipv6_ifaddr").tap do |ifaddr| + allow(ifaddr).to receive(:name) + allow(ifaddr).to receive_message_chain(:addr, :ipv4?).and_return(false) + end + end + let(:ifaddrs) { [ ipv4_ifaddr, ipv6_ifaddr ] } before do - allow(ipv4_ifaddr).to receive_message_chain(:addr, :ipv4?).and_return(true) - allow(ipv4_ifaddr).to receive_message_chain(:addr, :ip_address).and_return("127.0.0.1") - allow(ipv6_ifaddr).to receive_message_chain(:addr, :ipv4?).and_return(false) allow(Socket).to receive(:getifaddrs).and_return(ifaddrs) end - it "returns a list of all IPv4 addresses" do - expect(instance.send(:ipv4_addresses)).to eq([ "127.0.0.1" ]) + it "returns a list of IPv4 interfaces with their names and addresses" do + expect(instance.send(:ipv4_interfaces)).to eq([ [name, address] ]) end context "with nil interface address" do @@ -169,7 +182,7 @@ describe Vagrant::Action::Builtin::HandleForwardedPortCollisions do let(:ifaddrs) { [ ipv4_ifaddr, ipv6_ifaddr, nil_ifaddr ] } it "filters out nil addr info" do - expect(instance.send(:ipv4_addresses)).to eq([ "127.0.0.1" ]) + expect(instance.send(:ipv4_interfaces)).to eq([ [name, address] ]) end end end @@ -177,11 +190,11 @@ describe Vagrant::Action::Builtin::HandleForwardedPortCollisions do describe "#port_check" do let(:host_ip){ "127.0.0.1" } let(:host_port){ 8080 } - let(:test_ips) { [ "127.0.0.1", "192.168.1.7" ] } + let(:interfaces) { [ ["lo0", "127.0.0.1"], ["eth0", "192.168.1.7"] ] } before do instance.instance_variable_set(:@machine, machine) - allow(instance).to receive(:ipv4_addresses).and_return(test_ips) + allow(instance).to receive(:ipv4_interfaces).and_return(interfaces) end it "should check if the port is open" do @@ -196,23 +209,23 @@ describe Vagrant::Action::Builtin::HandleForwardedPortCollisions do let(:guest) { :windows } it "should check the port on every IPv4 interface" do - expect(instance).to receive(:is_port_open?).with(test_ips.first, host_port) - expect(instance).to receive(:is_port_open?).with(test_ips.last, host_port) + expect(instance).to receive(:is_port_open?).with(interfaces[0][1], host_port) + expect(instance).to receive(:is_port_open?).with(interfaces[1][1], host_port) instance.send(:port_check, host_ip, host_port) end it "should return false if the port is closed on any IPv4 interfaces" do - expect(instance).to receive(:is_port_open?).with(test_ips.first, host_port). + expect(instance).to receive(:is_port_open?).with(interfaces[0][1], host_port). and_return(true) - expect(instance).to receive(:is_port_open?).with(test_ips.last, host_port). + expect(instance).to receive(:is_port_open?).with(interfaces[1][1], host_port). and_return(false) expect(instance.send(:port_check, host_ip, host_port)).to be(false) end it "should return true if the port is open on all IPv4 interfaces" do - expect(instance).to receive(:is_port_open?).with(test_ips.first, host_port). + expect(instance).to receive(:is_port_open?).with(interfaces[0][1], host_port). and_return(true) - expect(instance).to receive(:is_port_open?).with(test_ips.last, host_port). + expect(instance).to receive(:is_port_open?).with(interfaces[1][1], host_port). and_return(true) expect(instance.send(:port_check, host_ip, host_port)).to be(true) end