From af9d0df6353ea63d0c664fad524622f5f16ec7c4 Mon Sep 17 00:00:00 2001 From: Pravinchandar Raajendiran Date: Sun, 14 Feb 2016 22:16:24 +1100 Subject: [PATCH] Fix for #4608 Added support for Port forwarding in an IP aliased environment. The change makes the following forwarding rule(s) possible. Ex: eth0 is ip aliased to have a range of IP addresses 10.20.30.0/24. In the Vagrant file, we can now have an entry like the following and it will just work! Note the host port 8081 is the same for both .1 and .2. Vagrant.configure("2") do |config| config.vm.network "forwarded_port", guest: 80, host: 8080 config.vm.network "forwarded_port", guest: 81, host: 8081, host_ip: 10.20.30.1 config.vm.network "forwarded_port", guest: 82, host: 8081, host_ip: 10.20.30.2 end --- .../handle_forwarded_port_collisions.rb | 59 ++++++++++++++----- plugins/kernel_v2/config/vm.rb | 10 ++-- .../virtualbox/driver/version_5_0.rb | 15 +++-- .../util/compile_forwarded_ports.rb | 4 +- 4 files changed, 63 insertions(+), 25 deletions(-) diff --git a/lib/vagrant/action/builtin/handle_forwarded_port_collisions.rb b/lib/vagrant/action/builtin/handle_forwarded_port_collisions.rb index 05e8edfa3..f3fb3da97 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[:protocol] && options[:protocol] != "tcp" @logger.debug("Skipping #{host_port} because UDP protocol.") @@ -105,9 +106,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, @@ -124,9 +125,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 @@ -158,13 +159,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| @@ -173,7 +180,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 @@ -182,13 +189,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 @@ -203,8 +210,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 13e0102bd..4e31457d0 100644 --- a/plugins/kernel_v2/config/vm.rb +++ b/plugins/kernel_v2/config/vm.rb @@ -244,9 +244,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 @@ -676,7 +678,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 75a71c4f1..77bd88432 100644 --- a/plugins/providers/virtualbox/driver/version_5_0.rb +++ b/plugins/providers/virtualbox/driver/version_5_0.rb @@ -366,8 +366,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 @@ -556,7 +558,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 @@ -564,13 +566,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