#!/bin/sh
set -e

usage() {
    echo "Usage:"
    echo "weave launch <ipaddr>/<subnet> [-passwd <passwd>] <peer_host> ..."
    echo "weave run    <ipaddr>/<subnet> <docker run args> ..."
    echo "weave attach <ipaddr>/<subnet> <container_id>"
    echo "weave detach <ipaddr>/<subnet> <container_id>"
    echo "weave expose <ipaddr>/<subnet>"
    echo "weave hide   <ipaddr>/<subnet>"
    echo "weave status"
    echo "weave version"
    echo "weave stop"
    echo "weave reset"
    exit 1
}

# utility function to check whether a command can be executed by the shell
# see http://stackoverflow.com/questions/592620/how-to-check-if-a-program-exists-from-a-bash-script
command_exists () {
    command -v $1 >/dev/null 2>&1
}

[ `id -u` = 0 ] || {
    echo "weave must be run as 'root'" >&2
    exit 1
}

[ $# -gt 0 ] || usage

BRIDGE=weave
CONTAINER_IFNAME=ethwe
MTU=65535

COMMAND=$1

shift 1

check_command_and_run() {
    if command_exists $1
    then
        shift 1
        "$@"
    else
        echo "Cannot find $1; please install it. Continuing without it." >&2
    fi
}

create_bridge() {
    [ ! -d /sys/class/net/$BRIDGE ] && {
        ip link add dev $BRIDGE type bridge
        ip link set dev $BRIDGE address 7a:$(od -txC -An -N5 /dev/random | tr \  : | tail -c+2)
        # Attempting to set the bridge MTU to a high value directly
        # fails. Bridges take the lowest MTU of their interfaces. So
        # instead we create a temporary interface with the desired
        # MTU, attach that to the bridge, and then remove it again.
        ip link add name v${CONTAINER_IFNAME}du mtu $MTU type dummy
        ip link set dev v${CONTAINER_IFNAME}du master $BRIDGE
        ip link del dev v${CONTAINER_IFNAME}du
        # disable offloading
        check_command_and_run ethtool ethtool -K $BRIDGE tx off >/dev/null
    }
    ip link set dev $BRIDGE up
}

destroy_bridge() {
    if [ -d /sys/class/net/$BRIDGE ] ; then
        ip link del dev $BRIDGE
    fi
}

run_iptables() {
    # -w is recent addition to iptables
    if [ -z "$CHECKED_IPTABLES_W" ] ; then
        if iptables -S -w >/dev/null 2>&1 ; then
            IPTABLES_W=-w
        fi
        CHECKED_IPTABLES_W=1
    fi

    iptables $IPTABLES_W "$@"
}

add_nat_rule() {
    if ! run_iptables -t nat -C "$@" >/dev/null 2>&1
    then
        run_iptables -t nat -A "$@" >/dev/null
    fi
}

setup_masquerading() {
    run_iptables -t nat -N WEAVE >/dev/null 2>&1 || true
    add_nat_rule POSTROUTING -j WEAVE
    add_nat_rule WEAVE -o $BRIDGE ! -s $1 -j MASQUERADE
    add_nat_rule WEAVE -s $1 ! -o $BRIDGE -j MASQUERADE
}

stop_masquerading() {
    run_iptables -t nat -F WEAVE >/dev/null 2>&1 || true
    run_iptables -t nat -D POSTROUTING -j WEAVE >/dev/null 2>&1 || true
    run_iptables -t nat -X WEAVE >/dev/null 2>&1 || true
}

# the following borrows from https://github.com/jpetazzo/pipework
container_netns_preamble() {
    DOCKERPID=$(docker inspect --format='{{ .State.Pid }}' $1)
    [ "$DOCKERPID" = 0 ] && {
        echo "Container $1 not running." >&2
        exit 1
    }
    [ "$DOCKERPID" = "<no value>" ] && {
        echo "Container $1 unknown to Docker." >&2
        exit 1
    }
    NSPID=$DOCKERPID

    [ ! -d /var/run/netns ] && mkdir -p /var/run/netns
    rm -f /var/run/netns/$NSPID
    ln -s /proc/$NSPID/ns/net /var/run/netns/$NSPID
    LOCAL_IFNAME="v${CONTAINER_IFNAME}pl${NSPID}"
    GUEST_IFNAME="v${CONTAINER_IFNAME}pg${NSPID}"

    return 0
}

container_netns_cleanup() {
    rm -f /var/run/netns/$NSPID
}

# Parameters are container ID, CIDR address
configure_container_networking() {
    container_netns_preamble $1

    # If veth name already exists then assume we set it up already;
    # just add the IP address, if it's not already there
    if ip link show $LOCAL_IFNAME >/dev/null 2>&1 ; then
        if ! ip netns exec $NSPID ip addr list dev $CONTAINER_IFNAME | grep -qF $2 ; then
            if ! ip netns exec $NSPID ip addr add $2 dev $CONTAINER_IFNAME ; then
                echo "Failed to attach address $2 to container $1" >&2
                container_netns_cleanup
                return 1
            fi
        fi
        container_netns_cleanup
        return 0
    fi

    EXITCODE=1
    IP_TMPFILE=/tmp/weave_ip_output_$$
    rm -f $IP_TMPFILE
    if ip link add name $LOCAL_IFNAME mtu $MTU type veth peer name $GUEST_IFNAME mtu $MTU >$IP_TMPFILE 2>&1 ; then
        if  (ip link set $LOCAL_IFNAME master $BRIDGE &&
                ip link set $LOCAL_IFNAME up &&
                ip link set $GUEST_IFNAME netns $NSPID) >$IP_TMPFILE 2>&1
        then
            (ip netns exec $NSPID ip link set $GUEST_IFNAME name $CONTAINER_IFNAME &&
                ip netns exec $NSPID ip addr add $2 dev $CONTAINER_IFNAME &&
                ip netns exec $NSPID ip link set $CONTAINER_IFNAME up &&
                check_command_and_run ethtool ip netns exec $NSPID ethtool -K $3 tx off >/dev/null) >$IP_TMPFILE 2>&1 &&
            EXITCODE=0
        else
            # failed before we assigned the veth to the container's namespace - delete it
            ip link del $LOCAL_IFNAME type veth || true
        fi
    fi

    if [ "$EXITCODE" -ne 0 ] ; then
        if ! ps -p $DOCKERPID >/dev/null 2>&1 ; then
            echo "Container $1 died" >&2
        else
            echo "Failure during network configuration for container $1:" >&2
            cat $IP_TMPFILE >&2
        fi
    fi

    rm -f $IP_TMPFILE
    container_netns_cleanup

    return $EXITCODE
}

# Parameters are container ID, CIDR address, interface-name
detach() {
    container_netns_preamble $1

    EXITCODE=1
    if ip netns exec $NSPID ip addr list dev $CONTAINER_IFNAME | grep -qF $2 ; then
        if ip netns exec $NSPID ip addr del $2 dev $CONTAINER_IFNAME ; then
            # if no inet addresses remain then delete the veth
            if [ -z "$(ip netns exec $NSPID ip -f inet addr list dev ethwe)" ] ; then
                if ip link del $LOCAL_IFNAME type veth ; then
                    EXITCODE=0
                else
                    echo "Failed to delete virtual ethernet device $LOCAL_IFNAME from container $1" >&2
                fi
            else
                EXITCODE=0
            fi
        else
            echo "Failed to detach address $2 from container $1" >&2
        fi
    fi

    container_netns_cleanup
    return $EXITCODE
}

case "$COMMAND" in
    launch)
        [ $# -gt 0 ] || usage
        IPADDR=$1
        shift 1
        case $(docker inspect --format '{{ .State.Running }} {{ .Config.Image }}' weave 2>/dev/null) in
            "true zettio/weave")
                echo "Weave is already running." >&2
                exit 1
                ;;
            "false zettio/weave")
                docker rm weave >/dev/null
                ;;
            true*)
                echo "Found another running container named 'weave'. Aborting." >&2
                exit 1
                ;;
            false*)
                echo "Found another container named 'weave'. Aborting." >&2
                exit 1
                ;;
        esac
        create_bridge
        stop_masquerading
        setup_masquerading $IPADDR
        # We set the router name to the bridge mac since that is
        # stable across re-creations of the containers.
        #
        # TODO use the mac of one of the physical host interfaces
        # (eth0, wlan0, etc) so the name becomes stable across host
        # restarts.
        MACADDR=`cat /sys/class/net/$BRIDGE/address`
        CONTAINER=$(docker run --privileged -d --name=weave -p 6783:6783/tcp -p 6783:6783/udp $WEAVE_DOCKER_ARGS zettio/weave -name $MACADDR "$@")
        configure_container_networking $CONTAINER $IPADDR eth0
        echo $CONTAINER
        ;;
    status)
        if ! DOCKERIP=$(docker inspect --format='{{ .NetworkSettings.IPAddress }}' weave 2>/dev/null) ; then
            echo "Weave is not running." >&2
            exit 1
        fi
        if [ -z "$DOCKERIP" ] ; then
            echo "Weave is not running." >&2
            exit 1
        fi
        if command_exists wget ; then
            wget -O - -q $DOCKERIP:6784/status
        elif command_exists curl ; then
            curl $DOCKERIP:6784/status
        else
            echo "Cannot find wget or curl. Install one of them for 'weave status' to function." >&2
            exit 1
        fi
        ;;
    version)
        # Try the running container first; if no luck try the image
        if ! DOCKERIMAGE=$(docker inspect --format='{{ .Image }}' weave 2>/dev/null) ; then
            if ! DOCKERIMAGE=$(docker inspect --format='{{ .Id }}' zettio/weave 2>/dev/null) ; then
                echo "Unable to find zettio/weave image." >&2
                exit 1
            fi
        fi
        docker images --no-trunc | grep $DOCKERIMAGE | grep -v latest | tr -s ' ' | cut -d ' ' -f 2
        ;;
    run)
        [ $# -gt 0 ] || usage
        IPADDR=$1
        shift 1
        create_bridge
        CONTAINER=$(docker run -d "$@")
        configure_container_networking $CONTAINER $IPADDR $CONTAINER_IFNAME
        echo $CONTAINER
        ;;
    attach)
        [ $# -gt 1 ] || usage
        create_bridge
        configure_container_networking $2 $1 $CONTAINER_IFNAME
        ;;
    detach)
        [ $# -gt 1 ] || usage
        detach $2 $1
        ;;
    expose)
        [ $# -gt 0 ] || usage
        IPADDR=$1
        shift 1
        create_bridge
        if ! ip addr list dev $BRIDGE | grep -qF $IPADDR
        then
            ip addr add dev $BRIDGE $IPADDR
        fi
        ;;
    hide)
        [ $# -gt 0 ] || usage
        IPADDR=$1
        shift 1
        create_bridge
        if ip addr list dev $BRIDGE | grep -qF $IPADDR
        then
            ip addr del dev $BRIDGE $IPADDR
        fi
        ;;
    stop)
        if ! docker kill weave >/dev/null 2>&1 ; then
            echo "Weave is not running."
        fi
        docker rm -f weave >/dev/null 2>&1 || true
        check_command_and_run conntrack conntrack -D -p udp --dport 6783 >/dev/null 2>&1 || true
        stop_masquerading
        ;;
    reset)
        docker kill weave >/dev/null 2>&1 || true
        docker rm -f weave >/dev/null 2>&1 || true
        check_command_and_run conntrack conntrack -D -p udp --dport 6783 >/dev/null 2>&1 || true
        stop_masquerading
        destroy_bridge
        ;;
    *)
        echo "Unknown weave command '$COMMAND'" >&2
        usage
        ;;
esac
