#!/bin/sh set -e [ -n "$WEAVE_DEBUG" ] && set -x SCRIPT_VERSION="(unreleased version)" IMAGE_VERSION=latest [ "$SCRIPT_VERSION" = "(unreleased version)" ] || 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 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}" usage_no_exit() { cat >&2 < launch-router [--password ] [--name ] [--nickname ] [--ipalloc-range [--ipalloc-default-subnet ]] [--init-peer-count ] [--no-discovery] [--trusted-subnets ,...] ... launch-proxy [-H ] [--without-dns] [--no-multicast-route] [--no-rewrite-hosts] [--no-default-ipalloc] [--hostname-from-label ] [--hostname-match ] [--hostname-replacement ] [--rewrite-inspect] launch-plugin [--no-multicast-route] weave env [--restore] config dns-args weave connect [--replace] [ ...] forget ... weave run [--without-dns] [--no-rewrite-hosts] [--no-multicast-route] [ ...] ... start [ ...] attach [ ...] detach [ ...] restart weave expose [ ...] [-h ] hide [ ...] weave dns-add [ ...] [-h ] | ... -h dns-remove [ ...] [-h ] | ... -h dns-lookup weave status [targets | connections | peers | dns] report [-f ] ps [ ...] weave stop stop-router stop-proxy stop-plugin weave reset rmpeer | where = [:] = / = [ip:] | net: | net:default = [tcp://][]: | [unix://]/path/to/socket EOF } usage() { usage_no_exit exit 1 } 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 } exec_remote() { docker $DOCKER_CLIENT_ARGS run --rm --privileged --net=host --pid=host $(docker_sock_options) \ -e DOCKERHUB_USER="$DOCKERHUB_USER" \ -e WEAVE_VERSION \ -e WEAVE_DEBUG \ -e WEAVE_DOCKER_ARGS \ -e WEAVEPROXY_DOCKER_ARGS \ -e WEAVE_PASSWORD \ -e WEAVE_PORT \ -e WEAVE_HTTP_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 \ $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 } 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 } 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 </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 } 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 } check_docker_version [ "$1" = "--local" ] && shift 1 && IS_LOCAL=1 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" = "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 [ "$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 "$@") exec_remote attach $CIDR_ARGS --or-die $DNS_EXTRA_HOSTS_ARGS $ATTACH_ARGS $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 BASE_PLUGIN_IMAGE=$DOCKERHUB_USER/plugin PLUGIN_IMAGE=$BASE_PLUGIN_IMAGE:$IMAGE_VERSION PLUGIN_CONTAINER_NAME=weaveplugin VOLUMES_CONTAINER_NAME=weavevolumes-$IMAGE_VERSION DOCKER_BRIDGE=${DOCKER_BRIDGE:-docker0} CONTAINER_NAME=${WEAVE_CONTAINER_NAME:-weave} 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} 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 run_iptables -t $IPTABLES_TABLE -A "$@" >/dev/null 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 run_iptables -t $IPTABLES_TABLE -I "$@" >/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 # (>). 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)) } # Generate a random MAC value random_mac() { od -txC -An -N6 /dev/urandom | mac_from_hex } ###################################################################### # weave and docker specific helpers ###################################################################### 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 } # 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 # 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 else if [ -n "$LAUNCHING_ROUTER" ] ; then if [ "$BRIDGE_TYPE" = bridge -a -z "$WEAVE_NO_FASTDP" ] && util_op create-datapath $DATAPATH 2>/dev/null && util_op delete-datapath $DATAPATH 2>/dev/null ; then cat <&1 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 <&1 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" ] || 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 } # 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 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() { # 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, if # present. Elsewhere we in turn derive the peer name from that, # which we want to be stable across reboots but otherwise # unique. The system 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 UUID we # just generate a random MAC. if SYSTEM_UUID=$(cat /sys/class/dmi/id/product_uuid 2>/dev/null) ; then # 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. MAC=$(echo "9oBJ0Jmip-$SYSTEM_UUID" | sha256sum | sed 's/\(..\)/\1 /g' | cut -c1-17 | mac_from_hex) else MAC=$(random_mac) fi 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_bridged_fastdp() { # Initialise the datapath as normal. NB sets MTU for use below init_fastdp # Initialise the bridge using fast datapath MTU init_bridge # 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 add_iface_bridge $BRIDGE_IFNAME } 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 -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]*') DOCKER_BRIDGE_IP=${DOCKER_BRIDGE_IP#inet } } # the following borrows from https://github.com/jpetazzo/pipework # Set $CONTAINER_NETNS to the network namespace 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" = "" ] ; then echo "Container $CONTAINER unknown to Docker." >&2 exit 1 fi CONTAINER_NETNS=/proc/$CONTAINER_PID/ns/net 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 /proc/$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 $CONTAINER_NETNS network namespace netnsenter() { nsenter --net=$CONTAINER_NETNS "$@" } # connect_container_to_bridge connect_container_to_bridge() { ip link add name $LOCAL_IFNAME mtu $MTU type veth peer name $GUEST_IFNAME mtu $MTU || return 1 if ! ethtool_tx_off_$BRIDGE_TYPE $GUEST_IFNAME || ! ip link set $GUEST_IFNAME netns $CONTAINER_NETNS ; 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 $1 || ! ip link set $LOCAL_IFNAME up || ! add_iface_$BRIDGE_TYPE $LOCAL_IFNAME || ! configure_arp_cache $1 "netnsenter" ; then return 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 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 } ###################################################################### # functions invoked through with_container_netns ###################################################################### setup_router_iface_fastdp() { true } setup_router_iface_bridge() { create_veth $BRIDGE_IFNAME $PCAP_IFNAME add_iface_bridge $BRIDGE_IFNAME } setup_router_iface_bridged_fastdp() { setup_router_iface_fastdp "$@" } container_in_host_ns() { [ -h "$CONTAINER_NETNS" -a -h "/proc/self/ns/net" -a "$(readlink $CONTAINER_NETNS)" = "$(readlink /proc/self/ns/net)" ] } attach() { if container_in_host_ns ; 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 if ! netnsenter ip link show $CONTAINER_IFNAME >/dev/null 2>&1 ; then connect_container_to_bridge $CONTAINER_IFNAME || 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. # # The MTU lock prevents PMTU discovery for multicast # destinations. Without that, the kernel sets the DF flag on # multicast packets. Since RFC1122 prohibits sending of ICMP # errors for packets with multicast destinations, that causes # packets larger than the PMTU to be dropped silently. if [ -z "$NO_MULTICAST_ROUTE" ] ; then 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 mtu lock $MTU fi 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 } ###################################################################### # functions for interacting with containers ###################################################################### args_match() { prev_args="$(docker inspect -f '{{.Args}} {{.Config.Env}}' $1)" shift dummy_container=$(docker create "$@" 2>/dev/null) || return 1 dummy_args="$(docker inspect -f '{{.Args}} {{.Config.Env}}' $dummy_container)" docker rm $dummy_container >/dev/null [ "$prev_args" = "$dummy_args" ] } # Check if a container for component $1 named $2 with image $3 and args $4... is running, # and start it if necessary start_if_necessary() { component=$1 name=$2 image=$3 shift 3 image_base=${image%:*} IMAGE_ID=$(docker inspect --format='{{.Id}}' $image) RUN_STATUS=$(docker inspect --format='{{.State.Running}} {{.Image}} {{.Config.Image}} {{.Id}}' $name 2>/dev/null) || true CONTAINER_ID=${RUN_STATUS##* } case ${RUN_STATUS%:*} in "true $IMAGE_ID "*) if args_match $CONTAINER_ID "$@" ; then ALREADY_RUNNING_CONTAINER=$CONTAINER_ID echo $CONTAINER_ID else echo "$name is already running with different arguments; you can stop it with 'weave stop-$component'." >&2 return 1 fi ;; "false $IMAGE_ID "*) if args_match $CONTAINER_ID "$@" ; then docker start $CONTAINER_ID else docker rm $name >/dev/null docker run -d --name=$name "$@" fi ;; true*$image_base) echo "Found another version of $name running. Aborting." >&2 return 2 ;; true*) echo "Found another running container named '$name'. Aborting." >&2 return 2 ;; false*) docker rm $name >/dev/null docker run -d --name=$name "$@" ;; "") docker run -d --name=$name "$@" ;; 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 --connect-timeout 3 -s -S -X $http_verb "$@" http://$addr$url } 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) # in some docker versions, `docker exec` does not fail when the executed command fails [ -n "$output" ] || return 1 echo $output } call_weave() { check_running $CONTAINER_NAME || return 1 if ! http_call $HTTP_ADDR "$@" ; then echo "Call to $CONTAINER_NAME failed." >&2 return 1 fi } 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 docker kill $container >/dev/null 2>&1 || true # 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 util_op container-addrs $BRIDGE "$@" | while read CONTAINER_ID CONTAINER_MAC CONTAINER_IPS; do $COMMAND "$CONTAINER_ID" "$CONTAINER_MAC" "$CONTAINER_IPS" done } echo_addresses() { echo $1 $2 $3 } echo_ips() { for CIDR in $3; do echo ${3%/*} done } echo_cidrs() { echo $3 } 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= 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 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 } 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) 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 [ -n "$ALREADY_RUNNING_CONTAINER" ] && return 0 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 } 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() { for CIDR in $3 ; do http_call $HTTP_ADDR PUT /ip/$1/${CIDR%/*}?noErrorOnUnknown=true done } # 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" ;; 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 CIDR=$(call_weave $METHOD $IPAM_URL$CHECK_ALIVE) || return 1 if [ "$CIDR" = "404 page not found" ] ; then if [ "$METHOD" = "POST" ] ; then echo "IP address allocation must be enabled to use 'net:'" >&2 return 1 fi elif ! is_cidr "$CIDR" ; then echo "$CIDR" >&2 return 1 else IPAM_CIDRS="$IPAM_CIDRS $CIDR" ALL_CIDRS="$ALL_CIDRS $CIDR" fi else if [ "$METHOD" = "POST" ] ; then # Assignment of a plain IP address; warn if it clashes but carry on check_overlap $arg || true when_weave_running http_call $HTTP_ADDR PUT /ip/$CONTAINER_ID/${arg%/*} >&2 fi ALL_CIDRS="$ALL_CIDRS $arg" fi done } ipam_cidrs_or_die() { if ! ipam_cidrs "$@" ; then docker kill $2 >/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() { 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 ;; *) 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 "Unable to auto-detect proxy TLS configuration; you must launch the proxy with" >&2 echo "'weave launch-proxy' and supply '--no-detect-tls' or TLS options." >&2 exit 1 fi proxy_parse_args $DOCKER_TLS_ARGS 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 --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 create_bridge docker_bridge_ip # 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) # 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_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" 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 ! 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 "$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 # Set WEAVE_DOCKER_ARGS in the environment in order to supply # additional parameters, such as resource limits, to docker # when launching the weave container. start_if_necessary router $CONTAINER_NAME $IMAGE \ --privileged --net=host $(docker_sock_options) \ -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 \ --http-addr $HTTP_ADDR \ "$@" setup_router_iface_$BRIDGE_TYPE wait_for_status $CONTAINER_NAME http_call $HTTP_ADDR [ -n "$ALREADY_RUNNING_CONTAINER" ] || attach_router } # Recreate the parameter values that are set when the router is first launched fetch_router_args() { CONTAINER_ARGS=$(docker inspect -f '{{.Args}}' $CONTAINER_NAME) IPRANGE=$(echo $CONTAINER_ARGS | grep -o -e '-ipalloc-range [^ ]*') || true NO_DNS_OPT=$(echo $CONTAINER_ARGS | grep -o -e '--no-dns') || true } attach_router() { 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 [ -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 docker create -v /w -v /w-noop -v /w-nomcast --name=$VOLUMES_CONTAINER_NAME \ --label=weavevolumes --entrypoint=/bin/false $EXEC_IMAGE >/dev/null fi start_if_necessary proxy $PROXY_CONTAINER_NAME $EXEC_IMAGE \ --privileged --net=host --pid=host $(docker_sock_options) \ $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() { start_if_necessary plugin $PLUGIN_CONTAINER_NAME $PLUGIN_IMAGE \ --privileged --net=host $(docker_sock_options) \ --restart=always \ -v /run/docker/plugins:/run/docker/plugins \ -e WEAVE_HTTP_ADDR \ $PLUGIN_IMAGE "$@" wait_for_status $PLUGIN_CONTAINER_NAME http_call_unix $PLUGIN_CONTAINER_NAME status.sock util_op create-plugin-network weave weavemesh } plugin_disabled() { [ -n "$WEAVE_NO_PLUGIN" ] || ! check_docker_server_api_version 1.21 } warn_plugin_active() { echo "WARNING: docker may hang in this state; re-launch weave and remove attached containers before shutting down" >&2 } stop_plugin() { util_op remove-plugin-network weave || warn_plugin_active stop $PLUGIN_CONTAINER_NAME "Plugin" } ########################################################################################## [ $(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" ;; --with-dns) echo "Warning: $1 has been removed; DNS is on by default" >&2 ;; esac shift done } [ $# -gt 0 ] || usage COMMAND=$1 shift 1 case "$COMMAND" in setup) for img in $IMAGE $EXEC_IMAGE $PLUGIN_IMAGE ; do docker pull $img done ;; 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) cat 1>&2 <&2 <&2 exit 1 fi ;; bridge-type) detect_bridge_type && echo $BRIDGE_TYPE ;; launch) deprecation_warnings "$@" COMMON_ARGS=$(common_launch_args "$@") launch_router "$@" >/dev/null launch_proxy $COMMON_ARGS >/dev/null plugin_disabled || launch_plugin_if_not_running $COMMON_ARGS >/dev/null ;; launch-router) deprecation_warnings "$@" launch_router "$@" ;; attach-router) check_running $CONTAINER_NAME enforce_docker_bridge_addr_assign_type create_bridge fetch_router_args with_container_netns $CONTAINER_NAME setup_router_iface_$BRIDGE_TYPE wait_for_status $CONTAINER_NAME http_call $HTTP_ADDR attach_router ;; launch-proxy) deprecation_warnings "$@" launch_proxy "$@" ;; launch-plugin) launch_plugin_if_not_running "$@" ;; 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 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_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 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 allocate $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) DNS_EXTRA_HOSTS= REWRITE_HOSTS= NO_MULTICAST_ROUTE= 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 ;; --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$ATTACH_TYPE allocate $CONTAINER $CIDR_ARGS [ -n "$REWRITE_HOSTS" ] && rewrite_etc_hosts $DNS_EXTRA_HOSTS 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 lookup $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 ;; 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%/*} done with_container_netns_or_die $1 attach $ALL_CIDRS when_weave_running with_container_fqdn $1 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 ipam_cidrs allocate_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 fi [ -n "$FQDN" ] && when_weave_running put_dns_fqdn_no_check_alive weave:expose $FQDN $CIDR done 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 ] || usage plugin_disabled || util_op remove-plugin-network weave || warn_plugin_active warn_if_stopping_proxy_in_env call_weave DELETE /peer >/dev/null 2>&1 || true 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 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) [ $# -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