#!/bin/sh
set -e

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

SCRIPT_VERSION="unreleased"
IMAGE_VERSION=latest
[ "$SCRIPT_VERSION" = "unreleased" ] || IMAGE_VERSION=$SCRIPT_VERSION
IMAGE_VERSION=${WEAVE_VERSION:-$IMAGE_VERSION}

# - Docker versions prior to 1.6 cannot pull images from DockerHub
# - The weavexec image embeds a Docker 1.6.2 client. Docker will give
#   a "client is newer than server error" if the daemon has an older
#   API version, which could be confusing if the user's Docker client
#   is correctly matched with that older version.
#
# We therefore check that the user's Docker *client* is >= 1.6
MIN_DOCKER_VERSION=1.6.0

# 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
WEAVEDB_IMAGE=$DOCKERHUB_USER/weavedb
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}

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

######################################################################
# helpers that run locally, even without --local
######################################################################

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

weave --help | help
      setup
      version

weave launch        <same arguments as 'weave launch-router'>
      launch-router [--password <pass>] [--trusted-subnets <cidr>,...]
                      [--host <ip_address>]
                      [--name <mac>] [--nickname <nickname>]
                      [--no-restart] [--resume] [--no-discovery] [--no-dns]
                      [--ipalloc-init <mode>]
                      [--ipalloc-range <cidr> [--ipalloc-default-subnet <cidr>]]
                      [--log-level=debug|info|warning|error]
                      <peer> ...
      launch-proxy  [-H <endpoint>] [--without-dns] [--no-multicast-route]
                      [--no-rewrite-hosts] [--no-default-ipalloc] [--no-restart]
                      [--hostname-from-label <labelkey>]
                      [--hostname-match <regexp>]
                      [--hostname-replacement <replacement>]
                      [--rewrite-inspect]
                      [--log-level=debug|info|warning|error]
      launch-plugin [--no-restart] [--no-multicast-route]
                      [--log-level=debug|info|warning|error]

weave prime

weave env           [--restore]
      config
      dns-args

weave connect       [--replace] [<peer> ...]
      forget        <peer> ...

weave run           [--without-dns] [--no-rewrite-hosts] [--no-multicast-route]
                      [<addr> ...] <docker run args> ...
      start         [<addr> ...] <container_id>
      attach        [<addr> ...] <container_id>
      detach        [<addr> ...] <container_id>
      restart       <container_id>

weave expose        [<addr> ...] [-h <fqdn>]
      hide          [<addr> ...]

weave dns-add       [<ip_address> ...] <container_id> [-h <fqdn>] |
                    <ip_address> ... -h <fqdn>
      dns-remove    [<ip_address> ...] <container_id> [-h <fqdn>] |
                    <ip_address> ... -h <fqdn>
      dns-lookup    <unqualified_name>

weave status        [targets | connections | peers | dns | ipam]
      report        [-f <format>]
      ps            [<container_id> ...]

weave stop
      stop-router
      stop-proxy
      stop-plugin

weave reset         [--force]
      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> | <weave internal peer ID>
      <mode>     = consensus[=<count>] | seed=<mac>,... | observer
EOF
}

usage() {
    usage_no_exit
    exit 1
}

handle_help_arg() {
    if [ "$1" = "--help" ] ; then
        usage_no_exit
        exit 0
    fi
}

docker_sock_options() {
    # Pass through DOCKER_HOST if it is a Unix socket;
    # a TCP socket may be secured by TLS, in which case we can't use it
    if echo "$DOCKER_HOST" | grep -q "^unix://" >/dev/null; then
        echo "-v ${DOCKER_HOST#unix://}:${DOCKER_HOST#unix://} -e DOCKER_HOST"
    else
        echo "-v /var/run/docker.sock:/var/run/docker.sock"
    fi
}

docker_run_options() {
    echo --privileged --net=host $(docker_sock_options)
}

exec_options() {
    case "$1" in
        setup|setup-cni|launch|launch-router)
            echo -v /:/host -e HOST_ROOT=/host
            ;;
    esac
}

exec_remote() {
    docker $DOCKER_CLIENT_ARGS run --rm \
        $(docker_run_options) \
        --pid=host \
        $(exec_options "$@") \
        -e DOCKERHUB_USER="$DOCKERHUB_USER" \
        -e WEAVE_VERSION \
        -e WEAVE_DEBUG \
        -e WEAVE_DOCKER_ARGS \
        -e WEAVEPROXY_DOCKER_ARGS \
        -e WEAVEPLUGIN_DOCKER_ARGS \
        -e WEAVE_PASSWORD \
        -e WEAVE_PORT \
        -e WEAVE_HTTP_ADDR \
        -e WEAVE_STATUS_ADDR \
        -e WEAVE_CONTAINER_NAME \
        -e WEAVE_MTU \
        -e WEAVE_NO_FASTDP \
        -e WEAVE_NO_BRIDGED_FASTDP \
        -e WEAVE_NO_PLUGIN \
        -e DOCKER_BRIDGE \
        -e DOCKER_CLIENT_HOST="$DOCKER_CLIENT_HOST" \
        -e DOCKER_CLIENT_ARGS \
        -e PROXY_HOST="$PROXY_HOST" \
        -e COVERAGE \
        -e CHECKPOINT_DISABLE \
        -e AWSVPC   \
        $WEAVEEXEC_DOCKER_ARGS $EXEC_IMAGE --local "$@"
}

# Given $1 and $2 as semantic version numbers like 3.1.2, return [ $1 < $2 ]
version_lt() {
    VERSION_MAJOR=${1%.*.*}
    REST=${1%.*} VERSION_MINOR=${REST#*.}
    VERSION_PATCH=${1#*.*.}

    MIN_VERSION_MAJOR=${2%.*.*}
    REST=${2%.*} MIN_VERSION_MINOR=${REST#*.}
    MIN_VERSION_PATCH=${2#*.*.}

    if [ \( "$VERSION_MAJOR" -lt "$MIN_VERSION_MAJOR" \) -o \
        \( "$VERSION_MAJOR" -eq "$MIN_VERSION_MAJOR" -a \
        \( "$VERSION_MINOR" -lt "$MIN_VERSION_MINOR" -o \
        \( "$VERSION_MINOR" -eq "$MIN_VERSION_MINOR" -a \
        \( "$VERSION_PATCH" -lt "$MIN_VERSION_PATCH" \) \) \) \) ] ; then
        return 0
    fi
    return 1
}

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

    if version_lt $DOCKER_VERSION $MIN_DOCKER_VERSION ; then
        echo "ERROR: weave requires Docker version $MIN_DOCKER_VERSION or later; you are running $DOCKER_VERSION" >&2
        exit 1
    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
}

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

extra_hosts_args() {
    DNS_EXTRA_HOSTS=
    DNS_EXTRA_HOSTS_ARGS=
    while [ $# -gt 0 ] ; do
        case "$1" in
            --add-host)
                DNS_EXTRA_HOSTS="$2 $DNS_EXTRA_HOSTS"
                DNS_EXTRA_HOSTS_ARGS="--add-host=$2 $DNS_EXTRA_HOSTS_ARGS"
                shift
                ;;
            --add-host=*)
                DNS_EXTRA_HOSTS="${1#*=} $DNS_EXTRA_HOSTS"
                DNS_EXTRA_HOSTS_ARGS="--add-host=${1#*=} $DNS_EXTRA_HOSTS_ARGS"
                ;;
        esac
        shift
    done
}

kill_container() {
    docker kill $1 >/dev/null 2>&1 || true
}

######################################################################
# main
######################################################################

check_docker_version

[ "$1" = "--local" ] && shift 1 && IS_LOCAL=1

# "--help|help" are special because we always want to process them
# at the client end.
handle_help_arg "$1" || handle_help_arg "--$1"

if [ "$1" = "version" -a -z "$IS_LOCAL" ] ; then
    # non-local "version" is special because we want to show the
    # version of the script executed by the user rather than what is
    # embedded in weaveexec.
    echo "weave script $SCRIPT_VERSION"
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

    handle_help_arg "$1"
    [ "$1" = "--without-dns" ] || DNS_ARGS=$(exec_remote dns-args "$@" || true)
    shift $(dns_arg_count "$@")

    REWRITE_HOSTS=1
    NO_MULTICAST_ROUTE=
    while [ $# -gt 0 ]; do
        case "$1" in
            --no-rewrite-hosts)
                REWRITE_HOSTS=
                ;;
            --no-multicast-route)
                NO_MULTICAST_ROUTE=1
                ;;
            *)
                break
                ;;
        esac
        shift
    done

    [ -n "$REWRITE_HOSTS" ] && extra_hosts_args "$@" && DNS_EXTRA_HOSTS_ARGS="--rewrite-hosts $DNS_EXTRA_HOSTS_ARGS"
    [ -n "$NO_MULTICAST_ROUTE" ] && ATTACH_ARGS="--no-multicast-route"

    collect_cidr_args "$@"
    shift $CIDR_ARG_COUNT
    CONTAINER=$(docker $DOCKER_CLIENT_ARGS run -e WEAVE_CIDR=none $DNS_ARGS -d "$@")
    if ! exec_remote attach $CIDR_ARGS $DNS_EXTRA_HOSTS_ARGS $ATTACH_ARGS $CONTAINER >/dev/null ; then
        kill_container $CONTAINER
        exit 1
    fi
    echo $CONTAINER
    exit 0
fi

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

######################################################################
# main (remote and --local) - settings
######################################################################

# Default restart policy for router/proxy/plugin
RESTART_POLICY="--restart=always"
BASE_IMAGE=$DOCKERHUB_USER/weave
IMAGE=$BASE_IMAGE:$IMAGE_VERSION
CONTAINER_NAME=${WEAVE_CONTAINER_NAME:-weave}

BASE_PLUGIN_IMAGE=$DOCKERHUB_USER/plugin
PLUGIN_IMAGE=$BASE_PLUGIN_IMAGE:$IMAGE_VERSION
PLUGIN_CONTAINER_NAME=weaveplugin
# Note VOLUMES_CONTAINER which is for weavewait should change when you upgrade Weave
VOLUMES_CONTAINER_NAME=weavevolumes-$IMAGE_VERSION
# DB files should remain when you upgrade, so version number not included in name
DB_CONTAINER_NAME=${CONTAINER_NAME}db

DOCKER_BRIDGE=${DOCKER_BRIDGE:-docker0}
BRIDGE=weave
# This value is overridden when the datapath is used unbridged
DATAPATH=datapath
CONTAINER_IFNAME=ethwe
BRIDGE_IFNAME=v${CONTAINER_IFNAME}-bridge
DATAPATH_IFNAME=v${CONTAINER_IFNAME}-datapath
PCAP_IFNAME=v${CONTAINER_IFNAME}-pcap
PORT=${WEAVE_PORT:-6783}
HTTP_ADDR=${WEAVE_HTTP_ADDR:-127.0.0.1:6784}
STATUS_ADDR=${WEAVE_STATUS_ADDR:-127.0.0.1:6782}
PROXY_PORT=12375
PROXY_CONTAINER_NAME=weaveproxy
COVERAGE_ARGS=""
[ -n "$COVERAGE" ] && COVERAGE_ARGS="-test.coverprofile=/home/weave/cover.prof --"

######################################################################
# 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
        iptables -S -w >/dev/null 2>&1 && IPTABLES_W=-w
        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
        ## Loop until we get an exit code other than "temporarily unavailable"
        while true ; do
            run_iptables -t $IPTABLES_TABLE -A "$@" >/dev/null && return 0
            if [ $? != 4 ] ; then
                return 1
            fi
        done
    fi
}

# Insert a rule in iptables, if it doesn't exist already
insert_iptables_rule() {
    IPTABLES_TABLE="$1"
    shift 1
    if ! run_iptables -t $IPTABLES_TABLE -C "$@" >/dev/null 2>&1 ; then
        ## Loop until we get an exit code other than "temporarily unavailable"
        while true ; do
            run_iptables -t $IPTABLES_TABLE -I "$@" >/dev/null && return 0
            if [ $? != 4 ] ; then
                return 1
            fi
        done
    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.
    ! command_exists arping || $3 arping -U -q -I $1 -c 1 ${2%/*}
}

# Generate a MAC value from a stdin containing six space separated
# 2-digit hexadecimal numbers.
mac_from_hex() {
    # 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.
    read a b c d e f && printf "%02x:$b:$c:$d:$e:$f" $((0x$a & ~1 | 2))
}

mac_from_file() {
    UUID=$(cat $1 2>/dev/null) || return 1
    # We salt the input just as a precaution to avoid clashes with
    # other applications who might have had the bright idea of
    # generating MACs in the same way.
    echo "9oBJ0Jmip-$UUID" | sha256sum | sed 's/\(..\)/\1 /g' | cut -c1-17 | mac_from_hex
}

# Generate a random MAC value
random_mac() {
    od -txC -An -N6 /dev/urandom | mac_from_hex
}

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

check_docker_server_api_version() {
    # Cope with various versions of `docker version` output format
    if ! DOCKER_API_VERSION=$(docker version -f '{{.Server.APIVersion}}' 2> /dev/null ) ; then
        if ! DOCKER_API_VERSION=$(docker version -f '{{.Server.ApiVersion}}' 2> /dev/null ) ; then
            if ! DOCKER_API_VERSION=$(docker version | sed -n -e 's|^Server API version: *\([0-9][0-9]*\.[0-9][0-9]\).*|\1|p') || [ -z "$DOCKER_API_VERSION" ] ; then
                echo "ERROR: Unable to determine docker version" >&2
                exit 1
            fi
        fi
    fi

    if version_lt ${DOCKER_API_VERSION}.0 ${1}.0 ; then
        return 1
    fi
}

util_op() {
    if command_exists weaveutil ; then
        weaveutil "$@"
    else
        docker run --rm --privileged --net=host --pid=host $(docker_sock_options) \
            --entrypoint=/usr/bin/weaveutil $EXEC_IMAGE "$@"
    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
}

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
}

# Detect the current bridge/datapath state. When invoked, the values of
# $BRIDGE and $DATAPATH are expected to be distinct. $BRIDGE_TYPE and
# $DATAPATH are set correctly on success; failure indicates that the
# bridge/datapath devices have yet to be configured. If netdevs do exist
# but are in an inconsistent state the script aborts with an error.
detect_bridge_type() {
    BRIDGE_TYPE=
    if [ -d /sys/class/net/$DATAPATH ] ; then
        # Unfortunately there's no simple way to positively check whether
        # $DATAPATH is an ODP netdev so we have to make sure it isn't
        # a bridge instead (and that $BRIDGE is).
        if [ ! -d /sys/class/net/$DATAPATH/bridge -a -d /sys/class/net/$BRIDGE/bridge ] ; then
            BRIDGE_TYPE=bridged_fastdp
        else
            echo "Inconsistent bridge state detected. Please do 'weave reset' and try again." >&2
            exit 1
        fi
    elif [ -d /sys/class/net/$BRIDGE ] ; then
        if [ -d /sys/class/net/$BRIDGE/bridge ] ; then
            BRIDGE_TYPE=bridge
        else
            BRIDGE_TYPE=fastdp
            # The datapath is the bridge when there is no intermediary
            DATAPATH="$BRIDGE"
        fi
    else
        # No bridge/datapath devices configured
        return 1
    fi

    # WEAVE_MTU may have been specified when the bridge was
    # created (perhaps implicitly with WEAVE_NO_FASTDP).  So take
    # the MTU from the bridge unless it is explicitly specified
    # for this invocation.
    MTU=${WEAVE_MTU:-$(cat /sys/class/net/$BRIDGE/mtu)}
}

create_bridge() {
    if ! detect_bridge_type ; then
        BRIDGE_TYPE=bridge
        if [ -z "$WEAVE_NO_FASTDP" ] ; then
            BRIDGE_TYPE=bridged_fastdp
            if [ -n "$WEAVE_NO_BRIDGED_FASTDP" ] ; then
                BRIDGE_TYPE=fastdp
                # The datapath is the bridge when there is no intermediary
                DATAPATH="$BRIDGE"
            fi
            if util_op create-datapath $DATAPATH ; then
                : # ODP datapath created successfully
            elif [ $? = 17 ] ; then
                # Exit status of 17 means the kernel doesn't have ODP
                BRIDGE_TYPE=bridge
            else
                return 1
            fi
        fi

        init_$BRIDGE_TYPE

        # 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

        [ -n "$DOCKER_BRIDGE_IP" ] || docker_bridge_ip

        # forbid traffic to the Weave port from other containers
        add_iptables_rule filter INPUT -i $DOCKER_BRIDGE -p tcp --dst $DOCKER_BRIDGE_IP --dport $PORT          -j DROP
        add_iptables_rule filter INPUT -i $DOCKER_BRIDGE -p udp --dst $DOCKER_BRIDGE_IP --dport $PORT          -j DROP
        add_iptables_rule filter INPUT -i $DOCKER_BRIDGE -p udp --dst $DOCKER_BRIDGE_IP --dport $(($PORT + 1)) -j DROP

        # let DNS traffic to weaveDNS, since otherwise it might get blocked by the likes of UFW
        add_iptables_rule filter INPUT -i $DOCKER_BRIDGE -p udp --dport 53  -j ACCEPT
        add_iptables_rule filter INPUT -i $DOCKER_BRIDGE -p tcp --dport 53  -j ACCEPT

        if [ "$2" = "--expect-npc" ] ; then # matches usage in weave-kube launch.sh
            # Steer traffic via the NPC
            run_iptables -N WEAVE-NPC >/dev/null 2>&1 || true
            add_iptables_rule filter FORWARD -o $BRIDGE -j WEAVE-NPC
            add_iptables_rule filter FORWARD -o $BRIDGE -m state --state NEW -j NFLOG --nflog-group 86
            add_iptables_rule filter FORWARD -o $BRIDGE -j DROP
        else
            # 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
        fi

        # create a chain for masquerading
        run_iptables -t nat -N WEAVE >/dev/null 2>&1 || true
        add_iptables_rule nat POSTROUTING -j WEAVE
    else
        if [ -n "$LAUNCHING_ROUTER" ] ; then
            if [ "$BRIDGE_TYPE" = bridge -a -z "$WEAVE_NO_FASTDP" ] &&
                util_op check-datapath $DATAPATH 2>/dev/null ; then
                cat <<EOF >&2
WEAVE_NO_FASTDP is not set, but there is already a bridge present of
the wrong type for fast datapath.  Please do 'weave reset' to remove
the bridge first.
EOF
                return 1
            fi
            if [ "$BRIDGE_TYPE" != bridge -a -n "$WEAVE_NO_FASTDP" ] ; then
                cat <<EOF >&2
WEAVE_NO_FASTDP is set, but there is already a weave fast datapath
bridge present.  Please do 'weave reset' to remove the bridge first.
EOF
                return 1
            fi
        fi
    fi

    [ "$1" = "--without-ethtool" -o -n "$AWSVPC" ] || ethtool_tx_off_$BRIDGE_TYPE $BRIDGE

    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
}

expose_ip() {
    ipam_cidrs allocate_no_check_alive weave:expose $CIDR_ARGS
    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 || true
            # Remove a default route installed by the kernel, because awsvpc
            # has installed it as well
            if [ -n "$AWSVPC" ]; then
                RCIDR=$(ip route list exact $CIDR proto kernel | head -n1 | cut -d' ' -f1)
                [ -n "$RCIDR" ] && ip route del dev $BRIDGE proto kernel $RCIDR
            fi
        fi
        [ -z "$FQDN" ] || when_weave_running put_dns_fqdn_no_check_alive weave:expose $FQDN $CIDR
    done
}

# create veth with ends $1-$2, and then invoke $3..., removing the
# veth on failure. No-op of veth already exists.
create_veth() {
    VETHL=$1
    VETHR=$2
    shift 2

    ip link show $VETHL >/dev/null 2>&1 && ip link show $VETHR >/dev/null 2>&1 && return 0

    ip link add name $VETHL mtu $MTU type veth peer name $VETHR mtu $MTU || return 1

    if ! ip link set $VETHL up || ! ip link set $VETHR up || ! "$@" ; then
        ip link del $VETHL >/dev/null 2>&1 || true
        ip link del $VETHR >/dev/null 2>&1 || true
        return 1
    fi
}

init_fastdp() {
    # GCE has the lowest underlay network MTU we're likely to encounter on
    # a local network, at 1460 bytes.  To get the overlay MTU from that we
    # subtract 20 bytes for the outer IPv4 header, 8 bytes for the outer
    # UDP header, 8 bytes for the vxlan header, and 14 bytes for the inner
    # ethernet header.
    MTU=${WEAVE_MTU:-1410}

    # create_bridge already created the datapath netdev
    ip link set dev $DATAPATH mtu $MTU
}

init_bridge_prep() {
    # Observe any MTU that is already set
    [ -n "$MTU" ] || MTU=${WEAVE_MTU:-65535}

    ip link add name $BRIDGE type bridge

    # Derive the bridge MAC from the system (aka bios) UUID, or,
    # failing that, the hypervisor UUID. Elsewhere we in turn derive
    # the peer name from that, which we want to be stable across
    # reboots but otherwise unique. The system/hypervisor UUID fits
    # that bill, unlike, say, /etc/machine-id, which is often
    # identical on VMs created from cloned filesystems. If we cannot
    # determine the system/hypervisor UUID we just generate a random MAC.
    MAC=$(mac_from_file /sys/class/dmi/id/product_uuid) || \
        MAC=$(mac_from_file /sys/hypervisor/uuid) || \
        MAC=$(random_mac)

    ip link set dev $BRIDGE address $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
}

init_bridge() {
    init_bridge_prep
    create_veth $BRIDGE_IFNAME $PCAP_IFNAME add_iface_bridge $BRIDGE_IFNAME
}

init_bridged_fastdp() {
    # Initialise the datapath as normal. NB sets MTU for use below
    init_fastdp

    # Initialise the bridge using fast datapath MTU
    init_bridge_prep

    # Create linking veth pair
    create_veth $BRIDGE_IFNAME $DATAPATH_IFNAME configure_veth_bridged_fastdp

    # Finally, bring the datapath up
    ip link set dev $DATAPATH up
}

configure_veth_bridged_fastdp() {
    add_iface_fastdp $DATAPATH_IFNAME || return 1
    add_iface_bridge $BRIDGE_IFNAME || return 1
}

ethtool_tx_off_fastdp() {
    true
}

ethtool_tx_off_bridge() {
    ethtool -K $1 tx off >/dev/null
}

ethtool_tx_off_bridged_fastdp() {
    true
}

destroy_bridge() {
    # It's important that detect_bridge_type has not been called so
    # we have distinct values for $BRIDGE and $DATAPATH. Make best efforts
    # to remove netdevs of any type with those names so `weave reset` can
    # recover from inconsistent states.
    for NETDEV in $BRIDGE $DATAPATH ; do
        if [ -d /sys/class/net/$NETDEV ] ; then
            if [ -d /sys/class/net/$NETDEV/bridge ] ; then
                ip link del $NETDEV
            else
                util_op delete-datapath $NETDEV
            fi
        fi
    done

    # Remove any lingering bridged fastdp, pcap and attach-bridge veths
    for VETH in $(ip -o link show | grep -o v${CONTAINER_IFNAME}[^:@]*) ; do
        ip link del $VETH >/dev/null 2>&1 || true
    done

    if [ "$DOCKER_BRIDGE" != "$BRIDGE" ] ; then
        run_iptables -t filter -D FORWARD -i $DOCKER_BRIDGE -o $BRIDGE -j DROP 2>/dev/null || true
    fi

    [ -n "$DOCKER_BRIDGE_IP" ] || docker_bridge_ip

    run_iptables -t filter -D INPUT -i $DOCKER_BRIDGE -p udp --dport 53  -j ACCEPT  >/dev/null 2>&1 || true
    run_iptables -t filter -D INPUT -i $DOCKER_BRIDGE -p tcp --dport 53  -j ACCEPT  >/dev/null 2>&1 || true

    run_iptables -t filter -D INPUT -i $DOCKER_BRIDGE -p tcp --dst $DOCKER_BRIDGE_IP --dport $PORT          -j DROP >/dev/null 2>&1 || true
    run_iptables -t filter -D INPUT -i $DOCKER_BRIDGE -p udp --dst $DOCKER_BRIDGE_IP --dport $PORT          -j DROP >/dev/null 2>&1 || true
    run_iptables -t filter -D INPUT -i $DOCKER_BRIDGE -p udp --dst $DOCKER_BRIDGE_IP --dport $(($PORT + 1)) -j DROP >/dev/null 2>&1 || true

    run_iptables -t filter -D FORWARD -i $BRIDGE -o $BRIDGE -j ACCEPT 2>/dev/null || true
    run_iptables -F WEAVE-NPC >/dev/null 2>&1 || true
    run_iptables -t filter -D FORWARD -o $BRIDGE -j WEAVE-NPC 2>/dev/null || true
    run_iptables -t filter -D FORWARD -o $BRIDGE -m state --state NEW -j NFLOG --nflog-group 86 2>/dev/null || true
    run_iptables -t filter -D FORWARD -o $BRIDGE -j DROP 2>/dev/null || true
    run_iptables -X WEAVE-NPC >/dev/null 2>&1 || 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 -D POSTROUTING -o $BRIDGE -j ACCEPT >/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]*') || return 1
    DOCKER_BRIDGE_IP=${DOCKER_BRIDGE_IP#inet }
}

do_or_die() {
    CONTAINER="$1"
    shift 1
    if ! "$@" ; then
        kill_container $CONTAINER
        exit 1
    fi
}

add_iface_fastdp() {
    util_op add-datapath-interface $DATAPATH $1
}

add_iface_bridge() {
    ip link set $1 master $BRIDGE
}

add_iface_bridged_fastdp() {
    add_iface_bridge "$@"
}

attach_bridge() {
    bridge="$1"
    LOCAL_IFNAME=v${CONTAINER_IFNAME}bl$bridge
    GUEST_IFNAME=v${CONTAINER_IFNAME}bg$bridge

    create_veth $LOCAL_IFNAME $GUEST_IFNAME configure_veth_attached_bridge
}

configure_veth_attached_bridge() {
    add_iface_$BRIDGE_TYPE $LOCAL_IFNAME || return 1
    ip link set $GUEST_IFNAME master $bridge
}

router_opts_fastdp() {
    echo "--datapath $DATAPATH"
}

router_opts_bridge() {
    echo "--iface $PCAP_IFNAME"
}

router_opts_bridged_fastdp() {
    router_opts_fastdp "$@"
}

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

attach() {
    ATTACH_ARGS=""
    [ -n "$NO_MULTICAST_ROUTE" ] && ATTACH_ARGS="--no-multicast-route"
    # Relying on AWSVPC being set in 'ipam_cidrs allocate', except for 'weave restart'
    [ -n "$AWSVPC" ] && ATTACH_ARGS="--no-multicast-route --keep-tx-on"
    util_op attach-container $ATTACH_ARGS $CONTAINER $BRIDGE $MTU "$@"
}

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

# Check that a container for component $1 named $2 with image $3 is not running
check_not_running() {
    RUN_STATUS=$(docker inspect --format='{{.State.Running}} {{.State.Status}} {{.Config.Image}}' $2 2>/dev/null) || true
    case ${RUN_STATUS%:*} in
        "true restarting $3")
            echo "$2 is restarting; you can stop it with 'weave stop-$1'." >&2
            return 3
            ;;
        "true "*" $3")
            echo "$2 is already running; you can stop it with 'weave stop-$1'." >&2
            return 1
            ;;
        "false "*" $3")
            docker rm $2 >/dev/null
            ;;
        true*)
            echo "Found another running container named '$2'. Aborting." >&2
            return 2
            ;;
        false*)
            echo "Found another container named '$2'. Aborting." >&2
            return 2
            ;;
    esac
}

stop() {
    docker stop $1 >/dev/null 2>&1 || echo "$2 is not running." >&2
}

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

http_call() {
    addr="$1"
    http_verb="$2"
    url="$3"
    shift 3
    CURL_TMPOUT=/tmp/weave_curl_out_$$
    HTTP_CODE=$(curl -o $CURL_TMPOUT -w '%{http_code}' --connect-timeout 3 -s -S -X $http_verb "$@" http://$addr$url) || return $?
    case "$HTTP_CODE" in
        2??) # 2xx -> not an error; output response on stdout
            [ -f $CURL_TMPOUT ] && cat $CURL_TMPOUT
            retval=0
            ;;
        404) # treat as error but swallow response
            retval=4
            ;;
        *) # anything else is an error; output response on stderr
            [ -f $CURL_TMPOUT ] && cat $CURL_TMPOUT >&2
            retval=1
    esac
    rm -f $CURL_TMPOUT
    return $retval
}

http_call_unix() {
    container="$1"
    socket="$2"
    http_verb="$3"
    url="$4"
    shift 4
    # NB: requires curl >= 7.40
    output=$(docker exec $container curl -s -S -X $http_verb --unix-socket $socket "$@" http:$url) || return 1
    # in some docker versions, `docker exec` does not fail when the executed command fails
    [ -n "$output" ] || return 1
    echo $output
}

call_weave() {
    TMPERR=/tmp/call_weave_err_$$
    retval=0
    http_call $HTTP_ADDR "$@" 2>$TMPERR || retval=$?
    if [ $retval -ne 0 ] ; then
        check_running $CONTAINER_NAME && cat $TMPERR >&2
    fi
    rm -f $TMPERR
    return $retval
}

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

# Wait until container $1 is alive enough to respond to "GET /status"
# http request
wait_for_status() {
    container="$1"
    shift
    while true ; do
        "$@" GET /status >/dev/null 2>&1 && return 0
        if ! check_running $container >/dev/null 2>&1 ; then
            kill_container $container # stop it restarting
            echo $(death_msg $container) >&2
            return 1
        fi
        fractional_sleep 0.1
    done
}

# Call $1 for all containers, passing container ID, all MACs and all IPs
with_container_addresses() {
    COMMAND=$1
    shift 1

    CONTAINER_ADDRS=$(util_op container-addrs $BRIDGE "$@") || return 1
    echo "$CONTAINER_ADDRS" |  while read CONTAINER_ID CONTAINER_IFACE CONTAINER_MAC CONTAINER_IPS; do
        $COMMAND "$CONTAINER_ID" "$CONTAINER_IFACE" "$CONTAINER_MAC" "$CONTAINER_IPS"
    done
}

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

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

echo_cidrs() {
    echo $4
}

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

######################################################################
# CNI helpers
######################################################################

create_cni_script() {
    # If target file is a symlink, it's probably a remnant of a weave-kube
    # installation - if the user has now switched back to `weave launch`,
    # we need to remove the link before rewriting the contents:
    [ -L "$1" ] && rm "$1" # fail if we can't remove the link

    cat >"$1" <<EOF
#!/bin/sh
[ -z "\$CNI_NETNS" -o \$(echo "\$CNI_NETNS" | grep "^/proc/") ] || \\
    EXTRA_VOLUMES="-v \$(dirname \$CNI_NETNS):\$(dirname \$CNI_NETNS)"
docker run --rm $(docker_run_options) --pid=host -i \\
 -e CNI_VERSION -e CNI_COMMAND -e CNI_CONTAINERID -e CNI_NETNS \\
 -e CNI_IFNAME -e CNI_ARGS -e CNI_PATH \\
 -v /etc/cni:/etc/cni -v /opt/cni:/opt/cni \$EXTRA_VOLUMES \\
--entrypoint=/usr/bin/weaveutil $EXEC_IMAGE $2
EOF
    chmod a+x "$1"
}

create_cni_config() {
    cat >"$1" <<EOF
{
    "name": "weave",
    "type": "weave-net"
}
EOF
}

setup_cni() {
    if [ -d $HOST_ROOT/opt/cni/bin ] ; then
        create_cni_script $HOST_ROOT/opt/cni/bin/weave-net  cni-net
        create_cni_script $HOST_ROOT/opt/cni/bin/weave-ipam cni-ipam
    fi
    if [ -d $HOST_ROOT/etc/cni/net.d -a ! -f $HOST_ROOT/etc/cni/net.d/10-weave.conf ] ; then
        create_cni_config $HOST_ROOT/etc/cni/net.d/10-weave.conf
    fi
}

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

dns_args() {
    retval=0
    # NB: this is memoized
    DNS_DOMAIN=${DNS_DOMAIN:-$(call_weave GET /domain 2>/dev/null)} || retval=$?
    [ "$retval" -eq 4 ] && return 0
    DNS_DOMAIN=${DNS_DOMAIN:-weave.local.}

    NAME_ARG=""
    HOSTNAME_SPECIFIED=
    DNS_SEARCH_SPECIFIED=
    WITHOUT_DNS=
    while [ $# -gt 0 ] ; do
        case "$1" in
            --with-dns)
                echo "Warning: $1 is deprecated; it is on by default" >&2
                ;;
            --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

    docker_bridge_ip
    DNS_ARGS="--dns=$DOCKER_BRIDGE_IP"
    if [ -n "$NAME_ARG" -a -z "$HOSTNAME_SPECIFIED" ] ; then
        HOSTNAME="$NAME_ARG.${DNS_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
}

etc_hosts_contents() {
    FQDN=$1
    shift
    NAME=${FQDN%%.*}
    HOSTNAMES="$NAME"
    [ "$NAME" = "$FQDN" -o "$NAME." = "$FQDN" ] || HOSTNAMES="${FQDN%.} $HOSTNAMES"

    echo "# created by Weave - BEGIN"
    echo "# container hostname"
    for CIDR in $ALL_CIDRS ; do
        echo "${CIDR%/*}    $HOSTNAMES"
    done
    echo
    echo "# static names added with --add-host"
    for EXTRA_HOST in "$@" ; do
        echo "${EXTRA_HOST#*:}     ${EXTRA_HOST%:*}"
    done

    cat <<-EOF

# default localhost entries
127.0.0.1       localhost
::1             ip6-localhost ip6-loopback
fe00::0         ip6-localnet
ff00::0         ip6-mcastprefix
ff02::1         ip6-allnodes
ff02::2         ip6-allrouters
EOF

    echo "# created by Weave - END"
}

rewrite_etc_hosts() {
    HOSTS_PATH_AND_FQDN=$(docker inspect -f '{{.HostsPath}} {{.Config.Hostname}}.{{.Config.Domainname}}' $CONTAINER) || return 1
    HOSTS=${HOSTS_PATH_AND_FQDN% *}
    FQDN=${HOSTS_PATH_AND_FQDN#* }
    CONTAINERS_PATH=$(dirname $HOSTS)
    MNT=/container
    MNT_HOSTS=$MNT/$(basename $HOSTS)
    CONTENTS="$(etc_hosts_contents $FQDN "$@")"
    # rewrite /etc/hosts, unlinking the file (so Docker does not modify it again) but
    # leaving it with valid contents...
    docker run --rm --net=none -e WEAVE_CIDR=none --privileged \
        -v $CONTAINERS_PATH:$MNT \
        --entrypoint=sh \
        $EXEC_IMAGE -c "echo '$CONTENTS' > $MNT_HOSTS && rm -f $MNT_HOSTS && echo '$CONTENTS' > $MNT_HOSTS"
}

# 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}} {{.State.Restarting}}' $1 2>/dev/null) ; then
        echo  "$1 container is not present. Have you launched it?" >&2
        return 1
    elif [ "$STATUS" = "true true" ] ; then
        echo "$1 container is restarting." >&2
        return 2
    elif [ "$STATUS" != "true false" ] ; then
        echo "$1 container is not running." >&2
        return 2
    fi
}

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

# 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() {
    CONT="$1"
    COMMAND="$2"
    shift 2

    CONT_FQDN=$(docker inspect --format='{{.Config.Hostname}}.{{.Config.Domainname}}' $CONT 2>/dev/null) || return 0
    CONT_NAME=${CONT_FQDN%%.*}
    [ "$CONT_NAME" = "$CONT_FQDN" -o "$CONT_NAME." = "$CONT_FQDN" ] || $COMMAND "$CONT" "$CONT_FQDN" "$@"
}

# Register FQDN in $2 as names for addresses $3.. under full container ID $1
put_dns_fqdn() {
    CHECK_ALIVE="-d check-alive=true"
    put_dns_fqdn_helper "$@"
}

put_dns_fqdn_no_check_alive() {
    CHECK_ALIVE=
    put_dns_fqdn_helper "$@"
}

put_dns_fqdn_helper() {
    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
}

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
}

collect_dns_add_remove_args() {
    collect_ip_args "$@"
    shift $IP_COUNT
    [ $# -gt 0 -a "$1" != "-h" ] &&    C="$1" && shift 1
    [ $# -eq 2 -a "$1"  = "-h" ] && FQDN="$2" && shift 2
    [ $# -eq 0 -a \( -n "$C" -o \( $IP_COUNT -gt 0 -a -n "$FQDN" \) \) ] || usage
    check_running $CONTAINER_NAME
    if [ -n "$C" ] ; then
        check_running $C
        CONTAINER=$(container_id $C)
        [ $IP_COUNT -gt 0 ] || IP_ARGS=$(with_container_addresses echo_ips $CONTAINER)
    fi
}

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

check_overlap() {
    util_op netcheck $1 $BRIDGE
}

# Claim addresses for a container in IPAM.  Expects to be called from
# with_container_addresses.
ipam_reclaim() {
    CONTAINER_ID="$1"
    # The weave IP addresses of containers attached by the plugin are
    # recorded specially in IPAM, since the container id is not know
    # at the time.
    [ "$2" = "$CONTAINER_IFNAME" ] || CONTAINER_ID="_"
    for CIDR in $4 ; do
        http_call $HTTP_ADDR PUT "/ip/$CONTAINER_ID/$CIDR?noErrorOnUnknown=true&?check-alive=true" || true
    done
}

ipam_reclaim_no_check_alive() {
    for CIDR in $4 ; do
        http_call $HTTP_ADDR PUT /ip/$1/$CIDR?noErrorOnUnknown=true || true
    done
}

detect_awsvpc() {
    # Ignoring errors here: if we cannot detect AWSVPC we will skip the relevant
    # steps, because "attach" should work without the weave router running.
    [ "$(call_weave GET /ipinfo/tracker)" != "awsvpc" ] || AWSVPC=1
}

# Call IPAM as necessary to lookup or allocate addresses
#
# $1 is one of 'lookup', 'allocate' or 'allocate_no_check_alive', $2
# is the full container id. The remaining args are previously parsed
# CIDR_ARGS.
#
# Populates ALL_CIDRS and IPAM_CIDRS
ipam_cidrs() {
    case $1 in
        lookup)
            METHOD=GET
            CHECK_ALIVE=
            ;;
        allocate)
            METHOD=POST
            CHECK_ALIVE="?check-alive=true"
            detect_awsvpc
            if [ -n "$AWSVPC" -a $# -gt 2 ] ; then
                echo "Error: no IP addresses or subnets may be specified in AWSVPC mode" >&2
                return 1
            fi
            ;;
        allocate_no_check_alive)
            METHOD=POST
            CHECK_ALIVE=
            ;;
    esac
    CONTAINER_ID="$2"
    shift 2
    ALL_CIDRS=""
    IPAM_CIDRS=""
    # If no addresses passed in, select 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
            retval=0
            CIDR=$(call_weave $METHOD $IPAM_URL$CHECK_ALIVE) || retval=$?
            if [ $retval -eq 4 -a "$METHOD" = "POST" ] ; then
                echo "IP address allocation must be enabled to use 'net:'" >&2
                return 1
            fi
            [ $retval -gt 0 ] && return $retval
            IPAM_CIDRS="$IPAM_CIDRS $CIDR"
            ALL_CIDRS="$ALL_CIDRS $CIDR"
        else
            if [ "$METHOD" = "POST" ] ; then
                # Assignment of a plain IP address; warn if it clashes but carry on
                check_overlap $arg || true
                # Abort on failure, but not 4 (=404), which means IPAM is disabled
                when_weave_running http_call $HTTP_ADDR PUT /ip/$CONTAINER_ID/$arg$CHECK_ALIVE || [ $? -eq 4 ] || return 1
            fi
            ALL_CIDRS="$ALL_CIDRS $arg"
        fi
    done
}

ipam_cidrs_or_die() {
    if ! ipam_cidrs "$@" ; then
        kill_container $2
        exit 1
    fi
}

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

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

docker_client_args() {
    while [ $# -gt 0 ]; do
        case "$1" in
          -H|--host)
            DOCKER_CLIENT_HOST="$2"
            shift
            ;;
          -H=*|--host=*)
            DOCKER_CLIENT_HOST="${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"
}

# TODO: Handle relative paths for args
# TODO: Handle args with spaces
host_arg() {
  PROXY_HOST="$1"
  if [ "$PROXY_HOST" != "${PROXY_HOST#unix://}" ]; then
    host=$(dirname ${PROXY_HOST#unix://})
    if [ "$host" = "${host#/}" ]; then
      echo "When launching the proxy, unix sockets must be specified as an absolute path." >&2
      exit 1
    fi
    PROXY_VOLUMES="$PROXY_VOLUMES -v /var/run/weave:/var/run/weave"
  fi
  PROXY_ARGS="$PROXY_ARGS -H $1"
}

proxy_parse_args() {
    while [ $# -gt 0 ]; do
        case "$1" in
          -H)
            host_arg "$2"
            shift
            ;;
          -H=*)
            host_arg "${1#*=}"
            ;;
          -no-detect-tls|--no-detect-tls)
            PROXY_TLS_DETECTION_DISABLED=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
            ;;
          --no-restart)
            RESTART_POLICY=
            ;;
          *)
            PROXY_ARGS="$PROXY_ARGS $1"
            ;;
        esac
        shift
    done
}

proxy_args() {
    PROXY_VOLUMES=""
    PROXY_ARGS=""
    PROXY_TLS_ENABLED=""
    PROXY_TLS_DETECTION_DISABLED=""
    PROXY_HOST=""
    proxy_parse_args "$@"

    if [ -z "$PROXY_TLS_ENABLED" -a -z "$PROXY_TLS_DETECTION_DISABLED" ] ; then
        if ! DOCKER_TLS_ARGS=$(util_op docker-tls-args) ; then
            echo -n "Warning: unable to detect proxy TLS configuration. To enable TLS, " >&2
            echo -n "launch the proxy with 'weave launch-proxy' and supply TLS options. " >&2
            echo "To suppress this warning, supply the '--no-detect-tls' option." >&2
        else
            proxy_parse_args $DOCKER_TLS_ARGS
        fi
    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:$PROXY_PORT"
          ;;
      esac
      host_arg "$PROXY_HOST"
    fi
}

proxy_addrs() {
    if addr="$(http_call_unix $PROXY_CONTAINER_NAME status.sock GET /status 2>/dev/null)" ; 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
}

proxy_addr() {
    addr=$(proxy_addrs) || return 1
    echo "$addr" | cut -d ' ' -f1
}

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
            --no-restart)
                args="$args $1"
                ;;
            --log-level)
                [ $# -gt 1 ] || usage
                args="$args $1 $2"
                shift
                ;;
            --log-level=*)
                args="$args $1"
                ;;
        esac
        shift
    done
    echo "$args"
}

launch_router() {
    LAUNCHING_ROUTER=1
    check_forwarding_rules
    enforce_docker_bridge_addr_assign_type

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

    docker_bridge_ip
    DNS_ROUTER_OPTS="--dns-listen-address $DOCKER_BRIDGE_IP:53"
    NO_DNS_OPT=

    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_ROUTER_OPTS=
                NO_DNS_OPT="--no-dns"
                ;;
            --no-restart)
                RESTART_POLICY=
                ;;
            --awsvpc)
                AWSVPC_ARGS="--awsvpc"
                AWSVPC=1
                ;;
            *)
                ARGS="$ARGS '$(echo "$1" | sed "s|'|'\"'\"'|g")'"
                ;;
        esac
        shift
    done
    eval "set -- $ARGS"

    setup_cni
    create_bridge
    # We set the router name to the bridge MAC, which in turn is
    # derived from the system UUID (if available), and thus stable
    # across reboots.
    PEERNAME=$(cat /sys/class/net/$BRIDGE/address)

    if [ -z "$IPRANGE_SPECIFIED" ] ; then
        IPRANGE="10.32.0.0/12"
        if ! check_overlap $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 "$AWSVPC" -a -z "$IPRANGE" ] ; then
            echo "ERROR: Empty --ipalloc-range is not compatible with --awsvpc." >&2
            exit 1
        fi
        if [ -n "$IPRANGE" ] && ! check_overlap $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

    # Create a data-only container for persistence data
    if ! docker inspect -f ' ' $DB_CONTAINER_NAME > /dev/null 2>&1 ; then
       protect_against_docker_hang
       docker create -v /weavedb --name=$DB_CONTAINER_NAME \
           --label=weavevolumes $WEAVEDB_IMAGE >/dev/null
    fi

    RESOLV_CONF=$(chroot ${HOST_ROOT:-/} readlink -f /etc/resolv.conf)
    RESOLV_CONF_DIR=$(dirname "$RESOLV_CONF")
    RESOLV_CONF_BASE=$(basename "$RESOLV_CONF")

    # 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 -d --name=$CONTAINER_NAME \
        $(docker_run_options) \
        $RESTART_POLICY \
        --volumes-from $DB_CONTAINER_NAME \
        -v $RESOLV_CONF_DIR:/var/run/weave/etc \
        -e WEAVE_PASSWORD \
        -e CHECKPOINT_DISABLE \
        $WEAVE_DOCKER_ARGS $IMAGE $COVERAGE_ARGS \
        --port $CONTAINER_PORT --name "$PEERNAME" --nickname "$(hostname)" \
        $(router_opts_$BRIDGE_TYPE) \
        --ipalloc-range "$IPRANGE" \
        --dns-effective-listen-address $DOCKER_BRIDGE_IP \
        $DNS_ROUTER_OPTS $NO_DNS_OPT \
        $AWSVPC_ARGS \
        --http-addr $HTTP_ADDR \
        --status-addr $STATUS_ADDR \
        --resolv-conf "/var/run/weave/etc/$RESOLV_CONF_BASE" \
        "$@")
    wait_for_status $CONTAINER_NAME http_call $HTTP_ADDR
    setup_awsvpc
    populate_router
}

setup_awsvpc() {
    if [ -n "$AWSVPC" ]; then
        # Set proxy_arp on the bridge, so that it could accept packets destined
        # to containers within the same subnet but running on remote hosts.
        # Without it, exact routes on each container are required.
        echo 1 >/proc/sys/net/ipv4/conf/$BRIDGE/proxy_arp
        # Avoid delaying the first ARP request. Also, setting it to 0 avoids
        # placing the request into a bounded queue as it can be seen:
        # https://git.kernel.org/cgit/linux/kernel/git/stable/linux-stable.git/tree/net/ipv4/arp.c?id=refs/tags/v4.6.1#n819
        echo 0 >/proc/sys/net/ipv4/neigh/$BRIDGE/proxy_delay
        expose_ip
    fi
}

# Recreate the parameter values that are set when the router is first launched
fetch_router_args() {
    CONTAINER_ARGS=$(docker inspect -f '{{.Args}}' $CONTAINER_NAME) || return 1
    IPRANGE=$(echo $CONTAINER_ARGS | grep -o -e '-ipalloc-range [^ ]*') || true
    NO_DNS_OPT=$(echo $CONTAINER_ARGS | grep -o -e '--no-dns') || true
}

populate_router() {
    if [ -n "$IPRANGE" ] ; then
        # Tell the newly-started weave IP allocator about existing weave IPs
        # In the case of AWSVPC, we do expose before calling populate_router
        [ -n "$AWSVPC" ] || with_container_addresses ipam_reclaim_no_check_alive weave:expose
        with_container_addresses ipam_reclaim $(docker ps -q --no-trunc)
    fi
    if [ -z "$NO_DNS_OPT" ] ; then
        # Tell the newly-started weaveDNS about existing weave IPs
        for CONTAINER in $(docker ps -q --no-trunc) ; do
            if CONTAINER_IPS=$(with_container_addresses echo_ips $CONTAINER) && [ -n "$CONTAINER_IPS" ] ; then
                with_container_fqdn $CONTAINER put_dns_fqdn $CONTAINER_IPS
            fi
        done
    fi
}

stop_router() {
    stop $CONTAINER_NAME "Weave"
    conntrack -D -p udp --dport $PORT >/dev/null 2>&1 || true
}

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 "$@"
    mkdir -p /var/run/weave
    # Create a data-only container to mount the weavewait files from
    if ! docker inspect -f ' ' $VOLUMES_CONTAINER_NAME > /dev/null 2>&1 ; then
       protect_against_docker_hang
       docker create -v /w -v /w-noop -v /w-nomcast --name=$VOLUMES_CONTAINER_NAME \
           --label=weavevolumes --entrypoint=/bin/false $EXEC_IMAGE >/dev/null
    fi
    detect_awsvpc
    [ -n "$AWSVPC" ] && PROXY_ARGS="$PROXY_ARGS --no-multicast-route"
    PROXY_CONTAINER=$(docker run -d --name=$PROXY_CONTAINER_NAME \
        $(docker_run_options) \
        $RESTART_POLICY \
        --pid=host \
        $PROXY_VOLUMES \
        --volumes-from $VOLUMES_CONTAINER_NAME \
        -v /var/run/weave:/var/run/weave \
        -e DOCKER_BRIDGE \
        -e WEAVE_DEBUG \
        -e COVERAGE \
        -e WEAVE_HTTP_ADDR \
        -e EXEC_IMAGE=$EXEC_IMAGE \
        --entrypoint=/home/weave/weaveproxy \
        $WEAVEPROXY_DOCKER_ARGS $EXEC_IMAGE $COVERAGE_ARGS $PROXY_ARGS)
    wait_for_status $PROXY_CONTAINER_NAME http_call_unix $PROXY_CONTAINER_NAME status.sock
}

stop_proxy() {
    warn_if_stopping_proxy_in_env
    stop $PROXY_CONTAINER_NAME "Proxy"
}

launch_plugin_if_not_running() {
    while [ $# -gt 0 ]; do
        case "$1" in
            --no-restart)
                RESTART_POLICY=
                ;;
            *)
                break
                ;;
        esac
        shift
    done

    retval=0
    check_not_running plugin $PLUGIN_CONTAINER_NAME $BASE_PLUGIN_IMAGE || retval=$?
    # If an existing plugin is running (we start it with restart=always), return its ID
    [ $retval = 1 ] && PLUGIN_CONTAINER=$(container_id $PLUGIN_CONTAINER_NAME) && return 0
    # Any other kind of error code from check_not_running is a failure.
    [ $retval -gt 0 ] && return $retval

    if ! PLUGIN_CONTAINER=$(docker run -d --name=$PLUGIN_CONTAINER_NAME \
        $(docker_run_options) \
        $RESTART_POLICY \
        --pid=host \
        -v /run/docker/plugins:/run/docker/plugins \
        -e WEAVE_HTTP_ADDR \
        $WEAVEPLUGIN_DOCKER_ARGS $PLUGIN_IMAGE $COVERAGE_ARGS \
        "$@") ; then
        return 1
    fi
    wait_for_status $PLUGIN_CONTAINER_NAME http_call_unix $PLUGIN_CONTAINER_NAME status.sock
    WEAVE_IPAM_SUBNET=$(call_weave GET /ipinfo/defaultsubnet)
    util_op create-plugin-network weave weavemesh $WEAVE_IPAM_SUBNET
}

plugin_disabled() {
    [ -n "$WEAVE_NO_PLUGIN" ] || ! check_docker_server_api_version 1.21
}

stop_plugin() {
    util_op remove-plugin-network weave || true
    stop $PLUGIN_CONTAINER_NAME "Plugin"
}

protect_against_docker_hang() {
    # If the plugin is not running, remove its socket so Docker doesn't try to talk to it
    if ! check_running $PLUGIN_CONTAINER_NAME 2>/dev/null ; then
        rm -f /run/docker/plugins/weave.sock /run/docker/plugins/weavemesh.sock
    fi
}

######################################################################
# argument deprecation handling
######################################################################

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 "--ipalloc-init consensus=<count>"
                [ ${1#--} = "initpeercount" ] && shift
                ;;
            --init-peer-count|--init-peer-count=*)
                deprecation_warning $1 "--ipalloc-init consensus=<count>"
                [ ${1#--} = "init-peer-count" ] && shift
                shift
                ;;
            -no-default-ipam|--no-default-ipam)
                deprecation_warning $1 "--no-default-ipalloc"
                ;;
            --with-dns)
                echo "Warning: $1 has been removed; DNS is on by default" >&2
                ;;
        esac
        shift
    done
}

######################################################################
# main (remote and --local)
######################################################################

[ $(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

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

handle_help_arg "$1"

case "$COMMAND" in
    setup)
        for img in $IMAGE $EXEC_IMAGE $PLUGIN_IMAGE $WEAVEDB_IMAGE ; do
            docker pull $img
        done
        setup_cni
        ;;
    setup-cni)
        setup_cni
        ;;
    version)
        [ $# -eq 0 ] || usage
        ask_version $CONTAINER_NAME $IMAGE || true
        ask_version $PROXY_CONTAINER_NAME $EXEC_IMAGE --entrypoint=/home/weave/weaveproxy || true
        ask_version $PLUGIN_CONTAINER_NAME $PLUGIN_IMAGE || true
        ;;
    # intentionally undocumented since it assumes knowledge of weave
    # internals
    create-bridge)
        if [ "$1" != "--force" ] ; then
            cat 1>&2 <<EOF
WARNING: 'weave create-bridge' is deprecated. Instead use 'weave attach-bridge'
to link the docker and weave bridges after both docker and weave have been
started. Note that, unlike 'weave create-bridge', 'weave attach-bridge' is
compatible with fast data path.
EOF
            # This subcommand may be run without the docker daemon, but
            # fastdp needs to run the router container to setup the ODP
            # bridge, hence:
            if [ -z "$WEAVE_NO_FASTDP" ] ; then
                cat 1>&2 <<EOF

ERROR: 'weave create-bridge' is not compatible with fast data path. If you
really want to do this please set the WEAVE_NO_FASTDP environment variable.
EOF
                exit 1
            fi
        else
            shift 1
        fi
        create_bridge --without-ethtool "$@"
        ;;
    attach-bridge)
        if detect_bridge_type ; then
            attach_bridge ${1:-$DOCKER_BRIDGE}
            insert_iptables_rule nat POSTROUTING -o $BRIDGE -j ACCEPT
        else
            echo "Weave bridge not found. Please run 'weave launch' and try again" >&2
            exit 1
        fi
        ;;
    bridge-type)
        detect_bridge_type && echo $BRIDGE_TYPE
        ;;
    launch)
        deprecation_warnings "$@"
        check_not_running router $CONTAINER_NAME        $BASE_IMAGE
        check_not_running proxy  $PROXY_CONTAINER_NAME  $BASE_EXEC_IMAGE
        check_not_running plugin $PLUGIN_CONTAINER_NAME $BASE_PLUGIN_IMAGE
        COMMON_ARGS=$(common_launch_args "$@")
        launch_router "$@"
        launch_proxy  $COMMON_ARGS
        plugin_disabled || launch_plugin_if_not_running $COMMON_ARGS
        ;;
    launch-router)
        deprecation_warnings "$@"
        check_not_running router $CONTAINER_NAME $BASE_IMAGE
        launch_router "$@"
        echo $ROUTER_CONTAINER
        ;;
    attach-router)
        check_running $CONTAINER_NAME
        enforce_docker_bridge_addr_assign_type
        # We cannot use detect_awsvpc here, because HTTP server might not be started
        ! docker inspect -f '{{.Config.Cmd}}' $CONTAINER_NAME | grep -q -- "--awsvpc" || AWSVPC=1
        create_bridge
        fetch_router_args
        wait_for_status $CONTAINER_NAME http_call $HTTP_ADDR
        setup_awsvpc
        populate_router
        ;;
    launch-proxy)
        deprecation_warnings "$@"
        check_not_running proxy $PROXY_CONTAINER_NAME $BASE_EXEC_IMAGE
        launch_proxy "$@"
        echo $PROXY_CONTAINER
        ;;
    launch-plugin)
        if ! check_running $CONTAINER_NAME 2>/dev/null ; then
            echo "ERROR:" $CONTAINER_NAME "container must be running before plugin can be launched" >&2
            exit 1
        fi
        launch_plugin_if_not_running "$@"
        echo $PLUGIN_CONTAINER
        ;;
    env|proxy-env)
        [ "$COMMAND" = "env" ] || deprecation_warning "$COMMAND" "'weave env'"
        if PROXY_ADDR=$(proxy_addr) ; then
            [ "$PROXY_ADDR" = "$DOCKER_CLIENT_HOST" ] || RESTORE="ORIG_DOCKER_HOST=$DOCKER_CLIENT_HOST"
            echo "export DOCKER_HOST=$PROXY_ADDR $RESTORE"
        fi
        ;;
    config|proxy-config)
        [ "$COMMAND" = "config" ] || deprecation_warning "$COMMAND" "'weave config'"
        PROXY_ADDR=$(proxy_addr) && echo "-H=$PROXY_ADDR"
        ;;
    connect)
        [ $# -gt 0 ] || usage
        [ "$1" = "--replace" ] && replace="-d replace=true" && shift
        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
        call_weave GET $STATUS_URL || res=$?
        if [ $res -eq 4 ] ; then
            echo "Invalid 'weave status' sub-command: $SUB_COMMAND" >&2
            usage
        fi
        if [ -z "$SUB_STATUS" ] && check_running $PROXY_CONTAINER_NAME 2>/dev/null && PROXY_ADDRS=$(proxy_addrs) ; then
            echo
            echo "        Service: proxy"
            echo "        Address: $PROXY_ADDRS"
        fi
        if [ -z "$SUB_STATUS" ] && check_running $PLUGIN_CONTAINER_NAME 2>/dev/null ; then
            echo
            echo "        Service: plugin"
            echo "     DriverName: weave"
        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 "$@")
        REWRITE_HOSTS=1
        NO_MULTICAST_ROUTE=
        while [ $# -gt 0 ]; do
            case "$1" in
                --no-rewrite-hosts)
                    REWRITE_HOSTS=
                    ;;
                --no-multicast-route)
                    NO_MULTICAST_ROUTE=1
                    ;;
                *)
                    break
                    ;;
            esac
            shift
        done
        collect_cidr_args "$@"
        shift $CIDR_ARG_COUNT
        CONTAINER=$(docker run -e WEAVE_CIDR=none $DNS_ARGS -d "$@")
        create_bridge
        ipam_cidrs_or_die allocate $CONTAINER $CIDR_ARGS
        [ -n "$REWRITE_HOSTS" ] && extra_hosts_args "$@" && rewrite_etc_hosts $DNS_EXTRA_HOSTS
        do_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 allocate $CONTAINER $CIDR_ARGS
        do_or_die $CONTAINER attach $ALL_CIDRS
        when_weave_running with_container_fqdn $CONTAINER put_dns_fqdn $ALL_CIDRS
        echo $RES
        ;;
    attach)
        DNS_EXTRA_HOSTS=
        REWRITE_HOSTS=
        NO_MULTICAST_ROUTE=
        collect_cidr_args "$@"
        shift $CIDR_ARG_COUNT
        while [ $# -gt 0 ]; do
            case "$1" in
                --rewrite-hosts)
                    REWRITE_HOSTS=1
                    ;;
                --add-host)
                    DNS_EXTRA_HOSTS="$2 $DNS_EXTRA_HOSTS"
                    shift
                    ;;
                --add-host=*)
                    DNS_EXTRA_HOSTS="${1#*=} $DNS_EXTRA_HOSTS"
                    ;;
                --no-multicast-route)
                    NO_MULTICAST_ROUTE=1
                    ;;
                *)
                    break
                    ;;
            esac
            shift
        done
        [ $# -eq 1 ] || usage
        CONTAINER=$(container_id $1)
        create_bridge
        ipam_cidrs allocate $CONTAINER $CIDR_ARGS
        [ -n "$REWRITE_HOSTS" ] && rewrite_etc_hosts $DNS_EXTRA_HOSTS
        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 lookup $CONTAINER $CIDR_ARGS
        util_op detach-container $CONTAINER $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
        ;;
    restart)
        [ $# -ge 1 ] || usage
        create_bridge
        ALL_CIDRS=$(with_container_addresses echo_cidrs $1)
        RES=$(docker restart $1)
        CONTAINER=$(container_id $1)
        for CIDR in $ALL_CIDRS ; do
            call_weave PUT /ip/$CONTAINER/$CIDR?check-alive=true
        done
        detect_awsvpc
        do_or_die $CONTAINER attach $ALL_CIDRS
        when_weave_running with_container_fqdn $CONTAINER put_dns_fqdn $ALL_CIDRS
        echo $RES
        ;;
    dns-add)
        collect_dns_add_remove_args "$@"
        FN=put_dns_fqdn
        [ -z "$CONTAINER" ] && CONTAINER=weave:extern && FN=put_dns_fqdn_no_check_alive
        if [ -n "$FQDN" ] ; then
            $FN $CONTAINER $FQDN $IP_ARGS
        else
            with_container_fqdn $CONTAINER $FN $IP_ARGS
        fi
        ;;
    dns-remove)
        collect_dns_add_remove_args "$@"
        [ -z "$CONTAINER" ] && CONTAINER=weave:extern
        if [ -n "$FQDN" ] ; then
            delete_dns_fqdn $CONTAINER $FQDN $IP_ARGS
        else
            delete_dns $CONTAINER $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
        if [ $# -eq 0 ] ; then
            FQDN=""
        else
            [ $# -eq 2 -a "$1" = "-h" ] || usage
            FQDN="$2"
        fi
        create_bridge --without-ethtool
        expose_ip
        util_op expose-nat $ALL_CIDRS
        show_addrs $ALL_CIDRS
        ;;
    hide)
        collect_cidr_args "$@"
        shift $CIDR_ARG_COUNT
        ipam_cidrs lookup 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
        plugin_disabled || stop_plugin
        stop_router
        stop_proxy
        ;;
    stop-router)
        [ $# -eq 0 ] || usage
        stop_router
        ;;
    stop-proxy)
        [ $# -eq 0 ] || usage
        stop_proxy
        ;;
    stop-plugin)
        [ $# -eq 0 ] || usage
        stop_plugin
        ;;
    reset)
        [ $# -eq 0 ] || [ $# -eq 1 -a "$1" = "--force" ] || usage
        plugin_disabled || util_op remove-plugin-network weave || true
        warn_if_stopping_proxy_in_env
        res=0
        [ "$1" = "--force" ] || check_running $CONTAINER_NAME 2>/dev/null || res=$?
        case $res in
            0)
                call_weave DELETE /peer >/dev/null 2>&1 || true
                fractional_sleep 0.5 # Allow some time for broadcast updates to go out
                ;;
            1)
                # No such container; assume user already did reset
                ;;
            2)
                echo "ERROR: weave is not running; unable to remove from cluster." >&2
                echo "Re-launch weave before reset or use --force to override." >&2
                exit 1
                ;;
        esac
        for NAME in $PLUGIN_CONTAINER_NAME $CONTAINER_NAME $PROXY_CONTAINER_NAME ; do
            docker stop  $NAME >/dev/null 2>&1 || true
            docker rm -f $NAME >/dev/null 2>&1 || true
        done
        protect_against_docker_hang
        VOLUME_CONTAINERS=$(docker ps -qa --filter label=weavevolumes)
        [ -n "$VOLUME_CONTAINERS" ] && docker rm -v $VOLUME_CONTAINERS  >/dev/null 2>&1 || true
        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)
        [ $# -gt 0 ] || usage
        res=0
        for PEER in "$@" ; do
            call_weave DELETE /peer/$PEER || res=1
        done
        [ $res -eq 0 ]
        ;;
    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
        ;;
    prime)
        call_weave GET /ring
        ;;
    *)
        echo "Unknown weave command '$COMMAND'" >&2
        usage
        ;;
esac
