Fix #11373: Check all interfaces for port collisions
This commit changes the behavior of the port check to check all possible IPv4 network interfaces when the host IP is `nil` or `0.0.0.0`. This means that if the desired port is available on any network interfaces, a forward from 0.0.0.0 will use that interface. If the port is open (in use) on all interfaces, then it's treated as a collision and will either throw an error or auto-correct the port, based on the Vagrantfile configuration.
This commit is contained in:
parent
94c1c605cd
commit
d4acdd06ec
@ -1,6 +1,7 @@
|
||||
require "set"
|
||||
|
||||
require "log4r"
|
||||
require 'socket'
|
||||
|
||||
require "vagrant/util/is_port_open"
|
||||
|
||||
@ -242,20 +243,35 @@ module Vagrant
|
||||
return extra_in_use.fetch(hostport).include?(hostip)
|
||||
end
|
||||
|
||||
def port_check(host_ip, host_port)
|
||||
# If no host_ip is specified, intention taken to be list on all interfaces.
|
||||
# If platform is windows, default back to localhost only
|
||||
test_host_ip = host_ip || "0.0.0.0"
|
||||
begin
|
||||
is_port_open?(test_host_ip, host_port)
|
||||
rescue Errno::EADDRNOTAVAIL
|
||||
if !host_ip && test_host_ip == "0.0.0.0"
|
||||
test_host_ip = "127.0.0.1"
|
||||
retry
|
||||
else
|
||||
raise
|
||||
def ipv4_addresses
|
||||
ip_addresses = []
|
||||
Socket.getifaddrs.each do |ifaddr|
|
||||
if ifaddr.addr.ipv4?
|
||||
ip_addresses << ifaddr.addr.ip_address
|
||||
end
|
||||
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.
|
||||
if host_ip.nil? || 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)
|
||||
end
|
||||
if available_ips.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(', ')}")
|
||||
false
|
||||
end
|
||||
else
|
||||
# Do a regular check
|
||||
is_port_open?(host_ip, host_port)
|
||||
end
|
||||
end
|
||||
|
||||
def with_forwarded_ports(env)
|
||||
|
||||
@ -72,6 +72,7 @@ describe Vagrant::Action::Builtin::HandleForwardedPortCollisions do
|
||||
let(:port_options){ {guest: 80, host: 8080} }
|
||||
before do
|
||||
expect(vm_config).to receive(:networks).and_return([[:forwarded_port, port_options]]).twice
|
||||
allow(instance).to receive(:ipv4_addresses).and_return(["127.0.0.1"])
|
||||
end
|
||||
|
||||
it "should check if host port is in use" do
|
||||
@ -144,6 +145,23 @@ 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") }
|
||||
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)
|
||||
end
|
||||
|
||||
it "returns a list of all IPv4 addresses" do
|
||||
allow(Socket).to receive(:getifaddrs).and_return(ifaddrs)
|
||||
expect(instance.send(:ipv4_addresses)).to eq([ "127.0.0.1" ])
|
||||
end
|
||||
end
|
||||
|
||||
describe "#port_check" do
|
||||
let(:host_ip){ "127.0.0.1" }
|
||||
let(:host_port){ 8080 }
|
||||
@ -153,18 +171,34 @@ describe Vagrant::Action::Builtin::HandleForwardedPortCollisions do
|
||||
instance.send(:port_check, host_ip, host_port)
|
||||
end
|
||||
|
||||
context "when host_ip is not set" do
|
||||
let(:host_ip){ nil }
|
||||
context "when host_ip is 0.0.0.0" do
|
||||
let(:host_ip) { "0.0.0.0" }
|
||||
let(:test_ips) { [ "127.0.0.1", "192.168.1.7" ] }
|
||||
|
||||
it "should set host_ip to 0.0.0.0 when unset" do
|
||||
expect(instance).to receive(:is_port_open?).with("0.0.0.0", host_port).and_return(true)
|
||||
before do
|
||||
allow(instance).to receive(:ipv4_addresses).and_return(test_ips)
|
||||
end
|
||||
|
||||
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)
|
||||
instance.send(:port_check, host_ip, host_port)
|
||||
end
|
||||
|
||||
it "should set host_ip to 127.0.0.1 when 0.0.0.0 is not available" do
|
||||
expect(instance).to receive(:is_port_open?).with("0.0.0.0", host_port).and_raise(Errno::EADDRNOTAVAIL)
|
||||
expect(instance).to receive(:is_port_open?).with("127.0.0.1", host_port).and_return(true)
|
||||
instance.send(:port_check, host_ip, host_port)
|
||||
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).
|
||||
and_return(true)
|
||||
expect(instance).to receive(:is_port_open?).with(test_ips.last, 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).
|
||||
and_return(true)
|
||||
expect(instance).to receive(:is_port_open?).with(test_ips.last, host_port).
|
||||
and_return(true)
|
||||
expect(instance.send(:port_check, host_ip, host_port)).to be(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user