#!/bin/sh set -e [ -n "$WEAVE_DEBUG" ] && set -x SCRIPT_VERSION="(unreleased version)" if [ "$SCRIPT_VERSION" = "(unreleased version)" ] ; then IMAGE_VERSION=latest else IMAGE_VERSION=$SCRIPT_VERSION fi IMAGE_VERSION=${VERSION:-$IMAGE_VERSION} # 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} # 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() { echo "Usage:" echo "weave setup" echo "weave launch [-password ] [-nickname ] [-nodiscovery]" echo " [-iprange [-ipsubnet ]] ..." echo "weave launch-dns []" echo "weave launch-proxy [-D ] [-H ]" echo " [--with-dns | --without-dns] [--no-default-ipam]" echo "weave connect [--replace] [ ...]" echo "weave forget ..." echo "weave run [--with-dns | --without-dns] [ ...] ..." echo "weave start [ ...] " echo "weave attach [ ...] " echo "weave detach [ ...] " echo "weave dns-add [ ...] [-h ]" echo "weave dns-remove [ ...] [-h ]" echo "weave expose [ ...] [-h ]" echo "weave hide [ ...]" echo "weave ps [ ...]" echo "weave status" echo "weave proxy-env" echo "weave proxy-config" echo "weave version" echo "weave stop" echo "weave stop-dns" echo "weave stop-proxy" echo "weave reset" echo "weave rmpeer " echo echo "where = [:]" echo " = /" echo " = [ip:] | net: | net:default" echo " = [tcp://][]:" echo " = or weave internal peer ID" exit 1 } exec_remote() { docker $DOCKER_CLIENT_ARGS run --rm --privileged --net=host \ -v /var/run/docker.sock:/var/run/docker.sock \ -v /proc:/hostproc \ -e PROCFS=/hostproc \ -e DOCKERHUB_USER="$DOCKERHUB_USER" \ -e VERSION \ -e WEAVE_DEBUG \ -e WEAVE_DOCKER_ARGS \ -e WEAVEDNS_DOCKER_ARGS \ -e WEAVEPROXY_DOCKER_ARGS \ -e WEAVE_PASSWORD \ -e WEAVE_PORT \ -e WEAVE_CONTAINER_NAME \ -e DOCKER_BRIDGE \ -e PROXY_HOST="$PROXY_HOST" \ -e WEAVE_CIDR=none \ $WEAVEEXEC_DOCKER_ARGS $EXEC_IMAGE --local "$@" } check_docker_version() { if ! DOCKER_VERSION=$(docker -v | sed -n -e 's|^Docker version \([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*|\1|p') || [ -z "$DOCKER_VERSION" ] ; then echo "ERROR: Unable to parse docker version" >&2 exit 1 fi # guard against https://github.com/docker/docker/issues/8632 if [ "$DOCKER_VERSION" = "1.3.0" ] ; then echo "You are running Docker version $DOCKER_VERSION, which contains a bug that prevents weave from working properly. Please upgrade." >&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 } 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 } # "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. if [ "$1" = "run" ] ; then shift 1 check_docker_version if [ "$1" != "--without-dns" ] ; then DNS_ARGS=$(exec_remote dns-args "$@" || true) fi shift $(dns_arg_count "$@") collect_cidr_args "$@" shift $CIDR_ARG_COUNT CONTAINER=$(docker $DOCKER_CLIENT_ARGS run $DNS_ARGS -d "$@") exec_remote attach $CIDR_ARGS --or-die $CONTAINER echo $CONTAINER exit 0 elif [ "$1" != "--local" ] ; then exec_remote "$@" exit $? fi shift 1 BASE_IMAGE=$DOCKERHUB_USER/weave BASE_DNS_IMAGE=$DOCKERHUB_USER/weavedns IMAGE=$BASE_IMAGE:$IMAGE_VERSION DNS_IMAGE=$BASE_DNS_IMAGE:$IMAGE_VERSION PROCFS=${PROCFS:-/proc} DOCKER_BRIDGE=${DOCKER_BRIDGE:-docker0} CONTAINER_NAME=${WEAVE_CONTAINER_NAME:-weave} DNS_CONTAINER_NAME=weavedns BRIDGE=weave CONTAINER_IFNAME=ethwe MTU=65535 PORT=${WEAVE_PORT:-6783} HTTP_PORT=6784 DNS_HTTP_PORT=6785 DNS_HTTP_IFNAME=eth0 PROXY_PORT=12375 PROXY_CONTAINER_NAME=weaveproxy ###################################################################### # 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 } run_iptables() { # -w is recent addition to iptables if [ -z "$CHECKED_IPTABLES_W" ] ; then if iptables -S -w >/dev/null 2>&1 ; then IPTABLES_W=-w fi CHECKED_IPTABLES_W=1 fi iptables $IPTABLES_W "$@" } # Add a rule to iptables, if it doesn't exist already add_iptables_rule() { IPTABLES_TABLE="$1" shift 1 if ! run_iptables -t $IPTABLES_TABLE -C "$@" >/dev/null 2>&1 ; then run_iptables -t $IPTABLES_TABLE -A "$@" >/dev/null fi } # Delete a rule from iptables, if it exist delete_iptables_rule() { IPTABLES_TABLE="$1" shift 1 if run_iptables -t $IPTABLES_TABLE -C "$@" >/dev/null 2>&1 ; then run_iptables -t $IPTABLES_TABLE -D "$@" >/dev/null fi } # Configure the ARP cache parameters for the given interface. This # makes containers react more quickly to a change in the MAC address # associated with an IP address. configure_arp_cache() { $2 sh -c "echo 5 >/proc/sys/net/ipv4/neigh/$1/base_reachable_time && echo 2 >/proc/sys/net/ipv4/neigh/$1/delay_first_probe_time && echo 1 >/proc/sys/net/ipv4/neigh/$1/ucast_solicit" } # Send out an unsolicated ARP response to update ARP cache entries # accross 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. if command_exists arping ; then $3 arping -U -q -I $1 -c 1 ${2%/*} fi } # Generate a random MAC value random_mac() { # In the first byte of the MAC, the 'multicast' bit should be # clear and 'locally administered' bit should be set. All other # bits should be random. od -txC -An -N6 /dev/urandom | ( read a b c d e f && printf "%02x:$b:$c:$d:$e:$f" $((0x$a & ~1 | 2)) ) } ###################################################################### # weave and docker specific helpers ###################################################################### create_bridge() { if [ ! -d /sys/class/net/$BRIDGE ] ; then ip link add name $BRIDGE type bridge # Set a random MAC address on the bridge. Current Linux # kernels already do this when creating a bridge, but there # are rumours it was not always so. ip link set dev $BRIDGE address $(random_mac) # Attempting to set the bridge MTU to a high value directly # fails. Bridges take the lowest MTU of their interfaces. So # instead we create a temporary interface with the desired # MTU, attach that to the bridge, and then remove it again. ip link add name v${CONTAINER_IFNAME}du mtu $MTU type dummy ip link set dev v${CONTAINER_IFNAME}du master $BRIDGE ip link del dev v${CONTAINER_IFNAME}du # Drop traffic from Docker bridge to Weave; it can break subnet isolation if [ "$DOCKER_BRIDGE" != "$BRIDGE" ] ; then # Note using -I to insert ahead of Docker's bridge rules run_iptables -t filter -I FORWARD -i $DOCKER_BRIDGE -o $BRIDGE -j DROP fi # Work around the situation where there are no rules allowing traffic # across our bridge. E.g. ufw add_iptables_rule filter FORWARD -i $BRIDGE -o $BRIDGE -j ACCEPT # create a chain for masquerading run_iptables -t nat -N WEAVE >/dev/null 2>&1 || true add_iptables_rule nat POSTROUTING -j WEAVE fi [ "$1" != "--without-ethtool" ] && ethtool -K $BRIDGE tx off >/dev/null ip link set dev $BRIDGE up # Configure the ARP cache parameters on the bridge interface for # the sake of 'weave expose' configure_arp_cache $BRIDGE } destroy_bridge() { [ -d /sys/class/net/$BRIDGE ] && ip link del dev $BRIDGE if [ "$DOCKER_BRIDGE" != "$BRIDGE" ] ; then run_iptables -t filter -D FORWARD -i $DOCKER_BRIDGE -o $BRIDGE -j DROP 2>/dev/null || true fi run_iptables -t filter -D FORWARD -i $BRIDGE -o $BRIDGE -j ACCEPT 2>/dev/null || true run_iptables -t nat -F WEAVE >/dev/null 2>&1 || true run_iptables -t nat -D POSTROUTING -j WEAVE >/dev/null 2>&1 || true run_iptables -t nat -X WEAVE >/dev/null 2>&1 || true } docker_bridge_ip() { DOCKER_BRIDGE_IP=$(ip -4 addr show dev $DOCKER_BRIDGE | grep -m1 -o 'inet [.0-9]*') DOCKER_BRIDGE_IP=${DOCKER_BRIDGE_IP#inet } } # the following borrows from https://github.com/jpetazzo/pipework # Set $CONTAINER_PID to the PID of container $1, $LOCAL_IFNAME and # $GUEST_IFNAME to suitable names for two ends of a veth pair, specific # to the container, and execute args $2 $3 ... as a command. If an error # is caused by container dying, swallow output from error. with_container_netns() { CONTAINER="$1" CONTAINER_PID=$(docker inspect --format='{{.State.Pid}}' $CONTAINER) if [ "$CONTAINER_PID" = 0 ] ; then echo "Container $CONTAINER not running." >&2 exit 1 fi if [ "$CONTAINER_PID" = "" ] ; then echo "Container $CONTAINER unknown to Docker." >&2 exit 1 fi LOCAL_IFNAME="v${CONTAINER_IFNAME}pl${CONTAINER_PID}" GUEST_IFNAME="v${CONTAINER_IFNAME}pg${CONTAINER_PID}" IP_TMPOUT=/tmp/weave_ip_out_$$ IP_TMPERR=/tmp/weave_ip_err_$$ rm -f $IP_TMPOUT $IP_TMPERR # Run the wrapped command STATUS=0 shift 1 if ! "$@" >$IP_TMPOUT 2>$IP_TMPERR ; then STATUS=1 if [ ! -d $PROCFS/$CONTAINER_PID ] ; then echo "Container $CONTAINER died" >&2 else echo "Failure during network configuration for container $CONTAINER:" >&2 cat $IP_TMPERR >&2 fi else cat $IP_TMPOUT cat $IP_TMPERR >&2 fi rm -f $IP_TMPOUT $IP_TMPERR return $STATUS } with_container_netns_or_die() { if ! with_container_netns "$@" >/dev/null ; then docker kill $1 >/dev/null 2>&1 || true exit 1 fi } # Execute arguments as a command within the network namespace of $CONTAINER_PID netnsenter() { nsenter --net=$PROCFS/$CONTAINER_PID/ns/net "$@" } connect_container_to_bridge() { if [ -h "$PROCFS/$CONTAINER_PID/ns/net" -a -h "$PROCFS/$$/ns/net" -a "$(readlink $PROCFS/$CONTAINER_PID/ns/net)" = "$(readlink $PROCFS/$$/ns/net)" ] ; then echo "Container is running in the host network namespace, and therefore cannot be" >&2 echo "connected to weave. Perhaps the container was started with --net=host." >&2 return 1 fi ip link add name $LOCAL_IFNAME mtu $MTU type veth peer name $GUEST_IFNAME mtu $MTU || return 1 if ! ethtool -K $GUEST_IFNAME tx off >/dev/null || ! ip link set $LOCAL_IFNAME master $BRIDGE || ! ip link set $LOCAL_IFNAME up || ! ip link set $GUEST_IFNAME netns $PROCFS/$CONTAINER_PID/ns/net ; then # failed before we assigned the veth to the container's # namespace ip link del $LOCAL_IFNAME type veth || true return 1 fi if ! netnsenter ip link set $GUEST_IFNAME name $CONTAINER_IFNAME || ! configure_arp_cache $CONTAINER_IFNAME "netnsenter" ; then return 1 fi } ask_version() { if ! DOCKERIMAGE=$(docker inspect --format='{{.Image}}' $1 2>/dev/null) ; then if ! DOCKERIMAGE=$(docker inspect --format='{{.Id}}' $2 2>/dev/null) ; then echo "Unable to find $2 image." >&2 fi fi [ -n "$DOCKERIMAGE" ] && docker run --rm -e WEAVE_CIDR=none $DOCKERIMAGE --version } ###################################################################### # functions invoked through with_container_netns ###################################################################### launch() { if ! netnsenter ip link show eth0 >/dev/null ; then echo "Perhaps you are running the docker daemon with container networking disabled (-b=none)." >&2 return 1 fi connect_container_to_bridge && netnsenter ethtool -K eth0 tx off >/dev/null && netnsenter ip link set $CONTAINER_IFNAME up } attach() { if netnsenter ip link show $CONTAINER_IFNAME >/dev/null 2>&1 ; then for ADDR in "$@" ; do # container already has the expected network interface, so assume we set it up already; # just add the IP address. 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 done return 0 fi connect_container_to_bridge || return 1 for ADDR in "$@" ; do netnsenter ip addr add $ADDR dev $CONTAINER_IFNAME || return 1 done netnsenter ip link set $CONTAINER_IFNAME up || return 1 # Route multicast packets across the weave network. if ! netnsenter ip route show | grep '^224\.0\.0\.0/4' >/dev/null ; then netnsenter ip route add 224.0.0.0/4 dev $CONTAINER_IFNAME fi arp_update $CONTAINER_IFNAME $1 "netnsenter" } detach() { netnsenter ip link show $CONTAINER_IFNAME >/dev/null 2>&1 || return 0 for ADDR in "$@" ; do if ! netnsenter ip addr show dev $CONTAINER_IFNAME | grep -F $ADDR >/dev/null ; then # address is not there, leave the device alone continue fi netnsenter ip addr del $ADDR dev $CONTAINER_IFNAME || return 1 done if [ -n "$(netnsenter ip -f inet addr show dev $CONTAINER_IFNAME)" ] ; then # other addresses are left, leave the device alone return 0 fi # Deleting the interface will delete the multicast route we set up netnsenter ip link del $CONTAINER_IFNAME type veth } container_weave_addrs() { netnsenter ip addr show dev $CONTAINER_IFNAME } ###################################################################### # functions for interacting with containers ###################################################################### # Check that a container named $1 with image $2 is not running check_not_running() { case $(docker inspect --format='{{.State.Running}} {{.Config.Image}}' $1 2>/dev/null) in "true $2") echo "$1 is already running." >&2 exit 1 ;; "true $2:"*) echo "$1 is already running." >&2 exit 1 ;; "false $2") docker rm $1 >/dev/null ;; "false $2:"*) docker rm $1 >/dev/null ;; true*) echo "Found another running container named '$1'. Aborting." >&2 exit 1 ;; false*) echo "Found another container named '$1'. Aborting." >&2 exit 1 ;; esac } stop() { if ! docker stop $1 >/dev/null 2>&1 ; then echo "$2 is not running." >&2 fi docker rm -f $1 >/dev/null 2>&1 || true } # Given a container name or short ID in $1, ensure the specified # container is running and then print its full ID to stdout. If # it's not running or doesn't exist, print an error to stderr and # return with an indicative non-zero exit code. container_id() { if ! status=$(docker inspect --format='{{.State.Running}} {{.Id}}' $1 2>/dev/null) ; then echo "Error: No such container: $1" >&2 return 1 fi case "$status" in true*) echo "${status#true }" ;; *) echo "$1 container is not running." >&2 return 2 ;; esac } container_ip() { if ! status=$(docker inspect --format='{{.State.Running}} {{.NetworkSettings.IPAddress}}' $1 2>/dev/null) ; then echo "$2" >&2 return 1 fi case "$status" in "true ") echo "$1 container has no IP address; is Docker networking enabled?" >&2 return 1 ;; true*) CONTAINER_IP="${status#true }" ;; *) echo "$3" >&2 return 1 ;; esac } http_call_ip() { ip="$1" port="$2" http_verb="$3" url="$4" shift 4 curl --connect-timeout 3 -s -S -X $http_verb "$@" http://$ip:$port$url } # Call url $4 with http verb $3 on container $1 at port $2 http_call() { HTTP_CALL_CONTAINER=$1 container_ip $HTTP_CALL_CONTAINER \ "$HTTP_CALL_CONTAINER container is not present. Have you launched it?" \ "$HTTP_CALL_CONTAINER container is not running." \ || return 1 shift 1 if ! http_call_ip $CONTAINER_IP "$@" ; then echo "Call to $HTTP_CALL_CONTAINER failed." >&2 return 1 fi } call_weave() { http_call $CONTAINER_NAME $HTTP_PORT "$@" } call_dns() { http_call $DNS_CONTAINER_NAME $DNS_HTTP_PORT "$@" } death_msg() { echo "The $1 container has died. Consult the logs with 'docker logs $1' for further details." } # Wait until container $1 is alive enough to respond to "GET /status" # http request on its docker-assigned IP, port $2 wait_for_status() { err_msg=$(death_msg $1) container_ip $1 "$err_msg" "$err_msg" || return 1 while true ; do http_call_ip $CONTAINER_IP $2 GET /status >/dev/null 2>&1 && return 0 if ! container_id $1 >/dev/null 2>&1 ; then echo "$err_msg" >&2 return 1 fi sleep 1 done } # Wait until container $1 outputs a line containing $2 in its log wait_for_log() { fifo=/tmp/tmpfifo.$$ mkfifo $fifo || exit 1 docker logs -f $1 2>/dev/null >$fifo & res=0 grep -q "$2" $fifo || res=1 kill $! 2>/dev/null || true rm $fifo [ $res -eq 1 ] && echo "$(death_msg $1)" >&2 return $res } # Call $1 for all containers, passing container ID, all MACs and all IPs with_container_addresses() { COMMAND=$1 shift 1 for CONTAINER_ID in "$@" ; do if [ "$CONTAINER_ID" = "weave:expose" ] ; then ADDRS_CMD="ip addr show dev $BRIDGE" else ADDRS_CMD="with_container_netns $CONTAINER_ID container_weave_addrs" fi if CONTAINER_ADDRS=$($ADDRS_CMD 2>/dev/null) ; then CONTAINER_MAC=$(echo "$CONTAINER_ADDRS" | grep -o 'link/ether .*' | cut -d ' ' -f 2) CONTAINER_IPS=$(echo "$CONTAINER_ADDRS" | grep -o 'inet .*' | cut -d ' ' -f 2) $COMMAND "$CONTAINER_ID" "$CONTAINER_MAC" "$CONTAINER_IPS" fi done } echo_addresses() { echo $1 $2 $3 } 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_dns GET /domain 2>/dev/null || true)} DNS_DOMAIN=${DNS_DOMAIN:-weave.local.} echo $DNS_DOMAIN } dns_args() { if [ \( "$DOCKER_VERSION_MAJOR" -lt 1 \) -o \ \( "$DOCKER_VERSION_MAJOR" -eq 1 -a \ "$DOCKER_VERSION_MINOR" -lt 3 \) ] ; then # Versions prior to 1.2.0 cannot parse `--dns-search=.`, and # versions prior to 1.3.0 fail to include the domain when # setting the hostname, so lookups for non-fully-qualified # names fail. echo "ERROR: Need Docker 1.3.0 or later to configure container DNS; you are running $DOCKER_VERSION" >&2 exit 1 fi docker_bridge_ip NAME_ARG="" HOSTNAME_SPECIFIED= DNS_SEARCH_SPECIFIED= WITH_DNS= WITHOUT_DNS= while [ $# -gt 0 ] ; do case "$1" in --dns-search=*) DNS_SEARCH_SPECIFIED=1 ;; --name) NAME_ARG="$2" shift ;; --name=*) NAME_ARG="${1#*=}" ;; -h|--hostname|--hostname=*) HOSTNAME_SPECIFIED=1 ;; --with-dns) WITH_DNS=1 ;; --without-dns) WITHOUT_DNS=1 ;; esac shift done [ -n "$WITHOUT_DNS" ] && return 0 if [ -z "$WITH_DNS" ] ; then check_running $DNS_CONTAINER_NAME 2>/dev/null || return 0 fi DNS_ARGS="--dns $DOCKER_BRIDGE_IP" if [ -n "$NAME_ARG" -a -z "$HOSTNAME_SPECIFIED" ] ; then DOMAIN=$(dns_domain) DNS_ARGS="$DNS_ARGS --hostname=$NAME_ARG.${DOMAIN%.}" HOSTNAME_SPECIFIED=1 fi if [ -z "$DNS_SEARCH_SPECIFIED" ] ; then if [ -z "$HOSTNAME_SPECIFIED" ] ; then DNS_ARGS="$DNS_ARGS --dns-search=$(dns_domain)" else DNS_ARGS="$DNS_ARGS --dns-search=." fi fi } # Print an error to stderr and return with an indicative exit status # if the container $1 does not exist or isn't running. check_running() { if ! STATUS=$(docker inspect --format='{{.State.Running}}' $1 2>/dev/null); then echo "$1 container is not present. Have you launched it?" >&2 return 1 elif [ "$STATUS" != "true" ] ; then echo "$1 container is not running." >&2 return 2 fi } # Execute $@ only if the weaveDNS container is running when_dns_running() { if check_running $DNS_CONTAINER_NAME 2>/dev/null; then "$@" fi } # Iff the container in $1 has an FQDN, invoke $2 as a command passing # the container as the first argument, the FQDN as the second argument # and $3.. as additional arguments with_container_fqdn() { CONTAINER="$1" COMMAND="$2" shift 2 CONTAINER_FQDN=$(docker inspect --format='{{.Config.Hostname}}.{{.Config.Domainname}}' $CONTAINER 2>/dev/null) || return 0 if echo "$CONTAINER_FQDN" | grep -Eq '[^.]+\..+' ; then $COMMAND "$CONTAINER" "$CONTAINER_FQDN" "$@" fi } # Register FQDN in $2 as names for addresses $3.. under full container ID $1 put_dns_fqdn() { CONTAINER_ID="$1" FQDN="$2" shift 2 for ADDR in "$@" ; do call_dns PUT /name/$CONTAINER_ID/${ADDR%/*} --data-urlencode fqdn=$FQDN || 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_dns 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_dns DELETE /name/$CONTAINER_ID/${ADDR%/*}?fqdn=$FQDN || true done } # Tell the newly-started weaveDNS about existing weave IPs populate_dns() { if ! wait_for_status $DNS_CONTAINER_NAME $DNS_HTTP_PORT ; then echo "If running, it will not be pre-populated." >&2 return 0 fi for CONTAINER in $(docker ps -q --no-trunc) ; do if CONTAINER_IPS=$(with_container_netns $CONTAINER container_weave_addrs 2>&1 | sed -n -e 's/inet \([^/]*\)\/\(.*\)/\1/p') ; then with_container_fqdn $CONTAINER put_dns_fqdn $CONTAINER_IPS fi done } ###################################################################### # IP Allocation Management helpers ###################################################################### # Claim addresses for a container in IPAM. Expects to be called from # with_container_addresses. ipam_claim() { for CIDR in $3 ; do # NB: CONTAINER_IP is the IP of the weave container; it is set by wait_for_status. http_call_ip $CONTAINER_IP $HTTP_PORT PUT /ip/$1/${CIDR%/*} done } # Call IPAM as necessary to allocate addresses # $1 is the full container id and following args are previously parsed CIDR_ARGS. # Returns ALL_CIDRS and IPAM_CIDRS ipam_cidrs() { CONTAINER_ID="$1" shift 1 ALL_CIDRS="" IPAM_CIDRs="" if [ $# -eq 0 ] ; then # If no addresses passed in, get one in the default subnet if ! IPAM_CIDRS=$(call_weave POST /ip/$CONTAINER_ID) ; then return 1 fi if [ "$IPAM_CIDRS" = "404 page not found" ] ; then echo "No IP address supplied (use the -iprange option on 'weave launch' to enable IP address allocation)" >&2 return 1 fi ALL_CIDRS="$IPAM_CIDRS" fi while [ $# -gt 0 ] ; do if [ "${1%:*}" = "net" ] ; then if [ "$1" = "net:default" ] ; then IPAM_URL=/ip/$CONTAINER_ID else IPAM_URL=/ip/$CONTAINER_ID/"${1#net:}" fi if ! CIDR=$(call_weave POST $IPAM_URL) ; then return 1 fi if [ "$CIDR" = "404 page not found" ] ; then echo "IP address allocation must be enabled to use 'net:'" >&2 return 1 fi if ! is_cidr "$CIDR" ; then echo "$CIDR" >&2 return 1 fi IPAM_CIDRS="$IPAM_CIDRS $CIDR" ALL_CIDRS="$ALL_CIDRS $CIDR" else ALL_CIDRS="$ALL_CIDRS $1" fi shift 1 done } ipam_cidrs_or_die() { if ! ipam_cidrs "$@" ; then docker kill $1 >/dev/null 2>&1 || true exit 1 fi } ###################################################################### # weave proxy helpers ###################################################################### # TODO: Handle relative paths for args # TODO: Handle args with spaces tls_arg() { filename=${3:-$2} PROXY_VOLUMES="$PROXY_VOLUMES -v $1:/home/weave/tls/$filename.pem" PROXY_ARGS="$PROXY_ARGS --tls$2 /home/weave/tls/$filename.pem" } proxy_args() { PROXY_VOLUMES="" PROXY_ARGS="" while [ $# -gt 0 ]; do case "$1" in --tlscacert) shift tls_arg "$1" cacert ca ;; --tlscacert=*) tls_arg "${1#*=}" cacert ca ;; --tlscert) shift tls_arg "$1" cert ;; --tlscert=*) tls_arg "${1#*=}" cert ;; --tlskey) shift tls_arg "$1" key ;; --tlskey=*) tls_arg "${1#*=}" key ;; *) PROXY_ARGS="$PROXY_ARGS $1" ;; esac shift done } proxy_addr() { if addr=$(docker logs $PROXY_CONTAINER_NAME 2>/dev/null | head -n3 | grep -oE "proxy listening on .*"); then addr=${addr##* } host=${addr%:*} [ "$host" = "0.0.0.0" ] && host=$PROXY_HOST port=${addr#*:} echo "${1}tcp://${host:-$PROXY_HOST}:${port:-$PROXY_PORT}" return 0 fi echo "$PROXY_CONTAINER_NAME container is not present. Have you launched it?" >&2 return 1 } ########################################################################################## [ $(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 check_docker_version DOCKER_VERSION_MAJOR=$(echo "$DOCKER_VERSION" | cut -d. -f 1) DOCKER_VERSION_MINOR=$(echo "$DOCKER_VERSION" | cut -d. -f 2) DOCKER_VERSION_PATCH=$(echo "$DOCKER_VERSION" | cut -d. -f 3) [ $# -gt 0 ] || usage COMMAND=$1 shift 1 case "$COMMAND" in setup) for img in $IMAGE $DNS_IMAGE $EXEC_IMAGE ; do docker pull $img done ;; # intentionally undocumented since it assumes knowledge of weave # internals create-bridge) create_bridge --without-ethtool ;; launch) check_not_running $CONTAINER_NAME $BASE_IMAGE create_bridge # We set the router name to the bridge mac since that is # stable across re-creations of the containers. # # TODO use the mac of one of the physical host interfaces # (eth0, wlan0, etc) so the name becomes stable across host # restarts. PEERNAME=$(cat /sys/class/net/$BRIDGE/address) # backward compatibility... if is_cidr "$1" ; then echo "WARNING: $1 parameter ignored; 'weave launch' no longer takes a CIDR as the first parameter" >&2 shift 1 fi CONTAINER_PORT=$PORT ARGS="" IPRANGE= IPRANGE_SPECIFIED= while [ $# -gt 0 ] ; do case "$1" in -password) [ $# -gt 1 ] || usage WEAVE_PASSWORD="$2" export WEAVE_PASSWORD shift 2 ;; -port) [ $# -gt 1 ] || usage CONTAINER_PORT="$2" shift 2 ;; -iprange) [ $# -gt 1 ] || usage IPRANGE="$2" IPRANGE_SPECIFIED=1 shift 2 ;; *) ARGS="$ARGS '$(echo "$1" | sed "s|'|'\"'\"'|g")'" shift ;; esac done eval "set -- $ARGS" if [ -z "$IPRANGE_SPECIFIED" ] ; then IPRANGE="10.128.0.0/10" if command_exists netcheck && ! netcheck $IPRANGE ; then echo "Default -iprange $IPRANGE overlaps with existing route." >&2 echo "Please pick another range and set it on all hosts." >&2 exit 1 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. CONTAINER=$(docker run --privileged -d --name=$CONTAINER_NAME \ -p $PORT:$CONTAINER_PORT/tcp -p $PORT:$CONTAINER_PORT/udp \ -v /var/run/docker.sock:/var/run/docker.sock \ -e WEAVE_PASSWORD \ -e WEAVE_CIDR=none \ $WEAVE_DOCKER_ARGS $IMAGE -iface $CONTAINER_IFNAME -port $CONTAINER_PORT -name "$PEERNAME" -nickname "$(hostname)" -iprange "$IPRANGE" "$@") with_container_netns_or_die $CONTAINER launch wait_for_status $CONTAINER_NAME $HTTP_PORT if [ -n "$IPRANGE" ] ; then # Tell the newly-started weave IP allocator about existing weave IPs with_container_addresses ipam_claim weave:expose $(docker ps -q --no-trunc) fi echo $CONTAINER ;; launch-dns) collect_cidr_args "$@" [ $CIDR_ARG_COUNT -lt 2 ] || usage shift $CIDR_ARG_COUNT check_not_running $DNS_CONTAINER_NAME $BASE_DNS_IMAGE create_bridge docker_bridge_ip # Set WEAVEDNS_DOCKER_ARGS in the environment in order to supply # additional parameters, such as resource limits, to docker # when launching the weave container. DNS_CONTAINER=$(docker run --privileged -d --name=$DNS_CONTAINER_NAME \ -p $DOCKER_BRIDGE_IP:53:53/udp \ -e WEAVE_CIDR=none \ -v /var/run/docker.sock:/var/run/docker.sock \ $WEAVEDNS_DOCKER_ARGS $DNS_IMAGE -iface $CONTAINER_IFNAME -httpiface "$DNS_HTTP_IFNAME" "$@") ipam_cidrs_or_die $DNS_CONTAINER $CIDR_ARGS with_container_netns_or_die $DNS_CONTAINER attach $ALL_CIDRS populate_dns echo $DNS_CONTAINER ;; launch-proxy) check_not_running $PROXY_CONTAINER_NAME $EXEC_IMAGE # Set WEAVEPROXY_DOCKER_ARGS in the environment in order to supply # additional parameters, such as resource limits, to docker # when launching the weaveproxy container. proxy_args "$@" PROXY_CONTAINER=$(docker run --privileged -d --name=$PROXY_CONTAINER_NAME --net=host \ $PROXY_VOLUMES \ -v /var/run/docker.sock:/var/run/docker.sock \ -v /proc:/hostproc \ -e PROCFS=/hostproc \ -e WEAVE_CIDR=none \ --entrypoint=/home/weave/weaveproxy \ $WEAVEPROXY_DOCKER_ARGS $EXEC_IMAGE $PROXY_ARGS) wait_for_log $PROXY_CONTAINER_NAME "proxy listening" echo $PROXY_CONTAINER ;; proxy-env) proxy_addr "export DOCKER_HOST=" ;; proxy-config) proxy_addr "-H=" ;; connect) [ $# -gt 0 ] || usage if [ "$1" == "--replace" ]; then replace="-d replace=true" shift fi call_weave POST /connect $replace -d $(peer_args "$@") ;; forget) [ $# -gt 0 ] || usage call_weave POST /forget -d $(peer_args "$@") ;; status) res=0 call_weave GET /status || res=1 echo call_dns GET /status 2>/dev/null || true if check_running $PROXY_CONTAINER_NAME 2>/dev/null ; then echo echo "weave proxy is running" fi [ $res -eq 0 ] ;; ps) [ $# -eq 0 ] && CONTAINERS="weave:expose $(docker ps -q)" || CONTAINERS="$@" with_container_addresses echo_addresses $CONTAINERS ;; version) [ $# -eq 0 ] || usage echo weave script $SCRIPT_VERSION ask_version $CONTAINER_NAME $IMAGE || true ask_version $DNS_CONTAINER_NAME $DNS_IMAGE || true if ! EXEC_IMAGE_ID=$(docker inspect --format='{{.Id}}' $EXEC_IMAGE 2>/dev/null) ; then echo "Unable to find $EXEC_IMAGE image." >&2 else EXEC_VERSION=$(docker images --no-trunc | grep $EXEC_IMAGE_ID | grep -v latest | tr -s ' ' | cut -d ' ' -f 2 | tr "\n" ' ') if [ -n "$EXEC_VERSION" ] ; then echo "weave exec $EXEC_VERSION" else echo "weave exec (unreleased version)" fi fi ;; run) dns_args "$@" shift $(dns_arg_count "$@") collect_cidr_args "$@" shift $CIDR_ARG_COUNT CONTAINER=$(docker run $DNS_ARGS -d "$@") create_bridge ipam_cidrs_or_die $CONTAINER $CIDR_ARGS with_container_netns_or_die $CONTAINER attach $ALL_CIDRS when_dns_running with_container_fqdn $CONTAINER put_dns_fqdn $ALL_CIDRS echo $CONTAINER ;; dns-args) dns_args "$@" echo -n $DNS_ARGS ;; docker-bridge-ip) docker_bridge_ip echo -n $DOCKER_BRIDGE_IP ;; start) collect_cidr_args "$@" shift $CIDR_ARG_COUNT [ $# -eq 1 ] || usage RES=$(docker start $1) CONTAINER=$(container_id $1) create_bridge ipam_cidrs_or_die $CONTAINER $CIDR_ARGS with_container_netns_or_die $CONTAINER attach $ALL_CIDRS when_dns_running with_container_fqdn $CONTAINER put_dns_fqdn $ALL_CIDRS echo $RES ;; attach) collect_cidr_args "$@" shift $CIDR_ARG_COUNT if [ "$1" = "--or-die" ]; then ATTACH_TYPE="_or_die" shift fi [ $# -eq 1 ] || usage CONTAINER=$(container_id $1) create_bridge ipam_cidrs$ATTACH_TYPE $CONTAINER $CIDR_ARGS with_container_netns$ATTACH_TYPE $CONTAINER attach $ALL_CIDRS >/dev/null when_dns_running with_container_fqdn $CONTAINER put_dns_fqdn $ALL_CIDRS ;; detach) collect_cidr_args "$@" shift $CIDR_ARG_COUNT [ $# -eq 1 ] || usage CONTAINER=$(container_id $1) ipam_cidrs $CONTAINER $CIDR_ARGS with_container_netns $CONTAINER detach $ALL_CIDRS >/dev/null when_dns_running with_container_fqdn $CONTAINER delete_dns_fqdn $ALL_CIDRS for CIDR in $IPAM_CIDRS ; do call_weave DELETE /ip/$CONTAINER/${CIDR%/*} done ;; dns-add) collect_ip_args "$@" shift $IP_COUNT [ $IP_COUNT -gt 0 -a \( $# -eq 1 -o \( $# -eq 3 -a "$2" = "-h" \) \) ] || usage check_running $DNS_CONTAINER_NAME CONTAINER=$(container_id $1) if [ $# -eq 1 ] ; then with_container_fqdn $CONTAINER put_dns_fqdn $IP_ARGS else put_dns_fqdn $CONTAINER "$3" $IP_ARGS fi ;; dns-remove) collect_ip_args "$@" shift $IP_COUNT [ $IP_COUNT -gt 0 -a \( $# -eq 1 -o \( $# -eq 3 -a "$2" = "-h" \) \) ] || usage check_running $DNS_CONTAINER_NAME CONTAINER=$(container_id $1) if [ $# -eq 1 ] ; then delete_dns $CONTAINER $IP_ARGS else delete_dns_fqdn $CONTAINER "$3" $IP_ARGS fi ;; expose) collect_cidr_args "$@" shift $CIDR_ARG_COUNT ipam_cidrs weave:expose $CIDR_ARGS if [ $# -eq 0 ] ; then FQDN="" else [ $# -eq 2 -a "$1" = "-h" ] || usage FQDN="$2" fi create_bridge --without-ethtool for CIDR in $ALL_CIDRS ; do if ! ip addr show dev $BRIDGE | grep -qF $CIDR ; then ip addr add dev $BRIDGE $CIDR arp_update $BRIDGE $CIDR add_iptables_rule nat WEAVE -d $CIDR ! -s $CIDR -j MASQUERADE add_iptables_rule nat WEAVE -s $CIDR ! -d $CIDR -j MASQUERADE if [ -n "$FQDN" ] ; then when_dns_running put_dns_fqdn 'weave:expose' $FQDN $CIDR fi fi done ;; hide) collect_cidr_args "$@" shift $CIDR_ARG_COUNT ipam_cidrs 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_dns_running delete_dns 'weave:expose' $CIDR fi done for CIDR in $IPAM_CIDRS ; do call_weave DELETE /ip/weave:expose/${CIDR%/*} done ;; stop) [ $# -eq 0 ] || usage stop $CONTAINER_NAME "Weave" conntrack -D -p udp --dport $PORT >/dev/null 2>&1 || true ;; stop-dns) [ $# -eq 0 ] || usage stop $DNS_CONTAINER_NAME "WeaveDNS" ;; stop-proxy) [ $# -eq 0 ] || usage stop $PROXY_CONTAINER_NAME "Proxy" ;; reset) [ $# -eq 0 ] || usage call_weave DELETE /peer >/dev/null 2>&1 || true for NAME in $CONTAINER_NAME $DNS_CONTAINER_NAME $PROXY_CONTAINER_NAME ; do docker stop $NAME >/dev/null 2>&1 || true docker rm -f $NAME >/dev/null 2>&1 || true done conntrack -D -p udp --dport $PORT >/dev/null 2>&1 || true destroy_bridge for LOCAL_IFNAME in $(ip link show | grep v${CONTAINER_IFNAME}pl | cut -d ' ' -f 2 | tr -d ':') ; do ip link del $LOCAL_IFNAME done ;; rmpeer) [ $# -eq 1 ] || usage PEER=$1 call_weave DELETE /peer/$PEER ;; *) echo "Unknown weave command '$COMMAND'" >&2 usage ;; esac