Skip to content

Commit

Permalink
In containers view, show short lived connections to/from the internet…
Browse files Browse the repository at this point in the history
… by including port mappings in the join.
  • Loading branch information
Tom Wilkie committed Oct 1, 2015
1 parent 8a2fa22 commit 5764df8
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 17 deletions.
16 changes: 11 additions & 5 deletions probe/docker/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ type Container interface {
ID() string
Image() string
PID() int
GetNode() report.Node
GetNode([]net.IP) report.Node

StartGatheringStats() error
StopGatheringStats()
Expand Down Expand Up @@ -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 ""
}
Expand All @@ -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,
Expand Down
6 changes: 3 additions & 3 deletions probe/docker/container_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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"})
}
}
3 changes: 2 additions & 1 deletion probe/docker/registry_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package docker_test

import (
"net"
"runtime"
"sort"
"sync"
Expand Down Expand Up @@ -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,
Expand Down
13 changes: 10 additions & 3 deletions probe/docker/reporter.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package docker

import (
"net"

docker_client "github.com/fsouza/go-dockerclient"

"github.com/weaveworks/scope/report"
Expand Down Expand Up @@ -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
Expand Down
33 changes: 28 additions & 5 deletions render/mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package render
import (
"fmt"
"net"
"regexp"
"strconv"
"strings"

Expand Down Expand Up @@ -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{}
}
Expand All @@ -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.
Expand All @@ -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
}

Expand Down
35 changes: 35 additions & 0 deletions report/networks.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package report

import (
"net"
"strings"
)

// Networks represent a set of subnets
Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit 5764df8

Please sign in to comment.