Files
weave/weave
T
Adam Harrison b8afefa55f Merge pull request #931 from paulbellamy/weavedns_default_search_domain
[dns] resolve names in `weave.local` by default unless hostname or search path is specified
2015-06-16 18:13:17 +01:00

1277 lines
41 KiB
Bash
Executable File

#!/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 <password>] [-nickname <nickname>] [-nodiscovery]"
echo " [-iprange <cidr> [-ipsubnet <cidr>]] <peer> ..."
echo "weave launch-dns [<addr>]"
echo "weave launch-proxy [-D <endpoint>] [-H <endpoint>]"
echo " [--with-dns | --without-dns] [--with-ipam]"
echo "weave connect [--replace] [<peer> ...]"
echo "weave forget <peer> ..."
echo "weave run [--with-dns | --without-dns] [<addr> ...] <docker run args> ..."
echo "weave start [<addr> ...] <container_id>"
echo "weave attach [<addr> ...] <container_id>"
echo "weave detach [<addr> ...] <container_id>"
echo "weave dns-add <ip_address> [<ip_address> ...] <container_id> [-h <fqdn>]"
echo "weave dns-remove <ip_address> [<ip_address> ...] <container_id> [-h <fqdn>]"
echo "weave expose [<addr> ...] [-h <fqdn>]"
echo "weave hide [<addr> ...]"
echo "weave ps [<container_id> ...]"
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 <peer_id>"
echo
echo "where <peer> = <ip_address_or_fqdn>[:<port>]"
echo " <cidr> = <ip_address>/<routing_prefix_length>"
echo " <addr> = [ip:]<cidr> | net:<cidr> | net:default"
echo " <endpoint> = [tcp://][<ip_address>]:<port>"
echo " <peer_id> = <nickname> 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" \
$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
# (<https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=4bf6980dd0328530783fd657c776e3719b421d30>>).
arp_update() {
# It's not the end of the world if this doesn't run - we configure
# ARP caches so that stale entries will be noticed quickly.
if command_exists arping ; then
$3 arping -U -q -I $1 -c 1 ${2%/*}
fi
}
# Generate a random MAC value
random_mac() {
# In the first byte of the MAC, the 'multicast' bit should be
# clear and 'locally administered' bit should be set. All other
# bits should be random.
od -txC -An -N6 /dev/urandom | ( read a b c d e f && printf "%02x:$b:$c:$d:$e:$f" $((0x$a & ~1 | 2)) )
}
######################################################################
# weave and docker specific helpers
######################################################################
create_bridge() {
if [ ! -d /sys/class/net/$BRIDGE ] ; then
ip link add name $BRIDGE type bridge
# Set a random MAC address on the bridge. Current Linux
# kernels already do this when creating a bridge, but there
# are rumours it was not always so.
ip link set dev $BRIDGE address $(random_mac)
# Attempting to set the bridge MTU to a high value directly
# fails. Bridges take the lowest MTU of their interfaces. So
# instead we create a temporary interface with the desired
# MTU, attach that to the bridge, and then remove it again.
ip link add name v${CONTAINER_IFNAME}du mtu $MTU type dummy
ip link set dev v${CONTAINER_IFNAME}du master $BRIDGE
ip link del dev v${CONTAINER_IFNAME}du
# Drop traffic from Docker bridge to Weave; it can break subnet isolation
if [ "$DOCKER_BRIDGE" != "$BRIDGE" ] ; then
# Note using -I to insert ahead of Docker's bridge rules
run_iptables -t filter -I FORWARD -i $DOCKER_BRIDGE -o $BRIDGE -j DROP
fi
# Work around the situation where there are no rules allowing traffic
# across our bridge. E.g. ufw
add_iptables_rule filter FORWARD -i $BRIDGE -o $BRIDGE -j ACCEPT
# create a chain for masquerading
run_iptables -t nat -N WEAVE >/dev/null 2>&1 || true
add_iptables_rule nat POSTROUTING -j WEAVE
fi
[ "$1" != "--without-ethtool" ] && ethtool -K $BRIDGE tx off >/dev/null
ip link set dev $BRIDGE up
# Configure the ARP cache parameters on the bridge interface for
# the sake of 'weave expose'
configure_arp_cache $BRIDGE
}
destroy_bridge() {
[ -d /sys/class/net/$BRIDGE ] && ip link del dev $BRIDGE
if [ "$DOCKER_BRIDGE" != "$BRIDGE" ] ; then
run_iptables -t filter -D FORWARD -i $DOCKER_BRIDGE -o $BRIDGE -j DROP 2>/dev/null || true
fi
run_iptables -t filter -D FORWARD -i $BRIDGE -o $BRIDGE -j ACCEPT 2>/dev/null || true
run_iptables -t nat -F WEAVE >/dev/null 2>&1 || true
run_iptables -t nat -D POSTROUTING -j WEAVE >/dev/null 2>&1 || true
run_iptables -t nat -X WEAVE >/dev/null 2>&1 || true
}
docker_bridge_ip() {
DOCKER_BRIDGE_IP=$(ip -4 addr show dev $DOCKER_BRIDGE | grep -m1 -o 'inet [.0-9]*')
DOCKER_BRIDGE_IP=${DOCKER_BRIDGE_IP#inet }
}
# the following borrows from https://github.com/jpetazzo/pipework
# Set $CONTAINER_PID to the PID of container $1, $LOCAL_IFNAME and
# $GUEST_IFNAME to suitable names for two ends of a veth pair, specific
# to the container, and execute args $2 $3 ... as a command. If an error
# is caused by container dying, swallow output from error.
with_container_netns() {
CONTAINER="$1"
CONTAINER_PID=$(docker inspect --format='{{.State.Pid}}' $CONTAINER)
if [ "$CONTAINER_PID" = 0 ] ; then
echo "Container $CONTAINER not running." >&2
exit 1
fi
if [ "$CONTAINER_PID" = "<no value>" ] ; then
echo "Container $CONTAINER unknown to Docker." >&2
exit 1
fi
LOCAL_IFNAME="v${CONTAINER_IFNAME}pl${CONTAINER_PID}"
GUEST_IFNAME="v${CONTAINER_IFNAME}pg${CONTAINER_PID}"
IP_TMPOUT=/tmp/weave_ip_out_$$
IP_TMPERR=/tmp/weave_ip_err_$$
rm -f $IP_TMPOUT $IP_TMPERR
# Run the wrapped command
STATUS=0
shift 1
if ! "$@" >$IP_TMPOUT 2>$IP_TMPERR ; then
STATUS=1
if [ ! -d $PROCFS/$CONTAINER_PID ] ; then
echo "Container $CONTAINER died" >&2
else
echo "Failure during network configuration for container $CONTAINER:" >&2
cat $IP_TMPERR >&2
fi
else
cat $IP_TMPOUT
cat $IP_TMPERR >&2
fi
rm -f $IP_TMPOUT $IP_TMPERR
return $STATUS
}
with_container_netns_or_die() {
if ! with_container_netns "$@" >/dev/null ; then
docker kill $1 >/dev/null 2>&1 || true
exit 1
fi
}
# Execute arguments as a command within the network namespace of $CONTAINER_PID
netnsenter() {
nsenter --net=$PROCFS/$CONTAINER_PID/ns/net "$@"
}
connect_container_to_bridge() {
if [ -h "$PROCFS/$CONTAINER_PID/ns/net" -a -h "$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 $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 "$@"
}
# Wait until container $1 is alive enough to respond to "GET /status"
# http request on its docker-assigned IP, port $2
wait_for_status() {
container_ip $1 "$1 container has died." "$1 container has died." || 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 "$1 container has died." >&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 "$1 container has died." >&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 --subnet (use the -iprange option on 'weave launch')" >&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 weaveproxy | 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 \
$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 \
-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 \
--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