diff --git a/README.md b/README.md index c0560220..20202d17 100644 --- a/README.md +++ b/README.md @@ -55,20 +55,20 @@ containers, one on each host. On $HOST1 run (as root) - host1# weave launch 10.0.0.1/16 + host1# weave launch host1# C=$(weave run 10.0.1.1/24 -t -i ubuntu) The first line starts the weave router, in a container. This needs to -be done once on each host. We tell weave that its IP address should -be 10.0.0.1, and that the weave network is on 10.0.0.0/16. +be done once on each host. The second line runs our application container. We give it an IP -address and network (a subnet of the weave network). `weave run` -invokes `docker run -d` with all the parameters following the IP -address and network. So we could be launching any container this way; -here we just take a stock ubuntu container and launch a shell in -it. There's also a `weave start` command, which invokes `docker start` -for starting existing containers. +address and network, in +[CIDR notation](http://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_notation). +`weave run` invokes `docker run -d` with all the parameters following +the IP address and network. So we could be launching any container +this way; here we just take a stock ubuntu container and launch a +shell in it. There's also a `weave start` command, which invokes +`docker start` for starting existing containers. If our application consists of more than one container on this host we simply launch them with a variation on that second line. @@ -81,13 +81,13 @@ unique. We repeat similar steps on $HOST2... - host2# weave launch 10.0.0.2/16 $HOST1 + host2# weave launch $HOST1 host2# C=$(weave run 10.0.1.2/24 -t -i ubuntu) -The only difference, apart from the choice of IP addresses for the -weave router and the application container, is that we tell our weave -that it should peer with the weave on $HOST1 (specified as the IP -address or hostname by which $HOST2 can reach it). NB: if there is a +The only difference, apart from the choice of IP address for the +application container, is that we tell our weave that it should peer +with the weave on $HOST1 (specified as the IP address or hostname, and +optional `:port`, by which $HOST2 can reach it). NB: if there is a firewall between $HOST1 and $HOST2, you must open port 6783 for TCP and UDP. diff --git a/Vagrantfile b/Vagrantfile index 5ef24b19..c621d770 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -6,7 +6,7 @@ pkgs = "lxc-docker golang ethtool libpcap-dev git mercurial" Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| config.vm.box = "phusion/ubuntu-14.04-amd64" config.vm.box_url = "https://oss-binaries.phusionpassenger.com/vagrant/boxes/latest/ubuntu-14.04-amd64-vbox.box" - + config.vm.network "private_network", ip: vm_ip config.vm.synced_folder "./", "/home/vagrant/src/github.com/zettio/weave" diff --git a/router/consts.go b/router/consts.go index 2d851dfb..02a93edf 100644 --- a/router/consts.go +++ b/router/consts.go @@ -11,7 +11,7 @@ const ( EthernetOverhead = 14 UDPOverhead = 28 // 20 bytes for IPv4, 8 bytes for UDP Port = 6783 - StatusPort = Port + 1 + HttpPort = Port + 1 DefaultPMTU = 65535 MaxUDPPacketSize = 65536 ChannelSize = 16 diff --git a/router/ethernet_decoder.go b/router/ethernet_decoder.go index 62177383..65051909 100644 --- a/router/ethernet_decoder.go +++ b/router/ethernet_decoder.go @@ -67,24 +67,14 @@ func (dec *EthernetDecoder) formICMPMTUPacket(mtu int) ([]byte, error) { var ( // see http://en.wikipedia.org/wiki/Multicast_address#Ethernet - broadcastMAC, _ = net.ParseMAC("ff:ff:ff:ff:ff:ff") - zeroMAC, _ = net.ParseMAC("00:00:00:00:00:00") - stpMACPrefix = []byte{0x01, 0x80, 0xC2, 0x00, 0x00} - ipv4MulticastMACPrefix = []byte{0x01, 0x00, 0x5E} - ipv6MulticastMACPrefix = []byte{0x33, 0x33} + stpMACPrefix = []byte{0x01, 0x80, 0xC2, 0x00, 0x00} + zeroMAC, _ = net.ParseMAC("00:00:00:00:00:00") ) func (dec *EthernetDecoder) DropFrame() bool { return bytes.Equal(stpMACPrefix, dec.eth.DstMAC[:len(stpMACPrefix)]) } -func (dec *EthernetDecoder) BroadcastFrame() bool { - return bytes.Equal(broadcastMAC, dec.eth.DstMAC) || - // treat multicast frames as broadcast - bytes.Equal(ipv4MulticastMACPrefix, dec.eth.DstMAC[:len(ipv4MulticastMACPrefix)]) || - bytes.Equal(ipv6MulticastMACPrefix, dec.eth.DstMAC[:len(ipv6MulticastMACPrefix)]) -} - func (dec *EthernetDecoder) IsPMTUVerify() bool { return bytes.Equal(zeroMAC, dec.eth.SrcMAC) && bytes.Equal(zeroMAC, dec.eth.DstMAC) diff --git a/router/router.go b/router/router.go index 34541035..5d29ff70 100644 --- a/router/router.go +++ b/router/router.go @@ -34,7 +34,7 @@ func NewRouter(iface *net.Interface, name PeerName, password []byte, connLimit i ourself := NewPeer(name, 0, 0, router) router.Ourself = router.Peers.FetchWithDefault(ourself) router.Ourself.StartLocalPeer() - log.Println("Local identity is", router.Ourself.Name) + log.Println("Our name is", router.Ourself.Name) return router } @@ -58,7 +58,7 @@ func (router *Router) Start() { func (router *Router) Status() string { var buf bytes.Buffer - buf.WriteString(fmt.Sprintln("Local identity is", router.Ourself.Name)) + buf.WriteString(fmt.Sprintln("Our name is", router.Ourself.Name)) buf.WriteString(fmt.Sprintln("Sniffing traffic on", router.Iface)) buf.WriteString(fmt.Sprintf("MACs:\n%s", router.Macs)) buf.WriteString(fmt.Sprintf("Peers:\n%s", router.Peers)) @@ -124,7 +124,8 @@ func (router *Router) handleCapturedPacket(frameData []byte, dec *EthernetDecode frameLen := len(frameData) frameCopy := make([]byte, frameLen, frameLen) copy(frameCopy, frameData) - if !found || dec.BroadcastFrame() { + + if !found { return checkFrameTooBig(router.Ourself.Broadcast(df, frameCopy, dec)) } else { return checkFrameTooBig(router.Ourself.Forward(dstPeer, df, frameCopy, dec)) @@ -287,7 +288,7 @@ func (router *Router) handleUDPPacketFunc(dec *EthernetDecoder, po PacketSink) F checkWarn(po.WritePacket(frame)) dstPeer, found = router.Macs.Lookup(dec.eth.DstMAC) - if !found || dec.BroadcastFrame() || dstPeer != router.Ourself { + if !found || dstPeer != router.Ourself { return checkFrameTooBig(router.Ourself.RelayBroadcast(srcPeer, df, frame, dec), srcPeer) } diff --git a/weave b/weave index cd566cf3..a92dd811 100755 --- a/weave +++ b/weave @@ -3,20 +3,21 @@ set -e usage() { echo "Usage:" - echo "weave launch [-password ] ..." - echo "weave run ..." - echo "weave start " - echo "weave attach " - echo "weave detach " - echo "weave expose " - echo "weave hide " - echo "weave ps" + echo "weave launch [-password ] ..." + echo "weave connect " + echo "weave run ..." + echo "weave start " + echo "weave attach " + echo "weave detach " + echo "weave expose " + echo "weave hide " echo "weave status" echo "weave version" echo "weave stop" echo "weave reset" echo - echo "where is of the form /" + echo "where is of the form [:], and" + echo " is of the form /" exit 1 } @@ -34,9 +35,12 @@ command_exists () { [ $# -gt 0 ] || usage IMAGE=zettio/weave +CONTAINER_NAME=weave BRIDGE=weave CONTAINER_IFNAME=ethwe MTU=65535 +PORT=6783 +HTTP_PORT=6784 COMMAND=$1 @@ -52,6 +56,19 @@ check_command_and_run() { 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 /, 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 @@ -74,8 +91,14 @@ add_iptables_rule() { fi } -add_nat_rule() { - add_iptables_rule nat "$@" +# 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() { @@ -94,6 +117,9 @@ create_bridge() { # 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 } @@ -103,27 +129,28 @@ destroy_bridge() { ip link del dev $BRIDGE fi run_iptables -t filter -D FORWARD -i $BRIDGE -o $BRIDGE -j ACCEPT 2>/dev/null || true -} - -setup_masquerading() { - run_iptables -t nat -N WEAVE >/dev/null 2>&1 || true - add_nat_rule POSTROUTING -j WEAVE - add_nat_rule WEAVE -o $BRIDGE ! -s $1 -j MASQUERADE - add_nat_rule WEAVE -s $1 ! -o $BRIDGE -j MASQUERADE -} - -stop_masquerading() { 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_ip() { + if ! DOCKERIP=$(docker inspect --format='{{ .NetworkSettings.IPAddress }}' $CONTAINER_NAME 2>/dev/null) ; then + echo "Weave is not running." >&2 + exit 1 + fi + if [ -z "$DOCKERIP" ] ; then + echo "Weave is not running." >&2 + exit 1 + fi +} + # the following borrows from https://github.com/jpetazzo/pipework -# run all args as a command in the network namespace of container $2 +# run args $2 $3 ... as a command in the network namespace of container $1 # if an error is caused by container dying, swallow output from error with_container_netns () { - CONTAINER="$2" + CONTAINER="$1" CONTAINER_PID=$(docker inspect --format='{{ .State.Pid }}' $CONTAINER) if [ "$CONTAINER_PID" = 0 ] ; then @@ -148,6 +175,7 @@ with_container_netns () { # Run the wrapped command STATUS=0 + shift 1 if ! "$@" >$IP_TMPFILE 2>&1 ; then STATUS=1 if [ "$(docker inspect --format='{{ .State.Pid }}' $CONTAINER)" != "$CONTAINER_PID" ] ; then @@ -163,67 +191,69 @@ with_container_netns () { return $STATUS } -# Attach Container-ID $1 to the weave network at CIDR address $2, -# creating a virtual ethernet device if necessary. -# Also turn off tx offload on device $3 inside the namespace. -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 $2 >/dev/null ; then - # address was there already - return 0 - fi - - if ! ip netns exec $NETNS ip addr add $2 dev $CONTAINER_IFNAME ; then - return 1 - fi - - return 0 - fi - +connect_container_to_bridge() { if ! ip link add name $LOCAL_IFNAME mtu $MTU type veth peer name $GUEST_IFNAME mtu $MTU ; then return 1 fi - - if ! ip link set $LOCAL_IFNAME master $BRIDGE || - ! ip link set $LOCAL_IFNAME up || - ! ip link set $GUEST_IFNAME netns $NETNS ; then + if ! ip link set $GUEST_IFNAME netns $NETNS ; then # failed before we assigned the veth to the container's # namespace - delete it ip link del $LOCAL_IFNAME type veth || true return 1 fi - if ! ip netns exec $NETNS ip link set $GUEST_IFNAME name $CONTAINER_IFNAME || - ! ip netns exec $NETNS ip addr add $2 dev $CONTAINER_IFNAME || - ! ip netns exec $NETNS ip link set $CONTAINER_IFNAME up || - ! check_command_and_run ethtool ip netns exec $NETNS ethtool -K $3 tx off >/dev/null ; then - return 1 - fi + ip link set $LOCAL_IFNAME master $BRIDGE && + ip link set $LOCAL_IFNAME up && + ip netns exec $NETNS ip link set $GUEST_IFNAME name $CONTAINER_IFNAME +} + + +launch() { + connect_container_to_bridge && + ip netns exec $NETNS ip link set $CONTAINER_IFNAME up && + check_command_and_run ethtool ip netns exec $NETNS ethtool -K eth0 tx off >/dev/null +} + +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 - if ip netns exec $NETNS ip route show | grep '^224\.0\.0\.0/4' >/dev/null ; then - # There's a multicast route already present, leave it alone. 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 || + ! check_command_and_run ethtool ip netns exec $NETNS ethtool -K $CONTAINER_IFNAME tx off >/dev/null ; then + return 1 + fi + # Route multicast packets across the weave network. - ip netns exec $NETNS ip route add 224.0.0.0/4 dev $CONTAINER_IFNAME + 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 Container-ID $1 at CIDR address $2 from the weave network -# removing the virtual ethernet device if there are no more addresses detach() { - if ! ip netns exec $NETNS ip addr show dev $CONTAINER_IFNAME | grep -F $2 >/dev/null ; then + 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 $2 dev $CONTAINER_IFNAME ; then + 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 ethwe)" ] ; then + 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 @@ -239,7 +269,7 @@ container_weave_ips() { weave_ps() { for CONTAINER_ID in $(docker ps -q) ; do - CONTAINER_IPS=$(with_container_netns container_weave_ips $CONTAINER_ID) + CONTAINER_IPS=$(with_container_netns $CONTAINER_ID container_weave_ips) if [ -n "$CONTAINER_IPS" ] ; then echo $CONTAINER_ID $CONTAINER_IPS fi @@ -248,29 +278,24 @@ weave_ps() { case "$COMMAND" in launch) - [ $# -gt 0 ] || usage - IPADDR=$1 - shift 1 - case $(docker inspect --format '{{ .State.Running }} {{ .Config.Image }}' weave 2>/dev/null) in + case $(docker inspect --format '{{ .State.Running }} {{ .Config.Image }}' $CONTAINER_NAME 2>/dev/null) in "true $IMAGE") echo "Weave is already running." >&2 exit 1 ;; "false $IMAGE") - docker rm weave >/dev/null + docker rm $CONTAINER_NAME >/dev/null ;; true*) - echo "Found another running container named 'weave'. Aborting." >&2 + echo "Found another running container named '$CONTAINER_NAME'. Aborting." >&2 exit 1 ;; false*) - echo "Found another container named 'weave'. Aborting." >&2 + echo "Found another container named '$CONTAINER_NAME'. Aborting." >&2 exit 1 ;; esac create_bridge - stop_masquerading - setup_masquerading $IPADDR # We set the router name to the bridge mac since that is # stable across re-creations of the containers. # @@ -278,26 +303,35 @@ case "$COMMAND" in # (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 # 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=weave -p 6783:6783/tcp -p 6783:6783/udp $WEAVE_DOCKER_ARGS $IMAGE -name $MACADDR "$@" | tail -n 1) - with_container_netns attach $CONTAINER $IPADDR eth0 >/dev/null + CONTAINER=$(docker run --privileged -d --name=$CONTAINER_NAME -p $PORT:$PORT/tcp -p $PORT:$PORT/udp $WEAVE_DOCKER_ARGS $IMAGE -name $MACADDR "$@" | tail -n 1) + with_container_netns $CONTAINER launch >/dev/null echo $CONTAINER ;; + connect) + [ $# -eq 1 ] || usage + docker_ip + if command_exists curl ; then + curl -d peer=$1 $DOCKERIP:$HTTP_PORT/connect + else + echo "Cannot find curl. Install it for 'weave connect' to function." >&2 + exit 1 + fi + ;; status) - if ! DOCKERIP=$(docker inspect --format='{{ .NetworkSettings.IPAddress }}' weave 2>/dev/null) ; then - echo "Weave is not running." >&2 - exit 1 - fi - if [ -z "$DOCKERIP" ] ; then - echo "Weave is not running." >&2 - exit 1 - fi + [ $# -eq 0 ] || usage + docker_ip if command_exists wget ; then - wget -O - -q $DOCKERIP:6784/status + wget -O - -q $DOCKERIP:$HTTP_PORT/status elif command_exists curl ; then - curl $DOCKERIP:6784/status + curl $DOCKERIP:$HTTP_PORT/status else echo "Cannot find wget or curl. Install one of them for 'weave status' to function." >&2 exit 1 @@ -307,8 +341,9 @@ case "$COMMAND" in weave_ps ;; version) + [ $# -eq 0 ] || usage # Try the running container first; if no luck try the image - if ! DOCKERIMAGE=$(docker inspect --format='{{ .Image }}' weave 2>/dev/null) ; then + 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 @@ -318,63 +353,68 @@ case "$COMMAND" in ;; run) [ $# -gt 0 ] || usage - IPADDR=$1 + validate_cidr $1 + CIDR=$1 shift 1 create_bridge CONTAINER=$(docker run -d "$@" | tail -n 1) - with_container_netns attach $CONTAINER $IPADDR $CONTAINER_IFNAME >/dev/null + with_container_netns $CONTAINER attach $CIDR >/dev/null echo $CONTAINER ;; start) - [ $# -gt 1 ] || usage - IPADDR=$1 + [ $# -eq 2 ] || usage + validate_cidr $1 create_bridge CONTAINER=$(docker start $2) - with_container_netns attach $CONTAINER $IPADDR $CONTAINER_IFNAME + with_container_netns $CONTAINER attach $1 >/dev/null echo $CONTAINER ;; attach) - [ $# -gt 1 ] || usage + [ $# -eq 2 ] || usage + validate_cidr $1 create_bridge - with_container_netns attach $2 $1 $CONTAINER_IFNAME >/dev/null + with_container_netns $2 attach $1 >/dev/null ;; detach) - [ $# -gt 1 ] || usage - with_container_netns detach $2 $1 >/dev/null + [ $# -eq 2 ] || usage + validate_cidr $1 + with_container_netns $2 detach $1 >/dev/null ;; expose) - [ $# -gt 0 ] || usage - IPADDR=$1 - shift 1 + [ $# -eq 1 ] || usage + validate_cidr $1 create_bridge - if ! ip addr show dev $BRIDGE | grep -qF $IPADDR + if ! ip addr show dev $BRIDGE | grep -qF $1 then - ip addr add dev $BRIDGE $IPADDR + 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) - [ $# -gt 0 ] || usage - IPADDR=$1 - shift 1 + [ $# -eq 1 ] || usage + validate_cidr $1 create_bridge - if ip addr show dev $BRIDGE | grep -qF $IPADDR + if ip addr show dev $BRIDGE | grep -qF $1 then - ip addr del dev $BRIDGE $IPADDR + 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) - if ! docker kill weave >/dev/null 2>&1 ; then + [ $# -eq 0 ] || usage + if ! docker kill $CONTAINER_NAME >/dev/null 2>&1 ; then echo "Weave is not running." fi - docker rm -f weave >/dev/null 2>&1 || true - check_command_and_run conntrack conntrack -D -p udp --dport 6783 >/dev/null 2>&1 || true - stop_masquerading + docker rm -f $CONTAINER_NAME >/dev/null 2>&1 || true + check_command_and_run conntrack conntrack -D -p udp --dport $PORT >/dev/null 2>&1 || true ;; reset) - docker kill weave >/dev/null 2>&1 || true - docker rm -f weave >/dev/null 2>&1 || true - check_command_and_run conntrack conntrack -D -p udp --dport 6783 >/dev/null 2>&1 || true - stop_masquerading + [ $# -eq 0 ] || usage + docker kill $CONTAINER_NAME >/dev/null 2>&1 || true + docker rm -f $CONTAINER_NAME >/dev/null 2>&1 || true + check_command_and_run conntrack conntrack -D -p udp --dport $PORT >/dev/null 2>&1 || true destroy_bridge ;; *) diff --git a/weaver/main.go b/weaver/main.go index 7bb0c416..76f1d82f 100644 --- a/weaver/main.go +++ b/weaver/main.go @@ -141,7 +141,15 @@ func handleHttp(router *weave.Router) { http.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) { io.WriteString(w, router.Status()) }) - address := fmt.Sprintf(":%d", weave.StatusPort) + http.HandleFunc("/connect", func (w http.ResponseWriter, r *http.Request) { + peer := r.FormValue("peer") + if addr, err := net.ResolveTCPAddr("tcp4", weave.NormalisePeerAddr(peer)); err == nil { + router.ConnectionMaker.InitiateConnection(addr.String()) + } else { + http.Error(w, fmt.Sprint("invalid peer address: ", err), http.StatusBadRequest) + } + }) + address := fmt.Sprintf(":%d", weave.HttpPort) err := http.ListenAndServe(address, nil) if err != nil { log.Fatal("Unable to create http listener: ", err)