From 82a7f93e177a5e52a067fdbd33845ed2b59a29ac Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Mon, 22 Jun 2015 11:18:16 +0000 Subject: [PATCH] Treat addresses on the docker bridge as local. --- probe/main.go | 5 +++ render/mapping.go | 6 ++-- render/theinternet.go | 17 ++------- render/theinternet_test.go | 17 +-------- report/id.go | 15 +++++--- report/networks.go | 60 +++++++++++++++++++++++++++++++ report/networks_test.go | 72 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 154 insertions(+), 38 deletions(-) create mode 100644 report/networks.go create mode 100644 report/networks_test.go diff --git a/probe/main.go b/probe/main.go index da19c77f4..6ade55c89 100644 --- a/probe/main.go +++ b/probe/main.go @@ -34,6 +34,7 @@ func main() { spyProcs = flag.Bool("processes", true, "report processes (needs root)") dockerEnabled = flag.Bool("docker", true, "collect Docker-related attributes for processes") dockerInterval = flag.Duration("docker.interval", 10*time.Second, "how often to update Docker attributes") + dockerBridge = flag.String("docker.bridge", "docker0", "the docker bridge name") weaveRouterAddr = flag.String("weave.router.addr", "", "IP address or FQDN of the Weave router") procRoot = flag.String("proc.root", "/proc", "location of the proc filesystem") ) @@ -81,6 +82,10 @@ func main() { reporters := []tag.Reporter{} if *dockerEnabled && runtime.GOOS == linux { + if err = report.AddLocalBridge(*dockerBridge); err != nil { + log.Fatalf("failed to get docker bridge address: %v", err) + } + dockerRegistry, err := docker.NewRegistry(*dockerInterval) if err != nil { log.Fatalf("failed to start docker registry: %v", err) diff --git a/render/mapping.go b/render/mapping.go index bc0a59dd3..49e43627e 100644 --- a/render/mapping.go +++ b/render/mapping.go @@ -34,7 +34,7 @@ type LeafMapFunc func(report.NodeMetadata) (RenderableNode, bool) // The srcNode renderable node is essentially from MapFunc, representing one of // the rendered nodes this pseudo node refers to. srcNodeID and dstNodeID are // node IDs prior to mapping. -type PseudoFunc func(srcNodeID string, srcNode RenderableNode, dstNodeID string, local Networks) (RenderableNode, bool) +type PseudoFunc func(srcNodeID string, srcNode RenderableNode, dstNodeID string, local report.Networks) (RenderableNode, bool) // MapFunc is anything which can take an arbitrary RenderableNode and // return another RenderableNode. @@ -257,7 +257,7 @@ func MapAddress2Host(n RenderableNode) (RenderableNode, bool) { // the report's local networks. Otherwise, the returned function will // produce a single pseudo node per (dst address, src address, src port). func GenericPseudoNode(addresser func(id string) net.IP) PseudoFunc { - return func(src string, srcMapped RenderableNode, dst string, local Networks) (RenderableNode, bool) { + return func(src string, srcMapped RenderableNode, dst string, local report.Networks) (RenderableNode, bool) { // Use the addresser to extract the destination IP dstNodeAddr := addresser(dst) @@ -278,7 +278,7 @@ func GenericPseudoNode(addresser func(id string) net.IP) PseudoFunc { } // PanicPseudoNode just panics; it is for Topologies without edges -func PanicPseudoNode(src string, srcMapped RenderableNode, dst string, local Networks) (RenderableNode, bool) { +func PanicPseudoNode(src string, srcMapped RenderableNode, dst string, local report.Networks) (RenderableNode, bool) { panic(dst) } diff --git a/render/theinternet.go b/render/theinternet.go index 8bdcda937..2da92c3f5 100644 --- a/render/theinternet.go +++ b/render/theinternet.go @@ -7,16 +7,13 @@ import ( "github.com/weaveworks/scope/report" ) -// Networks represent a set of subnets local to a report. -type Networks []*net.IPNet - // LocalNetworks returns a superset of the networks (think: CIDRs) that are // "local" from the perspective of each host represented in the report. It's // used to determine which nodes in the report are "remote", i.e. outside of // our infrastructure. -func LocalNetworks(r report.Report) Networks { +func LocalNetworks(r report.Report) report.Networks { var ( - result = Networks{} + result = report.Networks{} networks = map[string]struct{}{} ) @@ -39,13 +36,3 @@ func LocalNetworks(r report.Report) Networks { } return result } - -// Contains returns true if IP is in Networks. -func (n Networks) Contains(ip net.IP) bool { - for _, net := range n { - if net.Contains(ip) { - return true - } - } - return false -} diff --git a/render/theinternet_test.go b/render/theinternet_test.go index cb04f6d2e..d5f4538af 100644 --- a/render/theinternet_test.go +++ b/render/theinternet_test.go @@ -16,7 +16,7 @@ func TestReportLocalNetworks(t *testing.T) { "nonets": {}, "foo": {"local_networks": "10.0.0.1/8 192.168.1.1/24 10.0.0.1/8 badnet/33"}, }}}) - want := render.Networks([]*net.IPNet{ + want := report.Networks([]*net.IPNet{ mustParseCIDR("10.0.0.1/8"), mustParseCIDR("192.168.1.1/24"), }) @@ -26,21 +26,6 @@ func TestReportLocalNetworks(t *testing.T) { } } -func TestContains(t *testing.T) { - networks := render.Networks([]*net.IPNet{ - mustParseCIDR("10.0.0.1/8"), - mustParseCIDR("192.168.1.1/24"), - }) - - if networks.Contains(net.ParseIP("52.52.52.52")) { - t.Errorf("52.52.52.52 not in %v", networks) - } - - if !networks.Contains(net.ParseIP("10.0.0.1")) { - t.Errorf("10.0.0.1 in %v", networks) - } -} - func mustParseCIDR(s string) *net.IPNet { _, ipNet, err := net.ParseCIDR(s) if err != nil { diff --git a/report/id.go b/report/id.go index f8756a821..4dcb4b9a2 100644 --- a/report/id.go +++ b/report/id.go @@ -55,11 +55,18 @@ func MakeEndpointNodeID(hostID, address, port string) string { // MakeAddressNodeID produces an address node ID from its composite parts. func MakeAddressNodeID(hostID, address string) string { - if !isLoopback(address) { - // Only loopback addresses get scoped by hostID. - hostID = "" + var scope string + + // Loopback addresses and addresses explicity marked as + // local get scoped by hostID + addressIP := net.ParseIP(address) + if addressIP != nil && LocalNetworks.Contains(addressIP) { + scope = hostID + } else if isLoopback(address) { + scope = hostID } - return hostID + ScopeDelim + address + + return scope + ScopeDelim + address } // MakeProcessNodeID produces a process node ID from its composite parts. diff --git a/report/networks.go b/report/networks.go new file mode 100644 index 000000000..4fe5f6fe4 --- /dev/null +++ b/report/networks.go @@ -0,0 +1,60 @@ +package report + +import ( + "net" +) + +// Networks represent a set of subnets +type Networks []*net.IPNet + +// Interface is exported for testing. +type Interface interface { + Addrs() ([]net.Addr, error) +} + +// Variables exposed for testing +var ( + LocalNetworks = Networks{} + InterfaceByNameStub = func(name string) (Interface, error) { + return net.InterfaceByName(name) + } +) + +// Contains returns true if IP is in Networks. +func (n Networks) Contains(ip net.IP) bool { + for _, net := range n { + if net.Contains(ip) { + return true + } + } + return false +} + +// AddLocalBridge records the subnet address associated with the bridge name +// supplied, such that MakeAddressNodeID will scope addresses in this subnet +// as local. +func AddLocalBridge(name string) error { + inf, err := InterfaceByNameStub(name) + if err != nil { + return err + } + + addrs, err := inf.Addrs() + if err != nil { + return err + } + for _, addr := range addrs { + _, network, err := net.ParseCIDR(addr.String()) + if err != nil { + return err + } + + if network == nil { + continue + } + + LocalNetworks = append(LocalNetworks, network) + } + + return nil +} diff --git a/report/networks_test.go b/report/networks_test.go new file mode 100644 index 000000000..0c18207f3 --- /dev/null +++ b/report/networks_test.go @@ -0,0 +1,72 @@ +package report_test + +import ( + "net" + "reflect" + "testing" + + "github.com/weaveworks/scope/report" + "github.com/weaveworks/scope/test" +) + +func TestContains(t *testing.T) { + networks := report.Networks([]*net.IPNet{ + mustParseCIDR("10.0.0.1/8"), + mustParseCIDR("192.168.1.1/24"), + }) + + if networks.Contains(net.ParseIP("52.52.52.52")) { + t.Errorf("52.52.52.52 not in %v", networks) + } + + if !networks.Contains(net.ParseIP("10.0.0.1")) { + t.Errorf("10.0.0.1 in %v", networks) + } +} + +func mustParseCIDR(s string) *net.IPNet { + _, ipNet, err := net.ParseCIDR(s) + if err != nil { + panic(err) + } + return ipNet +} + +type mockInterface struct { + addrs []net.Addr +} + +type mockAddr string + +func (m mockInterface) Addrs() ([]net.Addr, error) { + return m.addrs, nil +} + +func (m mockAddr) Network() string { + return "ip+net" +} + +func (m mockAddr) String() string { + return string(m) +} + +func TestAddLocal(t *testing.T) { + oldInterfaceByNameStub := report.InterfaceByNameStub + defer func() { report.InterfaceByNameStub = oldInterfaceByNameStub }() + + report.InterfaceByNameStub = func(name string) (report.Interface, error) { + return mockInterface{[]net.Addr{mockAddr("52.53.54.55/16")}}, nil + } + + err := report.AddLocalBridge("foo") + if err != nil { + t.Errorf("%v", err) + } + + want := report.Networks([]*net.IPNet{mustParseCIDR("52.53.54.55/16")}) + have := report.LocalNetworks + + if !reflect.DeepEqual(want, have) { + t.Errorf("%s", test.Diff(want, have)) + } +}