Only consider the VM interfaces in the IPv6 fixup.

Vagrant should only consider the host-only interfaces used by the
virtual machine in the IPv6 fixup code. There may be other interfaces
present on the system with IPv6 addresses that for various reasons
would fail the routing check (for example, an interface with no
machines attached).

The patch changes the behavior to not scan all of the host-only
interfaces and adds a unit test for the behavior (that the correct IP
is validated).

Lastly, there is a small fix here that may not be an issue for most
people where the IPv6 prefix was asummed to be a multiple of 16 for
the purposes of constructing the UDP probe datagram. This assumption
has been removed.

Fixes #6586
This commit is contained in:
Timur Alperovich 2015-11-23 14:58:32 -08:00 committed by Seth Vargo
parent a8d0c225c6
commit bcf61d001b
2 changed files with 144 additions and 11 deletions

View File

@ -41,21 +41,22 @@ module VagrantPlugins
# If we have no IPv6, forget it
return if !has_v6
# We do, so fix them if we must
env[:machine].provider.driver.read_host_only_interfaces.each do |interface|
# Ignore interfaces without an IPv6 address
next if interface[:ipv6] == ""
# Make the test IP. This is just the highest value IP
ip = IPAddr.new(interface[:ipv6])
ip |= IPAddr.new(":#{":FFFF" * (interface[:ipv6_prefix].to_i / 16)}")
ifaces = env[:machine].provider.driver.read_network_interfaces
ifaces.select! { |id, iface| iface[:type] == :hostonly }
iface_names = ifaces.values.map { |iface| iface[:hostonly] }
networks = env[:machine].provider.driver.read_host_only_interfaces
networks.select! { |network| iface_names.include?(network[:name]) }
networks.each do |network|
next if network[:ipv6] == ""
next if network[:status] != 'Up'
ip = IPAddr.new(network[:ipv6]) |
('1' * (128 - network[:ipv6_prefix].to_i)).to_i(2)
@logger.info("testing IPv6: #{ip}")
begin
UDPSocket.new(Socket::AF_INET6).connect(ip.to_s, 80)
rescue Errno::EHOSTUNREACH
@logger.info("IPv6 host unreachable. Fixing: #{ip}")
env[:machine].provider.driver.reconfig_host_only(interface)
env[:machine].provider.driver.reconfig_host_only(network)
end
end
end

View File

@ -1,4 +1,5 @@
require_relative "../base"
require 'socket'
describe VagrantPlugins::ProviderVirtualBox::Action::NetworkFixIPv6 do
include_context "unit"
@ -11,11 +12,14 @@ describe VagrantPlugins::ProviderVirtualBox::Action::NetworkFixIPv6 do
end
let(:machine) do
iso_env.machine(iso_env.machine_names[0], :dummy)
iso_env.machine(iso_env.machine_names[0], :dummy).tap do |m|
m.provider.stub(driver: driver)
end
end
let(:env) {{ machine: machine }}
let(:app) { lambda { |*args| }}
let(:driver) { double("driver") }
subject { described_class.new(app, env) }
@ -30,4 +34,132 @@ describe VagrantPlugins::ProviderVirtualBox::Action::NetworkFixIPv6 do
.and_return(private_network: { ip: "" })
expect { subject.call(env) }.to_not raise_error
end
context "with IPv6 interfaces" do
let(:socket) { double("socket") }
before do
# This address is only used to trigger the fixup code. It doesn't matter
# what it is.
allow(machine.config.vm).to receive(:networks)
.and_return(private_network: { ip: 'fe:80::' })
allow(UDPSocket).to receive(:new).with(Socket::AF_INET6)
.and_return(socket)
socket.stub(:connect)
end
it "only checks the interfaces associated with the VM" do
all_networks = [{name: "vboxnet0",
ipv6: "dead:beef::",
ipv6_prefix: 64,
status: 'Up'
},
{name: "vboxnet1",
ipv6: "badd:badd::",
ipv6_prefix: 64,
status: 'Up'
}
]
ifaces = { 1 => {type: :hostonly, hostonly: "vboxnet0"}
}
allow(machine.provider.driver).to receive(:read_network_interfaces)
.and_return(ifaces)
allow(machine.provider.driver).to receive(:read_host_only_interfaces)
.and_return(all_networks)
subject.call(env)
expect(socket).to have_received(:connect)
.with(all_networks[0][:ipv6] + (['ffff']*4).join(':'), 80)
end
it "correctly uses the netmask to figure out the probe address" do
all_networks = [{name: "vboxnet0",
ipv6: "dead:beef::",
ipv6_prefix: 113,
status: 'Up'
}
]
ifaces = { 1 => {type: :hostonly, hostonly: "vboxnet0"}
}
allow(machine.provider.driver).to receive(:read_network_interfaces)
.and_return(ifaces)
allow(machine.provider.driver).to receive(:read_host_only_interfaces)
.and_return(all_networks)
subject.call(env)
expect(socket).to have_received(:connect)
.with(all_networks[0][:ipv6] + '7fff', 80)
end
it "should ignore interfaces that are down" do
all_networks = [{name: "vboxnet0",
ipv6: "dead:beef::",
ipv6_prefix: 64,
status: 'Down'
}
]
ifaces = { 1 => {type: :hostonly, hostonly: "vboxnet0"}
}
allow(machine.provider.driver).to receive(:read_network_interfaces)
.and_return(ifaces)
allow(machine.provider.driver).to receive(:read_host_only_interfaces)
.and_return(all_networks)
subject.call(env)
expect(socket).to_not have_received(:connect)
end
it "should ignore interfaces without an IPv6 address" do
all_networks = [{name: "vboxnet0",
ipv6: "",
ipv6_prefix: 0,
status: 'Up'
}
]
ifaces = { 1 => {type: :hostonly, hostonly: "vboxnet0"}
}
allow(machine.provider.driver).to receive(:read_network_interfaces)
.and_return(ifaces)
allow(machine.provider.driver).to receive(:read_host_only_interfaces)
.and_return(all_networks)
subject.call(env)
expect(socket).to_not have_received(:connect)
end
it "should ignore nat interfaces" do
all_networks = [{name: "vboxnet0",
ipv6: "",
ipv6_prefix: 0,
status: 'Up'
}
]
ifaces = { 1 => {type: :nat}
}
allow(machine.provider.driver).to receive(:read_network_interfaces)
.and_return(ifaces)
allow(machine.provider.driver).to receive(:read_host_only_interfaces)
.and_return(all_networks)
subject.call(env)
expect(socket).to_not have_received(:connect)
end
it "should reconfigure an interface if unreachable" do
all_networks = [{name: "vboxnet0",
ipv6: "dead:beef::",
ipv6_prefix: 64,
status: 'Up'
}
]
ifaces = { 1 => {type: :hostonly, hostonly: "vboxnet0"}
}
allow(machine.provider.driver).to receive(:read_network_interfaces)
.and_return(ifaces)
allow(machine.provider.driver).to receive(:read_host_only_interfaces)
.and_return(all_networks)
allow(socket).to receive(:connect)
.with(all_networks[0][:ipv6] + (['ffff']*4).join(':'), 80)
.and_raise Errno::EHOSTUNREACH
allow(machine.provider.driver).to receive(:reconfig_host_only)
subject.call(env)
expect(machine.provider.driver).to have_received(:reconfig_host_only)
.with(all_networks[0])
end
end
end