diff --git a/lib/vagrant/action/builtin/handle_forwarded_port_collisions.rb b/lib/vagrant/action/builtin/handle_forwarded_port_collisions.rb index 468d82a98..6398b6ca0 100644 --- a/lib/vagrant/action/builtin/handle_forwarded_port_collisions.rb +++ b/lib/vagrant/action/builtin/handle_forwarded_port_collisions.rb @@ -62,7 +62,7 @@ module Vagrant @logger.info("Detecting any forwarded port collisions...") # 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 remap = env[:port_collision_remap] || {} @@ -81,7 +81,7 @@ module Vagrant # Determine a list of usable ports for repair 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 with_forwarded_ports(env) do |options| @@ -92,6 +92,7 @@ module Vagrant with_forwarded_ports(env) do |options| guest_port = options[:guest] host_port = options[:host] + host_ip = options[:host_ip] if options[:disabled] @logger.debug("Skipping disabled port #{host_port}.") @@ -110,9 +111,9 @@ module Vagrant end # If the port is open (listening for TCP connections) - in_use = extra_in_use.include?(host_port) || - port_checker[host_port] || - lease_check(host_port) + in_use = is_forwarded_already(extra_in_use, host_port, host_ip) || + port_checker[host_ip, host_port] || + lease_check(host_ip, host_port) if in_use if !repair || !options[:auto_correct] raise Errors::ForwardPortCollision, @@ -129,9 +130,9 @@ module Vagrant usable_ports.delete(repaired_port) # If the port is in use, then we can't use this either... - in_use = extra_in_use.include?(repaired_port) || - port_checker[repaired_port] || - lease_check(repaired_port) + in_use = is_forwarded_already(extra_in_use, repaired_port, host_ip) || + port_checker[host_ip, repaired_port] || + lease_check(host_ip, repaired_port) if in_use @logger.info("Repaired port also in use: #{repaired_port}. Trying another...") next @@ -163,13 +164,19 @@ module Vagrant 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 # about 60 seconds to avoid any forwarded port collisions in # a highly parallelized environment. leasedir = @machine.env.data_dir.join("fp-leases") 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 oldest = Time.now.to_i - 60 leasedir.children.each do |child| @@ -178,7 +185,7 @@ module Vagrant child.delete end - if child.basename.to_s == port.to_s + if child.basename.to_s == lease_file_name invalid = true end end @@ -187,13 +194,13 @@ module Vagrant return true if invalid # 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.write(Time.now.to_i.to_s + "\n") end # Add to the leased array so we unlease it right away - @leased << port.to_s + @leased << lease_file_name # Things look good to us! false @@ -208,8 +215,32 @@ module Vagrant end end - def port_check(port) - is_port_open?("127.0.0.1", port) + # This functions checks to see if the current instance's hostport and + # 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 def with_forwarded_ports(env) diff --git a/plugins/kernel_v2/config/vm.rb b/plugins/kernel_v2/config/vm.rb index 84a57f798..e8abe106c 100644 --- a/plugins/kernel_v2/config/vm.rb +++ b/plugins/kernel_v2/config/vm.rb @@ -259,9 +259,11 @@ module VagrantPlugins default_id = nil if type == :forwarded_port - # For forwarded ports, set the default ID to the - # host port so that host ports overwrite each other. - default_id = "#{options[:protocol]}#{options[:host]}" + # For forwarded ports, set the default ID to be the + # concat of host_ip, proto and host_port. This would ensure Vagrant + # 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 options[:id] = default_id || SecureRandom.uuid @@ -697,7 +699,7 @@ module VagrantPlugins end if options[:host] - key = "#{options[:protocol]}#{options[:host]}" + key = "#{options[:host_ip]}#{options[:protocol]}#{options[:host]}" if fp_used.include?(key) errors << I18n.t("vagrant.config.vm.network_fp_host_not_unique", host: options[:host].to_s, diff --git a/plugins/providers/virtualbox/driver/version_5_0.rb b/plugins/providers/virtualbox/driver/version_5_0.rb index 31e42fd45..aadabffd3 100644 --- a/plugins/providers/virtualbox/driver/version_5_0.rb +++ b/plugins/providers/virtualbox/driver/version_5_0.rb @@ -367,8 +367,10 @@ module VagrantPlugins end # Parse out the forwarded port information - if line =~ /^Forwarding.+?="(.+?),.+?,.*?,(.+?),.*?,(.+?)"$/ - result = [current_nic, $1.to_s, $2.to_i, $3.to_i] + # Forwarding(1)="172.22.8.201tcp32977,tcp,172.22.8.201,32977,,3777" + # 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}") results << result end @@ -557,7 +559,7 @@ module VagrantPlugins end 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| if line =~ /^".+?" \{(.+?)\}$/ uuid = $1.to_s @@ -565,13 +567,14 @@ module VagrantPlugins # Ignore our own used ports next if uuid == @uuid - read_forwarded_ports(uuid, true).each do |_, _, hostport, _| - ports << hostport + read_forwarded_ports(uuid, true).each do |_, _, hostport, _, hostip| + hostip = '*' if hostip.nil? || hostip.empty? + used_ports[hostport].add?(hostip) end end end - ports + used_ports end def read_vms diff --git a/plugins/providers/virtualbox/util/compile_forwarded_ports.rb b/plugins/providers/virtualbox/util/compile_forwarded_ports.rb index 363722b18..99f926c1c 100644 --- a/plugins/providers/virtualbox/util/compile_forwarded_ports.rb +++ b/plugins/providers/virtualbox/util/compile_forwarded_ports.rb @@ -15,6 +15,7 @@ module VagrantPlugins if type == :forwarded_port guest_port = options[:guest] host_port = options[:host] + host_ip = options[:host_ip] protocol = options[:protocol] || "tcp" options = scoped_hash_override(options, :virtualbox) id = options[:id] @@ -22,7 +23,8 @@ module VagrantPlugins # If the forwarded port was marked as disabled, ignore. 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) end end