#!/bin/sh
set -e

[ -n "$WEAVE_DEBUG" ] && set -x

SCRIPT_VERSION="(unreleased version)"
if [ "$SCRIPT_VERSION" = "(unreleased version)" ] ; then
    IMAGE_VERSION=latest
else
    IMAGE_VERSION=$SCRIPT_VERSION
fi
IMAGE_VERSION=${VERSION:-$IMAGE_VERSION}

# - Docker versions prior to 1.2.0 cannot parse `--dns-search=.`
# - Docker versions prior to 1.3.0 fail to include the domain when
#   setting the hostname, so lookups for non-fq names fail.
# - Docker version 1.3.0 contains the severe bug
#   https://github.com/docker/docker/issues/8632
# - The weavexec image embeds a Docker 1.3.1 client. Misleading
#   version mismatch errors are produced if the user is running with a
#   client *and* server with a lower version.
# - Docker will complain if you run a client against a server older
#   than that client.
#
# We therefore check that the user's Docker *client* is >= 1.3.1
MIN_DOCKER_VERSION=1.3.1

# These are needed for remote execs, hence we introduce them here
DOCKERHUB_USER=${DOCKERHUB_USER:-weaveworks}
BASE_EXEC_IMAGE=$DOCKERHUB_USER/weaveexec
EXEC_IMAGE=$BASE_EXEC_IMAGE:$IMAGE_VERSION
PROXY_HOST=${PROXY_HOST:-$(echo "${DOCKER_HOST#tcp://}" | cut -s -d: -f1)}
PROXY_HOST=${PROXY_HOST:-127.0.0.1}
DOCKER_CLIENT_HOST=${DOCKER_CLIENT_HOST:-$DOCKER_HOST}
DOCKER_CLIENT_TLS_VERIFY=${DOCKER_CLIENT_TLS_VERIFY:-$DOCKER_TLS_VERIFY}

# Define some regular expressions for matching addresses.
# The regexp here is far from precise, but good enough.
IP_REGEXP="[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}"
CIDR_REGEXP="$IP_REGEXP/[0-9]{1,2}"

usage_no_exit() {
    cat >&2 <<EOF
Usage:

weave --help | help
weave setup
weave version
weave launch        [--password <password>] [--nickname <nickname>]
                      [--ipalloc-range <cidr> [--ipalloc-default-subnet <cidr>]]
                      [--no-discovery] [--init-peer-count <count>] <peer> ...
weave launch-router [--password <password>] [--nickname <nickname>]
                      [--ipalloc-range <cidr> [--ipalloc-default-subnet <cidr>]]
                      [--no-discovery] [--init-peer-count <count>] <peer> ...
weave launch-proxy  [-H <endpoint>] [--with-dns | --without-dns]
                      [--no-default-ipalloc] [--no-rewrite-hosts]
                      [--hostname-from-label <labelkey>]
                      [--hostname-match <regexp>]
                      [--hostname-replacement <replacement>]
                      [--rewrite-inspect]
weave env           [--restore]
weave config
weave connect       [--replace] [<peer> ...]
weave forget        <peer> ...
weave status        [targets | connections | peers | dns]
weave report        [-f <format>]
weave run           [--with-dns | --without-dns] [<addr> ...]
                      <docker run args> ...
weave start         [<addr> ...] <container_id>
weave attach        [<addr> ...] <container_id>
weave detach        [<addr> ...] <container_id>
weave dns-add       [<ip_address> ...] <container_id> [-h <fqdn>]
weave dns-remove    [<ip_address> ...] <container_id> [-h <fqdn>]
weave dns-lookup    <unqualified_name>
weave expose        [<addr> ...] [-h <fqdn>]
weave hide          [<addr> ...]
weave ps            [<container_id> ...]
weave stop
weave stop-router
weave stop-proxy
weave reset
weave rmpeer        <peer_id>

where <peer>     = <ip_address_or_fqdn>[:<port>]
      <cidr>     = <ip_address>/<routing_prefix_length>
      <addr>     = [ip:]<cidr> | net:<cidr> | net:default
      <endpoint> = [tcp://][<ip_address>]:<port> | [unix://]/path/to/socket
      <peer_id>  = <nickname> or weave internal peer ID
EOF
}

usage() {
    usage_no_exit
    exit 1
}

exec_remote() {
    docker $DOCKER_CLIENT_ARGS run --rm --privileged --net=host \
        -v /var/run:/var/run \
        -v /proc:/hostproc \
        -e PROCFS=/hostproc \
        -e DOCKERHUB_USER="$DOCKERHUB_USER" \
        -e VERSION \
        -e WEAVE_DEBUG \
        -e WEAVE_DOCKER_ARGS \
        -e WEAVEPROXY_DOCKER_ARGS \
        -e WEAVE_PASSWORD \
        -e WEAVE_PORT \
        -e WEAVE_CONTAINER_NAME \
        -e DOCKER_BRIDGE \
        -e DOCKER_CLIENT_HOST="$DOCKER_CLIENT_HOST" \
        -e DOCKER_CLIENT_TLS_VERIFY="$DOCKER_CLIENT_TLS_VERIFY" \
        -e DOCKER_CLIENT_ARGS \
        -e PROXY_HOST="$PROXY_HOST" \
        -e WEAVE_CIDR=none \
        -e COVERAGE \
        $WEAVEEXEC_DOCKER_ARGS $EXEC_IMAGE --local "$@"
}

check_docker_version() {
    if ! DOCKER_VERSION=$(docker -v | sed -n -e 's|^Docker version \([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*|\1|p') || [ -z "$DOCKER_VERSION" ] ; then
        echo "ERROR: Unable to parse docker version" >&2
        exit 1
    fi

    DOCKER_VERSION_MAJOR=$(echo "$DOCKER_VERSION" | cut -d. -f 1)
    DOCKER_VERSION_MINOR=$(echo "$DOCKER_VERSION" | cut -d. -f 2)
    DOCKER_VERSION_PATCH=$(echo "$DOCKER_VERSION" | cut -d. -f 3)

    MIN_DOCKER_VERSION_MAJOR=$(echo "$MIN_DOCKER_VERSION" | cut -d. -f 1)
    MIN_DOCKER_VERSION_MINOR=$(echo "$MIN_DOCKER_VERSION" | cut -d. -f 2)
    MIN_DOCKER_VERSION_PATCH=$(echo "$MIN_DOCKER_VERSION" | cut -d. -f 3)

    if [ \( "$DOCKER_VERSION_MAJOR" -lt "$MIN_DOCKER_VERSION_MAJOR" \) -o \
        \( "$DOCKER_VERSION_MAJOR" -eq "$MIN_DOCKER_VERSION_MAJOR" -a \
        \( "$DOCKER_VERSION_MINOR" -lt "$MIN_DOCKER_VERSION_MINOR" -o \
        \( "$DOCKER_VERSION_MINOR" -eq "$MIN_DOCKER_VERSION_MINOR" -a \
        \( "$DOCKER_VERSION_PATCH" -lt "$MIN_DOCKER_VERSION_PATCH" \) \) \) \) ] ; then
        echo "ERROR: weave requires Docker version $MIN_DOCKER_VERSION or later; you are running $DOCKER_VERSION" >&2
        exit 1
    fi
}

enforce_docker_bridge_addr_assign_type() {
    if ! ADDR_ASSIGN_TYPE=$(cat /sys/class/net/$DOCKER_BRIDGE/addr_assign_type 2>/dev/null) ; then
        echo "Could not determine address assignment type of $DOCKER_BRIDGE" >&2
        return
    fi
    # From include/uapi/linux/netdevice.h
    # #define NET_ADDR_PERM       0   /* address is permanent (default) */
    # #define NET_ADDR_RANDOM     1   /* address is generated randomly */
    # #define NET_ADDR_STOLEN     2   /* address is stolen from other device */
    # #define NET_ADDR_SET        3   /* address is set using dev_set_mac_address() */
    if [ $ADDR_ASSIGN_TYPE != 3 ] ; then
        echo "Setting $DOCKER_BRIDGE MAC (mitigate https://github.com/docker/docker/issues/14908)" >&2
        ip link set dev $DOCKER_BRIDGE address $(random_mac) || true
    fi
}

check_forwarding_rules() {
    if run_iptables -C FORWARD -j REJECT --reject-with icmp-host-prohibited > /dev/null 2>&1; then
        cat >&2 <<EOF
WARNING: existing iptables rule

    '-A FORWARD -j REJECT --reject-with icmp-host-prohibited'

will block name resolution via weaveDNS - please reconfigure your firewall.
EOF
    fi
}

is_cidr() {
    echo "$1" | grep -E "^$CIDR_REGEXP$" >/dev/null
}

collect_cidr_args() {
    CIDR_ARGS=""
    CIDR_ARG_COUNT=0
    while [ "$1" = "net:default" ] || is_cidr "$1" || is_cidr "${1#ip:}" || is_cidr "${1#net:}" ; do
        CIDR_ARGS="$CIDR_ARGS ${1#ip:}"
        CIDR_ARG_COUNT=$((CIDR_ARG_COUNT + 1))
        shift 1
    done
}

is_ip() {
    echo "$1" | grep -E "^$IP_REGEXP$" >/dev/null
}

collect_ip_args() {
    IP_ARGS=""
    IP_COUNT=0
    while is_ip "$1" ; do
        IP_ARGS="$IP_ARGS $1"
        IP_COUNT=$((IP_COUNT + 1))
        shift 1
    done
}

dns_arg_count() {
    if [ "$1" = "--with-dns" -o "$1" = "--without-dns" ] ; then
        echo 1
    else
        echo 0
    fi
}

check_docker_version

if [ "$1" = "--local" ] ; then
    shift 1
    IS_LOCAL=1
fi

if [ "$1" = "--help" -o "$1" = "help" ] ; then
    # "--help|help" are special because we always want to process them
    # at the client end.
    usage_no_exit
    exit 0
elif [ "$1" = "env" -a "$2" = "--restore" ] ; then
    # "env --restore" is special because we always want to process it
    # at the client end.
    if [ "${ORIG_DOCKER_HOST-unset}" = "unset" ] ; then
        echo "Nothing to restore. This is most likely because there was no preceding invocation of 'eval \$(weave env)' in this shell." >&2
        exit 1
    else
        echo "DOCKER_HOST=$ORIG_DOCKER_HOST"
        exit 0
    fi
elif [ "$1" = "run" -a -z "$IS_LOCAL" ] ; then
    # non-local "run" is a special case because we want to use docker
    # directly, rather than the docker in $EXEC_IMAGE remotely. That's
    # because we are passing arbitrary arguments on to docker run, and
    # we can't rely on our baked-in docker to support those arguments.
    shift 1
    if [ "$1" != "--without-dns" ] ; then
        DNS_ARGS=$(exec_remote dns-args "$@" || true)
    fi
    shift $(dns_arg_count "$@")
    collect_cidr_args "$@"
    shift $CIDR_ARG_COUNT
    CONTAINER=$(docker $DOCKER_CLIENT_ARGS run -e WEAVE_CIDR=none $DNS_ARGS -d "$@")
    exec_remote attach $CIDR_ARGS --or-die $CONTAINER >/dev/null
    echo $CONTAINER
    exit 0
fi

if [ -z "$IS_LOCAL" ] ; then
    exec_remote "$@"
    exit $?
fi

BASE_IMAGE=$DOCKERHUB_USER/weave
IMAGE=$BASE_IMAGE:$IMAGE_VERSION

PROCFS=${PROCFS:-/proc}
DOCKER_BRIDGE=${DOCKER_BRIDGE:-docker0}
CONTAINER_NAME=${WEAVE_CONTAINER_NAME:-weave}
BRIDGE=weave
CONTAINER_IFNAME=ethwe
MTU=65535
PORT=${WEAVE_PORT:-6783}
HTTP_PORT=6784
PROXY_PORT=12375
PROXY_CONTAINER_NAME=weaveproxy
COVERAGE_ARGS=""
if [ -n "$COVERAGE" ] ; then
    COVERAGE_ARGS="-test.coverprofile=/home/weave/cover.prof --"
fi

######################################################################
# general helpers; independent of docker and weave
######################################################################

# 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
}

fractional_sleep() {
    case $1 in
        *.*)
            if [ -z "$NO_FRACTIONAL_SLEEP" ] ; then
                sleep $1 >/dev/null 2>&1 && return 0
                NO_FRACTIONAL_SLEEP=1
            fi
            sleep $((${1%.*} + 1))
            ;;
        *)
            sleep $1
            ;;
    esac
}

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 a rule to iptables, if it doesn't exist already
add_iptables_rule() {
    IPTABLES_TABLE="$1"
    shift 1
    if ! run_iptables -t $IPTABLES_TABLE -C "$@" >/dev/null 2>&1 ; then
        run_iptables -t $IPTABLES_TABLE -A "$@" >/dev/null
    fi
}

# Delete a rule from iptables, if it exist
delete_iptables_rule() {
    IPTABLES_TABLE="$1"
    shift 1
    if run_iptables -t $IPTABLES_TABLE -C "$@" >/dev/null 2>&1 ; then
        run_iptables -t $IPTABLES_TABLE -D "$@" >/dev/null
    fi
}

# Configure the ARP cache parameters for the given interface.  This
# makes containers react more quickly to a change in the MAC address
# associated with an IP address.
configure_arp_cache() {
    $2 sh -c "echo 5 >/proc/sys/net/ipv4/neigh/$1/base_reachable_time &&
              echo 2 >/proc/sys/net/ipv4/neigh/$1/delay_first_probe_time &&
              echo 1 >/proc/sys/net/ipv4/neigh/$1/ucast_solicit"
}

# Send out an ARP announcement
# (https://tools.ietf.org/html/rfc5227#page-15) to update ARP cache
# entries across the weave network.  We do this in addition to
# configure_arp_cache because a) with those ARP cache settings it
# still takes a few seconds to correct a stale ARP mapping, and b)
# there is a kernel bug that means that the base_reachable_time
# setting is not promptly obeyed
# (<https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=4bf6980dd0328530783fd657c776e3719b421d30>>).
arp_update() {
    # It's not the end of the world if this doesn't run - we configure
    # ARP caches so that stale entries will be noticed quickly.
    if command_exists arping ; then
        $3 arping -U -q -I $1 -c 1 ${2%/*}
    fi
}

# Generate a random MAC value
random_mac() {
    # In the first byte of the MAC, the 'multicast' bit should be
    # clear and 'locally administered' bit should be set.  All other
    # bits should be random.
    od -txC -An -N6 /dev/urandom | ( read a b c d e f && printf "%02x:$b:$c:$d:$e:$f" $((0x$a & ~1 | 2)) )
}

######################################################################
# weave and docker specific helpers
######################################################################

create_bridge() {
    if [ ! -d /sys/class/net/$BRIDGE ] ; then
        ip link add name $BRIDGE type bridge
        # Set a random MAC address on the bridge.  Current Linux
        # kernels already do this when creating a bridge, but there
        # are rumours it was not always so.
        ip link set dev $BRIDGE address $(random_mac)
        # 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
        # Drop traffic from Docker bridge to Weave; it can break subnet isolation
        if [ "$DOCKER_BRIDGE" != "$BRIDGE" ] ; then
            # Note using -I to insert ahead of Docker's bridge rules
            run_iptables -t filter -I FORWARD -i $DOCKER_BRIDGE -o $BRIDGE -j DROP
        fi
        # Work around the situation where there are no rules allowing traffic
        # across our bridge. E.g. ufw
        add_iptables_rule filter FORWARD -i $BRIDGE -o $BRIDGE -j ACCEPT
        # create a chain for masquerading
        run_iptables -t nat -N WEAVE >/dev/null 2>&1 || true
        add_iptables_rule nat POSTROUTING -j WEAVE
    fi

    [ "$1" != "--without-ethtool" ] && ethtool -K $BRIDGE tx off >/dev/null

    ip link set dev $BRIDGE up

    # Configure the ARP cache parameters on the bridge interface for
    # the sake of 'weave expose'
    configure_arp_cache $BRIDGE
}

destroy_bridge() {
    [ -d /sys/class/net/$BRIDGE ] && ip link del dev $BRIDGE
    if [ "$DOCKER_BRIDGE" != "$BRIDGE" ] ; then
        run_iptables -t filter -D FORWARD -i $DOCKER_BRIDGE -o $BRIDGE -j DROP 2>/dev/null || true
    fi
    run_iptables -t filter -D FORWARD -i $BRIDGE -o $BRIDGE -j ACCEPT 2>/dev/null || true
    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
}

docker_bridge_ip() {
    DOCKER_BRIDGE_IP=$(ip -4 addr show dev $DOCKER_BRIDGE | grep -m1 -o 'inet [.0-9]*')
    DOCKER_BRIDGE_IP=${DOCKER_BRIDGE_IP#inet }
}

# the following borrows from https://github.com/jpetazzo/pipework

# Set $CONTAINER_PID to the PID of container $1, $LOCAL_IFNAME and
# $GUEST_IFNAME to suitable names for two ends of a veth pair, specific
# to the container, and execute args $2 $3 ... as a command. If an error
# is caused by container dying, swallow output from error.
with_container_netns() {
    CONTAINER="$1"
    CONTAINER_PID=$(docker inspect --format='{{.State.Pid}}' $CONTAINER)

    if [ "$CONTAINER_PID" = 0 ] ; then
        echo "Container $CONTAINER not running." >&2
        exit 1
    fi

    if [ "$CONTAINER_PID" = "<no value>" ] ; then
        echo "Container $CONTAINER unknown to Docker." >&2
        exit 1
    fi

    LOCAL_IFNAME="v${CONTAINER_IFNAME}pl${CONTAINER_PID}"
    GUEST_IFNAME="v${CONTAINER_IFNAME}pg${CONTAINER_PID}"
    IP_TMPOUT=/tmp/weave_ip_out_$$
    IP_TMPERR=/tmp/weave_ip_err_$$
    rm -f $IP_TMPOUT $IP_TMPERR

    # Run the wrapped command
    STATUS=0
    shift 1
    if ! "$@" >$IP_TMPOUT 2>$IP_TMPERR ; then
        STATUS=1
        if [ ! -d $PROCFS/$CONTAINER_PID ] ; then
            echo "Container $CONTAINER died" >&2
        else
            echo "Failure during network configuration for container $CONTAINER:" >&2
            cat $IP_TMPERR >&2
        fi
    else
        cat $IP_TMPOUT
        cat $IP_TMPERR >&2
    fi
    rm -f $IP_TMPOUT $IP_TMPERR
    return $STATUS
}

with_container_netns_or_die() {
    if ! with_container_netns "$@" >/dev/null ; then
        docker kill $1 >/dev/null 2>&1 || true
        exit 1
    fi
}

# Execute arguments as a command within the network namespace of $CONTAINER_PID
netnsenter() {
    nsenter --net=$PROCFS/$CONTAINER_PID/ns/net "$@"
}

connect_container_to_bridge() {
    if [ -h "$PROCFS/$CONTAINER_PID/ns/net" -a -h "/proc/$$/ns/net" -a "$(readlink $PROCFS/$CONTAINER_PID/ns/net)" = "$(readlink /proc/$$/ns/net)" ] ; then
        echo "Container is running in the host network namespace, and therefore cannot be" >&2
        echo "connected to weave. Perhaps the container was started with --net=host." >&2
        return 1
    fi
    ip link add name $LOCAL_IFNAME mtu $MTU type veth peer name $GUEST_IFNAME mtu $MTU || return 1

    if ! ethtool -K $GUEST_IFNAME tx off >/dev/null ||
        ! ip link set $LOCAL_IFNAME master $BRIDGE ||
        ! ip link set $LOCAL_IFNAME up ||
        ! ip link set $GUEST_IFNAME netns $PROCFS/$CONTAINER_PID/ns/net ; then
        # failed before we assigned the veth to the container's
        # namespace
        ip link del $LOCAL_IFNAME type veth || true
        return 1
    fi

    if ! netnsenter ip link set $GUEST_IFNAME name $CONTAINER_IFNAME ||
       ! configure_arp_cache $CONTAINER_IFNAME "netnsenter" ; then
        return 1
    fi
}

ask_version() {
    if ! DOCKERIMAGE=$(docker inspect --format='{{.Image}}' $1 2>/dev/null) ; then
            if ! DOCKERIMAGE=$(docker inspect --format='{{.Id}}' $2 2>/dev/null) ; then
                echo "Unable to find $2 image." >&2
            fi
    fi
    VERSION_COVERAGE_ARGS=""
    if [ "$1" = "$CONTAINER_NAME" ] || [ "$1" == "$PROXY_CONTAINER_NAME"]  ; then
        VERSION_COVERAGE_ARGS="$COVERAGE_ARGS"
    fi
    [ -n "$DOCKERIMAGE" ] && docker run --rm -e WEAVE_CIDR=none $DOCKERIMAGE $VERSION_COVERAGE_ARGS --version
}

######################################################################
# functions invoked through with_container_netns
######################################################################

launch() {
    if ! netnsenter ip link show eth0 >/dev/null ; then
        echo "Perhaps you are running the docker daemon with container networking disabled (-b=none)." >&2
        return 1
    fi
    connect_container_to_bridge &&
        netnsenter ethtool -K eth0 tx off >/dev/null &&
        netnsenter ip link set $CONTAINER_IFNAME up
}

attach() {
    if ! netnsenter ip link show $CONTAINER_IFNAME >/dev/null 2>&1 ; then
        connect_container_to_bridge || return 1
    fi

    NEW_ADDRS=
    for ADDR in "$@" ; do
        if netnsenter ip addr show dev $CONTAINER_IFNAME | grep -F $ADDR >/dev/null ; then
            # address was there already
            continue
        fi
        netnsenter ip addr add $ADDR dev $CONTAINER_IFNAME || return 1
        NEW_ADDRS="$NEW_ADDRS $ADDR"
    done

    netnsenter ip link set $CONTAINER_IFNAME up || return 1

    for ADDR in $NEW_ADDRS ; do
        arp_update $CONTAINER_IFNAME $ADDR "netnsenter"
    done

    # Route multicast packets across the weave network.
    # This must come last in 'attach'. If you change this, change weavewait to match.
    if ! netnsenter ip route show | grep '^224\.0\.0\.0/4' >/dev/null ; then
        netnsenter ip route add 224.0.0.0/4 dev $CONTAINER_IFNAME
    fi
}

detach() {
    netnsenter ip link show $CONTAINER_IFNAME >/dev/null 2>&1 || return 0

    for ADDR in "$@" ; do
        if ! netnsenter ip addr show dev $CONTAINER_IFNAME | grep -F $ADDR >/dev/null ; then
            # address is not there, leave the device alone
            continue
        fi
        netnsenter ip addr del $ADDR dev $CONTAINER_IFNAME || return 1
    done

    if [ -n "$(netnsenter ip -f inet addr show dev $CONTAINER_IFNAME)" ] ; then
        # other addresses are left, leave the device alone
        return 0
    fi

    # Deleting the interface will delete the multicast route we set up
    netnsenter ip link del $CONTAINER_IFNAME type veth
}

container_weave_addrs() {
    netnsenter ip addr show dev $CONTAINER_IFNAME
}

######################################################################
# functions for interacting with containers
######################################################################

# Check that a container named $1 with image $2 is not running
check_not_running() {
    case $(docker inspect --format='{{.State.Running}} {{.Config.Image}}' $1 2>/dev/null) in
        "true $2")
            echo "$1 is already running." >&2
            exit 1
            ;;
        "true $2:"*)
            echo "$1 is already running." >&2
            exit 1
            ;;
        "false $2")
            docker rm $1 >/dev/null
            ;;
        "false $2:"*)
            docker rm $1 >/dev/null
            ;;
        true*)
            echo "Found another running container named '$1'. Aborting." >&2
            exit 1
            ;;
        false*)
            echo "Found another container named '$1'. Aborting." >&2
            exit 1
            ;;
    esac
}

stop() {
    if ! docker stop $1 >/dev/null 2>&1 ; then
        echo "$2 is not running." >&2
    fi
    docker rm -f $1 >/dev/null 2>&1 || true
}

# Given a container name or short ID in $1, ensure the specified
# container is running and then print its full ID to stdout. If
# it's not running or doesn't exist, print an error to stderr and
# return with an indicative non-zero exit code.
container_id() {
    if ! status=$(docker inspect --format='{{.State.Running}} {{.Id}}' $1 2>/dev/null) ; then
        echo "Error: No such container: $1" >&2
        return 1
    fi
    case "$status" in
        true*)
            echo "${status#true }"
            ;;
        *)
            echo "$1 container is not running." >&2
            return 2
            ;;
    esac
}

container_ip() {
    if ! status=$(docker inspect --format='{{.State.Running}} {{.NetworkSettings.IPAddress}}' $1 2>/dev/null) ; then
        echo "$2" >&2
        return 1
    fi
    case "$status" in
        "true ")
            echo "$1 container has no IP address; is Docker networking enabled?" >&2
            return 1
            ;;
        true*)
            CONTAINER_IP="${status#true }"
            ;;
        *)
            echo "$3" >&2
            return 1
            ;;
    esac
}

http_call_ip() {
    ip="$1"
    port="$2"
    http_verb="$3"
    url="$4"
    shift 4
    curl --connect-timeout 3 -s -S -X $http_verb "$@" http://$ip:$port$url
}

# Call url $4 with http verb $3 on container $1 at port $2
http_call() {
    HTTP_CALL_CONTAINER=$1
    container_ip $HTTP_CALL_CONTAINER \
        "$HTTP_CALL_CONTAINER container is not present. Have you launched it?" \
        "$HTTP_CALL_CONTAINER container is not running." \
        || return 1
    shift 1
    if ! http_call_ip $CONTAINER_IP "$@" ; then
        echo "Call to $HTTP_CALL_CONTAINER failed." >&2
        return 1
    fi
}

call_weave() {
    http_call $CONTAINER_NAME $HTTP_PORT "$@"
}

death_msg() {
    echo "The $1 container has died. Consult the logs with 'docker logs $1' for further details."
}

# Wait until container $1 is alive enough to respond to "GET /status"
# http request on its docker-assigned IP, port $2
wait_for_status() {
    err_msg=$(death_msg $1)
    container_ip $1 "$err_msg" "$err_msg" || return 1
    while true ; do
        http_call_ip $CONTAINER_IP $2 GET /status >/dev/null 2>&1 && return 0
        if ! container_id $1 >/dev/null 2>&1 ; then
            echo "$err_msg" >&2
            return 1
        fi
        fractional_sleep 0.1
    done
}

# Wait until container $1 outputs a line containing $2 in its log
wait_for_log() {
    fifo=/tmp/tmpfifo.$$
    mkfifo $fifo || exit 1
    docker logs -f $1 >$fifo 2>&1 &
    res=0
    grep -q "$2" $fifo || res=1
    kill $! 2>/dev/null || true
    rm $fifo
    [ $res -eq 1 ] && echo "$(death_msg $1)" >&2
    return $res
}

# Call $1 for all containers, passing container ID, all MACs and all IPs
with_container_addresses() {
    COMMAND=$1
    shift 1
    for CONTAINER_ID in "$@" ; do
        if [ "$CONTAINER_ID" = "weave:expose" ] ; then
            ADDRS_CMD="ip addr show dev $BRIDGE"
        else
            ADDRS_CMD="with_container_netns $CONTAINER_ID container_weave_addrs"
        fi
        if CONTAINER_ADDRS=$($ADDRS_CMD 2>/dev/null) ; then
            CONTAINER_MAC=$(echo "$CONTAINER_ADDRS" | grep -o 'link/ether .*' | cut -d ' ' -f 2)
            CONTAINER_IPS=$(echo "$CONTAINER_ADDRS" | grep -o 'inet .*'       | cut -d ' ' -f 2)
            $COMMAND "$CONTAINER_ID" "$CONTAINER_MAC" "$CONTAINER_IPS"
        fi
    done
}

echo_addresses() {
    echo $1 $2 $3
}

echo_ips() {
    for CIDR in $3; do
        echo ${3%/*}
    done
}

peer_args() {
  res=''
  sep=''
  for p in "$@" ; do
    res="$res${sep}peer=$p"
    sep="&"
  done
  echo "$res"
}

######################################################################
# weaveDNS helpers
######################################################################

# Memoized function to query the weaveDNS server for our dns domain.
dns_domain() {
  DNS_DOMAIN=${DNS_DOMAIN:-$(call_weave GET /domain 2>/dev/null || true)}
  DNS_DOMAIN=${DNS_DOMAIN:-weave.local.}
  echo $DNS_DOMAIN
}

dns_args() {
    docker_bridge_ip
    NAME_ARG=""
    HOSTNAME_SPECIFIED=
    DNS_SEARCH_SPECIFIED=
    WITH_DNS=
    WITHOUT_DNS=
    while [ $# -gt 0 ] ; do
        case "$1" in
            --with-dns)
                WITH_DNS=1
                ;;
            --without-dns)
                WITHOUT_DNS=1
                ;;
            --name)
                NAME_ARG="$2"
                shift
                ;;
            --name=*)
                NAME_ARG="${1#*=}"
                ;;
            -h|--hostname|--hostname=*)
                HOSTNAME_SPECIFIED=1
                ;;
            --dns-search|--dns-search=*)
                DNS_SEARCH_SPECIFIED=1
                ;;
        esac
        shift
    done
    [ -n "$WITHOUT_DNS" ] && return 0
    DNS_ARGS="--dns $DOCKER_BRIDGE_IP"
    if [ -n "$NAME_ARG" -a -z "$HOSTNAME_SPECIFIED" ] ; then
        DOMAIN=$(dns_domain)
        HOSTNAME="$NAME_ARG.${DOMAIN%.}"
        if [ ${#HOSTNAME} -gt 64 ] ; then
            echo "Container name too long to be used as hostname" >&2
        else
            DNS_ARGS="$DNS_ARGS --hostname=$HOSTNAME"
            HOSTNAME_SPECIFIED=1
        fi
    fi
    if [ -z "$DNS_SEARCH_SPECIFIED" ] ; then
      if [ -z "$HOSTNAME_SPECIFIED" ] ; then
        DNS_ARGS="$DNS_ARGS --dns-search=$(dns_domain)"
      else
        DNS_ARGS="$DNS_ARGS --dns-search=."
      fi
    fi
}

# Print an error to stderr and return with an indicative exit status
# if the container $1 does not exist or isn't running.
check_running() {
    if ! STATUS=$(docker inspect --format='{{.State.Running}}' $1 2>/dev/null) ; then
        echo  "$1 container is not present. Have you launched it?" >&2
        return 1
    elif [ "$STATUS" != "true" ] ; then
        echo "$1 container is not running." >&2
        return 2
    fi
}

# Execute $@ only if the weave container is running
when_weave_running() {
    if check_running $CONTAINER_NAME 2>/dev/null ; then
        "$@"
    fi
}

# Iff the container in $1 has an FQDN, invoke $2 as a command passing
# the container as the first argument, the FQDN as the second argument
# and $3.. as additional arguments
with_container_fqdn() {
    CONTAINER="$1"
    COMMAND="$2"
    shift 2

    CONTAINER_FQDN=$(docker inspect --format='{{.Config.Hostname}}.{{.Config.Domainname}}' $CONTAINER 2>/dev/null) || return 0

    if echo "$CONTAINER_FQDN" | grep -Eq '[^.]+\..+' ; then
        $COMMAND "$CONTAINER" "$CONTAINER_FQDN" "$@"
    fi
}

# Register FQDN in $2 as names for addresses $3.. under full container ID $1
put_dns_fqdn() {
    CHECK_ALIVE="-d check-alive=true"
    if [ "$1" = "--no-check-alive" ] ; then
        CHECK_ALIVE=
        shift 1
    fi
    CONTAINER_ID="$1"
    FQDN="$2"
    shift 2

    for ADDR in "$@" ; do
        call_weave PUT /name/$CONTAINER_ID/${ADDR%/*} --data-urlencode fqdn=$FQDN $CHECK_ALIVE || true
    done
}

# Delete all names for addresses $3.. under full container ID $1
delete_dns() {
    CONTAINER_ID="$1"
    shift 1

    for ADDR in "$@" ; do
        call_weave DELETE /name/$CONTAINER_ID/${ADDR%/*} || true
    done
}

# Delete any FQDNs $2 from addresses $3.. under full container ID $1
delete_dns_fqdn() {
    CONTAINER_ID="$1"
    FQDN="$2"
    shift 2

    for ADDR in "$@" ; do
        call_weave DELETE /name/$CONTAINER_ID/${ADDR%/*}?fqdn=$FQDN || true
    done
}

######################################################################
# IP Allocation Management helpers
######################################################################

# Claim addresses for a container in IPAM.  Expects to be called from
# with_container_addresses.
ipam_reclaim() {
    for CIDR in $3 ; do
        # NB: CONTAINER_IP is the IP of the weave container; it is set by wait_for_status.
        http_call_ip $CONTAINER_IP $HTTP_PORT PUT /ip/$1/${CIDR%/*}?noErrorOnUnknown=true
    done
}

# Call IPAM as necessary to allocate addresses
# $1 is the full container id and following args are previously parsed CIDR_ARGS.
# Returns ALL_CIDRS and IPAM_CIDRS
ipam_cidrs() {
    CHECK_ALIVE="?check-alive=true"
    if [ "$1" = "--no-check-alive" ] ; then
        CHECK_ALIVE=
        shift 1
    fi
    CONTAINER_ID="$1"
    shift 1
    ALL_CIDRS=""
    IPAM_CIDRs=""
    # If no addresses passed in, get one in the default subnet
    [ $# -gt 0 ] || set -- net:default
    for arg in "$@" ; do
        if [ "${arg%:*}" = "net" ] ; then
            if [ "$arg" = "net:default" ] ; then
                IPAM_URL=/ip/$CONTAINER_ID
            else
                IPAM_URL=/ip/$CONTAINER_ID/"${arg#net:}"
            fi
            CIDR=$(call_weave POST $IPAM_URL$CHECK_ALIVE) || return 1
            if [ "$CIDR" = "404 page not found" ] ; then
                echo "IP address allocation must be enabled to use 'net:'" >&2
                return 1
            elif ! is_cidr "$CIDR" ; then
                echo "$CIDR" >&2
                return 1
            fi
            IPAM_CIDRS="$IPAM_CIDRS $CIDR"
            ALL_CIDRS="$ALL_CIDRS $CIDR"
        else
            # This is a plain IP address; warn if it clashes but carry on
            command_exists netcheck && netcheck --ignore-iface=$BRIDGE $arg || true
            if container_ip $CONTAINER_NAME 2>/dev/null ; then
                http_call_ip $CONTAINER_IP $HTTP_PORT PUT /ip/$CONTAINER_ID/${arg%/*} >&2
            fi
            ALL_CIDRS="$ALL_CIDRS $arg"
        fi
    done
}

ipam_cidrs_or_die() {
    if ! ipam_cidrs "$@" ; then
        docker kill $1 >/dev/null 2>&1 || true
        exit 1
    fi
}

show_addrs() {
    addrs=
    for cidr in "$@" ; do
        addrs="$addrs ${cidr%/*}"
    done
    echo $addrs
}

######################################################################
# weave proxy helpers
######################################################################

docker_client_args() {
    CLIENT_TLS_ENABLED=""
    [ -z "$DOCKER_CLIENT_TLS_VERIFY" ] || CLIENT_TLS_ENABLED=1
    while [ $# -gt 0 ]; do
        case "$1" in
          -H|--host)
            DOCKER_CLIENT_HOST="$2"
            shift
            ;;
          -H=*|--host=*)
            DOCKER_CLIENT_HOST="${1#*=}"
            ;;
          -tls|--tls|-tlsverify|--tlsverify)
            CLIENT_TLS_ENABLED=1
            ;;
        esac
        shift
    done
}

# TODO: Handle relative paths for args
# TODO: Handle args with spaces
tls_arg() {
    PROXY_VOLUMES="$PROXY_VOLUMES -v $2:/home/weave/tls/$3.pem:ro"
    PROXY_ARGS="$PROXY_ARGS $1 /home/weave/tls/$3.pem"
}

proxy_args() {
    PROXY_VOLUMES=""
    PROXY_ARGS=""
    PROXY_TLS_ENABLED=""
    PROXY_HOST=""
    while [ $# -gt 0 ]; do
        case "$1" in
          -H)
            PROXY_HOST="$2"
            PROXY_ARGS="$PROXY_ARGS $1 $2"
            shift
            ;;
          -H=*)
            PROXY_HOST="${1#*=}"
            PROXY_ARGS="$PROXY_ARGS $1"
            ;;
          -tls|--tls|-tlsverify|--tlsverify)
            PROXY_TLS_ENABLED=1
            PROXY_ARGS="$PROXY_ARGS $1"
            ;;
          --tlscacert)
            tls_arg "$1" "$2" ca
            shift
            ;;
          --tlscacert=*)
            tls_arg "${1%%=*}" "${1#*=}" ca
            ;;
          --tlscert)
            tls_arg "$1" "$2" cert
            shift
            ;;
          --tlscert=*)
            tls_arg "${1%%=*}" "${1#*=}" cert
            ;;
          --tlskey)
            tls_arg "$1" "$2" key
            shift
            ;;
          --tlskey=*)
            tls_arg "${1%%=*}" "${1#*=}" key
            ;;
          *)
            PROXY_ARGS="$PROXY_ARGS $1"
            ;;
        esac
        shift
    done

    if [ -z "$PROXY_HOST" -a -n "$CLIENT_TLS_ENABLED" -a -z "$PROXY_TLS_ENABLED" ] ; then
      echo "When launching the proxy via TLS, -H and/or TLS options are required." >&2
      exit 1
    fi
    if [ -z "$PROXY_HOST" ] ; then
      case "$DOCKER_CLIENT_HOST" in
        ""|unix://*)
          PROXY_HOST="unix:///var/run/weave/weave.sock"
          ;;
        *)
          PROXY_HOST="tcp://0.0.0.0:12375"
          ;;
      esac
      PROXY_ARGS="$PROXY_ARGS -H $PROXY_HOST"
    fi
}

proxy_addr() {
    if addr=$(docker logs $PROXY_CONTAINER_NAME 2>&1 | head -n4 | grep -oE "proxy listening on .*") ; then
      echo "${addr##* }" | sed "s/0.0.0.0/$PROXY_HOST/g"
    else
      echo "$PROXY_CONTAINER_NAME container is not present. Have you launched it?" >&2
      return 1
    fi
}

warn_if_stopping_proxy_in_env() {
    if PROXY_ADDR=$(proxy_addr 2>/dev/null) ; then
        [ "$PROXY_ADDR" != "$DOCKER_CLIENT_HOST" ] || echo "WARNING: It appears that your environment is configured to use the Weave Docker API proxy. Stopping it will break this and subsequent docker invocations. To restore your environment, run 'eval \$(weave env --restore)'."
    fi
}

######################################################################
# launch helpers
######################################################################

common_launch_args() {
    args=""
    while [ $# -gt 0 ] ; do
        case "$1" in
            --log-level)
                [ $# -gt 1 ] || usage
                args="$args $1 $2"
                shift
                ;;
            --log-level=*)
                args="$args $1"
                ;;
        esac
        shift
    done
    echo "$args"
}

launch_router() {
    check_forwarding_rules
    enforce_docker_bridge_addr_assign_type
    create_bridge
    docker_bridge_ip
    # 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.
    PEERNAME=$(cat /sys/class/net/$BRIDGE/address)
    # backward compatibility...
    if is_cidr "$1" ; then
        echo "WARNING: $1 parameter ignored; 'weave launch' no longer takes a CIDR as the first parameter" >&2
        shift 1
    fi
    CONTAINER_PORT=$PORT
    ARGS=""
    IPRANGE=
    IPRANGE_SPECIFIED=
    DNS_PORT_MAPPING="-p $DOCKER_BRIDGE_IP:53:53/udp -p $DOCKER_BRIDGE_IP:53:53/tcp"
    while [ $# -gt 0 ] ; do
        case "$1" in
            -password|--password)
                [ $# -gt 1 ] || usage
                WEAVE_PASSWORD="$2"
                export WEAVE_PASSWORD
                shift
                ;;
            --password=*)
                WEAVE_PASSWORD="${1#*=}"
                export WEAVE_PASSWORD
                ;;
            -port|--port)
                [ $# -gt 1 ] || usage
                CONTAINER_PORT="$2"
                shift
                ;;
            --port=*)
                CONTAINER_PORT="${1#*=}"
                ;;
            -iprange|--iprange|--ipalloc-range)
                [ $# -gt 1 ] || usage
                IPRANGE="$2"
                IPRANGE_SPECIFIED=1
                shift
                ;;
            --ipalloc-range=*)
                IPRANGE="${1#*=}"
                IPRANGE_SPECIFIED=1
                ;;
            --no-dns)
                DNS_PORT_MAPPING=
                ARGS="$ARGS $1"
                ;;
            *)
                ARGS="$ARGS '$(echo "$1" | sed "s|'|'\"'\"'|g")'"
                ;;
        esac
        shift
    done
    eval "set -- $ARGS"
    if [ -z "$IPRANGE_SPECIFIED" ] ; then
        IPRANGE="10.32.0.0/12"
        if command_exists netcheck && ! netcheck --ignore-iface=$BRIDGE $IPRANGE ; then
                echo "ERROR: Default --ipalloc-range $IPRANGE overlaps with existing route on host." >&2
                echo "You must pick another range and set it on all hosts." >&2
            exit 1
            fi
        else
            if [ -n "$IPRANGE" ] && command_exists netcheck && ! netcheck --ignore-iface=$BRIDGE $IPRANGE ; then
                echo "WARNING: Specified --ipalloc-range $IPRANGE overlaps with existing route on host." >&2
                echo "Unless this is deliberate, you must pick another range and set it on all hosts." >&2
        fi
    fi
    # Set WEAVE_DOCKER_ARGS in the environment in order to supply
    # additional parameters, such as resource limits, to docker
    # when launching the weave container.
    ROUTER_CONTAINER=$(docker run --privileged -d --name=$CONTAINER_NAME \
        -p $PORT:$CONTAINER_PORT/tcp -p $PORT:$CONTAINER_PORT/udp $DNS_PORT_MAPPING \
        -v /var/run/docker.sock:/var/run/docker.sock \
        -e WEAVE_PASSWORD \
        -e WEAVE_CIDR=none \
        $WEAVE_DOCKER_ARGS $IMAGE $COVERAGE_ARGS --iface $CONTAINER_IFNAME --port $CONTAINER_PORT --name "$PEERNAME" --nickname "$(hostname)" --ipalloc-range "$IPRANGE" --dns-effective-listen-address "$DOCKER_BRIDGE_IP" "$@")
    with_container_netns_or_die $ROUTER_CONTAINER launch
    wait_for_status $CONTAINER_NAME $HTTP_PORT
    if [ -n "$IPRANGE" ] ; then
        # Tell the newly-started weave IP allocator about existing weave IPs
        with_container_addresses ipam_reclaim weave:expose $(docker ps -q --no-trunc)
    fi
    if [ -n "$DNS_PORT_MAPPING" ] ; then
        # Tell the newly-started weaveDNS about existing weave IPs
        for CONTAINER in $(docker ps -q --no-trunc) ; do
            if CONTAINER_IPS=$(with_container_netns $CONTAINER container_weave_addrs 2>&1 | sed -n -e 's/inet \([^/]*\)\/\(.*\)/\1/p') ; then
                with_container_fqdn $CONTAINER put_dns_fqdn $CONTAINER_IPS
            fi
        done
    fi
}

launch_proxy() {
    # Set WEAVEPROXY_DOCKER_ARGS in the environment in order to supply
    # additional parameters, such as resource limits, to docker
    # when launching the weaveproxy container.
    docker_client_args $DOCKER_CLIENT_ARGS
    proxy_args "$@"
    HOSTS_PATH=$(docker run --rm \
      -v /var/run/docker.sock:/var/run/docker.sock \
      --entrypoint /bin/sh \
      $EXEC_IMAGE -c 'docker inspect -f {{.HostsPath}} $(hostname)')
    CONTAINERS_PATH="$(dirname $(dirname "$HOSTS_PATH"))"
    PROXY_CONTAINER=$(docker run --privileged -d --name=$PROXY_CONTAINER_NAME --net=host \
        $PROXY_VOLUMES \
        -v "$CONTAINERS_PATH":"$CONTAINERS_PATH" \
        -v /var/run/docker.sock:/var/run/docker.sock \
        -v /var/run/weave:/var/run/weave \
        -v /proc:/hostproc \
        -e PROCFS=/hostproc \
        -e WEAVE_CIDR=none \
        -e DOCKER_BRIDGE \
        --entrypoint=/home/weave/weaveproxy \
        $WEAVEPROXY_DOCKER_ARGS $EXEC_IMAGE $COVERAGE_ARGS $PROXY_ARGS)
    wait_for_log $PROXY_CONTAINER_NAME "proxy listening"
    # backward compatibility
    ln -sf -T /var/run/weave/weave.sock /var/run/weave.sock
}

##########################################################################################

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

uname -s -r | sed -n -e 's|^\([^ ]*\) \([0-9][0-9]*\)\.\([0-9][0-9]*\).*|\1 \2 \3|p' | {
    if ! read sys maj min ; then
        echo "ERROR: Unable to parse operating system version $(uname -s -r)" >&2
        exit 1
    fi

    if [ "$sys" != 'Linux' ] ; then
        echo "ERROR: Operating systems other than Linux are not supported (you have $(uname -s -r))" >&2
        exit 1
    fi

    if ! [ \( "$maj" -eq 3 -a "$min" -ge 8 \) -o "$maj" -gt 3 ] ; then
        echo "WARNING: Linux kernel version 3.8 or newer is required (you have ${maj}.${min})" >&2
    fi
}

if ! command_exists ip ; then
    echo "ERROR: ip utility is missing. Please install it." >&2
    exit 1
fi

if ! ip netns list >/dev/null 2>&1 ; then
    echo "ERROR: $(ip -V) does not support network namespaces." >&2
    echo "       Please install iproute2-ss111010 or later." >&2
    exit 1
fi

if ! command_exists nsenter ; then
    echo "ERROR: nsenter utility missing. Please install it." >&2
    exit 1
fi

deprecation_warning() {
    echo "Warning: ${1%=*} is deprecated; please use $2" >&2
}

deprecation_warnings() {
    while [ $# -gt 0 ]; do
        case "$1" in
            -password|-password=*)
                deprecation_warning $1 "--password"
                [ "$1" == "-password" ] && shift
                ;;
            --password)
                shift
                ;;
            -nickname|-nickname=*)
                deprecation_warning $1 "--nickname"
                [ "$1" == "-nickname" ] && shift
                ;;
            --nickname)
                shift
                ;;
            -nodiscovery|--nodiscovery)
                deprecation_warning $1 "--no-discovery"
                ;;
            -iprange|--iprange|-iprange=*|--iprange=*)
                deprecation_warning $1 "--ipalloc-range"
                [ ${1#--} == "iprange" ] && shift
                ;;
            --ipalloc-range)
                shift
                ;;
            -ipsubnet|--ipsubnet|-ipsubnet=*|--ipsubnet=*)
                deprecation_warning $1 "--ipalloc-default-subnet"
                [ ${1#--} == "ipsubnet" ] && shift
                ;;
            --ipalloc-default-subnet)
                shift
                ;;
            -initpeercount|--initpeercount|-initpeercount=*|--initpeercount=*)
                deprecation_warning $1 "--init-peer-count"
                [ ${1#--} == "initpeercount" ] && shift
                ;;
            --init-peer-count)
                shift
                ;;
            -no-default-ipam|--no-default-ipam)
                deprecation_warning $1 "--no-default-ipalloc"
                ;;
        esac
        shift
    done
}

[ $# -gt 0 ] || usage
COMMAND=$1
shift 1

case "$COMMAND" in
    setup)
        for img in $IMAGE $EXEC_IMAGE ; do
            docker pull $img
        done
        ;;
    version)
        [ $# -eq 0 ] || usage
        echo weave script $SCRIPT_VERSION
        ask_version $CONTAINER_NAME $IMAGE || true
        if ! EXEC_IMAGE_ID=$(docker inspect --format='{{.Id}}' $EXEC_IMAGE 2>/dev/null) ; then
            echo "Unable to find $EXEC_IMAGE image." >&2
        else
            EXEC_VERSION=$(docker images --no-trunc | grep $EXEC_IMAGE_ID | grep -v latest | tr -s ' ' | cut -d ' ' -f 2 | tr "\n" ' ')
            if [ -n "$EXEC_VERSION" ] ; then
                echo "weave exec $EXEC_VERSION"
            else
                echo "weave exec (unreleased version)"
            fi
        fi
        ;;
    # intentionally undocumented since it assumes knowledge of weave
    # internals
    create-bridge)
        create_bridge --without-ethtool
        ;;
    launch)
        deprecation_warnings "$@"
        check_not_running $CONTAINER_NAME       $BASE_IMAGE
        check_not_running $PROXY_CONTAINER_NAME $BASE_EXEC_IMAGE
        COMMON_ARGS=$(common_launch_args "$@")
        launch_router "$@"
        launch_proxy  $COMMON_ARGS
        ;;
    launch-router)
        deprecation_warnings "$@"
        check_not_running $CONTAINER_NAME $BASE_IMAGE
        launch_router "$@"
        echo $ROUTER_CONTAINER
        ;;
    launch-proxy)
        deprecation_warnings "$@"
        check_not_running $PROXY_CONTAINER_NAME $BASE_EXEC_IMAGE
        launch_proxy "$@"
        echo $PROXY_CONTAINER
        ;;
    env|proxy-env)
        [ "$COMMAND" = "env" ] || deprecation_warning "$COMMAND" "'weave env'"
        if PROXY_ADDR=$(proxy_addr) ; then
            echo "export DOCKER_HOST=$PROXY_ADDR ORIG_DOCKER_HOST=$DOCKER_CLIENT_HOST"
        fi
        ;;
    config|proxy-config)
        [ "$COMMAND" = "config" ] || deprecation_warning "$COMMAND" "'weave config'"
        if PROXY_addr=$(proxy_addr) ; then
            echo "-H=$PROXY_addr"
        fi
        ;;
    connect)
        [ $# -gt 0 ] || usage
        if [ "$1" == "--replace" ] ; then
          replace="-d replace=true"
          shift
        fi
        call_weave POST /connect $replace -d $(peer_args "$@")
        ;;
    forget)
        [ $# -gt 0 ] || usage
        call_weave POST /forget -d $(peer_args "$@")
        ;;
    status)
        res=0
        SUB_STATUS=
        STATUS_URL="/status"
        SUB_COMMAND="$@"
        while [ $# -gt 0 ] ; do
            SUB_STATUS=1
            STATUS_URL="$STATUS_URL/$1"
            shift
        done
        [ -n "$SUB_STATUS" ] || echo
        if status=$(call_weave GET $STATUS_URL) ; then
            case "$status" in
                "")
                    ;;
                "404 page not found")
                    echo "Invalid 'weave status' sub-command: $SUB_COMMAND" >&2
                    usage
                    ;;
                *)
                    echo "$status"
                    ;;
            esac
        else
            res=1
        fi
        if [ -z "$SUB_STATUS" ] && check_running $PROXY_CONTAINER_NAME 2>/dev/null && PROXY_ADDR=$(proxy_addr) ; then
            echo
            echo "       Service: proxy"
            echo "       Address: $PROXY_ADDR"
        fi
        [ -n "$SUB_STATUS" ] || echo
        [ $res -eq 0 ]
        ;;
    report)
        if [ $# -gt 0 ] ; then
            [ $# -eq 2 -a "$1" = "-f" ] || usage
            call_weave GET /report --get --data-urlencode "format=$2"
        else
            call_weave GET /report -H 'Accept: application/json'
        fi
        ;;
    run)
        dns_args "$@"
        shift $(dns_arg_count "$@")
        collect_cidr_args "$@"
        shift $CIDR_ARG_COUNT
        CONTAINER=$(docker run -e WEAVE_CIDR=none $DNS_ARGS -d "$@")
        create_bridge
        ipam_cidrs_or_die $CONTAINER $CIDR_ARGS
        with_container_netns_or_die $CONTAINER attach $ALL_CIDRS
        when_weave_running with_container_fqdn $CONTAINER put_dns_fqdn $ALL_CIDRS
        echo $CONTAINER
        ;;
    dns-args)
        dns_args "$@"
        echo -n $DNS_ARGS
        ;;
    docker-bridge-ip)
        docker_bridge_ip
        echo -n $DOCKER_BRIDGE_IP
        ;;
    start)
        collect_cidr_args "$@"
        shift $CIDR_ARG_COUNT
        [ $# -eq 1 ] || usage
        RES=$(docker start $1)
        CONTAINER=$(container_id $1)
        create_bridge
        ipam_cidrs_or_die $CONTAINER $CIDR_ARGS
        with_container_netns_or_die $CONTAINER attach $ALL_CIDRS
        when_weave_running with_container_fqdn $CONTAINER put_dns_fqdn $ALL_CIDRS
        echo $RES
        ;;
    attach)
        collect_cidr_args "$@"
        shift $CIDR_ARG_COUNT
        while [ $# -gt 0 ]; do
            case "$1" in
                --or-die)
                    ATTACH_TYPE="_or_die"
                    ;;
                --rewrite-hosts)
                    REWRITE_HOSTS=1
                    ;;
                *)
                    break
                    ;;
            esac
            shift
        done
        [ $# -eq 1 ] || usage
        CONTAINER=$(container_id $1)
        create_bridge
        ipam_cidrs$ATTACH_TYPE $CONTAINER $CIDR_ARGS
        if [ -n "$REWRITE_HOSTS" ] && command_exists weavehosts ; then
            PATH_AND_NAME=$(docker inspect -f '{{.HostsPath}} {{.Config.Hostname}}' $CONTAINER)
            weavehosts $PATH_AND_NAME $ALL_CIDRS
        fi
        with_container_netns$ATTACH_TYPE $CONTAINER attach $ALL_CIDRS >/dev/null
        when_weave_running with_container_fqdn $CONTAINER put_dns_fqdn $ALL_CIDRS
        show_addrs $ALL_CIDRS
        ;;
    detach)
        collect_cidr_args "$@"
        shift $CIDR_ARG_COUNT
        [ $# -eq 1 ] || usage
        CONTAINER=$(container_id $1)
        ipam_cidrs $CONTAINER $CIDR_ARGS
        with_container_netns $CONTAINER detach $ALL_CIDRS >/dev/null
        when_weave_running with_container_fqdn $CONTAINER delete_dns_fqdn $ALL_CIDRS
        for CIDR in $IPAM_CIDRS ; do
            call_weave DELETE /ip/$CONTAINER/${CIDR%/*}
        done
        show_addrs $ALL_CIDRS
        ;;
    dns-add)
        collect_ip_args "$@"
        shift $IP_COUNT
        [ $# -eq 1 -o \( $# -eq 3 -a "$2" = "-h" \) ] || usage
        check_running $CONTAINER_NAME
        CONTAINER=$(container_id $1)
        if [ $IP_COUNT -eq 0 ] ; then
            IP_ARGS=$(with_container_addresses echo_ips $CONTAINER)
        fi
        if [ $# -eq 1 ] ; then
            with_container_fqdn $CONTAINER put_dns_fqdn $IP_ARGS
        else
            put_dns_fqdn $CONTAINER "$3" $IP_ARGS
        fi
        ;;
    dns-remove)
        collect_ip_args "$@"
        shift $IP_COUNT
        [ $# -eq 1 -o \( $# -eq 3 -a "$2" = "-h" \) ] || usage
        check_running $CONTAINER_NAME
        CONTAINER=$(container_id $1)
        if [ $IP_COUNT -eq 0 ] ; then
            IP_ARGS=$(with_container_addresses echo_ips $CONTAINER)
        fi
        if [ $# -eq 1 ] ; then
            delete_dns $CONTAINER $IP_ARGS
        else
            delete_dns_fqdn $CONTAINER "$3" $IP_ARGS
        fi
        ;;
    dns-lookup)
        [ $# -eq 1 ] || usage
        docker_bridge_ip
        dig @$DOCKER_BRIDGE_IP +short $1
        ;;
    expose)
        collect_cidr_args "$@"
        shift $CIDR_ARG_COUNT
        ipam_cidrs --no-check-alive weave:expose $CIDR_ARGS
        if [ $# -eq 0 ] ; then
            FQDN=""
        else
            [ $# -eq 2 -a "$1" = "-h" ] || usage
            FQDN="$2"
        fi
        create_bridge --without-ethtool
        for CIDR in $ALL_CIDRS ; do
            if ! ip addr show dev $BRIDGE | grep -qF $CIDR ; then
                ip addr add dev $BRIDGE $CIDR
                arp_update $BRIDGE $CIDR
                add_iptables_rule nat WEAVE -d $CIDR ! -s $CIDR -j MASQUERADE
                add_iptables_rule nat WEAVE -s $CIDR ! -d $CIDR -j MASQUERADE
                if [ -n "$FQDN" ] ; then
                    when_weave_running put_dns_fqdn --no-check-alive weave:expose $FQDN $CIDR
                fi
            fi
        done
        show_addrs $ALL_CIDRS
        ;;
    hide)
        collect_cidr_args "$@"
        shift $CIDR_ARG_COUNT
        ipam_cidrs --no-check-alive weave:expose $CIDR_ARGS
        create_bridge --without-ethtool
        for CIDR in $ALL_CIDRS ; do
            if ip addr show dev $BRIDGE | grep -qF $CIDR ; then
                ip addr del dev $BRIDGE $CIDR
                delete_iptables_rule nat WEAVE -d $CIDR ! -s $CIDR -j MASQUERADE
                delete_iptables_rule nat WEAVE -s $CIDR ! -d $CIDR -j MASQUERADE
                when_weave_running delete_dns weave:expose $CIDR
            fi
        done
        for CIDR in $IPAM_CIDRS ; do
            call_weave DELETE /ip/weave:expose/${CIDR%/*}
        done
        show_addrs $ALL_CIDRS
        ;;
    ps)
        [ $# -eq 0 ] && CONTAINERS="weave:expose $(docker ps -q)" || CONTAINERS="$@"
        with_container_addresses echo_addresses $CONTAINERS
        ;;
    stop)
        [ $# -eq 0 ] || usage
        warn_if_stopping_proxy_in_env
        stop $PROXY_CONTAINER_NAME "Proxy"
        stop $CONTAINER_NAME       "Weave"
        conntrack -D -p udp --dport $PORT >/dev/null 2>&1 || true
        ;;
    stop-router)
        [ $# -eq 0 ] || usage
        stop $CONTAINER_NAME "Weave"
        conntrack -D -p udp --dport $PORT >/dev/null 2>&1 || true
        ;;
    stop-proxy)
        [ $# -eq 0 ] || usage
        warn_if_stopping_proxy_in_env
        stop $PROXY_CONTAINER_NAME "Proxy"
        ;;
    reset)
        [ $# -eq 0 ] || usage
        warn_if_stopping_proxy_in_env
        call_weave DELETE /peer >/dev/null 2>&1 || true
        for NAME in $CONTAINER_NAME $PROXY_CONTAINER_NAME ; do
            docker stop  $NAME >/dev/null 2>&1 || true
            docker rm -f $NAME >/dev/null 2>&1 || true
        done
        conntrack -D -p udp --dport $PORT >/dev/null 2>&1 || true
        destroy_bridge
        for LOCAL_IFNAME in $(ip link show | grep v${CONTAINER_IFNAME}pl | cut -d ' ' -f 2 | tr -d ':') ; do
            ip link del ${LOCAL_IFNAME%@*} >/dev/null 2>&1 || true
        done
        ;;
    rmpeer)
        [ $# -eq 1 ] || usage
        PEER=$1
        call_weave DELETE /peer/$PEER
        ;;
    launch-dns)
        echo "The 'launch-dns' command has been removed; DNS is launched as part of 'launch' and 'launch-router'." >&2
        exit 0
        ;;
    stop-dns)
        echo "The 'stop-dns command has been removed; DNS is stopped as part of 'stop' and 'stop-router'." >&2
        exit 0
        ;;
    *)
        echo "Unknown weave command '$COMMAND'" >&2
        usage
        ;;
esac
