From ff5b2affe0a8abce59b1a83b6e7ba9df10924e85 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Sat, 2 Jun 2018 21:24:17 +0000 Subject: [PATCH] probe: fetch container IP addresses from inside its namespace So that we can pick up addresses added via CNI or other mechanisms that Docker is not aware of. --- probe/docker/container.go | 21 ++++++++++ probe/docker/network.go | 85 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 probe/docker/network.go diff --git a/probe/docker/container.go b/probe/docker/container.go index 5a86f726f..7e7d4edc9 100644 --- a/probe/docker/container.go +++ b/probe/docker/container.go @@ -268,6 +268,15 @@ func isIPv4(addr string) bool { return ip != nil && ip.To4() != nil } +func contains(strs []string, str string) bool { + for _, s := range strs { + if s == str { + return true + } + } + return false +} + func (c *container) NetworkInfo(localAddrs []net.IP) report.Sets { c.RLock() defer c.RUnlock() @@ -277,6 +286,18 @@ func (c *container) NetworkInfo(localAddrs []net.IP) report.Sets { ips = append(ips, c.container.NetworkSettings.IPAddress) } + // Fetch IP addresses from the container's namespace + cidrs, err := namespaceIPAddresses(c.container.State.Pid) + if err != nil { + log.Debugf("container %s: failed to get addresses: %s", c.container.ID, err) + } + for _, cidr := range cidrs { + ip := cidr.IP.String() + if !contains(ips, ip) { + ips = append(ips, ip) + } + } + // For now, for the proof-of-concept, we just add networks as a set of // names. For the next iteration, we will probably want to create a new // Network topology, populate the network nodes with all of the details diff --git a/probe/docker/network.go b/probe/docker/network.go new file mode 100644 index 000000000..c693c1580 --- /dev/null +++ b/probe/docker/network.go @@ -0,0 +1,85 @@ +// withNetNS function requires a fix that first appeared in Go version 1.10 +// +build go1.10 + +package docker + +import ( + "fmt" + "net" + "runtime" + + "github.com/vishvananda/netlink" + "github.com/vishvananda/netns" + "golang.org/x/sys/unix" +) + +// Code adapted from github.com/weaveworks/weave/net/netdev.go + +// Return any non-local IP addresses for processID if in a non-root namespace +func namespaceIPAddresses(processID int) ([]*net.IPNet, error) { + // Ignore if this process 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 { + return nil, err + } + defer netnsContainer.Close() + if netnsRoot.Equal(netnsContainer) { + return nil, nil + } + + var cidrs []*net.IPNet + err = withNetNS(netnsContainer, func() error { + cidrs, err = allNonLocalAddresses() + return err + }) + + return cidrs, err +} + +// return all non-local IP addresses from the current namespace +func allNonLocalAddresses() ([]*net.IPNet, error) { + var cidrs []*net.IPNet + links, err := netlink.LinkList() + if err != nil { + return nil, err + } + + for _, link := range links { + addrs, err := netlink.AddrList(link, netlink.FAMILY_ALL) + if err != nil { + return nil, err + } + for _, addr := range addrs { + // Exclude link-local ipv6 addresses, localhost, etc. Hope this is the correct test. + if addr.Scope == unix.RT_SCOPE_UNIVERSE { + cidrs = append(cidrs, addr.IPNet) + } + } + } + return cidrs, nil +} + +// Run the 'work' function in a different network namespace +func withNetNS(ns netns.NsHandle, work func() error) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + oldNs, err := netns.Get() + if err == nil { + defer oldNs.Close() + + err = netns.Set(ns) + if err == nil { + defer netns.Set(oldNs) + + err = work() + } + } + + return err +}