Files
weave/weave
T
Matthias Radestock f3faf83321 refuse to attach containers running in the host network namespace
Since bad things happen otherwise, i.e. we end up creating veth pairs
with an ethwe on the host

Closes #270.
2014-12-27 15:14:29 +00:00

608 lines
20 KiB
Bash
Executable File

#!/bin/sh
set -e
usage() {
echo "Usage:"
echo "weave launch [-password <password> | -passwordfile <path>] <peer> ..."
echo "weave launch-dns <cidr>"
echo "weave connect <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> <container_id>"
echo "weave expose <cidr>"
echo "weave hide <cidr>"
echo "weave ps"
echo "weave status"
echo "weave version"
echo "weave stop"
echo "weave stop-dns"
echo "weave reset"
echo
echo "where <peer> is of the form <ip_address_or_fqdn>[:<port>], and"
echo " <cidr> is of the form <ip_address>/<routing_prefix_length>"
exit 1
}
[ $(id -u) = 0 ] || {
echo "weave must be run as 'root'" >&2
exit 1
}
[ $# -gt 0 ] || usage
IMAGE=zettio/weave
DNS_IMAGE=zettio/weavedns
CONTAINER_NAME=weave
DNS_CONTAINER_NAME=weavedns
BRIDGE=weave
CONTAINER_IFNAME=ethwe
MTU=65535
PORT=6783
HTTP_PORT=6784
DNS_HTTP_PORT=6785
DOCKER_BRIDGE=${DOCKER_BRIDGE:-docker0}
COMMAND=$1
shift 1
# 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
}
command_exists_or_warn () {
if ! command_exists $1 ; then
echo "Cannot find $1; please install it. Continuing without it." >&2
return 1
fi
}
is_cidr() {
# The regexp here is far from precise, but good enough.
echo "$1" | grep -E '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/[0-9]{1,2}$' >/dev/null
}
validate_cidr() {
if ! is_cidr "$1" ; then
echo "Invalid CIDR: $1" >&2
echo "CIDR must of be of form <ip_address>/<routing_prefix_length>, e.g. 10.0.1.1/24" >&2
exit 1
fi
}
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
}
create_bridge() {
[ ! -d /sys/class/net/$BRIDGE ] && {
ip link add name $BRIDGE type bridge
ip link set dev $BRIDGE address 7a:$(od -txC -An -N5 /dev/urandom | tr \ : | tail -c+2)
# 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
# disable offloading
if command_exists_or_warn ethtool; then ethtool -K $BRIDGE tx off >/dev/null; 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
}
ip link set dev $BRIDGE up
}
destroy_bridge() {
if [ -d /sys/class/net/$BRIDGE ] ; then
ip link del dev $BRIDGE
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 $NETNS to the network namespace of container $1, $LOCAL_IFNAME
# and $GUEST_IFNAME to suitable names for two ends of a veth pair,
# specific to the container, and execute args $2 $3 ... as a command.
# If an error is caused by container dying, swallow output from error.
with_container_netns () {
CONTAINER="$1"
CONTAINER_PID=$(docker inspect --format='{{ .State.Pid }}' $CONTAINER)
if [ "$CONTAINER_PID" = 0 ] ; then
echo "Container $CONTAINER not running." >&2
exit 1
fi
if [ "$CONTAINER_PID" = "<no value>" ] ; then
echo "Container $CONTAINER unknown to Docker." >&2
exit 1
fi
NETNS=$CONTAINER_PID
[ ! -d /var/run/netns ] && mkdir -p /var/run/netns
rm -f /var/run/netns/$NETNS
ln -s /proc/$CONTAINER_PID/ns/net /var/run/netns/$NETNS
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 [ "$(docker inspect --format='{{ .State.Pid }}' $CONTAINER)" != "$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 /var/run/netns/$NETNS
return $STATUS
}
connect_container_to_bridge() {
if [ "$(readlink /proc/$CONTAINER_PID/ns/net)" = "$(readlink /proc/$$/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
if ! ip link add name $LOCAL_IFNAME mtu $MTU type veth peer name $GUEST_IFNAME mtu $MTU ; then
return 1
fi
if { if command_exists_or_warn ethtool; then ethtool -K $GUEST_IFNAME tx off >/dev/null; fi } &&
ip link set $LOCAL_IFNAME master $BRIDGE &&
ip link set $LOCAL_IFNAME up &&
ip link set $GUEST_IFNAME netns $NETNS ; then
ip netns exec $NETNS ip link set $GUEST_IFNAME name $CONTAINER_IFNAME
else
# failed before we assigned the veth to the container's
# namespace - delete it
ip link del $LOCAL_IFNAME type veth || true
return 1
fi
}
launch() {
if ! ip netns exec $NETNS 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 &&
{ if command_exists_or_warn ethtool ; then ip netns exec $NETNS ethtool -K eth0 tx off >/dev/null; fi } &&
ip netns exec $NETNS ip link set $CONTAINER_IFNAME up
}
attach() {
if ip link show $LOCAL_IFNAME >/dev/null 2>&1 ; then
# veth name already exists, so assume we set it up already;
# just add the IP address.
if ip netns exec $NETNS ip addr show dev $CONTAINER_IFNAME | grep -F $1 >/dev/null ; then
# address was there already
return 0
fi
if ! ip netns exec $NETNS ip addr add $1 dev $CONTAINER_IFNAME ; then
return 1
fi
return 0
fi
if ! connect_container_to_bridge ||
! ip netns exec $NETNS ip addr add $1 dev $CONTAINER_IFNAME ||
! ip netns exec $NETNS ip link set $CONTAINER_IFNAME up ; then
return 1
fi
# Route multicast packets across the weave network.
if ! ip netns exec $NETNS ip route show | grep '^224\.0\.0\.0/4' >/dev/null ; then
ip netns exec $NETNS ip route add 224.0.0.0/4 dev $CONTAINER_IFNAME
fi
}
detach() {
if ! ip netns exec $NETNS ip addr show dev $CONTAINER_IFNAME | grep -F $1 >/dev/null ; then
# address is not there, leave the device alone
return 0
fi
if ! ip netns exec $NETNS ip addr del $1 dev $CONTAINER_IFNAME ; then
return 1
fi
if [ -n "$(ip netns exec $NETNS 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
ip link del $LOCAL_IFNAME type veth
}
# Call url $4 with http verb $3 on container $1 at port $2
http_call() {
if ! status=$(docker inspect --format='{{ .State.Running }} {{ .NetworkSettings.IPAddress }}' $1 2>/dev/null); then
echo "$1 container is not present; have you launched it?" >&2
exit 1
fi
case "$status" in
"true ")
echo "$1 container has no IP address; is Docker networking enabled?" >&2
exit 1
;;
true*)
ip="${status#true }"
;;
*)
echo "$1 container is not running." >&2
exit 1
;;
esac
shift 1
http_call_ip $ip "$@"
}
http_call_ip() {
ip="$1"
port="$2"
http_verb="$3"
url="$4"
shift 4
if command_exists curl ; then
curl -s -X $http_verb "$@" http://$ip:$port$url
else
echo "Cannot find curl, which is required for this operation." >&2
exit 1
fi
}
# Perform operation $1 on container ID $2 to local DNS database at address $3
# This function is only called where we know $2 is a valid container name
tell_dns() {
if ! DNSID=$(docker inspect --format='{{ .Id }}' $DNS_CONTAINER_NAME 2>/dev/null) || [ -z "$DNSID" ] ; then
# weavedns not running - silently return
return
fi
# get the long form of the container ID
CONTAINER=$(docker inspect --format '{{.Id}}' $2 2>/dev/null)
# extract IP address and routing prefix from CIDR
WEAVE_ADDR_IP=$(echo $3 | sed -e 's/\([^/]*\)\/\(.*\)/\1/')
MORE_ARGS=$(docker inspect --format '--data-urlencode fqdn={{ .Config.Hostname }}.{{ .Config.Domainname }}.' $CONTAINER 2>/dev/null) && true
http_call $DNS_CONTAINER_NAME $DNS_HTTP_PORT $1 /name/$CONTAINER/$WEAVE_ADDR_IP $MORE_ARGS
}
# Tell the newly-started weaveDNS about existing weave IPs
populate_dns() {
DNS_IP=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' $DNS_CONTAINER_NAME)
WAIT_TIME=1
while ! http_call_ip $DNS_IP $DNS_HTTP_PORT GET /status >/dev/null && [ $WAIT_TIME -lt 4 ]; do
sleep $WAIT_TIME
WAIT_TIME=$((WAIT_TIME+1))
done
if [ $WAIT_TIME = 4 ]; then
echo "Timed out waiting for weaveDNS container to start." >&2
echo "If running, it will not be pre-populated." >&2
fi
for CONTAINER in $(docker ps -q --no-trunc); do
MORE_ARGS=$(docker inspect --format '--data-urlencode fqdn={{ .Config.Hostname }}.{{ .Config.Domainname }}.' $CONTAINER 2>/dev/null) && true
if CONTAINER_ADDRS=$(with_container_netns $CONTAINER container_weave_addrs 2>&1) ; then
CONTAINER_IPS=$(echo "$CONTAINER_ADDRS" | grep -o 'inet .*' | sed -e 's/inet \([^/]*\)\/\(.*\)/\1/')
for IP in $CONTAINER_IPS; do
http_call_ip $DNS_IP $DNS_HTTP_PORT PUT /name/$CONTAINER/$IP $MORE_ARGS
done
fi
done
}
# 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
;;
"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
}
container_weave_addrs() {
ip netns exec $NETNS ip addr show dev $CONTAINER_IFNAME
}
uname -s -r | sed -n 's|^\([^ ]*\) \([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 5 \) -o "$maj" -gt 3 ] ; then
echo "WARNING: Linux kernel version 3.5 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 ! DOCKER_VERSION=$(docker -v | sed -n 's|^Docker version \([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
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)
case "$COMMAND" in
launch)
check_not_running $CONTAINER_NAME $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.
MACADDR=$(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
if [ "$1" = "-password" ] ; then
[ $# -gt 1 ] || usage
PASSWORD=$2
shift 2
elif [ "$1" = "-passwordfile" ] ; then
[ $# -gt 1 ] || usage
PASSWORDFILE=$2
shift 2
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:$PORT/tcp -p $PORT:$PORT/udp \
${PASSWORD:+-e WEAVE_PASSWORD="$PASSWORD"} ${PASSWORDFILE:+--env-file="$PASSWORDFILE"} \
$WEAVE_DOCKER_ARGS $IMAGE -name $MACADDR -iface $CONTAINER_IFNAME "$@")
with_container_netns $CONTAINER launch >/dev/null
echo $CONTAINER
;;
launch-dns)
[ $# -gt 0 ] || usage
CIDR=$1
shift 1
check_not_running $DNS_CONTAINER_NAME $DNS_IMAGE
create_bridge
docker_bridge_ip
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 \
$DNS_IMAGE -iface $CONTAINER_IFNAME "$@")
with_container_netns $DNS_CONTAINER attach $CIDR >/dev/null
populate_dns
echo $DNS_CONTAINER
;;
connect)
[ $# -eq 1 ] || usage
http_call $CONTAINER_NAME $HTTP_PORT POST /connect -d "peer=$1"
;;
status)
http_call $CONTAINER_NAME $HTTP_PORT GET /status
;;
ps)
[ $# -eq 0 ] || usage
for CONTAINER_ID in $(docker ps -q) ; do
if CONTAINER_ADDRS=$(with_container_netns $CONTAINER_ID container_weave_addrs 2>&1) ; 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)
echo $CONTAINER_ID $CONTAINER_MAC $CONTAINER_IPS
fi
done
;;
version)
[ $# -eq 0 ] || usage
# Try the running container first; if no luck try the image
if ! DOCKERIMAGE=$(docker inspect --format='{{ .Image }}' $CONTAINER_NAME 2>/dev/null) ; then
if ! DOCKERIMAGE=$(docker inspect --format='{{ .Id }}' $IMAGE 2>/dev/null) ; then
echo "Unable to find $IMAGE image." >&2
exit 1
fi
fi
docker images --no-trunc | grep $DOCKERIMAGE | grep -v latest | tr -s ' ' | cut -d ' ' -f 2
;;
run)
[ $# -gt 0 ] || usage
if [ "$1" = "--with-dns" ] ; then
shift 1
if [ \( "$DOCKER_VERSION_MAJOR" -lt 1 \) -o \
\( "$DOCKER_VERSION_MAJOR" -eq 1 -a \
"$DOCKER_VERSION_MINOR" -lt 2 \) ] ; then
echo "ERROR: The '--with-dns' option requires Docker 1.2.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
fi
validate_cidr $1
CIDR=$1
shift 1
create_bridge
CONTAINER=$(docker run $DNS_ARG $DNS_SEARCH_ARG -d "$@")
with_container_netns $CONTAINER attach $CIDR >/dev/null
tell_dns PUT $CONTAINER $CIDR
echo $CONTAINER
;;
start)
[ $# -eq 2 ] || usage
validate_cidr $1
create_bridge
CONTAINER=$(docker start $2)
with_container_netns $CONTAINER attach $1 >/dev/null
tell_dns PUT $CONTAINER $1
echo $CONTAINER
;;
attach)
[ $# -eq 2 ] || usage
validate_cidr $1
create_bridge
with_container_netns $2 attach $1 >/dev/null
tell_dns PUT $2 $1
;;
detach)
[ $# -eq 2 ] || usage
validate_cidr $1
with_container_netns $2 detach $1 >/dev/null
tell_dns DELETE $2 $1
;;
expose)
[ $# -eq 1 ] || usage
validate_cidr $1
create_bridge
if ! ip addr show dev $BRIDGE | grep -qF $1
then
ip addr add dev $BRIDGE $1
add_iptables_rule nat WEAVE -o $BRIDGE ! -s $1 -j MASQUERADE
add_iptables_rule nat WEAVE -s $1 ! -o $BRIDGE -j MASQUERADE
fi
;;
hide)
[ $# -eq 1 ] || usage
validate_cidr $1
create_bridge
if ip addr show dev $BRIDGE | grep -qF $1
then
ip addr del dev $BRIDGE $1
delete_iptables_rule nat WEAVE -o $BRIDGE ! -s $1 -j MASQUERADE
delete_iptables_rule nat WEAVE -s $1 ! -o $BRIDGE -j MASQUERADE
fi
;;
stop)
[ $# -eq 0 ] || usage
if ! docker kill $CONTAINER_NAME >/dev/null 2>&1 ; then
echo "Weave is not running." >&2
fi
docker rm -f $CONTAINER_NAME >/dev/null 2>&1 || true
if command_exists_or_warn conntrack; then
conntrack -D -p udp --dport $PORT >/dev/null 2>&1 || true
fi
;;
stop-dns)
[ $# -eq 0 ] || usage
if ! docker kill $DNS_CONTAINER_NAME >/dev/null 2>&1 ; then
echo "WeaveDNS is not running." >&2
fi
docker rm -f $DNS_CONTAINER_NAME >/dev/null 2>&1 || true
;;
reset)
[ $# -eq 0 ] || usage
docker kill $CONTAINER_NAME >/dev/null 2>&1 || true
docker kill $DNS_CONTAINER_NAME >/dev/null 2>&1 || true
docker rm -f $CONTAINER_NAME >/dev/null 2>&1 || true
docker rm -f $DNS_CONTAINER_NAME >/dev/null 2>&1 || true
if command_exists_or_warn conntrack; then
conntrack -D -p udp --dport $PORT >/dev/null 2>&1 || true
fi
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
;;
*)
echo "Unknown weave command '$COMMAND'" >&2
usage
;;
esac