diff --git a/README.md b/README.md index dff2e6a..5033465 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ to see it in action. * [Vagrant 1.1+](http://www.vagrantup.com/downloads.html) * lxc 0.7.5+ * `redir` (if you are planning to use port forwarding) +* `brctl` (if you are planning to use private networks, on Ubuntu this means `apt-get install bridge-utils`) * A [kernel != 3.5.0-17.28](https://github.com/fgrehm/vagrant-lxc/wiki/Troubleshooting#wiki-im-unable-to-restart-containers) The plugin is known to work better and pretty much out of the box on Ubuntu 14.04+ diff --git a/lib/vagrant-lxc/action.rb b/lib/vagrant-lxc/action.rb index 851adc3..7ffd4ed 100644 --- a/lib/vagrant-lxc/action.rb +++ b/lib/vagrant-lxc/action.rb @@ -11,6 +11,7 @@ require 'vagrant-lxc/action/forward_ports' require 'vagrant-lxc/action/handle_box_metadata' require 'vagrant-lxc/action/prepare_nfs_settings' require 'vagrant-lxc/action/prepare_nfs_valid_ids' +require 'vagrant-lxc/action/private_networks' require 'vagrant-lxc/action/remove_temporary_files' require 'vagrant-lxc/action/setup_package_files' require 'vagrant-lxc/action/warn_networks' @@ -56,6 +57,7 @@ module Vagrant b.use ForwardPorts b.use Boot b.use Builtin::WaitForCommunicator + b.use PrivateNetworks end end diff --git a/lib/vagrant-lxc/action/private_networks.rb b/lib/vagrant-lxc/action/private_networks.rb new file mode 100644 index 0000000..cbbe576 --- /dev/null +++ b/lib/vagrant-lxc/action/private_networks.rb @@ -0,0 +1,60 @@ +module Vagrant + module LXC + module Action + class PrivateNetworks + def initialize(app, env) + @app = app + end + + def call(env) + @app.call(env) + + if private_network_configured?(env[:machine].config) + configure_private_networks(env) + end + end + + def private_network_configured?(config) + config.vm.networks.find do |type, _| + type.to_sym == :private_network + end + end + + def configure_private_networks(env) + env[:machine].config.vm.networks.find do |type, config| + next if type.to_sym != :private_network + + container_name = env[:machine].provider.driver.container_name + ip = config[:ip] + configure_single_network('br1', container_name, ip) + end + end + + def configure_single_network(bridge, container_name, ip) + cmd = [ + 'sudo', + Vagrant::LXC.source_root.join('scripts/private-network').to_s, + bridge, + container_name, + "#{ip}/24" + ] + puts cmd.join(' ') + system cmd.join(' ') + + cmd = [ + 'sudo', + 'ip', + 'addr', + 'add', + # TODO: This should not be hard coded and has to run once per bridge + "192.168.1.254/24", + 'dev', + bridge + ] + puts cmd.join(' ') + system cmd.join(' ') + end + end + end + end +end diff --git a/lib/vagrant-lxc/action/warn_networks.rb b/lib/vagrant-lxc/action/warn_networks.rb index fed6bd9..4982cbb 100644 --- a/lib/vagrant-lxc/action/warn_networks.rb +++ b/lib/vagrant-lxc/action/warn_networks.rb @@ -7,16 +7,16 @@ module Vagrant end def call(env) - if public_or_private_network_configured?(env[:machine].config) + if public_network_configured?(env[:machine].config) env[:ui].warn(I18n.t("vagrant_lxc.messages.warn_networks")) end @app.call(env) end - def public_or_private_network_configured?(config) + def public_network_configured?(config) config.vm.networks.find do |type, _| - [:private_network, :public_network].include?(type.to_sym) + type.to_sym == :public_network end end end diff --git a/locales/en.yml b/locales/en.yml index 32f635f..145cbf0 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -13,9 +13,8 @@ en: force_shutdown: |- Forcing shutdown of container... warn_networks: |- - Warning! The LXC provider doesn't support any of the Vagrant public / private - network configurations (ex: `config.vm.network :private_network, ip: "some-ip"`). - They will be silently ignored. + Warning! The LXC provider doesn't support public networks, the settings + will be silently ignored. warn_group: |- Warning! The LXC provider doesn't support the :group parameter for synced folders. It will be silently ignored. diff --git a/scripts/private-network b/scripts/private-network new file mode 100755 index 0000000..5b7c8be --- /dev/null +++ b/scripts/private-network @@ -0,0 +1,193 @@ +#!/bin/bash + +# This is a snapshot of https://github.com/jpetazzo/pipework/blob/edbd33ab49ab0dff0bee46b019055360f325a6e5/pipework +# with docker specifics trimmed out + +set -e + +case "$1" in + --wait) + WAIT=1 + ;; +esac + +IFNAME=$1 +if [ "$2" == "-i" ]; then + CONTAINER_IFNAME=$3 + shift 2 +else + CONTAINER_IFNAME=eth1 +fi +GUESTNAME=$2 +IPADDR=$3 +MACADDR=$4 + +[ "$WAIT" ] && { + while ! grep -q ^1$ /sys/class/net/$CONTAINER_IFNAME/carrier 2>/dev/null + do sleep 1 + done + exit 0 +} + +[ "$IPADDR" ] || { + echo "Syntax:" + echo "pipework [-i containerinterface] /[@default_gateway] [macaddr]" + echo "pipework [-i containerinterface] dhcp [macaddr]" + echo "pipework --wait" + exit 1 +} + +# First step: determine type of first argument (bridge, physical interface...) +if [ -d /sys/class/net/$IFNAME ] +then + if [ -d /sys/class/net/$IFNAME/bridge ] + then + IFTYPE=bridge + BRTYPE=linux + elif $(which ovs-vsctl >/dev/null) && $(ovs-vsctl list-br|grep -q ^$IFNAME$) + then + IFTYPE=bridge + BRTYPE=openvswitch + else IFTYPE=phys + fi +else + case "$IFNAME" in + br*) + IFTYPE=bridge + BRTYPE=linux + ;; + *) + echo "I do not know how to setup interface $IFNAME." + exit 1 + ;; + esac +fi + +# Second step: find the guest +while read dev mnt fstype options dump fsck +do + [ "$fstype" != "cgroup" ] && continue + echo $options | grep -qw devices || continue + CGROUPMNT=$mnt +done < /proc/mounts + +[ "$CGROUPMNT" ] || { + echo "Could not locate cgroup mount point." + exit 1 +} + +# Try to find a cgroup matching exactly the provided name. +N=$(find "$CGROUPMNT" -name "$GUESTNAME" | wc -l) +case "$N" in + 0) + echo "Container $GUESTNAME not found." + exit 1 + ;; + 1) + true + ;; + *) + echo "Found more than one container matching $GUESTNAME." + exit 1 + ;; +esac + +if [ "$IPADDR" = "dhcp" ] +then + # Check for first available dhcp client + DHCP_CLIENT_LIST="udhcpc dhcpcd dhclient" + for CLIENT in $DHCP_CLIENT_LIST; do + which $CLIENT >/dev/null && { + DHCP_CLIENT=$CLIENT + break + } + done + [ -z $DHCP_CLIENT ] && { + echo "You asked for DHCP; but no DHCP client could be found." + exit 1 + } +else + # Check if a subnet mask was provided. + echo $IPADDR | grep -q / || { + echo "The IP address should include a netmask." + echo "Maybe you meant $IPADDR/24 ?" + exit 1 + } + # Check if a gateway address was provided. + if echo $IPADDR | grep -q @ + then + GATEWAY=$(echo $IPADDR | cut -d@ -f2) + IPADDR=$(echo $IPADDR | cut -d@ -f1) + else + GATEWAY= + fi +fi + +NSPID=$(head -n 1 $(find "$CGROUPMNT" -name "$GUESTNAME" | head -n 1)/tasks) +[ "$NSPID" ] || { + echo "Could not find a process inside container $GUESTNAME." + exit 1 +} + +[ ! -d /var/run/netns ] && mkdir -p /var/run/netns +[ -f /var/run/netns/$NSPID ] && rm -f /var/run/netns/$NSPID +ln -s /proc/$NSPID/ns/net /var/run/netns/$NSPID + + +# Check if we need to create a bridge. +[ $IFTYPE = bridge ] && [ ! -d /sys/class/net/$IFNAME ] && { + (ip link set $IFNAME type bridge > /dev/null 2>&1) || (brctl addbr $IFNAME) + ip link set $IFNAME up +} + +# If it's a bridge, we need to create a veth pair +[ $IFTYPE = bridge ] && { + LOCAL_IFNAME=pl$NSPID$CONTAINER_IFNAME + GUEST_IFNAME=pg$NSPID$CONTAINER_IFNAME + ip link add name $LOCAL_IFNAME type veth peer name $GUEST_IFNAME + case "$BRTYPE" in + linux) + (ip link set $LOCAL_IFNAME master $IFNAME > /dev/null 2>&1) || (brctl addif $IFNAME $LOCAL_IFNAME) + ;; + openvswitch) + ovs-vsctl add-port $IFNAME $LOCAL_IFNAME + ;; + esac + ip link set $LOCAL_IFNAME up +} + +# If it's a physical interface, create a macvlan subinterface +[ $IFTYPE = phys ] && { + GUEST_IFNAME=ph$NSPID$CONTAINER_IFNAME + ip link add link $IFNAME dev $GUEST_IFNAME type macvlan mode bridge + ip link set $IFNAME up +} + +ip link set $GUEST_IFNAME netns $NSPID +ip netns exec $NSPID ip link set $GUEST_IFNAME name $CONTAINER_IFNAME +[ "$MACADDR" ] && ip netns exec $NSPID ip link set $CONTAINER_IFNAME address $MACADDR +if [ "$IPADDR" = "dhcp" ] +then + [ $DHCP_CLIENT = "udhcpc" ] && ip netns exec $NSPID $DHCP_CLIENT -qi $CONTAINER_IFNAME + [ $DHCP_CLIENT = "dhclient" ] && ip netns exec $NSPID $DHCP_CLIENT $CONTAINER_IFNAME + [ $DHCP_CLIENT = "dhcpcd" ] && ip netns exec $NSPID $DHCP_CLIENT -q $CONTAINER_IFNAME +else + ip netns exec $NSPID ip addr add $IPADDR dev $CONTAINER_IFNAME + [ "$GATEWAY" ] && { + ip netns exec $NSPID ip route delete default >/dev/null 2>&1 && true + } + ip netns exec $NSPID ip link set $CONTAINER_IFNAME up + [ "$GATEWAY" ] && { + ip netns exec $NSPID ip route replace default via $GATEWAY + } +fi + +# Give our ARP neighbors a nudge about the new interface +if which arping > /dev/null 2>&1 +then + IPADDR=$(echo $IPADDR | cut -d/ -f1) + ip netns exec $NSPID arping -c 1 -A -I $CONTAINER_IFNAME $IPADDR > /dev/null 2>&1 +else + echo "Warning: arping not found; interface may not be immediately reachable" +fi +exit 0