From ecbf7df859ab5c14d99daa7be6e924fc141b8131 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 21 Dec 2011 22:27:06 -0800 Subject: [PATCH] Host only networks now work with VBoxManage --- lib/vagrant/action/vm/network.rb | 103 +++++++++++++++++-------------- lib/vagrant/driver/virtualbox.rb | 80 ++++++++++++++++++++++++ lib/vagrant/errors.rb | 7 --- templates/locales/en.yml | 5 -- 4 files changed, 137 insertions(+), 58 deletions(-) diff --git a/lib/vagrant/action/vm/network.rb b/lib/vagrant/action/vm/network.rb index f886f079314..49359714dcd 100644 --- a/lib/vagrant/action/vm/network.rb +++ b/lib/vagrant/action/vm/network.rb @@ -8,10 +8,6 @@ def initialize(app, env) @app = app @env = env - if enable_network? && Util::Platform.windows? && Util::Platform.bit64? - raise Errors::NetworkNotImplemented - end - env[:vm].config.vm.network_options.compact.each do |network_options| raise Errors::NetworkCollision if !verify_no_bridge_collision(network_options) end @@ -42,17 +38,8 @@ def call(env) # Verifies that there is no collision with a bridged network interface # for the given network options. def verify_no_bridge_collision(net_options) - interfaces = VirtualBox::Global.global.host.network_interfaces - interfaces.each do |ni| - next if ni.interface_type == :host_only - - result = if net_options[:name] - true if net_options[:name] == ni.name - else - true if matching_network?(ni, net_options) - end - - return false if result + @env[:vm].driver.read_bridged_interfaces.each do |interface| + return false if matching_network?(interface, net_options) end true @@ -67,51 +54,75 @@ def enable_network? def assign_network @env[:ui].info I18n.t("vagrant.actions.vm.network.preparing") + networks = @env[:vm].driver.read_host_only_interfaces + adapters = [] + + # Build the networks and the list of adapters we need to enable @env[:vm].config.vm.network_options.compact.each do |network_options| - adapter = @env["vm"].vm.network_adapters[network_options[:adapter]] - adapter.enabled = true - adapter.attachment_type = :host_only - adapter.host_only_interface = network_name(network_options) - adapter.mac_address = network_options[:mac].gsub(':', '') if network_options[:mac] - adapter.save + network = find_matching_network(networks, network_options) + + if !network + # It is an error case if a specific name was given but the network + # doesn't exist. + if network_options[:name] + raise Errors::NetworkNotFound, :name => network_options[:name] + end + + # Otherwise, we create a new network and put the net network + # in the list of available networks so other network definitions + # can use it! + network = create_network(network_options) + networks << network + end + + adapters << { + :adapter => network_options[:adapter] + 1, + :type => :hostonly, + :hostonly => network[:name], + :mac_address => network_options[:mac] + } end + + # Enable the host only adapters! + @env[:vm].driver.enable_adapters(adapters) end - # Returns the name of the proper host only network, or creates - # it if it does not exist. Vagrant determines if the host only - # network exists by comparing the netmask and the IP. - def network_name(net_options) - # First try to find a matching network - interfaces = VirtualBox::Global.global.host.network_interfaces - interfaces.each do |ni| - # Ignore non-host only interfaces which may also match, - # since they're not valid options. - next if ni.interface_type != :host_only - - if net_options[:name] - return ni.name if net_options[:name] == ni.name - else - return ni.name if matching_network?(ni, net_options) + # This looks through a list of available host only networks and + # finds a matching network. + # + # If one is not available, `nil` is returned. + def find_matching_network(networks, needle_options) + networks.each do |network| + if needle_options[:name] && needle_options[:name] == network[:name] + return network + elsif matching_network?(network, needle_options) + return network end end - raise Errors::NetworkNotFound, :name => net_options[:name] if net_options[:name] - - # One doesn't exist, create it. - @env[:ui].info I18n.t("vagrant.actions.vm.network.creating") + nil + end - ni = interfaces.create - ni.enable_static(network_ip(net_options[:ip], net_options[:netmask]), - net_options[:netmask]) - ni.name + # Creates a host only network with the given options and returns + # the hash of the options it was created with. + # + # @return [Hash] + def create_network(network_options) + # Create the options for the host only network, specifically + # figuring out the host only network IP based on the netmask. + options = network_options.merge({ + :ip => network_ip(network_options[:ip], network_options[:netmask]) + }) + + @env[:vm].driver.create_host_only_network(options) end # Tests if a network matches the given options by applying the # netmask to the IP of the network and also to the IP of the # virtual machine and see if they match. def matching_network?(interface, net_options) - interface.network_mask == net_options[:netmask] && - apply_netmask(interface.ip_address, interface.network_mask) == + interface[:netmask] == net_options[:netmask] && + apply_netmask(interface[:ip], interface[:netmask]) == apply_netmask(net_options[:ip], net_options[:netmask]) end diff --git a/lib/vagrant/driver/virtualbox.rb b/lib/vagrant/driver/virtualbox.rb index a9fb9590fae..ca8cbaab9c1 100644 --- a/lib/vagrant/driver/virtualbox.rb +++ b/lib/vagrant/driver/virtualbox.rb @@ -47,6 +47,25 @@ def clear_shared_folders end end + # Creates a host only network with the given options. + def create_host_only_network(options) + # Create the interface + execute("hostonlyif", "create") =~ /^Interface '(.+?)' was successfully created$/ + name = $1.to_s + + # Configure it + execute("hostonlyif", "ipconfig", name, + "--ip", options[:ip], + "--netmask", options[:netmask]) + + # Return the details + return { + :name => name, + :ip => options[:ip], + :netmask => options[:netmask] + } + end + # This deletes the VM with the given name. def delete execute("unregistervm", @uuid, "--delete") @@ -79,6 +98,26 @@ def execute_command(command) raw(*command) end + # Enables network adapters on this virtual machine. + def enable_adapters(adapters) + args = [] + adapters.each do |adapter| + args.concat(["--nic#{adapter[:adapter]}", adapter[:type].to_s]) + + if adapter[:hostonly] + args.concat(["--hostonlyadapter#{adapter[:adapter]}", + adapter[:hostonly]]) + end + + if adapter[:mac_address] + args.concat(["--macaddress#{adapter[:adapter]}", + adapter[:mac_address]]) + end + end + + execute("modifyvm", @uuid, *args) + end + # Forwards a set of ports for a VM. # # This will not affect any previously set forwarded ports, @@ -173,6 +212,47 @@ def read_guest_additions_version return nil end + # This reads the list of host only networks. + def read_bridged_interfaces + execute("list", "bridgedifs").split("\n\n").collect do |block| + info = {} + + block.split("\n").each do |line| + if line =~ /^Name:\s+(.+?)$/ + info[:name] = $1.to_s + elsif line =~ /^IPAddress:\s+(.+?)$/ + info[:ip] = $1.to_s + elsif line =~ /^NetworkMask:\s+(.+?)$/ + info[:netmask] = $1.to_s + elsif line =~ /^Status:\s+(.+?)$/ + info[:status] = $1.to_s + end + end + + # Return the info to build up the results + info + end + end + + # Reads and returns the available host only interfaces. + def read_host_only_interfaces + execute("list", "hostonlyifs").split("\n\n").collect do |block| + info = {} + + block.split("\n").each do |line| + if line =~ /^Name:\s+(.+?)$/ + info[:name] = $1.to_s + elsif line =~ /^IPAddress:\s+(.+?)$/ + info[:ip] = $1.to_s + elsif line =~ /^NetworkMask:\s+(.+?)$/ + info[:netmask] = $1.to_s + end + end + + info + end + end + # This reads the folder where VirtualBox places it's VMs. def read_machine_folder execute("list", "systemproperties").split("\n").each do |line| diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index 7d1b099cc71..25d0ab77997 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -208,13 +208,6 @@ class NetworkNotFound < VagrantError error_key(:not_found, "vagrant.actions.vm.network") end - # Note: This is a temporary error for Windows users while host-only - # networking doesn't quite work. - class NetworkNotImplemented < VagrantError - status_code(49) - error_key(:windows_not_implemented, "vagrant.actions.vm.network") - end - class NFSHostRequired < VagrantError status_code(31) error_key(:host_required, "vagrant.actions.vm.nfs") diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 0e9550bc5f6..fdec49ede03 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -397,11 +397,6 @@ en: host only network for you. Alternatively, please create the specified network manually. preparing: "Preparing host only network..." - windows_not_implemented: |- - Host only networking is currently broken on Windows due to a bug - in jruby-win32ole. When the bug is fixed, a patch release for Vagrant - will be released to remove this error. Until then, please just use - forwarded ports. nfs: host_required: |- A host class is required for NFS shared folders. By default, these