diff --git a/net/veth.go b/net/veth.go index 3710fd7c..031b976f 100644 --- a/net/veth.go +++ b/net/veth.go @@ -85,14 +85,30 @@ func SetupGuest(guest netlink.Link, name string) error { return nil } -func AddAddresses(guest netlink.Link, cidrs []*net.IPNet) error { - for _, ipnet := range cidrs { - if err := netlink.AddrAdd(guest, &netlink.Addr{IPNet: ipnet}); err != nil { - return fmt.Errorf("failed to add IP address to %q: %v", guest.Attrs().Name, err) - } - arping.GratuitousArpOverIfaceByName(ipnet.IP, guest.Attrs().Name) +func AddAddresses(guest netlink.Link, cidrs []*net.IPNet) (newAddrs []*net.IPNet, err error) { + existingAddrs, err := netlink.AddrList(guest, netlink.FAMILY_V4) + if err != nil { + return nil, fmt.Errorf("failed to get IP address for %q: %v", guest.Attrs().Name, err) } - return nil + for _, ipnet := range cidrs { + if contains(existingAddrs, ipnet) { + continue + } + if err := netlink.AddrAdd(guest, &netlink.Addr{IPNet: ipnet}); err != nil { + return nil, fmt.Errorf("failed to add IP address to %q: %v", guest.Attrs().Name, err) + } + newAddrs = append(newAddrs, ipnet) + } + return newAddrs, nil +} + +func contains(addrs []netlink.Addr, addr *net.IPNet) bool { + for _, x := range addrs { + if addr.IP.Equal(x.IPNet.IP) { + return true + } + } + return false } func interfaceExistsInNamespace(ns netns.NsHandle, ifName string) bool { @@ -103,13 +119,14 @@ func interfaceExistsInNamespace(ns netns.NsHandle, ifName string) bool { return err == nil } -func AttachContainer(ns netns.NsHandle, id, ifName, bridgeName string, mtu int, cidrs []*net.IPNet) error { +func AttachContainer(ns netns.NsHandle, id, ifName, bridgeName string, mtu int, withMulticastRoute bool, cidrs []*net.IPNet) error { if !interfaceExistsInNamespace(ns, ifName) { if len(id) > 5 { id = id[:5] } name, peerName := "vethwepl"+id, "vethwg"+id _, err := CreateAndAttachVeth(name, peerName, bridgeName, mtu, func(local, guest netlink.Link) error { + EthtoolTXOff(peerName) // TODO: do we want to do this under fastdp? if err := netlink.LinkSetNsFd(guest, int(ns)); err != nil { return fmt.Errorf("failed to move veth to container netns: %s", err) } @@ -126,13 +143,62 @@ func AttachContainer(ns netns.NsHandle, id, ifName, bridgeName string, mtu int, } if err := WithNetNSLink(ns, ifName, func(guest netlink.Link) error { - if err := AddAddresses(guest, cidrs); err != nil { + newAddresses, err := AddAddresses(guest, cidrs) + if err != nil { return err } - return netlink.LinkSetUp(guest) + if err := netlink.LinkSetUp(guest); err != nil { + return err + } + for _, ipnet := range newAddresses { + arping.GratuitousArpOverIfaceByName(ipnet.IP, ifName) + } + if withMulticastRoute { + /* Route multicast packets across the weave network. + This must come last in 'attach'. If you change this, change weavewait to match. + + TODO: Add the MTU lock to prevent PMTU discovery for multicast + destinations. Without that, the kernel sets the DF flag on + multicast packets. Since RFC1122 prohibits sending of ICMP + errors for packets with multicast destinations, that causes + packets larger than the PMTU to be dropped silently. */ + + _, multicast, _ := net.ParseCIDR("224.0.0.0/4") + if err := AddRoute(guest, netlink.SCOPE_LINK, multicast, nil); err != nil { + return err + } + } + return nil }); err != nil { return err } return nil } + +func DetachContainer(ns netns.NsHandle, id, ifName string, cidrs []*net.IPNet) error { + return WithNetNSLink(ns, ifName, func(guest netlink.Link) error { + existingAddrs, err := netlink.AddrList(guest, netlink.FAMILY_V4) + if err != nil { + return fmt.Errorf("failed to get IP address for %q: %v", guest.Attrs().Name, err) + } + for _, ipnet := range cidrs { + if !contains(existingAddrs, ipnet) { + continue + } + if err := netlink.AddrDel(guest, &netlink.Addr{IPNet: ipnet}); err != nil { + return fmt.Errorf("failed to remove IP address from %q: %v", guest.Attrs().Name, err) + } + } + addrs, err := netlink.AddrList(guest, netlink.FAMILY_V4) + if err != nil { + return fmt.Errorf("failed to get IP address for %q: %v", guest.Attrs().Name, err) + } + if len(addrs) == 0 { // all addresses gone: remove the interface + if err := netlink.LinkDel(guest); err != nil { + return err + } + } + return nil + }) +} diff --git a/plugin/net/cni.go b/plugin/net/cni.go index f7a2b153..1c4727bd 100644 --- a/plugin/net/cni.go +++ b/plugin/net/cni.go @@ -92,7 +92,7 @@ func (c *CNIPlugin) CmdAdd(args *skel.CmdArgs) error { id = fmt.Sprintf("%x", data) } - if err := weavenet.AttachContainer(ns, id, args.IfName, conf.BrName, conf.MTU, []*net.IPNet{&result.IP4.IP}); err != nil { + if err := weavenet.AttachContainer(ns, id, args.IfName, conf.BrName, conf.MTU, false, []*net.IPNet{&result.IP4.IP}); err != nil { return err } if err := weavenet.WithNetNSLink(ns, args.IfName, func(guest netlink.Link) error { diff --git a/prog/weaveutil/attach.go b/prog/weaveutil/attach.go new file mode 100644 index 00000000..7d45f668 --- /dev/null +++ b/prog/weaveutil/attach.go @@ -0,0 +1,72 @@ +package main + +import ( + "net" + "strconv" + + "github.com/vishvananda/netns" + + weavenet "github.com/weaveworks/weave/net" +) + +func attach(args []string) error { + if len(args) < 4 { + cmdUsage("attach-container", "[--no-multicast-route] ...") + } + + withMulticastRoute := true + if args[0] == "--no-multicast-route" { + withMulticastRoute = false + args = args[1:] + } + + pid, err := strconv.Atoi(args[0]) + if err != nil { + return err + } + ns, err := netns.GetFromPid(pid) + if err != nil { + return err + } + mtu, err := strconv.Atoi(args[2]) + if err != nil && args[3] != "" { + return err + } + cidrs, err := parseCIDRs(args[3:]) + if err != nil { + return err + } + return weavenet.AttachContainer(ns, args[0], "ethwe", args[1], mtu, withMulticastRoute, cidrs) +} + +func parseCIDRs(args []string) (cidrs []*net.IPNet, err error) { + for _, ipstr := range args { + ip, ipnet, err := net.ParseCIDR(ipstr) + if err != nil { + return nil, err + } + ipnet.IP = ip // we want the specific IP plus the mask + cidrs = append(cidrs, ipnet) + } + return +} + +func detach(args []string) error { + if len(args) < 2 { + cmdUsage("detach-container", " ...") + } + + pid, err := strconv.Atoi(args[0]) + if err != nil { + return err + } + ns, err := netns.GetFromPid(pid) + if err != nil { + return err + } + cidrs, err := parseCIDRs(args[1:]) + if err != nil { + return err + } + return weavenet.DetachContainer(ns, args[0], "ethwe", cidrs) +} diff --git a/prog/weaveutil/main.go b/prog/weaveutil/main.go index 1d737341..182b5d3d 100644 --- a/prog/weaveutil/main.go +++ b/prog/weaveutil/main.go @@ -19,6 +19,8 @@ func init() { "create-plugin-network": createPluginNetwork, "remove-plugin-network": removePluginNetwork, "container-addrs": containerAddrs, + "attach-container": attach, + "detach-container": detach, } } diff --git a/weave b/weave index a3a8cabc..b7266366 100755 --- a/weave +++ b/weave @@ -847,26 +847,6 @@ netnsenter() { nsenter --net=$CONTAINER_NETNS "$@" } -# connect_container_to_bridge -connect_container_to_bridge() { - ip link add name $LOCAL_IFNAME mtu $MTU type veth peer name $GUEST_IFNAME mtu $MTU || return 1 - - if ! ethtool_tx_off_$BRIDGE_TYPE $GUEST_IFNAME || - ! ip link set $GUEST_IFNAME netns $CONTAINER_NETNS ; 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 $1 || - ! ip link set $LOCAL_IFNAME up || - ! add_iface_$BRIDGE_TYPE $LOCAL_IFNAME || - ! configure_arp_cache $1 "netnsenter" ; then - return 1 - fi -} - add_iface_fastdp() { util_op add-datapath-interface $DATAPATH $1 } @@ -940,59 +920,13 @@ attach() { return 1 fi - if ! netnsenter ip link show $CONTAINER_IFNAME >/dev/null 2>&1 ; then - connect_container_to_bridge $CONTAINER_IFNAME || return 1 - fi - - NEW_ADDRS= - for ADDR in "$@" ; do - 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 - NEW_ADDRS="$NEW_ADDRS $ADDR" - done - - netnsenter ip link set $CONTAINER_IFNAME up || return 1 - - for ADDR in $NEW_ADDRS ; do - arp_update $CONTAINER_IFNAME $ADDR "netnsenter" || true - done - - # Route multicast packets across the weave network. - # This must come last in 'attach'. If you change this, change weavewait to match. - # - # The MTU lock prevents PMTU discovery for multicast - # destinations. Without that, the kernel sets the DF flag on - # multicast packets. Since RFC1122 prohibits sending of ICMP - # errors for packets with multicast destinations, that causes - # packets larger than the PMTU to be dropped silently. - if [ -z "$NO_MULTICAST_ROUTE" ] ; then - 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 mtu lock $MTU - fi - fi + ATTACH_ARGS="" + [ -n "$NO_MULTICAST_ROUTE" ] && ATTACH_ARGS="--no-multicast-route" + util_op attach-container $ATTACH_ARGS $CONTAINER_PID $BRIDGE $MTU "$@" } 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 + util_op detach-container $CONTAINER_PID "$@" } ######################################################################