diff --git a/site/features.md b/site/features.md index d95922b7..2b732a07 100644 --- a/site/features.md +++ b/site/features.md @@ -90,6 +90,11 @@ each other but not the containers of our first application... This isolation-through-subnets scheme is an example of carrying over a well-known technique from the 'on metal' days to containers. +If desired, a container can be attached to multiple subnets when it is +started: + + host1# weave run 10.2.2.1/24 10.2.3.1/24 -t -i ubuntu + NB: By default docker permits communication between containers on the same host, via their docker-assigned IP addresses. For complete isolation between application containers, that feature needs to be @@ -128,6 +133,12 @@ sharing it between applications: host1# weave attach 10.2.1.1/24 $C host1# weave attach 10.2.2.1/24 $C +Finally, multiple addresses can be attached or detached with a single +invocation: + + host1# weave attach 10.2.1.1/24 10.2.2.1/24 10.2.3.1/24 $C + host1# weave detach 10.2.1.1/24 10.2.2.1/24 10.2.3.1/24 $C + ### Security In order to connect containers across untrusted networks, weave peers @@ -173,6 +184,12 @@ will work. And, more interestingly, will work too, which is talking to a container that resides on `$HOST1`. +Multiple subnet addresses can be exposed or hidden with a single +invocation: + + host2# weave expose 10.2.1.102/24 10.2.2.102/24 + host2# weave hide 10.2.1.102/24 10.2.2.102/24 + ### Service export Services running in containers on a weave network can be made diff --git a/test/500_weave_multi_cidr_test.sh b/test/500_weave_multi_cidr_test.sh new file mode 100755 index 00000000..bd1532c2 --- /dev/null +++ b/test/500_weave_multi_cidr_test.sh @@ -0,0 +1,94 @@ +#! /bin/bash + +. ./config.sh + +start_suite "Weave run/start/attach/detach with multiple cidr arguments" + +# Cleanup from previous run +weave_on $HOST1 stop || true +weave_on $HOST1 stop-dns || true +docker_on $HOST1 rm -f multicidr || true + +weave_on $HOST1 launch +weave_on $HOST1 launch-dns 10.254.254.254/24 + +# weave_status_on +weave_status_on() { + HOST=$1; shift + + DOCKER_HOST=tcp://$HOST:2375 $WEAVE status +} + +# weave_ps_on +weave_ps_on() { + HOST=$1; shift + + DOCKER_HOST=tcp://$HOST:2375 $WEAVE ps +} + +# assert_container_cidrs [ ...] +assert_container_cidrs() { + HOST=$1; shift + CID=$1; shift + CIDRS=$@ + + # Assert container has attached CIDRs + assert_raises "weave_ps_on $HOST | grep \"^$CID [^ ]* $CIDRS$\"" +} + +# assert_zone_records [ ...] +assert_zone_records() { + HOST=$1; shift + CID=$1; shift + FQDN=$1; shift + + # Assert correct number of records exist + assert "weave_status_on $HOST | grep \"^$CID\" | wc -l" $# + + # Assert correct records exist + for IP in $@; do + assert_raises "weave_status_on $HOST | grep \"^$CID $IP $FQDN$\"" + done +} + +# assert_bridge_cidrs [ ...] +assert_bridge_cidrs() { + HOST=$1; shift + DEV=$1; shift + CIDRS=$@ + + BRIDGE_CIDRS=$($SSH $HOST ip addr show dev $DEV | grep -o 'inet .*' | cut -d ' ' -f 2) + + assert "echo $BRIDGE_CIDRS" "$CIDRS" +} + +# Run container with three cidrs +CID=$(DOCKER_HOST=tcp://$HOST1:2375 $WEAVE run 10.0.1.1/24 10.0.2.1/24 10.0.3.1/24 -t --name multicidr -h multicidr.weave.local ubuntu | cut -b 1-12) +assert_container_cidrs $HOST1 $CID 10.0.1.1/24 10.0.2.1/24 10.0.3.1/24 +assert_zone_records $HOST1 $CID multicidr.weave.local. 10.0.1.1 10.0.2.1 10.0.3.1 + +# Remove two of them +weave_on $HOST1 detach 10.0.1.1/24 10.0.3.1/24 $CID +assert_container_cidrs $HOST1 $CID 10.0.2.1/24 +assert_zone_records $HOST1 $CID multicidr.weave.local. 10.0.2.1 + +# Put them both back +weave_on $HOST1 attach 10.0.1.1/24 10.0.3.1/24 $CID +assert_container_cidrs $HOST1 $CID 10.0.2.1/24 10.0.1.1/24 10.0.3.1/24 +assert_zone_records $HOST1 $CID multicidr.weave.local. 10.0.2.1 10.0.1.1 10.0.3.1 + +# Stop the container, restart with three IPs +docker_on $HOST1 stop $CID +weave_on $HOST1 start 10.0.1.1/24 10.0.2.1/24 10.0.3.1/24 $CID +assert_container_cidrs $HOST1 $CID 10.0.1.1/24 10.0.2.1/24 10.0.3.1/24 +assert_zone_records $HOST1 $CID multicidr.weave.local. 10.0.1.1 10.0.2.1 10.0.3.1 + +# Expose some cidrs +weave_on $HOST1 expose 10.0.1.2/24 10.0.2.2/24 10.0.3.2/24 +assert_bridge_cidrs $HOST1 weave 10.0.1.2/24 10.0.2.2/24 10.0.3.2/24 + +# Hide some cidrs +weave_on $HOST1 hide 10.0.1.2/24 10.0.3.2/24 +assert_bridge_cidrs $HOST1 weave 10.0.2.2/24 + +end_suite diff --git a/weave b/weave index 6cdc0550..bcbdb08d 100755 --- a/weave +++ b/weave @@ -19,10 +19,10 @@ usage() { echo "weave launch-dns " echo "weave connect " echo "weave forget " - echo "weave run [--with-dns] ..." - echo "weave start " - echo "weave attach " - echo "weave detach " + echo "weave run [--with-dns] [ ...] ..." + echo "weave start [ ...] " + echo "weave attach [ ...] " + echo "weave detach [ ...] " echo "weave expose " echo "weave hide " echo "weave ps" @@ -71,6 +71,7 @@ exec_remote() { if [ "$1" = "--local" ]; then LOCAL="$1" shift 1 + [ -t 0 ] && stty -onlcr elif [ "$1" != "run" ]; then exec_remote "$@" exit $? @@ -138,6 +139,16 @@ validate_cidr() { 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 +} + run_iptables() { # -w is recent addition to iptables if [ -z "$CHECKED_IPTABLES_W" ] ; then @@ -326,23 +337,33 @@ launch() { attach() { if ip netns exec $NETNS ip link show $CONTAINER_IFNAME >/dev/null 2>&1 ; then - # container already has the expected network interface, 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 + for ADDR; do + # container already has the expected network interface, 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 $ADDR >/dev/null ; then + # address was there already + continue + fi - if ! ip netns exec $NETNS ip addr add $1 dev $CONTAINER_IFNAME ; then - return 1 - fi + if ! ip netns exec $NETNS ip addr add $ADDR dev $CONTAINER_IFNAME ; then + return 1 + fi + done 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 + if ! connect_container_to_bridge; then + return 1 + fi + + for ADDR; do + if ! ip netns exec $NETNS ip addr add $ADDR dev $CONTAINER_IFNAME; then + return 1 + fi + done + + if ! ip netns exec $NETNS ip link set $CONTAINER_IFNAME up ; then return 1 fi @@ -355,14 +376,16 @@ attach() { } 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 + for ADDR; do + if ! ip netns exec $NETNS ip addr show dev $CONTAINER_IFNAME | grep -F $ADDR >/dev/null ; then + # address is not there, leave the device alone + continue + fi - if ! ip netns exec $NETNS ip addr del $1 dev $CONTAINER_IFNAME ; then - return 1 - fi + if ! ip netns exec $NETNS ip addr del $ADDR dev $CONTAINER_IFNAME ; then + return 1 + fi + done if [ -n "$(ip netns exec $NETNS ip -f inet addr show dev $CONTAINER_IFNAME)" ] ; then # other addresses are left, leave the device alone @@ -408,15 +431,21 @@ http_call_ip() { # 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() { + METHOD="$1" + CONTAINER_ID="$2" + shift 2 + if ! status=$(docker inspect --format='{{.State.Running}}' $DNS_CONTAINER_NAME 2>/dev/null) || [ "$status" != "true" ] ; 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) + CONTAINER=$(docker inspect --format='{{.Id}}' $CONTAINER_ID 2>/dev/null) # extract IP address and routing prefix from CIDR 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/${3%/*} $MORE_ARGS || true + for ADDR; do + http_call $DNS_CONTAINER_NAME $DNS_HTTP_PORT $METHOD /name/$CONTAINER/${ADDR%/*} $MORE_ARGS || true + done } # Tell the newly-started weaveDNS about existing weave IPs @@ -645,16 +674,15 @@ case "$COMMAND" in DNS_ARGS=$(exec_remote dns-args "$@") [ "$1" = "--with-dns" ] && shift 1 fi - CIDR=$1 - shift 1 - validate_cidr $CIDR + collect_cidr_args "$@" + shift $CIDR_COUNT CONTAINER=$(docker run $DNS_ARGS -d "$@") if [ "$LOCAL" = "--local" ]; then create_bridge - with_container_netns $CONTAINER attach $CIDR >/dev/null - tell_dns PUT $CONTAINER $CIDR + with_container_netns $CONTAINER attach $CIDR_ARGS >/dev/null + tell_dns PUT $CONTAINER $CIDR_ARGS else - exec_remote attach $CIDR $CONTAINER + exec_remote attach $CIDR_ARGS $CONTAINER fi echo $CONTAINER ;; @@ -663,54 +691,59 @@ case "$COMMAND" in echo -n $DNS_ARGS ;; start) - [ $# -eq 2 ] || usage - CIDR=$1 - validate_cidr $CIDR + [ $# -gt 1 ] || usage + collect_cidr_args "$@" + shift $CIDR_COUNT + CONTAINER_ID="$1" create_bridge - CONTAINER=$(docker start $2) - with_container_netns $CONTAINER attach $CIDR >/dev/null - tell_dns PUT $CONTAINER $CIDR + CONTAINER=$(docker start $CONTAINER_ID) + with_container_netns $CONTAINER_ID attach $CIDR_ARGS >/dev/null + tell_dns PUT $CONTAINER_ID $CIDR_ARGS echo $CONTAINER ;; attach) - [ $# -eq 2 ] || usage - CIDR=$1 - validate_cidr $CIDR + [ $# -gt 1 ] || usage + collect_cidr_args "$@" + shift $CIDR_COUNT + CONTAINER_ID="$1" create_bridge - with_container_netns $2 attach $CIDR >/dev/null - tell_dns PUT $2 $CIDR + with_container_netns $CONTAINER_ID attach $CIDR_ARGS >/dev/null + tell_dns PUT $CONTAINER_ID $CIDR_ARGS ;; detach) - [ $# -eq 2 ] || usage - CIDR=$1 - validate_cidr $CIDR - with_container_netns $2 detach $CIDR >/dev/null - tell_dns DELETE $2 $CIDR + [ $# -gt 1 ] || usage + collect_cidr_args "$@" + shift $CIDR_COUNT + CONTAINER_ID="$1" + with_container_netns $CONTAINER_ID detach $CIDR_ARGS >/dev/null + tell_dns DELETE $CONTAINER_ID $CIDR_ARGS ;; expose) - [ $# -eq 1 ] || usage - CIDR=$1 - validate_cidr $CIDR - create_bridge --without-ethtool - 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 -o $BRIDGE ! -s $CIDR -j MASQUERADE - add_iptables_rule nat WEAVE -s $CIDR ! -o $BRIDGE -j MASQUERADE - fi + [ $# -gt 0 ] || usage + for CIDR; do + validate_cidr $CIDR + create_bridge --without-ethtool + 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 -o $BRIDGE ! -s $CIDR -j MASQUERADE + add_iptables_rule nat WEAVE -s $CIDR ! -o $BRIDGE -j MASQUERADE + fi + done ;; hide) - [ $# -eq 1 ] || usage - CIDR=$1 - validate_cidr $CIDR - create_bridge --without-ethtool - if ip addr show dev $BRIDGE | grep -qF $CIDR - then - ip addr del dev $BRIDGE $CIDR - delete_iptables_rule nat WEAVE -o $BRIDGE ! -s $CIDR -j MASQUERADE - delete_iptables_rule nat WEAVE -s $CIDR ! -o $BRIDGE -j MASQUERADE - fi + [ $# -gt 0 ] || usage + for CIDR; do + validate_cidr $CIDR + create_bridge --without-ethtool + if ip addr show dev $BRIDGE | grep -qF $CIDR + then + ip addr del dev $BRIDGE $CIDR + delete_iptables_rule nat WEAVE -o $BRIDGE ! -s $CIDR -j MASQUERADE + delete_iptables_rule nat WEAVE -s $CIDR ! -o $BRIDGE -j MASQUERADE + fi + done ;; stop) [ $# -eq 0 ] || usage