#!/bin/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 <hostinterface> [-i containerinterface] <guest> <ipaddr>/<subnet>[@default_gateway] [macaddr][@vlan]"
    echo "pipework <hostinterface> [-i containerinterface] <guest> 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" = "<no value>" ] && {
      		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