diff --git a/net/if.go b/net/if.go index 1319dea7..8270bcec 100644 --- a/net/if.go +++ b/net/if.go @@ -15,11 +15,41 @@ func EnsureInterface(ifaceName string) (*net.Interface, error) { return nil, err } defer s.Close() + iface, err := ensureInterface(s, ifaceName) + if err != nil { + return nil, err + } + return iface, err +} + +func ensureInterface(s *nl.NetlinkSocket, ifaceName string) (*net.Interface, error) { if iface, err := findInterface(ifaceName); err == nil { return iface, nil } waitForIfUp(s, ifaceName) - return findInterface(ifaceName) + iface, err := findInterface(ifaceName) + return iface, err +} + +// Wait for an interface to come up and have a route added to the multicast subnet. +// This matches the behaviour in 'weave attach', which is the only context in which +// we expect this to be called. If you change one, change the other to match. +func EnsureInterfaceAndMcastRoute(ifaceName string) (*net.Interface, error) { + s, err := nl.Subscribe(syscall.NETLINK_ROUTE, syscall.RTNLGRP_LINK, syscall.RTNLGRP_IPV4_ROUTE) + if err != nil { + return nil, err + } + defer s.Close() + iface, err := ensureInterface(s, ifaceName) + if err != nil { + return nil, err + } + dest := net.IPv4(224, 0, 0, 0) + if CheckRouteExists(ifaceName, dest) { + return iface, err + } + waitForRoute(s, ifaceName, dest) + return iface, err } func findInterface(ifaceName string) (iface *net.Interface, err error) { diff --git a/net/route.go b/net/route.go index 463f843b..990458b3 100644 --- a/net/route.go +++ b/net/route.go @@ -3,14 +3,16 @@ package net import ( "fmt" "net" + "syscall" "github.com/vishvananda/netlink" + "github.com/vishvananda/netlink/nl" ) // A network is considered free if it does not overlap any existing // routes on this host. This is the same approach taken by Docker. func CheckNetworkFree(subnet *net.IPNet, ignoreIfaceNames map[string]struct{}) error { - return forEachRoute(ignoreIfaceNames, func(route netlink.Route) error { + return forEachRoute(ignoreIfaceNames, func(name string, route netlink.Route) error { if route.Dst != nil && overlaps(route.Dst, subnet) { return fmt.Errorf("Network %s overlaps with existing route %s on host.", subnet, route.Dst) } @@ -26,7 +28,7 @@ func overlaps(n1, n2 *net.IPNet) bool { // For a specific address, we only care if it is actually *inside* an // existing route, because weave-local traffic never hits IP routing. func CheckAddressOverlap(addr net.IP, ignoreIfaceNames map[string]struct{}) error { - return forEachRoute(ignoreIfaceNames, func(route netlink.Route) error { + return forEachRoute(ignoreIfaceNames, func(name string, route netlink.Route) error { if route.Dst != nil && route.Dst.Contains(addr) { return fmt.Errorf("Address %s overlaps with existing route %s on host.", addr, route.Dst) } @@ -34,20 +36,65 @@ func CheckAddressOverlap(addr net.IP, ignoreIfaceNames map[string]struct{}) erro }) } -func forEachRoute(ignoreIfaceNames map[string]struct{}, check func(netlink.Route) error) error { +func forEachRoute(ignoreIfaceNames map[string]struct{}, check func(name string, r netlink.Route) error) error { routes, err := netlink.RouteList(nil, netlink.FAMILY_V4) if err != nil { return err } for _, route := range routes { - if link, err := netlink.LinkByIndex(route.LinkIndex); err == nil { + link, err := netlink.LinkByIndex(route.LinkIndex) + if err == nil { if _, found := ignoreIfaceNames[link.Attrs().Name]; found { continue } } - if err := check(route); err != nil { + if err := check(link.Attrs().Name, route); err != nil { return err } } return nil } + +func CheckRouteExists(ifaceName string, dest net.IP) bool { + found := false + forEachRoute(map[string]struct{}{}, func(name string, route netlink.Route) error { + if name == ifaceName && route.Dst.IP.Equal(dest) { + found = true + } + return nil + }) + return found +} + +func waitForRoute(s *nl.NetlinkSocket, ifaceName string, dest net.IP) error { + native := nl.NativeEndian() + for { + msgs, err := s.Receive() + if err != nil { + return err + } + for _, m := range msgs { + switch m.Header.Type { + case syscall.RTM_NEWROUTE: + attrs, err := syscall.ParseNetlinkRouteAttr(&m) + if err != nil { + return err + } + var ip net.IP + var link netlink.Link + for _, attr := range attrs { + switch attr.Attr.Type { + case syscall.RTA_DST: + ip = attr.Value + case syscall.RTA_OIF: + index := int(native.Uint32(attr.Value[0:4])) + link, _ = netlink.LinkByIndex(index) + } + } + if link.Attrs().Name == ifaceName && ip.Equal(dest) { + return nil + } + } + } + } +} diff --git a/prog/weavewait/main.go b/prog/weavewait/main.go index 69315bb1..43158e93 100644 --- a/prog/weavewait/main.go +++ b/prog/weavewait/main.go @@ -19,7 +19,7 @@ func main() { args = os.Args[1:] ) - _, err := weavenet.EnsureInterface("ethwe") + _, err := weavenet.EnsureInterfaceAndMcastRoute("ethwe") checkErr(err) if len(args) == 0 { diff --git a/weave b/weave index 9a8b9855..fcea80cd 100755 --- a/weave +++ b/weave @@ -528,14 +528,15 @@ attach() { 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 - for ADDR in "$@" ; do arp_update $CONTAINER_IFNAME $ADDR "netnsenter" done + + # Route multicast packets across the weave network. + # This must come last in 'attach'. If you change this, change weavewait to match. + 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 } detach() {