From e97c330281a062d224bc96507d35f2fabd68db22 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 26 Feb 2013 12:10:26 -0800 Subject: [PATCH] First pass at a forwarded port collision middleware --- .../handle_forwarded_port_collisions.rb | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 lib/vagrant/action/builtin/handle_forwarded_port_collisions.rb diff --git a/lib/vagrant/action/builtin/handle_forwarded_port_collisions.rb b/lib/vagrant/action/builtin/handle_forwarded_port_collisions.rb new file mode 100644 index 000000000..f2b6e0d14 --- /dev/null +++ b/lib/vagrant/action/builtin/handle_forwarded_port_collisions.rb @@ -0,0 +1,93 @@ +require "set" + +require "log4r" + +require "vagrant/util/is_port_open" + +module Vagrant + module Action + module Builtin + # This middleware class will detect and handle collisions with + # forwarded ports, whether that means raising an error or repairing + # them automatically. + # + # Parameters it takes from the environment hash: + # + # * `:port_collision_repair` - If true, it will attempt to repair + # port collisions. If false, it will raise an exception when + # there is a collision. + # + class HandleForwardedPortCollisions + include Util::IsPortOpen + + def initialize(app, env) + @app = app + @logger = Log4r::Logger.new("vagrant::action::builtin::handle_port_collisions") + end + + def call(env) + @logger.debug("Detecting any forwarded port collisions...") + + # Determine the handler we'll use if we have any port collisions + repair = !!env[:port_collision_repair] + + # Determine a list of usable ports for repair + usable_ports = Set.new(env[:machine].config.vm.usable_port_range) + + # Pass one, remove all defined host ports from usable ports + with_forwarded_ports do |args| + usable_ports.delete(args[1]) + end + + # Pass two, detect/handle any collisions + with_forwarded_ports do |args| + # Get the host port of this forwarded port + # TODO: handle overrides in the case that an existing VM is + # already running with auto-corrected ports. + guest_port = args[0] + host_port = args[1] + + # If the port is open (listening for TCP connections) + if is_port_open?("127.0.0.1", host_port) + if !repair + raise Errors::ForwardPortCollision, + :guest_port => guest_port.to_s, + :host_port => host_port.to_s + end + + @logger.info("Attempting to repair FP collision: #{host_port}") + + # If we have no usable ports then we can't repair + if usable_ports.empty? + raise Errors::ForwardPortAutolistEmpty, + :vm_name => env[:machine].name, + :guest_port => guest_port.to_s, + :host_port => host_port.to_s + end + + # Attempt to repair the forwarded port + repaired_port = usable_ports.to_a.sort[0] + usable_ports.delete(repaired_port) + + # Modify the args in place + args[1] = repaired_port + + @logger.info("Repaired FP collision: #{host_port} to #{repaired_port}") + end + end + end + + protected + + def with_forwarded_ports + env[:machine].config.vm.networks.each do |type, args| + # Ignore anything but forwarded ports + next if type != :forwarded_port + + yield args + end + end + end + end + end +end