Re-implement how networking is done internally
This commit is contained in:
parent
76a77432b8
commit
97f7fa633d
|
@ -26,7 +26,6 @@ module Vagrant
|
|||
|
||||
module VM
|
||||
autoload :Boot, 'vagrant/action/vm/boot'
|
||||
autoload :BridgedNetwork, 'vagrant/action/vm/bridged_network'
|
||||
autoload :CheckAccessible, 'vagrant/action/vm/check_accessible'
|
||||
autoload :CheckBox, 'vagrant/action/vm/check_box'
|
||||
autoload :CheckGuestAdditions, 'vagrant/action/vm/check_guest_additions'
|
||||
|
@ -46,7 +45,7 @@ module Vagrant
|
|||
autoload :HostName, 'vagrant/action/vm/host_name'
|
||||
autoload :Import, 'vagrant/action/vm/import'
|
||||
autoload :MatchMACAddress, 'vagrant/action/vm/match_mac_address'
|
||||
autoload :HostOnlyNetwork, 'vagrant/action/vm/host_only_network'
|
||||
autoload :Network, 'vagrant/action/vm/network'
|
||||
autoload :NFS, 'vagrant/action/vm/nfs'
|
||||
autoload :Package, 'vagrant/action/vm/package'
|
||||
autoload :PackageVagrantfile, 'vagrant/action/vm/package_vagrantfile'
|
||||
|
|
|
@ -30,8 +30,7 @@ module Vagrant
|
|||
use VM::ShareFolders
|
||||
use VM::HostName
|
||||
use VM::ClearNetworkInterfaces
|
||||
use VM::BridgedNetwork
|
||||
use VM::HostOnlyNetwork
|
||||
use VM::Network
|
||||
use VM::Customize
|
||||
use VM::Boot
|
||||
end
|
||||
|
|
|
@ -55,7 +55,7 @@ module Vagrant
|
|||
|
||||
# Port forwarding requires the network interface to be a NAT interface,
|
||||
# so verify that that is the case.
|
||||
if interfaces[adapter][:type] != "nat"
|
||||
if interfaces[adapter][:type] != :nat
|
||||
@env[:ui].info(I18n.t("vagrant.actions.vm.forward_ports.non_nat",
|
||||
message_attributes))
|
||||
next
|
||||
|
|
|
@ -0,0 +1,284 @@
|
|||
require 'set'
|
||||
|
||||
require 'log4r'
|
||||
|
||||
require 'vagrant/util/network_ip'
|
||||
|
||||
module Vagrant
|
||||
module Action
|
||||
module VM
|
||||
# This action handles all `config.vm.network` configurations by
|
||||
# setting up the VM properly and enabling the networks afterword.
|
||||
class Network
|
||||
# Utilities to deal with network addresses
|
||||
include Util::NetworkIP
|
||||
|
||||
def initialize(app, env)
|
||||
@logger = Log4r::Logger.new("vagrant::action::vm::network")
|
||||
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
@env = env
|
||||
|
||||
# First we have to get the array of adapters that we need
|
||||
# to create on the virtual machine itself, as well as the
|
||||
# driver-agnostic network configurations for each.
|
||||
@logger.debug("Determining adapters and networks...")
|
||||
adapters = []
|
||||
networks = []
|
||||
env[:vm].config.vm.networks.each do |type, args|
|
||||
# Get the normalized configuration we'll use around
|
||||
config = send("#{type}_config", args)
|
||||
|
||||
# Get the virtualbox adapter configuration
|
||||
adapter = send("#{type}_adapter", config)
|
||||
adapters << adapter
|
||||
|
||||
# Get the network configuration
|
||||
network = send("#{type}_network_config", config)
|
||||
networks << network
|
||||
end
|
||||
|
||||
# Automatically assign an adapter number to any adapters
|
||||
# that aren't explicitly set.
|
||||
@logger.debug("Assigning adapter locations...")
|
||||
assign_adapter_locations(adapters)
|
||||
|
||||
# Create all the network interfaces
|
||||
@logger.info("Enabling adapters...")
|
||||
env[:ui].info I18n.t("vagrant.actions.vm.network.preparing")
|
||||
env[:vm].driver.enable_adapters(adapters)
|
||||
|
||||
# Continue the middleware chain. We're done with our VM
|
||||
# setup until after it is booted.
|
||||
@app.call(env)
|
||||
|
||||
# Determine the interface numbers for the guest.
|
||||
assign_interface_numbers(networks, adapters)
|
||||
|
||||
# Configure all the network interfaces on the guest.
|
||||
env[:ui].info I18n.t("vagrant.actions.vm.network.configuring")
|
||||
env[:vm].guest.configure_networks(networks)
|
||||
end
|
||||
|
||||
# This method assigns the adapter to use for the adapter.
|
||||
# e.g. it says that the first adapter is actually on the
|
||||
# virtual machine's 2nd adapter location.
|
||||
#
|
||||
# It determines the adapter numbers by simply finding the
|
||||
# "next available" in each case.
|
||||
#
|
||||
# The adapters are modified in place by adding an ":adapter"
|
||||
# field to each.
|
||||
def assign_adapter_locations(adapters)
|
||||
available = Set.new(1..8)
|
||||
|
||||
# Determine which NICs are actually available.
|
||||
interfaces = @env[:vm].driver.read_network_interfaces
|
||||
interfaces.each do |number, nic|
|
||||
# Remove the number from the available NICs if the
|
||||
# NIC is in use.
|
||||
available.delete(number) if nic[:type] != :none
|
||||
end
|
||||
|
||||
# Based on the available set, assign in order to
|
||||
# the adapters.
|
||||
available = available.to_a.sort
|
||||
@logger.debug("Available NICs: #{available.inspect}")
|
||||
adapters.each do |adapter|
|
||||
# Ignore the adapters that already have been assigned
|
||||
if !adapter[:adapter]
|
||||
adapter[:adapter] = available.shift
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Assigns the actual interface number of a network based on the
|
||||
# enabled NICs on the virtual machine.
|
||||
#
|
||||
# This interface number is used by the guest to configure the
|
||||
# NIC on the guest VM.
|
||||
#
|
||||
# The networks are modified in place by adding an ":interface"
|
||||
# field to each.
|
||||
def assign_interface_numbers(networks, adapters)
|
||||
current = 0
|
||||
adapter_to_interface = {}
|
||||
|
||||
# Make a first pass to assign interface numbers by adapter location
|
||||
vm_adapters = @env[:vm].driver.read_network_interfaces
|
||||
vm_adapters.each do |number, adapter|
|
||||
if adapter[:type] != :none
|
||||
# Not used, so assign the interface number and increment
|
||||
adapter_to_interface[number] = current
|
||||
current += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Make a pass through the adapters to assign the :interface
|
||||
# key to each network configuration.
|
||||
adapters.each_index do |i|
|
||||
adapter = adapters[i]
|
||||
network = networks[i]
|
||||
|
||||
# Figure out the interface number by simple lookup
|
||||
network[:interface] = adapter_to_interface[adapter[:adapter]]
|
||||
end
|
||||
end
|
||||
|
||||
def hostonly_config(args)
|
||||
ip = args[0]
|
||||
options = args[1] || {}
|
||||
|
||||
options = {
|
||||
:ip => ip,
|
||||
:netmask => "255.255.255.0",
|
||||
:adapter => nil,
|
||||
:mac => nil,
|
||||
:name => nil
|
||||
}.merge(options)
|
||||
|
||||
# Verify that this hostonly network wouldn't conflict with any
|
||||
# bridged interfaces
|
||||
verify_no_bridge_collision(options)
|
||||
|
||||
# Return the hostonly network configuration
|
||||
return options
|
||||
end
|
||||
|
||||
def hostonly_adapter(config)
|
||||
@logger.debug("Searching for matching network: #{config[:ip]}")
|
||||
interface = find_matching_hostonly_network(config)
|
||||
|
||||
if !interface
|
||||
@logger.debug("Network not found. Creating if we can.")
|
||||
|
||||
# It is an error case if a specific name was given but the network
|
||||
# doesn't exist.
|
||||
if config[:name]
|
||||
raise Errors::NetworkNotFound, :name => config[: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!
|
||||
interface = create_hostonly_network(config)
|
||||
@logger.debug("Created network: #{interface[:name]}")
|
||||
end
|
||||
|
||||
return {
|
||||
:adapter => config[:adapter],
|
||||
:type => :hostonly,
|
||||
:hostonly => interface[:name],
|
||||
:mac_address => config[:mac]
|
||||
}
|
||||
end
|
||||
|
||||
def hostonly_network_config(config)
|
||||
return {
|
||||
:type => :static,
|
||||
:ip => config[:ip],
|
||||
:netmask => config[:netmask]
|
||||
}
|
||||
end
|
||||
|
||||
# Creates a new hostonly network that matches the network requested
|
||||
# by the given host-only network configuration.
|
||||
def create_hostonly_network(config)
|
||||
# First we need to determine a good IP for the host machine
|
||||
# of this interface. We choose to use the network address
|
||||
# plus 1, which is usually what is expected.
|
||||
netaddr = network_address(config[:ip], config[:netmask])
|
||||
parts = netaddr.split(".").map { |i| i.to_i }
|
||||
parts[3] += 1
|
||||
ip = parts.join(".")
|
||||
|
||||
# Create the options that are going to be used to create our
|
||||
# new network.
|
||||
options = config.dup
|
||||
options[:ip] = ip
|
||||
|
||||
@env[:vm].driver.create_host_only_network(options)
|
||||
end
|
||||
|
||||
# Finds a host only network that matches our configuration on VirtualBox.
|
||||
# This will return nil if a matching network does not exist.
|
||||
def find_matching_hostonly_network(config)
|
||||
this_netaddr = network_address(config[:ip], config[:netmask])
|
||||
|
||||
@env[:vm].driver.read_host_only_interfaces.each do |interface|
|
||||
if config[:name] && config[:name] == interface[:name]
|
||||
return interface
|
||||
elsif this_netaddr == network_address(interface[:ip], interface[:netmask])
|
||||
return interface
|
||||
end
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
# Verifies that a host-only network subnet would not collide with
|
||||
# a bridged networking interface.
|
||||
#
|
||||
# If the subnets overlap in any way then the host only network
|
||||
# will not work because the routing tables will force the traffic
|
||||
# onto the real interface rather than the virtualbox interface.
|
||||
def verify_no_bridge_collision(options)
|
||||
this_netaddr = network_address(options[:ip], options[:netmask])
|
||||
|
||||
@env[:vm].driver.read_bridged_interfaces.each do |interface|
|
||||
that_netaddr = network_address(interface[:ip], interface[:netmask])
|
||||
raise Errors::NetworkCollision if this_netaddr == that_netaddr
|
||||
end
|
||||
end
|
||||
|
||||
def bridged_config(args)
|
||||
options = args[0] || {}
|
||||
|
||||
return {
|
||||
:adapter => nil,
|
||||
:mac => nil
|
||||
}.merge(options)
|
||||
end
|
||||
|
||||
def bridged_adapter(config)
|
||||
bridgedifs = @env[:vm].driver.read_bridged_interfaces
|
||||
|
||||
# Output all the interfaces that are available as choices
|
||||
@env[:ui].info I18n.t("vagrant.actions.vm.bridged_networking.available",
|
||||
:prefix => false)
|
||||
bridgedifs.each_index do |index|
|
||||
interface = bridgedifs[index]
|
||||
@env[:ui].info("#{index + 1}) #{interface[:name]}", :prefix => false)
|
||||
end
|
||||
|
||||
# The range of valid choices
|
||||
valid = Range.new(1, bridgedifs.length)
|
||||
|
||||
# The choice that the user has chosen as the bridging interface
|
||||
choice = nil
|
||||
while !valid.include?(choice)
|
||||
choice = @env[:ui].ask("What interface should the network bridge to? ")
|
||||
choice = choice.to_i
|
||||
end
|
||||
|
||||
# Given the choice we can now define the adapter we're using
|
||||
return {
|
||||
:adapter => config[:adapter],
|
||||
:type => :bridged,
|
||||
:bridge => bridgedifs[choice - 1][:name],
|
||||
:mac_address => config[:mac]
|
||||
}
|
||||
end
|
||||
|
||||
def bridged_network_config(config)
|
||||
return {
|
||||
:type => :dhcp
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -340,7 +340,7 @@ module Vagrant
|
|||
execute("showvminfo", @uuid, "--machinereadable").split("\n").each do |line|
|
||||
if line =~ /^nic(\d+)="(.+?)"$/
|
||||
adapter = $1.to_i
|
||||
type = $2.to_s
|
||||
type = $2.to_sym
|
||||
|
||||
nics[adapter] ||= {}
|
||||
nics[adapter][:type] = type
|
||||
|
|
|
@ -66,36 +66,22 @@ module Vagrant
|
|||
# via the host are already done.
|
||||
def mount_nfs(ip, folders); end
|
||||
|
||||
# Prepares the system for host only networks. This is called
|
||||
# once prior to any `enable_host_only_network` calls.
|
||||
def prepare_host_only_network(net_options=nil)
|
||||
raise BaseError, :_key => :unsupported_host_only
|
||||
end
|
||||
|
||||
# Setup the system by adding a new host only network. This
|
||||
# method should configure and bring up the interface for the
|
||||
# given options.
|
||||
# Configures the given list of networks on the virtual machine.
|
||||
#
|
||||
# @param [Hash] net_options The options for the network.
|
||||
def enable_host_only_network(net_options); end
|
||||
|
||||
# Prepares the guest for bridged networks.
|
||||
# The networks parameter will be an array of hashes where the hashes
|
||||
# represent the configuration of a network interface. The structure
|
||||
# of the hash will be roughly the following:
|
||||
#
|
||||
# @param [Array] networks Array of networks to prepare for.
|
||||
def prepare_bridged_networks(networks)
|
||||
raise BaseError, :_key => :unsupported_host_only
|
||||
end
|
||||
|
||||
# Enable bridged networks on a guest.
|
||||
# {
|
||||
# :type => :static,
|
||||
# :ip => "33.33.33.10",
|
||||
# :netmask => "255.255.255.0",
|
||||
# :interface => 1
|
||||
# }
|
||||
#
|
||||
# @param [Array] networks Array of networks to prepare for.
|
||||
def enable_bridged_networks(networks)
|
||||
raise BaseError, :_key => :unsupported_bridged
|
||||
end
|
||||
def configure_networks(networks); end
|
||||
|
||||
def change_host_name(name)
|
||||
raise BaseError, :_key => :unsupported_bridged
|
||||
end
|
||||
def change_host_name(name); end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,49 +1,49 @@
|
|||
require 'set'
|
||||
|
||||
require 'vagrant/util/template_renderer'
|
||||
|
||||
module Vagrant
|
||||
module Guest
|
||||
class Debian < Linux
|
||||
def prepare_host_only_network(net_options=nil)
|
||||
# Remove any previous host only network additions to the interface file.
|
||||
# Make the TemplateRenderer top-level
|
||||
include Vagrant::Util
|
||||
|
||||
def configure_networks(networks)
|
||||
# First, remove any previous network modifications
|
||||
# from the interface file.
|
||||
vm.ssh.execute do |ssh|
|
||||
ssh.exec!("sudo sed -e '/^#VAGRANT-BEGIN-HOSTONLY/,/^#VAGRANT-END-HOSTONLY/ d' /etc/network/interfaces > /tmp/vagrant-network-interfaces")
|
||||
ssh.exec!("sudo sed -e '/^#VAGRANT-BEGIN/,/^#VAGRANT-END/ d' /etc/network/interfaces > /tmp/vagrant-network-interfaces")
|
||||
ssh.exec!("sudo su -c 'cat /tmp/vagrant-network-interfaces > /etc/network/interfaces'")
|
||||
end
|
||||
end
|
||||
|
||||
def enable_host_only_network(net_options)
|
||||
entry = TemplateRenderer.render('guests/debian/network_hostonly',
|
||||
:net_options => net_options)
|
||||
vm.ssh.upload!(StringIO.new(entry), "/tmp/vagrant-network-entry")
|
||||
|
||||
vm.ssh.execute do |ssh|
|
||||
ssh.exec!("sudo /sbin/ifdown eth#{net_options[:adapter]} 2> /dev/null")
|
||||
ssh.exec!("sudo su -c 'cat /tmp/vagrant-network-entry >> /etc/network/interfaces'")
|
||||
ssh.exec!("sudo /sbin/ifup eth#{net_options[:adapter]}")
|
||||
# Accumulate the configurations to add to the interfaces file as
|
||||
# well as what interfaces we're actually configuring since we use that
|
||||
# later.
|
||||
interfaces = Set.new
|
||||
entries = []
|
||||
networks.each do |network|
|
||||
interfaces.add(network[:interface])
|
||||
entries << TemplateRenderer.render("guests/debian/network_#{network[:type]}",
|
||||
:options => network)
|
||||
end
|
||||
end
|
||||
|
||||
def prepare_bridged_networks(networks)
|
||||
# Remove any previous bridged network additions to the interface file.
|
||||
vm.ssh.execute do |ssh|
|
||||
ssh.exec!("sudo sed -e '/^#VAGRANT-BEGIN-BRIDGED/,/^#VAGRANT-END-BRIDGED/ d' /etc/network/interfaces > /tmp/vagrant-network-interfaces")
|
||||
ssh.exec!("sudo su -c 'cat /tmp/vagrant-network-interfaces > /etc/network/interfaces'")
|
||||
end
|
||||
end
|
||||
|
||||
def enable_bridged_networks(networks)
|
||||
entry = TemplateRenderer.render('guests/debian/network_bridged',
|
||||
:networks => networks)
|
||||
|
||||
vm.ssh.upload!(StringIO.new(entry), "/tmp/vagrant-network-entry")
|
||||
# Perform the careful dance necessary to to reconfigure
|
||||
# the network interfaces
|
||||
vm.ssh.upload!(StringIO.new(entries.join("\n")), "/tmp/vagrant-network-entry")
|
||||
|
||||
vm.ssh.execute do |ssh|
|
||||
networks.each do |network|
|
||||
ssh.exec!("sudo /sbin/ifdown eth#{network[:adapter]} 2> /dev/null")
|
||||
# Bring down all the interfaces we're reconfiguring. By bringing down
|
||||
# each specifically, we avoid reconfiguring eth0 (the NAT interface) so
|
||||
# SSH never dies.
|
||||
interfaces.each do |interface|
|
||||
ssh.exec!("sudo /sbin/ifdown eth#{interface} 2> /dev/null")
|
||||
end
|
||||
|
||||
ssh.exec!("sudo su -c 'cat /tmp/vagrant-network-entry >> /etc/network/interfaces'")
|
||||
|
||||
networks.each do |network|
|
||||
ssh.exec!("sudo /sbin/ifup eth#{network[:adapter]}")
|
||||
# Bring back up each network interface, reconfigured
|
||||
interfaces.each do |interface|
|
||||
ssh.exec!("sudo /sbin/ifup eth#{interface}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
module Vagrant
|
||||
module Util
|
||||
module NetworkIP
|
||||
# Returns the network address of the given IP and subnet.
|
||||
#
|
||||
# @return [String]
|
||||
def network_address(ip, subnet)
|
||||
ip = ip_parts(ip)
|
||||
netmask = ip_parts(subnet)
|
||||
|
||||
# Bitwise-AND each octet to get the network address
|
||||
# in octets and join each part with a period to get
|
||||
# the resulting network address.
|
||||
ip.map { |part| part & netmask.shift }.join(".")
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Splits an IP into the four octets and returns each as an
|
||||
# integer in an array.
|
||||
#
|
||||
# @return [Array<Integer>]
|
||||
def ip_parts(ip)
|
||||
ip.split(".").map { |i| i.to_i }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,7 +0,0 @@
|
|||
<% networks.each do |network| -%>
|
||||
#VAGRANT-BEGIN-BRIDGED
|
||||
# The contents below are automatically generated by Vagrant. Do not modify.
|
||||
auto eth<%= network[:adapter] %>
|
||||
iface eth<%= network[:adapter] %> inet dhcp
|
||||
#VAGRANT-END-BRIDGED
|
||||
<% end -%>
|
|
@ -0,0 +1,5 @@
|
|||
#VAGRANT-BEGIN
|
||||
# The contents below are automatically generated by Vagrant. Do not modify.
|
||||
auto eth<%= options[:interface] %>
|
||||
iface eth<%= options[:interface] %> inet dhcp
|
||||
#VAGRANT-END
|
|
@ -1,8 +0,0 @@
|
|||
#VAGRANT-BEGIN-HOSTONLY
|
||||
# The contents below are automatically generated by Vagrant.
|
||||
# Please do not modify any of these contents.
|
||||
auto eth<%= net_options[:adapter] %>
|
||||
iface eth<%= net_options[:adapter] %> inet static
|
||||
address <%= net_options[:ip] %>
|
||||
netmask <%= net_options[:netmask] %>
|
||||
#VAGRANT-END-HOSTONLY
|
|
@ -0,0 +1,7 @@
|
|||
#VAGRANT-BEGIN
|
||||
# The contents below are automatically generated by Vagrant. Do not modify.
|
||||
auto eth<%= options[:interface] %>
|
||||
iface eth<%= options[:interface] %> inet static
|
||||
address <%= options[:ip] %>
|
||||
netmask <%= options[:netmask] %>
|
||||
#VAGRANT-END
|
|
@ -394,6 +394,11 @@ en:
|
|||
to work properly (and hence port forwarding, SSH, etc.). Specifying this
|
||||
MAC address is typically up to the box and box maintiner. Please contact
|
||||
the relevant person to solve this issue.
|
||||
network:
|
||||
preparing: |-
|
||||
Preparing network interfaces based on configuration...
|
||||
configuring: |-
|
||||
Configuring and enabling network interfaces...
|
||||
host_only_network:
|
||||
collides: |-
|
||||
The specified host network collides with a non-hostonly network!
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
require File.expand_path("../../../base", __FILE__)
|
||||
|
||||
require "vagrant/util/network_ip"
|
||||
|
||||
describe Vagrant::Util::NetworkIP do
|
||||
let(:klass) do
|
||||
Class.new do
|
||||
extend Vagrant::Util::NetworkIP
|
||||
end
|
||||
end
|
||||
|
||||
describe "network address" do
|
||||
it "calculates it properly" do
|
||||
klass.network_address("192.168.2.234", "255.255.255.0").should == "192.168.2.0"
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue