From cdd6ed548c723d1a9c498868fb955be04f6fe19d Mon Sep 17 00:00:00 2001 From: Martynas Pumputis Date: Wed, 19 Oct 2016 15:51:25 +0100 Subject: [PATCH 1/5] Add netdev subcommands to weaveutil The subcommands are going to be used with WithNetNS wrapper. --- common/utils.go | 4 +- prog/weaveutil/main.go | 4 ++ prog/weaveutil/netdev.go | 97 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 2 deletions(-) diff --git a/common/utils.go b/common/utils.go index 19c0d2dd..71cdaf84 100644 --- a/common/utils.go +++ b/common/utils.go @@ -76,7 +76,7 @@ func forEachLink(f func(netlink.Link) error) error { return nil } -func linkToNetDev(link netlink.Link) (NetDev, error) { +func LinkToNetDev(link netlink.Link) (NetDev, error) { addrs, err := netlink.AddrList(link, netlink.FAMILY_V4) if err != nil { return NetDev{}, err @@ -164,5 +164,5 @@ func GetBridgeNetDev(bridgeName string) (NetDev, error) { return NetDev{}, err } - return linkToNetDev(link) + return LinkToNetDev(link) } diff --git a/prog/weaveutil/main.go b/prog/weaveutil/main.go index 5cf72e46..2381827d 100644 --- a/prog/weaveutil/main.go +++ b/prog/weaveutil/main.go @@ -24,6 +24,10 @@ func init() { "attach-container": attach, "detach-container": detach, "configure-arp": configureARP, + "check-iface": checkIface, + "del-iface": delIface, + "setup-iface": setupIface, + "list-netdevs": listNetDevs, } } diff --git a/prog/weaveutil/netdev.go b/prog/weaveutil/netdev.go index 4fee6ee4..a89b82af 100644 --- a/prog/weaveutil/netdev.go +++ b/prog/weaveutil/netdev.go @@ -1,14 +1,75 @@ package main import ( + "fmt" + "strconv" "strings" + "github.com/coreos/go-iptables/iptables" "github.com/j-keck/arping" "github.com/vishvananda/netlink" + "github.com/weaveworks/weave/common" weavenet "github.com/weaveworks/weave/net" ) +// checkIface returns an error if the given interface cannot be found. +func checkIface(args []string) error { + if len(args) != 1 { + cmdUsage("check-iface", "") + } + ifaceName := args[0] + + if _, err := netlink.LinkByName(ifaceName); err != nil { + return err + } + + return nil +} + +func delIface(args []string) error { + if len(args) != 1 { + cmdUsage("del-iface", "") + } + ifName := args[0] + + link, err := netlink.LinkByName(ifName) + if err != nil { + return err + } + return netlink.LinkDel(link) +} + +// setupIface renames the given iface and configures its ARP cache settings. +func setupIface(args []string) error { + if len(args) != 2 { + cmdUsage("setup-iface", " ") + } + ifaceName := args[0] + newIfName := args[1] + + ipt, err := iptables.New() + if err != nil { + return err + } + + link, err := netlink.LinkByName(ifaceName) + if err != nil { + return err + } + if err := netlink.LinkSetName(link, newIfName); err != nil { + return err + } + if err := weavenet.ConfigureARPCache(newIfName); err != nil { + return err + } + if err := ipt.Append("filter", "INPUT", "-i", newIfName, "-d", "224.0.0.0/4", "-j", "DROP"); err != nil { + return err + } + + return nil +} + func configureARP(args []string) error { if len(args) != 1 { cmdUsage("configure-arp", "") @@ -33,3 +94,39 @@ func configureARP(args []string) error { return nil } + +// listNetDevs outputs network ifaces identified by the given indexes +// in the format of weavenet.Dev. +func listNetDevs(args []string) error { + if len(args) != 1 { + cmdUsage("list-netdevs", "[,]") + } + + indexes := make(map[int]struct{}) + for _, index := range strings.Split(args[0], ",") { + if index != "" { + id, err := strconv.Atoi(index) + if err != nil { + return err + } + indexes[id] = struct{}{} + } + } + + links, err := netlink.LinkList() + if err != nil { + return err + } + + for _, link := range links { + if _, found := indexes[link.Attrs().Index]; found { + netdev, err := common.LinkToNetDev(link) + if err != nil { + return err + } + fmt.Println(netdev) + } + } + + return nil +} From 947ae11fee62052ada95c0beb6f938a5023bb53c Mon Sep 17 00:00:00 2001 From: Martynas Pumputis Date: Wed, 19 Oct 2016 16:03:17 +0100 Subject: [PATCH 2/5] Refactor NetDev utilities - Make them safe by using the safe version of WithNetNS. - Improve naming. --- common/utils.go | 162 ++++++++++++++++++----------------- prog/weaveutil/addrs.go | 12 +-- prog/weaveutil/proc-addrs.go | 7 +- 3 files changed, 91 insertions(+), 90 deletions(-) diff --git a/common/utils.go b/common/utils.go index 71cdaf84..fcf512d9 100644 --- a/common/utils.go +++ b/common/utils.go @@ -4,6 +4,8 @@ import ( "fmt" "net" "os" + "regexp" + "strconv" "strings" "github.com/vishvananda/netlink" @@ -27,53 +29,43 @@ func ErrorMessages(errors []error) string { return strings.Join(result, "\n") } +var netdevRegExp = regexp.MustCompile(`^([^ ]+?) ([^ ]+?) \[([^]]*)\]$`) + type NetDev struct { Name string MAC net.HardwareAddr CIDRs []*net.IPNet } -// Search the network namespace of a process for interfaces matching a predicate -// Note that the predicate is called while the goroutine is inside the process' netns -func FindNetDevs(processID int, match func(link netlink.Link) bool) ([]NetDev, error) { - var netDevs []NetDev - - ns, err := netns.GetFromPid(processID) - if err != nil { - if os.IsNotExist(err) { - return nil, nil - } - return nil, err - } - defer ns.Close() - - err = weavenet.WithNetNSUnsafe(ns, func() error { - return forEachLink(func(link netlink.Link) error { - if match(link) { - netDev, err := linkToNetDev(link) - if err != nil { - return err - } - netDevs = append(netDevs, netDev) - } - return nil - }) - }) - - return netDevs, err +func (d NetDev) String() string { + return fmt.Sprintf("%s %s %s", d.Name, d.MAC, d.CIDRs) } -func forEachLink(f func(netlink.Link) error) error { - links, err := netlink.LinkList() - if err != nil { - return err +func ParseNetDev(netdev string) (NetDev, error) { + match := netdevRegExp.FindStringSubmatch(netdev) + if match == nil { + return NetDev{}, fmt.Errorf("invalid netdev: %s", netdev) } - for _, link := range links { - if err := f(link); err != nil { - return err + + iface := match[1] + mac, err := net.ParseMAC(match[2]) + if err != nil { + return NetDev{}, fmt.Errorf("cannot parse mac %s: %s", match[2], err) + } + + var cidrs []*net.IPNet + for _, cidr := range strings.Split(match[3], " ") { + if cidr != "" { + ip, ipnet, err := net.ParseCIDR(cidr) + if err != nil { + return NetDev{}, fmt.Errorf("cannot parse cidr %s: %s", cidr, err) + } + ipnet.IP = ip + cidrs = append(cidrs, ipnet) } } - return nil + + return NetDev{Name: iface, MAC: mac, CIDRs: cidrs}, nil } func LinkToNetDev(link netlink.Link) (NetDev, error) { @@ -89,48 +81,54 @@ func LinkToNetDev(link netlink.Link) (NetDev, error) { return netDev, nil } -// ConnectedToBridgePredicate returns a function which is used to query whether -// a given link is a veth interface which one end is connected to a bridge. -// The returned function should be called from a container network namespace which -// the bridge does NOT belong to. -func ConnectedToBridgePredicate(bridgeName string) (func(link netlink.Link) bool, error) { - indexes := make(map[int]struct{}) +// ConnectedToBridgeVethPeerIds returns peer indexes of veth links connected to +// the given bridge. The peer index is used to query from a container netns +// whether the container is connected to the bridge. +func ConnectedToBridgeVethPeerIds(bridgeName string) ([]int, error) { + var ids []int - // Scan devices in root namespace to find those attached to weave bridge - err := weavenet.WithNetNSLinkByPidUnsafe(1, bridgeName, - func(br netlink.Link) error { - return forEachLink(func(link netlink.Link) error { - if link.Attrs().MasterIndex == br.Attrs().Index { - peerIndex := link.Attrs().ParentIndex - if peerIndex == 0 { - // perhaps running on an older kernel where ParentIndex doesn't work. - // as fall-back, assume the indexes are consecutive - peerIndex = link.Attrs().Index - 1 - } - indexes[peerIndex] = struct{}{} - } - return nil - }) - }) + br, err := netlink.LinkByName(bridgeName) + if err != nil { + return nil, err + } + links, err := netlink.LinkList() if err != nil { return nil, err } - return func(link netlink.Link) bool { - _, isveth := link.(*netlink.Veth) - _, found := indexes[link.Attrs().Index] - return isveth && found - }, nil + for _, link := range links { + if _, isveth := link.(*netlink.Veth); isveth && link.Attrs().MasterIndex == br.Attrs().Index { + peerID := link.Attrs().ParentIndex + if peerID == 0 { + // perhaps running on an older kernel where ParentIndex doesn't work. + // as fall-back, assume the peers are consecutive + peerID = link.Attrs().Index - 1 + } + ids = append(ids, peerID) + } + } + + return ids, nil } -func GetNetDevsWithPredicate(processID int, predicate func(link netlink.Link) bool) ([]NetDev, error) { - // Bail out if this process is running in the root namespace - nsToplevel, err := netns.GetFromPid(1) +// Lookup the weave interface of a container +func GetWeaveNetDevs(processID int) ([]NetDev, error) { + peerIDs, err := ConnectedToBridgeVethPeerIds("weave") + if err != nil { + return nil, err + } + + return GetNetDevsByVethPeerIds(processID, peerIDs) +} + +func GetNetDevsByVethPeerIds(processID int, peerIDs []int) ([]NetDev, error) { + // Bail out if this container is running in the root namespace + netnsRoot, err := netns.GetFromPid(1) if err != nil { return nil, fmt.Errorf("unable to open root namespace: %s", err) } - defer nsToplevel.Close() - nsContainr, err := netns.GetFromPid(processID) + defer netnsRoot.Close() + netnsContainer, err := netns.GetFromPid(processID) if err != nil { // Unable to find a namespace for this process - just return nothing if os.IsNotExist(err) { @@ -138,22 +136,32 @@ func GetNetDevsWithPredicate(processID int, predicate func(link netlink.Link) bo } return nil, fmt.Errorf("unable to open process %d namespace: %s", processID, err) } - defer nsContainr.Close() - if nsToplevel.Equal(nsContainr) { + defer netnsContainer.Close() + if netnsRoot.Equal(netnsContainer) { return nil, nil } - return FindNetDevs(processID, predicate) -} + var netdevs []NetDev -// Lookup the weave interface of a container -func GetWeaveNetDevs(processID int) ([]NetDev, error) { - p, err := ConnectedToBridgePredicate("weave") + peersStr := make([]string, len(peerIDs)) + for i, id := range peerIDs { + peersStr[i] = strconv.Itoa(id) + } + netdevsStr, err := weavenet.WithNetNSByPid(processID, "list-netdevs", strings.Join(peersStr, ",")) if err != nil { - return nil, err + return nil, fmt.Errorf("list-netdevs failed: %s", err) + } + for _, netdevStr := range strings.Split(netdevsStr, "\n") { + if netdevStr != "" { + netdev, err := ParseNetDev(netdevStr) + if err != nil { + return nil, fmt.Errorf("cannot parse netdev %s: %s", netdevStr, err) + } + netdevs = append(netdevs, netdev) + } } - return GetNetDevsWithPredicate(processID, p) + return netdevs, nil } // Get the weave bridge interface. diff --git a/prog/weaveutil/addrs.go b/prog/weaveutil/addrs.go index fdcad785..3ab130ae 100644 --- a/prog/weaveutil/addrs.go +++ b/prog/weaveutil/addrs.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/fsouza/go-dockerclient" - "github.com/vishvananda/netlink" "github.com/weaveworks/weave/common" weavenet "github.com/weaveworks/weave/net" @@ -21,7 +20,7 @@ func containerAddrs(args []string) error { return err } - pred, err := common.ConnectedToBridgePredicate(bridgeName) + peerIDs, err := common.ConnectedToBridgeVethPeerIds(bridgeName) if err != nil { if err == weavenet.ErrLinkNotFound { return nil @@ -51,11 +50,8 @@ func containerAddrs(args []string) error { containerIDs = append(containerIDs, cid) } - // NB: Because network namespaces (netns) are changed many times inside the loop, - // it's NOT safe to exec any code depending on the root netns without - // wrapping with WithNetNS*. for _, cid := range containerIDs { - netDevs, err := getNetDevs(client, containers[cid], pred) + netDevs, err := getNetDevs(client, containers[cid], peerIDs) if err != nil { return err } @@ -65,11 +61,11 @@ func containerAddrs(args []string) error { return nil } -func getNetDevs(c *docker.Client, container *docker.Container, pred func(netlink.Link) bool) ([]common.NetDev, error) { +func getNetDevs(c *docker.Client, container *docker.Container, peerIDs []int) ([]common.NetDev, error) { if container.State.Pid == 0 { return nil, nil } - return common.GetNetDevsWithPredicate(container.State.Pid, pred) + return common.GetNetDevsByVethPeerIds(container.State.Pid, peerIDs) } func printNetDevs(cid string, netDevs []common.NetDev) { diff --git a/prog/weaveutil/proc-addrs.go b/prog/weaveutil/proc-addrs.go index 23e16479..bf11cb2e 100644 --- a/prog/weaveutil/proc-addrs.go +++ b/prog/weaveutil/proc-addrs.go @@ -13,7 +13,7 @@ func processAddrs(args []string) error { } bridgeName := args[0] - pred, err := common.ConnectedToBridgePredicate(bridgeName) + peerIDs, err := common.ConnectedToBridgeVethPeerIds(bridgeName) if err != nil { if err == weavenet.ErrLinkNotFound { return nil @@ -26,11 +26,8 @@ func processAddrs(args []string) error { return err } - // NB: Because network namespaces (netns) are changed many times inside the loop, - // it's NOT safe to exec any code depending on the root netns without - // wrapping with WithNetNS*. for _, pid := range pids { - netDevs, err := common.GetNetDevsWithPredicate(pid, pred) + netDevs, err := common.GetNetDevsByVethPeerIds(pid, peerIDs) if err != nil { return err } From 3492cd93d09e88326b6d7c5d0407cf611d86f89f Mon Sep 17 00:00:00 2001 From: Martynas Pumputis Date: Wed, 19 Oct 2016 16:04:18 +0100 Subject: [PATCH 3/5] Use safe version of WithNetNS --- net/netns.go | 10 ---------- net/veth.go | 42 +++++++++++++++++++++------------------- plugin/net/cni.go | 16 ++------------- prog/weaveutil/attach.go | 27 +++++++++++++------------- 4 files changed, 38 insertions(+), 57 deletions(-) diff --git a/net/netns.go b/net/netns.go index a501f158..5e9e205f 100644 --- a/net/netns.go +++ b/net/netns.go @@ -57,16 +57,6 @@ func WithNetNSLinkUnsafe(ns netns.NsHandle, ifName string, work func(link netlin }) } -func WithNetNSLinkByPidUnsafe(pid int, ifName string, work func(link netlink.Link) error) error { - ns, err := netns.GetFromPid(pid) - if err != nil { - return err - } - defer ns.Close() - - return WithNetNSLinkUnsafe(ns, ifName, work) -} - // A safe version of WithNetNS* which creates a process executing // "nsenter --net= weaveutil [args]". func WithNetNS(nsPath string, cmd string, args ...string) (string, error) { diff --git a/net/veth.go b/net/veth.go index e88be9ed..f33d7b58 100644 --- a/net/veth.go +++ b/net/veth.go @@ -105,21 +105,26 @@ const ( vethPrefix = "v" + VethName // starts with "veth" to suppress UI notifications ) -func interfaceExistsInNamespace(ns netns.NsHandle, ifName string) bool { - err := WithNetNSUnsafe(ns, func() error { - _, err := netlink.LinkByName(ifName) - return err - }) +func interfaceExistsInNamespace(netNSPath string, ifName string) bool { + _, err := WithNetNS(netNSPath, "check-iface", ifName) return err == nil } -func AttachContainer(ns netns.NsHandle, id, ifName, bridgeName string, mtu int, withMulticastRoute bool, cidrs []*net.IPNet, keepTXOn bool) error { +// NB: This function can be used only by a process that terminates immediately +// after calling the function as it changes netns via WithNetNSLinkUnsafe. +func AttachContainer(netNSPath, id, ifName, bridgeName string, mtu int, withMulticastRoute bool, cidrs []*net.IPNet, keepTXOn bool) error { + ns, err := netns.GetFromPath(netNSPath) + if err != nil { + return err + } + defer ns.Close() + ipt, err := iptables.New() if err != nil { return err } - if !interfaceExistsInNamespace(ns, ifName) { + if !interfaceExistsInNamespace(netNSPath, ifName) { maxIDLen := IFNAMSIZ - 1 - len(vethPrefix+"pl") if len(id) > maxIDLen { id = id[:maxIDLen] // trim passed ID if too long @@ -129,18 +134,7 @@ func AttachContainer(ns netns.NsHandle, id, ifName, bridgeName string, mtu int, if err := netlink.LinkSetNsFd(veth, int(ns)); err != nil { return fmt.Errorf("failed to move veth to container netns: %s", err) } - if err := WithNetNSUnsafe(ns, func() error { - if err := netlink.LinkSetName(veth, ifName); err != nil { - return err - } - if err := ConfigureARPCache(ifName); err != nil { - return err - } - if err := ipt.Append("filter", "INPUT", "-i", ifName, "-d", "224.0.0.0/4", "-j", "DROP"); err != nil { - return err - } - return nil - }); err != nil { + if _, err := WithNetNS(netNSPath, "setup-iface", peerName, ifName); err != nil { return fmt.Errorf("error setting up interface: %s", err) } return nil @@ -201,7 +195,15 @@ func AttachContainer(ns netns.NsHandle, id, ifName, bridgeName string, mtu int, return nil } -func DetachContainer(ns netns.NsHandle, id, ifName string, cidrs []*net.IPNet) error { +// NB: This function can be used only by a process that terminates immediately +// after calling the function as it changes netns via WithNetNSLinkUnsafe. +func DetachContainer(netNSPath, id, ifName string, cidrs []*net.IPNet) error { + ns, err := netns.GetFromPath(netNSPath) + if err != nil { + return err + } + defer ns.Close() + ipt, err := iptables.New() if err != nil { return err diff --git a/plugin/net/cni.go b/plugin/net/cni.go index 0fdd5b41..37ae5663 100644 --- a/plugin/net/cni.go +++ b/plugin/net/cni.go @@ -107,7 +107,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, false, []*net.IPNet{&result.IP4.IP}, false); err != nil { + if err := weavenet.AttachContainer(args.Netns, id, args.IfName, conf.BrName, conf.MTU, false, []*net.IPNet{&result.IP4.IP}, false); err != nil { return err } if err := weavenet.WithNetNSLinkUnsafe(ns, args.IfName, func(link netlink.Link) error { @@ -181,19 +181,7 @@ func (c *CNIPlugin) CmdDel(args *skel.CmdArgs) error { return err } - ns, err := netns.GetFromPath(args.Netns) - if err != nil { - return fmt.Errorf("error accessing namespace %q: %s", args.Netns, err) - } - defer ns.Close() - err = weavenet.WithNetNSUnsafe(ns, func() error { - link, err := netlink.LinkByName(args.IfName) - if err != nil { - return err - } - return netlink.LinkDel(link) - }) - if err != nil { + if _, err = weavenet.WithNetNS(args.Netns, "del-iface", args.IfName); err != nil { return fmt.Errorf("error removing interface %q: %s", args.IfName, err) } diff --git a/prog/weaveutil/attach.go b/prog/weaveutil/attach.go index c3c469de..fd45810e 100644 --- a/prog/weaveutil/attach.go +++ b/prog/weaveutil/attach.go @@ -32,10 +32,15 @@ func attach(args []string) error { } } - pid, nsContainer, err := containerPidAndNs(args[0]) + pid, err := containerPid(args[0]) if err != nil { return err } + nsContainer, err := netns.GetFromPid(pid) + if err != nil { + return fmt.Errorf("unable to open namespace for container %s: %s", args[0], err) + } + if nsHost, err := netns.GetFromPid(1); err != nil { return fmt.Errorf("unable to open host namespace: %s", err) } else if nsHost.Equal(nsContainer) { @@ -50,7 +55,7 @@ func attach(args []string) error { return err } - err = weavenet.AttachContainer(nsContainer, fmt.Sprint(pid), weavenet.VethName, args[1], mtu, withMulticastRoute, cidrs, keepTXOn) + err = weavenet.AttachContainer(weavenet.NSPathByPid(pid), fmt.Sprint(pid), weavenet.VethName, args[1], mtu, withMulticastRoute, cidrs, keepTXOn) // If we detected an error but the container has died, tell the user that instead. if err != nil && !processExists(pid) { err = fmt.Errorf("Container %s died", args[0]) @@ -58,23 +63,19 @@ func attach(args []string) error { return err } -func containerPidAndNs(containerID string) (int, netns.NsHandle, error) { +func containerPid(containerID string) (int, error) { c, err := docker.NewVersionedClientFromEnv("1.18") if err != nil { - return 0, 0, fmt.Errorf("unable to connect to docker: %s", err) + return 0, fmt.Errorf("unable to connect to docker: %s", err) } container, err := c.InspectContainer(containerID) if err != nil { - return 0, 0, fmt.Errorf("unable to inspect container %s: %s", containerID, err) + return 0, fmt.Errorf("unable to inspect container %s: %s", containerID, err) } if container.State.Pid == 0 { - return 0, 0, fmt.Errorf("container %s not running", containerID) + return 0, fmt.Errorf("container %s not running", containerID) } - ns, err := netns.GetFromPid(container.State.Pid) - if err != nil { - return 0, 0, fmt.Errorf("unable to open namespace for container %s: %s", containerID, err) - } - return container.State.Pid, ns, nil + return container.State.Pid, nil } func processExists(pid int) bool { @@ -99,7 +100,7 @@ func detach(args []string) error { cmdUsage("detach-container", " ...") } - _, ns, err := containerPidAndNs(args[0]) + pid, err := containerPid(args[0]) if err != nil { return err } @@ -107,5 +108,5 @@ func detach(args []string) error { if err != nil { return err } - return weavenet.DetachContainer(ns, args[0], weavenet.VethName, cidrs) + return weavenet.DetachContainer(weavenet.NSPathByPid(pid), args[0], weavenet.VethName, cidrs) } From e611f04f41d554b57e531bdf7da1a265c4c58918 Mon Sep 17 00:00:00 2001 From: Martynas Pumputis Date: Wed, 19 Oct 2016 16:26:53 +0100 Subject: [PATCH 4/5] Move NetDev utils from common/ to net/ --- common/utils.go | 160 +---------------------------------- net/netdev.go | 159 ++++++++++++++++++++++++++++++++++ plugin/net/cni.go | 3 +- prog/weaveutil/addrs.go | 13 ++- prog/weaveutil/netdev.go | 3 +- prog/weaveutil/proc-addrs.go | 4 +- proxy/proxy.go | 5 +- 7 files changed, 172 insertions(+), 175 deletions(-) create mode 100644 net/netdev.go diff --git a/common/utils.go b/common/utils.go index fcf512d9..4307c44e 100644 --- a/common/utils.go +++ b/common/utils.go @@ -1,18 +1,6 @@ package common -import ( - "fmt" - "net" - "os" - "regexp" - "strconv" - "strings" - - "github.com/vishvananda/netlink" - "github.com/vishvananda/netns" - - weavenet "github.com/weaveworks/weave/net" -) +import "strings" // Assert test is true, panic otherwise func Assert(test bool) { @@ -28,149 +16,3 @@ func ErrorMessages(errors []error) string { } return strings.Join(result, "\n") } - -var netdevRegExp = regexp.MustCompile(`^([^ ]+?) ([^ ]+?) \[([^]]*)\]$`) - -type NetDev struct { - Name string - MAC net.HardwareAddr - CIDRs []*net.IPNet -} - -func (d NetDev) String() string { - return fmt.Sprintf("%s %s %s", d.Name, d.MAC, d.CIDRs) -} - -func ParseNetDev(netdev string) (NetDev, error) { - match := netdevRegExp.FindStringSubmatch(netdev) - if match == nil { - return NetDev{}, fmt.Errorf("invalid netdev: %s", netdev) - } - - iface := match[1] - mac, err := net.ParseMAC(match[2]) - if err != nil { - return NetDev{}, fmt.Errorf("cannot parse mac %s: %s", match[2], err) - } - - var cidrs []*net.IPNet - for _, cidr := range strings.Split(match[3], " ") { - if cidr != "" { - ip, ipnet, err := net.ParseCIDR(cidr) - if err != nil { - return NetDev{}, fmt.Errorf("cannot parse cidr %s: %s", cidr, err) - } - ipnet.IP = ip - cidrs = append(cidrs, ipnet) - } - } - - return NetDev{Name: iface, MAC: mac, CIDRs: cidrs}, nil -} - -func LinkToNetDev(link netlink.Link) (NetDev, error) { - addrs, err := netlink.AddrList(link, netlink.FAMILY_V4) - if err != nil { - return NetDev{}, err - } - - netDev := NetDev{Name: link.Attrs().Name, MAC: link.Attrs().HardwareAddr} - for _, addr := range addrs { - netDev.CIDRs = append(netDev.CIDRs, addr.IPNet) - } - return netDev, nil -} - -// ConnectedToBridgeVethPeerIds returns peer indexes of veth links connected to -// the given bridge. The peer index is used to query from a container netns -// whether the container is connected to the bridge. -func ConnectedToBridgeVethPeerIds(bridgeName string) ([]int, error) { - var ids []int - - br, err := netlink.LinkByName(bridgeName) - if err != nil { - return nil, err - } - links, err := netlink.LinkList() - if err != nil { - return nil, err - } - - for _, link := range links { - if _, isveth := link.(*netlink.Veth); isveth && link.Attrs().MasterIndex == br.Attrs().Index { - peerID := link.Attrs().ParentIndex - if peerID == 0 { - // perhaps running on an older kernel where ParentIndex doesn't work. - // as fall-back, assume the peers are consecutive - peerID = link.Attrs().Index - 1 - } - ids = append(ids, peerID) - } - } - - return ids, nil -} - -// Lookup the weave interface of a container -func GetWeaveNetDevs(processID int) ([]NetDev, error) { - peerIDs, err := ConnectedToBridgeVethPeerIds("weave") - if err != nil { - return nil, err - } - - return GetNetDevsByVethPeerIds(processID, peerIDs) -} - -func GetNetDevsByVethPeerIds(processID int, peerIDs []int) ([]NetDev, error) { - // Bail out if this container is running in the root namespace - netnsRoot, err := netns.GetFromPid(1) - if err != nil { - return nil, fmt.Errorf("unable to open root namespace: %s", err) - } - defer netnsRoot.Close() - netnsContainer, err := netns.GetFromPid(processID) - if err != nil { - // Unable to find a namespace for this process - just return nothing - if os.IsNotExist(err) { - return nil, nil - } - return nil, fmt.Errorf("unable to open process %d namespace: %s", processID, err) - } - defer netnsContainer.Close() - if netnsRoot.Equal(netnsContainer) { - return nil, nil - } - - var netdevs []NetDev - - peersStr := make([]string, len(peerIDs)) - for i, id := range peerIDs { - peersStr[i] = strconv.Itoa(id) - } - netdevsStr, err := weavenet.WithNetNSByPid(processID, "list-netdevs", strings.Join(peersStr, ",")) - if err != nil { - return nil, fmt.Errorf("list-netdevs failed: %s", err) - } - for _, netdevStr := range strings.Split(netdevsStr, "\n") { - if netdevStr != "" { - netdev, err := ParseNetDev(netdevStr) - if err != nil { - return nil, fmt.Errorf("cannot parse netdev %s: %s", netdevStr, err) - } - netdevs = append(netdevs, netdev) - } - } - - return netdevs, nil -} - -// Get the weave bridge interface. -// NB: Should be called from the root network namespace. -func GetBridgeNetDev(bridgeName string) (NetDev, error) { - link, err := netlink.LinkByName(bridgeName) - if err != nil { - return NetDev{}, err - } - - return LinkToNetDev(link) -} diff --git a/net/netdev.go b/net/netdev.go new file mode 100644 index 00000000..1c777d29 --- /dev/null +++ b/net/netdev.go @@ -0,0 +1,159 @@ +package net + +import ( + "fmt" + "net" + "os" + "regexp" + "strconv" + "strings" + + "github.com/vishvananda/netlink" + "github.com/vishvananda/netns" +) + +var netdevRegExp = regexp.MustCompile(`^([^ ]+?) ([^ ]+?) \[([^]]*)\]$`) + +type Dev struct { + Name string + MAC net.HardwareAddr + CIDRs []*net.IPNet +} + +func (d Dev) String() string { + return fmt.Sprintf("%s %s %s", d.Name, d.MAC, d.CIDRs) +} + +func ParseNetDev(netdev string) (Dev, error) { + match := netdevRegExp.FindStringSubmatch(netdev) + if match == nil { + return Dev{}, fmt.Errorf("invalid netdev: %s", netdev) + } + + iface := match[1] + mac, err := net.ParseMAC(match[2]) + if err != nil { + return Dev{}, fmt.Errorf("cannot parse mac %s: %s", match[2], err) + } + + var cidrs []*net.IPNet + for _, cidr := range strings.Split(match[3], " ") { + if cidr != "" { + ip, ipnet, err := net.ParseCIDR(cidr) + if err != nil { + return Dev{}, fmt.Errorf("cannot parse cidr %s: %s", cidr, err) + } + ipnet.IP = ip + cidrs = append(cidrs, ipnet) + } + } + + return Dev{Name: iface, MAC: mac, CIDRs: cidrs}, nil +} + +func LinkToNetDev(link netlink.Link) (Dev, error) { + addrs, err := netlink.AddrList(link, netlink.FAMILY_V4) + if err != nil { + return Dev{}, err + } + + netDev := Dev{Name: link.Attrs().Name, MAC: link.Attrs().HardwareAddr} + for _, addr := range addrs { + netDev.CIDRs = append(netDev.CIDRs, addr.IPNet) + } + return netDev, nil +} + +// ConnectedToBridgeVethPeerIds returns peer indexes of veth links connected to +// the given bridge. The peer index is used to query from a container netns +// whether the container is connected to the bridge. +func ConnectedToBridgeVethPeerIds(bridgeName string) ([]int, error) { + var ids []int + + br, err := netlink.LinkByName(bridgeName) + if err != nil { + return nil, err + } + links, err := netlink.LinkList() + if err != nil { + return nil, err + } + + for _, link := range links { + if _, isveth := link.(*netlink.Veth); isveth && link.Attrs().MasterIndex == br.Attrs().Index { + peerID := link.Attrs().ParentIndex + if peerID == 0 { + // perhaps running on an older kernel where ParentIndex doesn't work. + // as fall-back, assume the peers are consecutive + peerID = link.Attrs().Index - 1 + } + ids = append(ids, peerID) + } + } + + return ids, nil +} + +// Lookup the weave interface of a container +func GetWeaveNetDevs(processID int) ([]Dev, error) { + peerIDs, err := ConnectedToBridgeVethPeerIds("weave") + if err != nil { + return nil, err + } + + return GetNetDevsByVethPeerIds(processID, peerIDs) +} + +func GetNetDevsByVethPeerIds(processID int, peerIDs []int) ([]Dev, error) { + // Bail out if this container is running in the root namespace + netnsRoot, err := netns.GetFromPid(1) + if err != nil { + return nil, fmt.Errorf("unable to open root namespace: %s", err) + } + defer netnsRoot.Close() + netnsContainer, err := netns.GetFromPid(processID) + if err != nil { + // Unable to find a namespace for this process - just return nothing + if os.IsNotExist(err) { + return nil, nil + } + return nil, fmt.Errorf("unable to open process %d namespace: %s", processID, err) + } + defer netnsContainer.Close() + if netnsRoot.Equal(netnsContainer) { + return nil, nil + } + + var netdevs []Dev + + peersStr := make([]string, len(peerIDs)) + for i, id := range peerIDs { + peersStr[i] = strconv.Itoa(id) + } + netdevsStr, err := WithNetNSByPid(processID, "list-netdevs", strings.Join(peersStr, ",")) + if err != nil { + return nil, fmt.Errorf("list-netdevs failed: %s", err) + } + for _, netdevStr := range strings.Split(netdevsStr, "\n") { + if netdevStr != "" { + netdev, err := ParseNetDev(netdevStr) + if err != nil { + return nil, fmt.Errorf("cannot parse netdev %s: %s", netdevStr, err) + } + netdevs = append(netdevs, netdev) + } + } + + return netdevs, nil +} + +// Get the weave bridge interface. +// NB: Should be called from the root network namespace. +func GetBridgeNetDev(bridgeName string) (Dev, error) { + link, err := netlink.LinkByName(bridgeName) + if err != nil { + return Dev{}, err + } + + return LinkToNetDev(link) +} diff --git a/plugin/net/cni.go b/plugin/net/cni.go index 37ae5663..1d9e4407 100644 --- a/plugin/net/cni.go +++ b/plugin/net/cni.go @@ -12,7 +12,6 @@ import ( "github.com/vishvananda/netlink" "github.com/vishvananda/netns" weaveapi "github.com/weaveworks/weave/api" - "github.com/weaveworks/weave/common" weavenet "github.com/weaveworks/weave/net" ipamplugin "github.com/weaveworks/weave/plugin/ipam" ) @@ -159,7 +158,7 @@ func assignBridgeIP(bridgeName string, ipnet net.IPNet) error { var errBridgeNoIP = fmt.Errorf("Bridge has no IP address") func findBridgeIP(bridgeName string, subnet net.IPNet) (net.IP, error) { - netdev, err := common.GetBridgeNetDev(bridgeName) + netdev, err := weavenet.GetBridgeNetDev(bridgeName) if err != nil { return nil, fmt.Errorf("Failed to get netdev for %q bridge: %s", bridgeName, err) } diff --git a/prog/weaveutil/addrs.go b/prog/weaveutil/addrs.go index 3ab130ae..8c18ee20 100644 --- a/prog/weaveutil/addrs.go +++ b/prog/weaveutil/addrs.go @@ -5,7 +5,6 @@ import ( "github.com/fsouza/go-dockerclient" - "github.com/weaveworks/weave/common" weavenet "github.com/weaveworks/weave/net" ) @@ -20,7 +19,7 @@ func containerAddrs(args []string) error { return err } - peerIDs, err := common.ConnectedToBridgeVethPeerIds(bridgeName) + peerIDs, err := weavenet.ConnectedToBridgeVethPeerIds(bridgeName) if err != nil { if err == weavenet.ErrLinkNotFound { return nil @@ -33,11 +32,11 @@ func containerAddrs(args []string) error { for _, cid := range args[1:] { if cid == "weave:expose" { - netDev, err := common.GetBridgeNetDev(bridgeName) + netDev, err := weavenet.GetBridgeNetDev(bridgeName) if err != nil { return err } - printNetDevs(cid, []common.NetDev{netDev}) + printNetDevs(cid, []weavenet.Dev{netDev}) continue } if containers[cid], err = client.InspectContainer(cid); err != nil { @@ -61,14 +60,14 @@ func containerAddrs(args []string) error { return nil } -func getNetDevs(c *docker.Client, container *docker.Container, peerIDs []int) ([]common.NetDev, error) { +func getNetDevs(c *docker.Client, container *docker.Container, peerIDs []int) ([]weavenet.Dev, error) { if container.State.Pid == 0 { return nil, nil } - return common.GetNetDevsByVethPeerIds(container.State.Pid, peerIDs) + return weavenet.GetNetDevsByVethPeerIds(container.State.Pid, peerIDs) } -func printNetDevs(cid string, netDevs []common.NetDev) { +func printNetDevs(cid string, netDevs []weavenet.Dev) { for _, netDev := range netDevs { fmt.Printf("%12s %s %s", cid, netDev.Name, netDev.MAC.String()) for _, cidr := range netDev.CIDRs { diff --git a/prog/weaveutil/netdev.go b/prog/weaveutil/netdev.go index a89b82af..a2f29fb0 100644 --- a/prog/weaveutil/netdev.go +++ b/prog/weaveutil/netdev.go @@ -9,7 +9,6 @@ import ( "github.com/j-keck/arping" "github.com/vishvananda/netlink" - "github.com/weaveworks/weave/common" weavenet "github.com/weaveworks/weave/net" ) @@ -120,7 +119,7 @@ func listNetDevs(args []string) error { for _, link := range links { if _, found := indexes[link.Attrs().Index]; found { - netdev, err := common.LinkToNetDev(link) + netdev, err := weavenet.LinkToNetDev(link) if err != nil { return err } diff --git a/prog/weaveutil/proc-addrs.go b/prog/weaveutil/proc-addrs.go index bf11cb2e..17e08770 100644 --- a/prog/weaveutil/proc-addrs.go +++ b/prog/weaveutil/proc-addrs.go @@ -13,7 +13,7 @@ func processAddrs(args []string) error { } bridgeName := args[0] - peerIDs, err := common.ConnectedToBridgeVethPeerIds(bridgeName) + peerIDs, err := weavenet.ConnectedToBridgeVethPeerIds(bridgeName) if err != nil { if err == weavenet.ErrLinkNotFound { return nil @@ -27,7 +27,7 @@ func processAddrs(args []string) error { } for _, pid := range pids { - netDevs, err := common.GetNetDevsByVethPeerIds(pid, peerIDs) + netDevs, err := weavenet.GetNetDevsByVethPeerIds(pid, peerIDs) if err != nil { return err } diff --git a/proxy/proxy.go b/proxy/proxy.go index 53985ffc..6c2d4017 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -17,7 +17,6 @@ import ( docker "github.com/fsouza/go-dockerclient" weaveapi "github.com/weaveworks/weave/api" - "github.com/weaveworks/weave/common" weavedocker "github.com/weaveworks/weave/common/docker" weavenet "github.com/weaveworks/weave/net" "github.com/weaveworks/weave/net/address" @@ -149,7 +148,7 @@ func NewProxy(c Config) (*Proxy, error) { p.client = client.Client if !p.WithoutDNS { - netDev, err := common.GetBridgeNetDev(c.DockerBridge) + netDev, err := weavenet.GetBridgeNetDev(c.DockerBridge) if err != nil { return nil, err } @@ -625,7 +624,7 @@ func (proxy *Proxy) updateContainerNetworkSettings(container jsonObject) error { if err := proxy.waitForStartByIdent(containerID); err != nil { return err } - netDevs, err := common.GetWeaveNetDevs(pid) + netDevs, err := weavenet.GetWeaveNetDevs(pid) if err != nil || len(netDevs) == 0 || len(netDevs[0].CIDRs) == 0 { return err } From 9754da15d6864f8f608b4418cfa77220c0888e4b Mon Sep 17 00:00:00 2001 From: Martynas Pumputis Date: Thu, 20 Oct 2016 17:48:47 +0100 Subject: [PATCH 5/5] Encode "list-netdevs" output in JSON --- net/netdev.go | 57 +++++----------------------------------- net/netns.go | 8 +++--- prog/weaveutil/netdev.go | 17 +++++++++--- 3 files changed, 24 insertions(+), 58 deletions(-) diff --git a/net/netdev.go b/net/netdev.go index 1c777d29..35480cbf 100644 --- a/net/netdev.go +++ b/net/netdev.go @@ -1,54 +1,20 @@ package net import ( + "encoding/json" "fmt" "net" "os" - "regexp" "strconv" - "strings" "github.com/vishvananda/netlink" "github.com/vishvananda/netns" ) -var netdevRegExp = regexp.MustCompile(`^([^ ]+?) ([^ ]+?) \[([^]]*)\]$`) - type Dev struct { - Name string - MAC net.HardwareAddr - CIDRs []*net.IPNet -} - -func (d Dev) String() string { - return fmt.Sprintf("%s %s %s", d.Name, d.MAC, d.CIDRs) -} - -func ParseNetDev(netdev string) (Dev, error) { - match := netdevRegExp.FindStringSubmatch(netdev) - if match == nil { - return Dev{}, fmt.Errorf("invalid netdev: %s", netdev) - } - - iface := match[1] - mac, err := net.ParseMAC(match[2]) - if err != nil { - return Dev{}, fmt.Errorf("cannot parse mac %s: %s", match[2], err) - } - - var cidrs []*net.IPNet - for _, cidr := range strings.Split(match[3], " ") { - if cidr != "" { - ip, ipnet, err := net.ParseCIDR(cidr) - if err != nil { - return Dev{}, fmt.Errorf("cannot parse cidr %s: %s", cidr, err) - } - ipnet.IP = ip - cidrs = append(cidrs, ipnet) - } - } - - return Dev{Name: iface, MAC: mac, CIDRs: cidrs}, nil + Name string `json:"Name,omitempty"` + MAC net.HardwareAddr `json:"MAC,omitempty"` + CIDRs []*net.IPNet `json:"CIDRs,omitempty"` } func LinkToNetDev(link netlink.Link) (Dev, error) { @@ -125,26 +91,17 @@ func GetNetDevsByVethPeerIds(processID int, peerIDs []int) ([]Dev, error) { } var netdevs []Dev - peersStr := make([]string, len(peerIDs)) for i, id := range peerIDs { peersStr[i] = strconv.Itoa(id) } - netdevsStr, err := WithNetNSByPid(processID, "list-netdevs", strings.Join(peersStr, ",")) + nds, err := WithNetNSByPid(processID, "list-netdevs", peersStr...) if err != nil { return nil, fmt.Errorf("list-netdevs failed: %s", err) } - for _, netdevStr := range strings.Split(netdevsStr, "\n") { - if netdevStr != "" { - netdev, err := ParseNetDev(netdevStr) - if err != nil { - return nil, fmt.Errorf("cannot parse netdev %s: %s", netdevStr, err) - } - netdevs = append(netdevs, netdev) - } - } + err = json.Unmarshal(nds, &netdevs) - return netdevs, nil + return netdevs, err } // Get the weave bridge interface. diff --git a/net/netns.go b/net/netns.go index 5e9e205f..f3a8816a 100644 --- a/net/netns.go +++ b/net/netns.go @@ -59,7 +59,7 @@ func WithNetNSLinkUnsafe(ns netns.NsHandle, ifName string, work func(link netlin // A safe version of WithNetNS* which creates a process executing // "nsenter --net= weaveutil [args]". -func WithNetNS(nsPath string, cmd string, args ...string) (string, error) { +func WithNetNS(nsPath string, cmd string, args ...string) ([]byte, error) { var stdout, stderr bytes.Buffer args = append([]string{"--net=" + nsPath, "weaveutil", cmd}, args...) @@ -67,13 +67,13 @@ func WithNetNS(nsPath string, cmd string, args ...string) (string, error) { c.Stdout = &stdout c.Stderr = &stderr if err := c.Run(); err != nil { - return "", fmt.Errorf("%s: %s", string(stderr.Bytes()), err) + return nil, fmt.Errorf("%s: %s", string(stderr.Bytes()), err) } - return string(stdout.Bytes()), nil + return stdout.Bytes(), nil } -func WithNetNSByPid(pid int, cmd string, args ...string) (string, error) { +func WithNetNSByPid(pid int, cmd string, args ...string) ([]byte, error) { return WithNetNS(NSPathByPid(pid), cmd, args...) } diff --git a/prog/weaveutil/netdev.go b/prog/weaveutil/netdev.go index a2f29fb0..d35e395b 100644 --- a/prog/weaveutil/netdev.go +++ b/prog/weaveutil/netdev.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "fmt" "strconv" "strings" @@ -97,12 +98,12 @@ func configureARP(args []string) error { // listNetDevs outputs network ifaces identified by the given indexes // in the format of weavenet.Dev. func listNetDevs(args []string) error { - if len(args) != 1 { - cmdUsage("list-netdevs", "[,]") + if len(args) == 0 { + cmdUsage("list-netdevs", "[ ]") } indexes := make(map[int]struct{}) - for _, index := range strings.Split(args[0], ",") { + for _, index := range args { if index != "" { id, err := strconv.Atoi(index) if err != nil { @@ -117,15 +118,23 @@ func listNetDevs(args []string) error { return err } + var netdevs []weavenet.Dev + for _, link := range links { if _, found := indexes[link.Attrs().Index]; found { netdev, err := weavenet.LinkToNetDev(link) if err != nil { return err } - fmt.Println(netdev) + netdevs = append(netdevs, netdev) } } + nds, err := json.Marshal(netdevs) + if err != nil { + return err + } + fmt.Println(string(nds)) + return nil }