#!/usr/bin/env bash # Borrowed from https://github.com/jpetazzo/pipework set -e case "$1" in --wait) WAIT=1 ;; esac IFNAME=$1 # default value set further down if not set here CONTAINER_IFNAME= if [ "$2" = "-i" ]; then CONTAINER_IFNAME=$3 shift 2 fi GUESTNAME=$2 IPADDR=$3 MACADDR=$4 if echo $MACADDR | grep -q @ then VLAN=$(echo $MACADDR | cut -d@ -f2) MACADDR=$(echo $MACADDR | cut -d@ -f1) else VLAN= fi [ "$IPADDR" ] || [ "$WAIT" ] || { echo "Syntax:" echo "pipework [-i containerinterface] /[@default_gateway] [macaddr][@vlan]" echo "pipework [-i containerinterface] dhcp [macaddr][@vlan]" echo "pipework --wait [-i containerinterface]" exit 1 } # First step: determine type of first argument (bridge, physical interface...), skip if --wait set if [ -z "$WAIT" ]; then 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 2>&1) && $(ovs-vsctl list-br|grep -q ^$IFNAME$) then IFTYPE=bridge BRTYPE=openvswitch elif [ $(cat /sys/class/net/$IFNAME/type) -eq 32 ]; # Infiniband IPoIB interface type 32 then IFTYPE=ipoib # The IPoIB kernel module is fussy, set device name to ib0 if not overridden CONTAINER_IFNAME=${CONTAINER_IFNAME:-ib0} else IFTYPE=phys fi else # case "$IFNAME" in # br*) IFTYPE=bridge BRTYPE=linux # ;; # ovs*) # if ! $(which ovs-vsctl >/dev/null) # then # echo "Need OVS installed on the system to create an ovs bridge" # exit 1 # fi # IFTYPE=bridge # BRTYPE=openvswitch # ;; # *) # echo "I do not know how to setup interface $IFNAME." # exit 1 # ;; # esac fi fi # Set the default container interface name to eth1 if not already set CONTAINER_IFNAME=${CONTAINER_IFNAME:-eth1} [ "$WAIT" ] && { while ! grep -q ^1$ /sys/class/net/$CONTAINER_IFNAME/carrier 2>/dev/null do sleep 1 done exit 0 } [ $IFTYPE = bridge ] && [ $BRTYPE = linux ] && [ "$VLAN" ] && { echo "VLAN configuration currently unsupported for Linux bridge." exit 1 } [ $IFTYPE = ipoib ] && [ $MACADDR ] && { echo "MACADDR configuration unsupported for IPoIB interfaces." exit 1 } # Second step: find the guest (for now, we only support LXC containers) 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) # If we didn't find anything, try to lookup the container with Docker. if which docker >/dev/null then RETRIES=3 while [ $RETRIES -gt 0 ]; do DOCKERPID=$(docker inspect --format='{{ .State.Pid }}' $GUESTNAME) [ $DOCKERPID != 0 ] && break sleep 1 RETRIES=$((RETRIES - 1)) done [ "$DOCKERPID" = 0 ] && { echo "Docker inspect returned invalid PID 0" exit 1 } [ "$DOCKERPID" = "" ] && { echo "Container $GUESTNAME not found, and unknown to Docker." exit 1 } else echo "Container $GUESTNAME not found, and Docker not installed." exit 1 fi ;; 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 if [ $DOCKERPID ]; then NSPID=$DOCKERPID else NSPID=$(head -n 1 $(find "$CGROUPMNT" -name "$GUESTNAME" | head -n 1)/tasks) [ "$NSPID" ] || { echo "Could not find a process inside container $GUESTNAME." exit 1 } fi # Check if an incompatible VLAN device already exists [ $IFTYPE = phys ] && [ "$VLAN" ] && [ -d /sys/class/net/$IFNAME.VLAN ] && { [ -z "$(ip -d link show $IFNAME.$VLAN | grep "vlan.*id $VLAN")" ] && { echo "$IFNAME.VLAN already exists but is not a VLAN device for tag $VLAN" 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 ] && { [ $BRTYPE = linux ] && { (ip link add dev $IFNAME type bridge > /dev/null 2>&1) || (brctl addbr $IFNAME) ip link set $IFNAME up } [ $BRTYPE = openvswitch ] && { ovs-vsctl add-br $IFNAME } } MTU=$(ip link show $IFNAME | awk '{print $5}') # If it's a bridge, we need to create a veth pair [ $IFTYPE = bridge ] && { LOCAL_IFNAME="v${CONTAINER_IFNAME}pl${NSPID}" GUEST_IFNAME="v${CONTAINER_IFNAME}pg${NSPID}" ip link add name $LOCAL_IFNAME mtu $MTU type veth peer name $GUEST_IFNAME mtu $MTU 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 ${VLAN:+"tag=$VLAN"} ;; esac ip link set $LOCAL_IFNAME up } # Note: if no container interface name was specified, pipework will default to ib0 # Note: no macvlan subinterface or ethernet bridge can be created against an # ipoib interface. Infiniband is not ethernet. ipoib is an IP layer for it. # To provide additional ipoib interfaces to containers use SR-IOV and pipework # to assign them. [ $IFTYPE = ipoib ] && { GUEST_IFNAME=$CONTAINER_IFNAME } # If it's a physical interface, create a macvlan subinterface [ $IFTYPE = phys ] && { [ "$VLAN" ] && { [ ! -d /sys/class/net/$IFNAME.$VLAN ] && { ip link add link $IFNAME name $IFNAME.$VLAN mtu $MTU type vlan id $VLAN } ip link set $IFNAME up IFNAME=$IFNAME.$VLAN } GUEST_IFNAME=ph$NSPID$CONTAINER_IFNAME ip link add link $IFNAME dev $GUEST_IFNAME mtu $MTU 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 dev $CONTAINER_IFNAME address $MACADDR if [ "$IPADDR" = "dhcp" ] then [ $DHCP_CLIENT = "udhcpc" ] && ip netns exec $NSPID $DHCP_CLIENT -qi $CONTAINER_IFNAME -x hostname:$GUESTNAME if [ $DHCP_CLIENT = "dhclient" ] then # kill dhclient after get ip address to prevent device be used after container close ip netns exec $NSPID $DHCP_CLIENT -pf "/var/run/dhclient.$NSPID.pid" $CONTAINER_IFNAME kill "$(cat "/var/run/dhclient.$NSPID.pid")" rm "/var/run/dhclient.$NSPID.pid" fi [ $DHCP_CLIENT = "dhcpcd" ] && ip netns exec $NSPID $DHCP_CLIENT -q $CONTAINER_IFNAME -h $GUESTNAME 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 get $GATEWAY >/dev/null 2>&1 || \ ip netns exec $NSPID ip route add $GATEWAY/32 dev $CONTAINER_IFNAME 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 || true else echo "Warning: arping not found; interface may not be immediately reachable" fi # Remove NSPID to avoid `ip netns` catch it. [ -f /var/run/netns/$NSPID ] && rm -f /var/run/netns/$NSPID exit 0