diff --git a/probe/docker/container.go b/probe/docker/container.go index 6bbae82d33..113a99a63b 100644 --- a/probe/docker/container.go +++ b/probe/docker/container.go @@ -70,7 +70,7 @@ type Container interface { ID() string Image() string PID() int - GetNode() report.Node + GetNode([]net.IP) report.Node StartGatheringStats() error StopGatheringStats() @@ -183,7 +183,7 @@ func (c *container) StopGatheringStats() { return } -func (c *container) ports() string { +func (c *container) ports(localAddrs []net.IP) string { if c.container.NetworkSettings == nil { return "" } @@ -195,21 +195,27 @@ func (c *container) ports() string { continue } for _, b := range bindings { - ports = append(ports, fmt.Sprintf("%s:%s->%s", b.HostIP, b.HostPort, port)) + if b.HostIP == "0.0.0.0" { + for _, ip := range localAddrs { + ports = append(ports, fmt.Sprintf("%s:%s->%s", ip, b.HostPort, port)) + } + } else { + ports = append(ports, fmt.Sprintf("%s:%s->%s", b.HostIP, b.HostPort, port)) + } } } return strings.Join(ports, ", ") } -func (c *container) GetNode() report.Node { +func (c *container) GetNode(localAddrs []net.IP) report.Node { c.RLock() defer c.RUnlock() result := report.MakeNodeWith(map[string]string{ ContainerID: c.ID(), ContainerName: strings.TrimPrefix(c.container.Name, "/"), - ContainerPorts: c.ports(), + ContainerPorts: c.ports(localAddrs), ContainerCreated: c.container.Created.Format(time.RFC822), ContainerCommand: c.container.Path + " " + strings.Join(c.container.Args, " "), ImageID: c.container.Image, diff --git a/probe/docker/container_linux_test.go b/probe/docker/container_linux_test.go index a5714b6e57..a7a027acdc 100644 --- a/probe/docker/container_linux_test.go +++ b/probe/docker/container_linux_test.go @@ -78,7 +78,7 @@ func TestContainer(t *testing.T) { "memory_usage": "12345", }) test.Poll(t, 100*time.Millisecond, want, func() interface{} { - node := c.GetNode() + node := c.GetNode([]net.IP{}) for k, v := range node.Metadata { if v == "0" { delete(node.Metadata, k) @@ -93,7 +93,7 @@ func TestContainer(t *testing.T) { if c.PID() != 1 { t.Errorf("%s != 1", c.PID()) } - if !reflect.DeepEqual(docker.ExtractContainerIPs(c.GetNode()), []string{"1.2.3.4"}) { - t.Errorf("%v != %v", docker.ExtractContainerIPs(c.GetNode()), []string{"1.2.3.4"}) + if !reflect.DeepEqual(docker.ExtractContainerIPs(c.GetNode([]net.IP{})), []string{"1.2.3.4"}) { + t.Errorf("%v != %v", docker.ExtractContainerIPs(c.GetNode([]net.IP{})), []string{"1.2.3.4"}) } } diff --git a/probe/docker/registry_test.go b/probe/docker/registry_test.go index 42559f2e0b..dae59098c1 100644 --- a/probe/docker/registry_test.go +++ b/probe/docker/registry_test.go @@ -1,6 +1,7 @@ package docker_test import ( + "net" "runtime" "sort" "sync" @@ -36,7 +37,7 @@ func (c *mockContainer) StartGatheringStats() error { func (c *mockContainer) StopGatheringStats() {} -func (c *mockContainer) GetNode() report.Node { +func (c *mockContainer) GetNode(_ []net.IP) report.Node { return report.MakeNodeWith(map[string]string{ docker.ContainerID: c.c.ID, docker.ContainerName: c.c.Name, diff --git a/probe/docker/reporter.go b/probe/docker/reporter.go index 38f64c0591..5e7b0053da 100644 --- a/probe/docker/reporter.go +++ b/probe/docker/reporter.go @@ -1,6 +1,8 @@ package docker import ( + "net" + docker_client "github.com/fsouza/go-dockerclient" "github.com/weaveworks/scope/report" @@ -28,18 +30,23 @@ func NewReporter(registry Registry, hostID string) *Reporter { // Report generates a Report containing Container and ContainerImage topologies func (r *Reporter) Report() (report.Report, error) { + localAddrs, err := report.LocalAddresses() + if err != nil { + return report.MakeReport(), nil + } + result := report.MakeReport() - result.Container = result.Container.Merge(r.containerTopology()) + result.Container = result.Container.Merge(r.containerTopology(localAddrs)) result.ContainerImage = result.ContainerImage.Merge(r.containerImageTopology()) return result, nil } -func (r *Reporter) containerTopology() report.Topology { +func (r *Reporter) containerTopology(localAddrs []net.IP) report.Topology { result := report.MakeTopology() r.registry.WalkContainers(func(c Container) { nodeID := report.MakeContainerNodeID(r.hostID, c.ID()) - result.AddNode(nodeID, c.GetNode()) + result.AddNode(nodeID, c.GetNode(localAddrs)) }) return result diff --git a/render/mapping.go b/render/mapping.go index fd8d337406..4ba880660a 100644 --- a/render/mapping.go +++ b/render/mapping.go @@ -3,6 +3,7 @@ package render import ( "fmt" "net" + "regexp" "strconv" "strings" @@ -240,7 +241,8 @@ func MapHostIdentity(m RenderableNode, _ report.Networks) RenderableNodes { // will be joined to containers through the process topology, and we // don't want to double count edges. func MapEndpoint2IP(m RenderableNode, local report.Networks) RenderableNodes { - _, ok := m.Metadata[process.PID] + // Don't include procspied connections, to prevent double counting + _, ok := m.Metadata[endpoint.Procspied] if ok { return RenderableNodes{} } @@ -251,9 +253,20 @@ func MapEndpoint2IP(m RenderableNode, local report.Networks) RenderableNodes { if !local.Contains(net.ParseIP(addr)) { return RenderableNodes{TheInternetID: newDerivedPseudoNode(TheInternetID, TheInternetMajor, m)} } - return RenderableNodes{addr: NewRenderableNodeWith(addr, "", "", "", m)} + + result := RenderableNodes{addr: NewRenderableNodeWith(addr, "", "", "", m)} + // Emit addr:port nodes as well, so connections from the internet to containers + // via port mapping also works. + port, ok := m.Metadata[endpoint.Addr] + if ok { + id := fmt.Sprintf("%s:%s", addr, port) + result[id] = NewRenderableNodeWith(id, "", "", "", m) + } + return result } +var portMappingMatch = regexp.MustCompile(`([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}):([0-9]+)->([0-9]+)/tcp`) + // MapContainer2IP maps container nodes to their IP addresses (outputs // multiple nodes). This allows container to be joined directly with // the endpoint topology. @@ -264,10 +277,20 @@ func MapContainer2IP(m RenderableNode, _ report.Networks) RenderableNodes { return result } for _, addr := range strings.Fields(addrs) { - n := NewRenderableNodeWith(addr, "", "", "", m) - n.Node.Counters[containersKey] = 1 - result[addr] = n + node := NewRenderableNodeWith(addr, "", "", "", m) + node.Counters[containersKey] = 1 + result[addr] = node + } + + // also output all the host:port port mappings + for _, mapping := range portMappingMatch.FindAllStringSubmatch(m.Metadata[docker.ContainerPorts], -1) { + ip, port := mapping[1], mapping[2] + id := fmt.Sprintf("%s:%s", ip, port) + node := NewRenderableNodeWith(id, "", "", "", m) + node.Counters[containersKey] = 1 + result[id] = node } + return result } diff --git a/report/networks.go b/report/networks.go index a1aa9e6e47..42efd5e4ba 100644 --- a/report/networks.go +++ b/report/networks.go @@ -2,6 +2,7 @@ package report import ( "net" + "strings" ) // Networks represent a set of subnets @@ -29,6 +30,40 @@ func (n Networks) Contains(ip net.IP) bool { return false } +// LocalAddresses returns a list of the local IP addresses. +func LocalAddresses() ([]net.IP, error) { + result := []net.IP{} + + infs, err := net.Interfaces() + if err != nil { + return []net.IP{}, err + } + + for _, inf := range infs { + if strings.HasPrefix(inf.Name, "veth") || + strings.HasPrefix(inf.Name, "docker") || + strings.HasPrefix(inf.Name, "lo") { + continue + } + + addrs, err := inf.Addrs() + if err != nil { + return []net.IP{}, err + } + + for _, addr := range addrs { + ipnet, ok := addr.(*net.IPNet) + if !ok { + continue + } + + result = append(result, ipnet.IP) + } + } + + return result, nil +} + // AddLocalBridge records the subnet address associated with the bridge name // supplied, such that MakeAddressNodeID will scope addresses in this subnet // as local.