Merge pull request #7035 from pravinchandar/4608

Fix for #4608: Support for port forwarding in an IP aliased environment
This commit is contained in:
Chris Roberts 2017-03-03 15:57:04 -08:00 committed by GitHub
commit 6d3ab39b27
4 changed files with 63 additions and 25 deletions

View File

@ -62,7 +62,7 @@ module Vagrant
@logger.info("Detecting any forwarded port collisions...") @logger.info("Detecting any forwarded port collisions...")
# Get the extra ports we consider in use # Get the extra ports we consider in use
extra_in_use = env[:port_collision_extra_in_use] || [] extra_in_use = env[:port_collision_extra_in_use] || {}
# Get the remap # Get the remap
remap = env[:port_collision_remap] || {} remap = env[:port_collision_remap] || {}
@ -81,7 +81,7 @@ module Vagrant
# Determine a list of usable ports for repair # Determine a list of usable ports for repair
usable_ports = Set.new(env[:machine].config.vm.usable_port_range) usable_ports = Set.new(env[:machine].config.vm.usable_port_range)
usable_ports.subtract(extra_in_use) usable_ports.subtract(extra_in_use.keys)
# Pass one, remove all defined host ports from usable ports # Pass one, remove all defined host ports from usable ports
with_forwarded_ports(env) do |options| with_forwarded_ports(env) do |options|
@ -92,6 +92,7 @@ module Vagrant
with_forwarded_ports(env) do |options| with_forwarded_ports(env) do |options|
guest_port = options[:guest] guest_port = options[:guest]
host_port = options[:host] host_port = options[:host]
host_ip = options[:host_ip]
if options[:disabled] if options[:disabled]
@logger.debug("Skipping disabled port #{host_port}.") @logger.debug("Skipping disabled port #{host_port}.")
@ -110,9 +111,9 @@ module Vagrant
end end
# If the port is open (listening for TCP connections) # If the port is open (listening for TCP connections)
in_use = extra_in_use.include?(host_port) || in_use = is_forwarded_already(extra_in_use, host_port, host_ip) ||
port_checker[host_port] || port_checker[host_ip, host_port] ||
lease_check(host_port) lease_check(host_ip, host_port)
if in_use if in_use
if !repair || !options[:auto_correct] if !repair || !options[:auto_correct]
raise Errors::ForwardPortCollision, raise Errors::ForwardPortCollision,
@ -129,9 +130,9 @@ module Vagrant
usable_ports.delete(repaired_port) usable_ports.delete(repaired_port)
# If the port is in use, then we can't use this either... # If the port is in use, then we can't use this either...
in_use = extra_in_use.include?(repaired_port) || in_use = is_forwarded_already(extra_in_use, repaired_port, host_ip) ||
port_checker[repaired_port] || port_checker[host_ip, repaired_port] ||
lease_check(repaired_port) lease_check(host_ip, repaired_port)
if in_use if in_use
@logger.info("Repaired port also in use: #{repaired_port}. Trying another...") @logger.info("Repaired port also in use: #{repaired_port}. Trying another...")
next next
@ -163,13 +164,19 @@ module Vagrant
end end
end end
def lease_check(port) def lease_check(host_ip=nil, host_port)
# Check if this port is "leased". We use a leasing system of # Check if this port is "leased". We use a leasing system of
# about 60 seconds to avoid any forwarded port collisions in # about 60 seconds to avoid any forwarded port collisions in
# a highly parallelized environment. # a highly parallelized environment.
leasedir = @machine.env.data_dir.join("fp-leases") leasedir = @machine.env.data_dir.join("fp-leases")
leasedir.mkpath leasedir.mkpath
if host_ip.nil?
lease_file_name = host_port.to_s
else
lease_file_name = "#{host_ip.gsub('.','_')}_#{host_port.to_s}"
end
invalid = false invalid = false
oldest = Time.now.to_i - 60 oldest = Time.now.to_i - 60
leasedir.children.each do |child| leasedir.children.each do |child|
@ -178,7 +185,7 @@ module Vagrant
child.delete child.delete
end end
if child.basename.to_s == port.to_s if child.basename.to_s == lease_file_name
invalid = true invalid = true
end end
end end
@ -187,13 +194,13 @@ module Vagrant
return true if invalid return true if invalid
# Otherwise, create the lease # Otherwise, create the lease
leasedir.join(port.to_s).open("w+") do |f| leasedir.join(lease_file_name).open("w+") do |f|
f.binmode f.binmode
f.write(Time.now.to_i.to_s + "\n") f.write(Time.now.to_i.to_s + "\n")
end end
# Add to the leased array so we unlease it right away # Add to the leased array so we unlease it right away
@leased << port.to_s @leased << lease_file_name
# Things look good to us! # Things look good to us!
false false
@ -208,8 +215,32 @@ module Vagrant
end end
end end
def port_check(port) # This functions checks to see if the current instance's hostport and
is_port_open?("127.0.0.1", port) # hostip for forwarding is in use by the virtual machines created
# previously.
def is_forwarded_already(extra_in_use, hostport, hostip)
hostip = '*' if hostip.nil? || hostip.empty?
# ret. false if none of the VMs we spun up had this port forwarded.
return false if not extra_in_use.has_key?(hostport)
# ret. true if the user has requested to bind on all interfaces but
# we already have a rule in one the VMs we spun up.
if hostip == '*'
if extra_in_use.fetch(hostport).size != 0
return true
else
return false
end
end
return extra_in_use.fetch(hostport).include?(hostip)
end
def port_check(host_ip, host_port)
# If the user hasn't specified a host_ip, his/her intention is taken to be
# to listen on all interfaces.
return is_port_open?("0.0.0.0", host_port) if host_ip.nil?
return is_port_open?(host_ip, host_port)
end end
def with_forwarded_ports(env) def with_forwarded_ports(env)

View File

@ -259,9 +259,11 @@ module VagrantPlugins
default_id = nil default_id = nil
if type == :forwarded_port if type == :forwarded_port
# For forwarded ports, set the default ID to the # For forwarded ports, set the default ID to be the
# host port so that host ports overwrite each other. # concat of host_ip, proto and host_port. This would ensure Vagrant
default_id = "#{options[:protocol]}#{options[:host]}" # caters for port forwarding in an IP aliased environment where
# different host IP addresses are to be listened on the same port.
default_id = "#{options[:host_ip]}#{options[:protocol]}#{options[:host]}"
end end
options[:id] = default_id || SecureRandom.uuid options[:id] = default_id || SecureRandom.uuid
@ -697,7 +699,7 @@ module VagrantPlugins
end end
if options[:host] if options[:host]
key = "#{options[:protocol]}#{options[:host]}" key = "#{options[:host_ip]}#{options[:protocol]}#{options[:host]}"
if fp_used.include?(key) if fp_used.include?(key)
errors << I18n.t("vagrant.config.vm.network_fp_host_not_unique", errors << I18n.t("vagrant.config.vm.network_fp_host_not_unique",
host: options[:host].to_s, host: options[:host].to_s,

View File

@ -367,8 +367,10 @@ module VagrantPlugins
end end
# Parse out the forwarded port information # Parse out the forwarded port information
if line =~ /^Forwarding.+?="(.+?),.+?,.*?,(.+?),.*?,(.+?)"$/ # Forwarding(1)="172.22.8.201tcp32977,tcp,172.22.8.201,32977,,3777"
result = [current_nic, $1.to_s, $2.to_i, $3.to_i] # Forwarding(2)="tcp32978,tcp,,32978,,3777"
if line =~ /^Forwarding.+?="(.+?),.+?,(.*?),(.+?),.*?,(.+?)"$/
result = [current_nic, $1.to_s, $3.to_i, $4.to_i, $2]
@logger.debug(" - #{result.inspect}") @logger.debug(" - #{result.inspect}")
results << result results << result
end end
@ -557,7 +559,7 @@ module VagrantPlugins
end end
def read_used_ports def read_used_ports
ports = [] used_ports = Hash.new{|hash, key| hash[key] = Set.new}
execute("list", "vms", retryable: true).split("\n").each do |line| execute("list", "vms", retryable: true).split("\n").each do |line|
if line =~ /^".+?" \{(.+?)\}$/ if line =~ /^".+?" \{(.+?)\}$/
uuid = $1.to_s uuid = $1.to_s
@ -565,13 +567,14 @@ module VagrantPlugins
# Ignore our own used ports # Ignore our own used ports
next if uuid == @uuid next if uuid == @uuid
read_forwarded_ports(uuid, true).each do |_, _, hostport, _| read_forwarded_ports(uuid, true).each do |_, _, hostport, _, hostip|
ports << hostport hostip = '*' if hostip.nil? || hostip.empty?
used_ports[hostport].add?(hostip)
end end
end end
end end
ports used_ports
end end
def read_vms def read_vms

View File

@ -15,6 +15,7 @@ module VagrantPlugins
if type == :forwarded_port if type == :forwarded_port
guest_port = options[:guest] guest_port = options[:guest]
host_port = options[:host] host_port = options[:host]
host_ip = options[:host_ip]
protocol = options[:protocol] || "tcp" protocol = options[:protocol] || "tcp"
options = scoped_hash_override(options, :virtualbox) options = scoped_hash_override(options, :virtualbox)
id = options[:id] id = options[:id]
@ -22,7 +23,8 @@ module VagrantPlugins
# If the forwarded port was marked as disabled, ignore. # If the forwarded port was marked as disabled, ignore.
next if options[:disabled] next if options[:disabled]
mappings[host_port.to_s + protocol.to_s] = key = "#{host_ip}#{protocol}#{host_port}"
mappings[key] =
Model::ForwardedPort.new(id, host_port, guest_port, options) Model::ForwardedPort.new(id, host_port, guest_port, options)
end end
end end