Files
weave/weave
T
Matthias Radestock fee2898c05 remove detailed proxy status
There are two problems with obtaining the proxy status via the normal
proxy endpoint...

1. when TLS is enabled, this requires using TLS at the client end

2. the status URL is mixed in with the docker remote API, which is
rather unclean

There is limited value to having some status output from the proxy,
since all the output is constant.

So instead we log the version and command line args, and in `weave
status` only report whether the proxy is running.

We still want a way to wait for the proxy to start up, and log a nice
error if it dies. We do this by polling the docker logs for a startup
message.

Fixes #877. Fixes #876.

Undoes most of #756, #800, #854.
2015-06-09 14:03:52 +01:00

1150 lines
37 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 potentially used for remote execs, and have defaults, so
# we introduce them here
DOCKERHUB_USER=weaveworks
BASE_EXEC_IMAGE=$DOCKERHUB_USER/weaveexec
EXEC_IMAGE=$BASE_EXEC_IMAGE:$IMAGE_VERSION
# 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>] <peer> ..."
echo "weave launch-dns <cidr>"
echo "weave launch-proxy [-H <docker_endpoint>] [--with-dns] [--with-ipam]"
echo "weave connect [--replace] [<peer> ...]"
echo "weave forget <peer> ..."
echo "weave run [--with-dns] [<cidr> ...] <docker run args> ..."
echo "weave start [<cidr> ...] <container_id>"
echo "weave attach [<cidr> ...] <container_id>"
echo "weave detach <cidr> [<cidr> ...] <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 [<cidr> ...] [-h <fqdn>]"
echo "weave hide [<cidr> ...]"
echo "weave ps [<container_id> ...]"
echo "weave status"
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 " <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 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 \
$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
}
validate_cidr() {
if ! is_cidr "$1" ; then
echo "Invalid address prefix: $1" >&2
echo "Must be in CIDR notation <ip_address>/<prefix_length>, e.g. 10.2.1.1/24" >&2
exit 1
fi
}
collect_cidr_args() {
CIDR_ARGS=""
CIDR_COUNT=0
while is_cidr "$1" ; do
CIDR_ARGS="$CIDR_ARGS $1"
CIDR_COUNT=$((CIDR_COUNT + 1))
shift 1
done
}
is_ip() {
echo "$1" | grep -E "^$IP_REGEXP$" >/dev/null
}
validate_ip() {
if ! is_ip "$1" ; then
echo "Invalid IP: $1" >&2
echo "IP must be of the form <aaa.bbb.ccc.ddd>, e.g. 10.2.1.1" >&2
exit 1
fi
}
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
if [ $IP_COUNT -eq 0 ] ; then
[ $# -gt 0 ] && validate_ip "$1" || usage
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" = "--with-dns" ] ; then
shift 1
DNS_ARGS=$(exec_remote dns-args "$@")
fi
collect_cidr_args "$@"
shift $CIDR_COUNT
CONTAINER=$(docker $DOCKER_CLIENT_ARGS run $DNS_ARGS -d "$@")
exec_remote attach $CIDR_ARGS $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
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
if [ ! "$1" = "--without-ethtool" ] ; then
ethtool -K $BRIDGE tx off >/dev/null
fi
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() {
if [ -d /sys/class/net/$BRIDGE ] ; then
ip link del dev $BRIDGE
fi
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 2>/dev/null || 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 -X $http_verb "$@" http://$ip:$port$url
}
# Call url $4 with http verb $3 on container $1 at port $2
http_call() {
container_ip $1 \
"$1 container is not present. Have you launched it?" \
"$1 container is not running." \
|| return 1
shift 1
http_call_ip $CONTAINER_IP "$@"
}
# 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 && 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
if [ $res -eq 1 ] ; then
echo "$1 container has died." >&2
fi
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
######################################################################
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: The '--with-dns' option requires Docker 1.3.0 or later; you are running $DOCKER_VERSION" >&2
exit 1
fi
docker_bridge_ip
DNS_ARG="--dns $DOCKER_BRIDGE_IP"
DNS_SEARCH_ARG="--dns-search=."
for arg in "$@" ; do
case $arg in
--dns-search=*)
DNS_SEARCH_ARG=""
;;
*)
;;
esac
done
DNS_ARGS="$DNS_ARG $DNS_SEARCH_ARG"
}
# 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 -qv '\.\.$' ; 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
http_call $DNS_CONTAINER_NAME $DNS_HTTP_PORT 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
http_call $DNS_CONTAINER_NAME $DNS_HTTP_PORT 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
http_call $DNS_CONTAINER_NAME $DNS_HTTP_PORT 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
}
# If we don't already have any CIDRs passed from the command-line, get
# one from IPAM. $1 is the full container id.
get_ipam_cidr() {
if [ -n "$CIDR_ARGS" ] ; then
return 0
fi
CIDR_ARGS="$(http_call $CONTAINER_NAME $HTTP_PORT POST /ip/$1)"
if [ "$CIDR_ARGS" = "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
}
######################################################################
# 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
}
##########################################################################################
[ $(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
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=""
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
validate_cidr $2
IPRANGE="-iprange $2"
shift 2
;;
*)
ARGS="$ARGS '$(echo "$1" | sed "s|'|'\"'\"'|g")'"
shift
;;
esac
done
eval "set -- $ARGS"
# 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 "$@")
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)
[ $# -gt 0 ] || usage
CIDR=$1
validate_cidr $CIDR
shift 1
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 "$@")
with_container_netns_or_die $DNS_CONTAINER attach $CIDR
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 \
-p $PROXY_PORT:$PROXY_PORT/tcp \
$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
;;
connect)
[ $# -gt 0 ] || usage
if [ "$1" == "--replace" ]; then
replace="-d replace=true"
shift
fi
http_call $CONTAINER_NAME $HTTP_PORT POST /connect $replace -d $(peer_args "$@")
;;
forget)
[ $# -gt 0 ] || usage
http_call $CONTAINER_NAME $HTTP_PORT POST /forget -d $(peer_args "$@")
;;
status)
res=0
http_call $CONTAINER_NAME $HTTP_PORT GET /status || res=1
echo
http_call $DNS_CONTAINER_NAME $DNS_HTTP_PORT 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)
[ $# -gt 0 ] || usage
if [ "$1" = "--with-dns" ] ; then
shift 1
dns_args "$@"
fi
collect_cidr_args "$@"
shift $CIDR_COUNT
CONTAINER=$(docker run $DNS_ARGS -d "$@")
create_bridge
get_ipam_cidr $CONTAINER
with_container_netns_or_die $CONTAINER attach $CIDR_ARGS
when_dns_running with_container_fqdn $CONTAINER put_dns_fqdn $CIDR_ARGS
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_COUNT
[ $# -eq 1 ] || usage
CONTAINER=$(docker start $1)
create_bridge
get_ipam_cidr $CONTAINER
with_container_netns_or_die $CONTAINER attach $CIDR_ARGS
when_dns_running with_container_fqdn $CONTAINER put_dns_fqdn $CIDR_ARGS
echo $CONTAINER
;;
attach)
collect_cidr_args "$@"
shift $CIDR_COUNT
[ $# -eq 1 ] || usage
CONTAINER=$(container_id $1)
create_bridge
get_ipam_cidr $CONTAINER
with_container_netns $CONTAINER attach $CIDR_ARGS >/dev/null
when_dns_running with_container_fqdn $CONTAINER put_dns_fqdn $CIDR_ARGS
;;
detach)
collect_cidr_args "$@"
shift $CIDR_COUNT
[ $# -eq 1 ] || usage
CONTAINER=$(container_id $1)
with_container_netns $CONTAINER detach $CIDR_ARGS >/dev/null
when_dns_running with_container_fqdn $CONTAINER delete_dns_fqdn $CIDR_ARGS
;;
dns-add)
collect_ip_args "$@"
shift $IP_COUNT
[ $# -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
[ $# -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_COUNT
get_ipam_cidr weave:expose
if [ $# -eq 0 ] ; then
FQDN=""
else
[ $# -eq 2 -a "$1" = "-h" ] || usage
FQDN="$2"
fi
create_bridge --without-ethtool
for CIDR in $CIDR_ARGS ; 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 [ "$FQDN" ] ; then
when_dns_running put_dns_fqdn 'weave:expose' $FQDN $CIDR
fi
fi
done
;;
hide)
collect_cidr_args "$@"
shift $CIDR_COUNT
get_ipam_cidr weave:expose
create_bridge --without-ethtool
for CIDR in $CIDR_ARGS ; 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
[ $CIDR_COUNT -eq 0 ] && http_call $CONTAINER_NAME $HTTP_PORT DELETE /ip/weave:expose 2>/dev/null || true
;;
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
http_call $CONTAINER_NAME $HTTP_PORT 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
http_call $CONTAINER_NAME $HTTP_PORT DELETE /peer/$PEER
;;
*)
echo "Unknown weave command '$COMMAND'" >&2
usage
;;
esac