Merge pull request #1780 from weaveworks/1733-scope-loopback-connections

Handle loopback addresses correctly when tracking connections
This commit is contained in:
Alfonso Acosta
2016-08-10 14:52:44 +01:00
committed by GitHub
12 changed files with 80 additions and 91 deletions

View File

@@ -52,9 +52,9 @@ func (n natMapper) applyNAT(rpt report.Report, scope string) {
n.flowWalker.walkFlows(func(f flow) {
var (
mapping = toMapping(f)
realEndpointID = report.MakeEndpointNodeID(scope, mapping.originalIP, strconv.Itoa(mapping.originalPort))
realEndpointID = report.MakeEndpointNodeID(scope, "", mapping.originalIP, strconv.Itoa(mapping.originalPort))
copyEndpointPort = strconv.Itoa(mapping.rewrittenPort)
copyEndpointID = report.MakeEndpointNodeID(scope, mapping.rewrittenIP, copyEndpointPort)
copyEndpointID = report.MakeEndpointNodeID(scope, "", mapping.rewrittenIP, copyEndpointPort)
node, ok = rpt.Endpoint.Nodes[realEndpointID]
)
if !ok {

View File

@@ -43,7 +43,7 @@ func TestNat(t *testing.T) {
}
have := report.MakeReport()
originalID := report.MakeEndpointNodeID("host1", "10.0.47.1", "80")
originalID := report.MakeEndpointNodeID("host1", "", "10.0.47.1", "80")
have.Endpoint.AddNode(report.MakeNodeWith(originalID, map[string]string{
Addr: "10.0.47.1",
Port: "80",
@@ -52,7 +52,7 @@ func TestNat(t *testing.T) {
}))
want := have.Copy()
wantID := report.MakeEndpointNodeID("host1", "1.2.3.4", "80")
wantID := report.MakeEndpointNodeID("host1", "", "1.2.3.4", "80")
want.Endpoint.AddNode(report.MakeNodeWith(wantID, map[string]string{
Addr: "1.2.3.4",
Port: "80",
@@ -78,7 +78,7 @@ func TestNat(t *testing.T) {
}
have := report.MakeReport()
originalID := report.MakeEndpointNodeID("host2", "10.0.47.2", "22222")
originalID := report.MakeEndpointNodeID("host2", "", "10.0.47.2", "22222")
have.Endpoint.AddNode(report.MakeNodeWith(originalID, map[string]string{
Addr: "10.0.47.2",
Port: "22222",
@@ -87,7 +87,7 @@ func TestNat(t *testing.T) {
}))
want := have.Copy()
want.Endpoint.AddNode(report.MakeNodeWith(report.MakeEndpointNodeID("host2", "2.3.4.5", "22223"), map[string]string{
want.Endpoint.AddNode(report.MakeNodeWith(report.MakeEndpointNodeID("host2", "", "2.3.4.5", "22223"), map[string]string{
Addr: "2.3.4.5",
Port: "22223",
"copy_of": originalID,

View File

@@ -126,7 +126,7 @@ func readProcessConnections(buf *bytes.Buffer, namespaceProcs []*process.Process
}
// walkNamespace does the work of walk for a single namespace
func (w pidWalker) walkNamespace(buf *bytes.Buffer, sockets map[uint64]*Proc, namespaceProcs []*process.Process) error {
func (w pidWalker) walkNamespace(namespaceID uint64, buf *bytes.Buffer, sockets map[uint64]*Proc, namespaceProcs []*process.Process) error {
if found, err := readProcessConnections(buf, namespaceProcs); err != nil || !found {
return err
@@ -181,8 +181,9 @@ func (w pidWalker) walkNamespace(buf *bytes.Buffer, sockets map[uint64]*Proc, na
// garbage
if proc == nil {
proc = &Proc{
PID: uint(p.PID),
Name: p.Name,
PID: uint(p.PID),
Name: p.Name,
NetNamespaceID: namespaceID,
}
}
@@ -225,10 +226,10 @@ func (w pidWalker) walk(buf *bytes.Buffer) (map[uint64]*Proc, error) {
namespaces[namespaceID] = append(namespaces[namespaceID], &p)
})
for _, procs := range namespaces {
for namespaceID, procs := range namespaces {
select {
case <-w.tickc:
w.walkNamespace(buf, sockets, procs)
w.walkNamespace(namespaceID, buf, sockets, procs)
case <-w.stopc:
break // abort
}

View File

@@ -24,8 +24,9 @@ type Connection struct {
// Proc is a single process with PID and process name.
type Proc struct {
PID uint
Name string
PID uint
Name string
NetNamespaceID uint64
}
// ConnIter is returned by Connections().

View File

@@ -132,7 +132,7 @@ func (r *Reporter) Report() (report.Report, error) {
}
seenTuples[tuple.key()] = tuple
r.addConnection(&rpt, tuple, extraNodeInfo, extraNodeInfo)
r.addConnection(&rpt, tuple, "", extraNodeInfo, extraNodeInfo)
})
}
@@ -143,7 +143,8 @@ func (r *Reporter) Report() (report.Report, error) {
}
for conn := conns.Next(); conn != nil; conn = conns.Next() {
var (
tuple = fourTuple{
namespaceID string
tuple = fourTuple{
conn.LocalAddress.String(),
conn.RemoteAddress.String(),
conn.LocalPort,
@@ -157,6 +158,10 @@ func (r *Reporter) Report() (report.Report, error) {
fromNodeInfo[report.HostNodeID] = hostNodeID
}
if conn.Proc.NetNamespaceID > 0 {
namespaceID = strconv.FormatUint(conn.Proc.NetNamespaceID, 10)
}
// If we've already seen this connection, we should know the direction
// (or have already figured it out), so we normalize and use the
// canonical direction. Otherwise, we can use a port-heuristic to guess
@@ -166,7 +171,7 @@ func (r *Reporter) Report() (report.Report, error) {
tuple.reverse()
toNodeInfo, fromNodeInfo = fromNodeInfo, toNodeInfo
}
r.addConnection(&rpt, tuple, fromNodeInfo, toNodeInfo)
r.addConnection(&rpt, tuple, namespaceID, fromNodeInfo, toNodeInfo)
}
}
@@ -174,10 +179,10 @@ func (r *Reporter) Report() (report.Report, error) {
return rpt, nil
}
func (r *Reporter) addConnection(rpt *report.Report, t fourTuple, extraFromNode, extraToNode map[string]string) {
func (r *Reporter) addConnection(rpt *report.Report, t fourTuple, namespaceID string, extraFromNode, extraToNode map[string]string) {
var (
fromEndpointNodeID = report.MakeEndpointNodeID(r.hostID, t.fromAddr, strconv.Itoa(int(t.fromPort)))
toEndpointNodeID = report.MakeEndpointNodeID(r.hostID, t.toAddr, strconv.Itoa(int(t.toPort)))
fromEndpointNodeID = report.MakeEndpointNodeID(r.hostID, namespaceID, t.fromAddr, strconv.Itoa(int(t.fromPort)))
toEndpointNodeID = report.MakeEndpointNodeID(r.hostID, namespaceID, t.toAddr, strconv.Itoa(int(t.toPort)))
fromNode = report.MakeNodeWith(fromEndpointNodeID, map[string]string{
Addr: t.fromAddr,

View File

@@ -91,8 +91,8 @@ func TestSpyWithProcesses(t *testing.T) {
// buf, _ := json.MarshalIndent(r, "", " ") ; t.Logf("\n%s\n", buf)
var (
scopedLocal = report.MakeEndpointNodeID(nodeID, fixLocalAddress.String(), strconv.Itoa(int(fixLocalPort)))
scopedRemote = report.MakeEndpointNodeID(nodeID, fixRemoteAddress.String(), strconv.Itoa(int(fixRemotePort)))
scopedLocal = report.MakeEndpointNodeID(nodeID, "", fixLocalAddress.String(), strconv.Itoa(int(fixLocalPort)))
scopedRemote = report.MakeEndpointNodeID(nodeID, "", fixRemoteAddress.String(), strconv.Itoa(int(fixRemotePort)))
)
if want, have := 1, len(r.Endpoint.Nodes[scopedRemote].Adjacency); want != have {

View File

@@ -10,7 +10,7 @@ import (
func TestTagger(t *testing.T) {
var (
hostID = "foo"
endpointNodeID = report.MakeEndpointNodeID(hostID, "1.2.3.4", "56789") // hostID ignored
endpointNodeID = report.MakeEndpointNodeID(hostID, "", "1.2.3.4", "56789") // hostID ignored
node = report.MakeNodeWith(endpointNodeID, map[string]string{"foo": "bar"})
)

View File

@@ -264,6 +264,11 @@ func MapContainer2IP(m report.Node) []string {
if !ok {
continue
}
// loopback addresses are shared among all namespaces
// so we can't use them to attribute connections to a container
if report.IsLoopback(addr) {
continue
}
id := report.MakeScopedEndpointNodeID(scope, addr, "")
result = append(result, id)
}

View File

@@ -19,11 +19,11 @@ var (
randomIP = "3.4.5.6"
randomPort = "56789"
randomEndpointNodeID = report.MakeEndpointNodeID(serverHostID, randomIP, randomPort)
randomEndpointNodeID = report.MakeEndpointNodeID(serverHostID, "", randomIP, randomPort)
serverIP = "192.168.1.1"
serverPort = "80"
serverEndpointNodeID = report.MakeEndpointNodeID(serverHostID, serverIP, serverPort)
serverEndpointNodeID = report.MakeEndpointNodeID(serverHostID, "", serverIP, serverPort)
container1ID = "11b2c3d4e5"
container1IP = "192.168.0.1"
@@ -31,11 +31,11 @@ var (
container1NodeID = report.MakeContainerNodeID(container1ID)
container1Port = "16782"
container1EndpointNodeID = report.MakeEndpointNodeID(serverHostID, container1IP, container1Port)
container1EndpointNodeID = report.MakeEndpointNodeID(serverHostID, "", container1IP, container1Port)
duplicatedIP = "192.168.0.2"
duplicatedPort = "80"
duplicatedEndpointNodeID = report.MakeEndpointNodeID(serverHostID, duplicatedIP, duplicatedPort)
duplicatedEndpointNodeID = report.MakeEndpointNodeID(serverHostID, "", duplicatedIP, duplicatedPort)
container2ID = "21b2c3d4e5"
container2IP = duplicatedIP

View File

@@ -1,13 +1,8 @@
package report
import (
"hash"
"hash/fnv"
"net"
"strings"
"sync"
"github.com/bluele/gcache"
)
// TheInternet is used as a node ID to indicate a remote IP.
@@ -29,66 +24,46 @@ const (
DoesNotMakeConnections = "does_not_make_connections"
)
var (
idCache = gcache.New(1024).LRU().Build()
hashers = sync.Pool{
New: func() interface{} {
return fnv.New64a()
},
}
)
func lookupID(part1, part2, part3 string, f func() string) string {
h := hashers.Get().(hash.Hash64)
h.Write([]byte(part1))
h.Write([]byte(part2))
h.Write([]byte(part3))
sum := h.Sum64()
var result string
if id, err := idCache.Get(sum); id != nil && err != nil {
result = id.(string)
} else {
result = f()
idCache.Set(sum, result)
}
h.Reset()
hashers.Put(h)
return result
}
// MakeEndpointNodeID produces an endpoint node ID from its composite parts.
func MakeEndpointNodeID(hostID, address, port string) string {
return lookupID(hostID, address, port, func() string {
return MakeAddressNodeID(hostID, address) + ScopeDelim + port
})
func MakeEndpointNodeID(hostID, namespaceID, address, port string) string {
return makeAddressID(hostID, namespaceID, address) + ScopeDelim + port
}
// MakeAddressNodeID produces an address node ID from its composite parts.
func MakeAddressNodeID(hostID, address string) string {
return makeAddressID(hostID, "", address)
}
func makeAddressID(hostID, namespaceID, address string) string {
var scope string
// Loopback addresses and addresses explicitly marked as
// local get scoped by hostID
// Loopback addresses and addresses explicitly marked as local get
// scoped by hostID
// Loopback addresses are also scoped by the networking
// namespace if available, since they can clash.
addressIP := net.ParseIP(address)
if addressIP != nil && LocalNetworks.Contains(addressIP) {
scope = hostID
} else if isLoopback(address) {
} else if IsLoopback(address) {
scope = hostID
if namespaceID != "" {
scope += "-" + namespaceID
}
}
return scope + ScopeDelim + address
}
// MakeScopedEndpointNodeID is like MakeEndpointNodeID, but it always
// prefixes the ID witha scope.
func MakeScopedEndpointNodeID(hostID, address, port string) string {
return hostID + ScopeDelim + address + ScopeDelim + port
// prefixes the ID with a scope.
func MakeScopedEndpointNodeID(scope, address, port string) string {
return scope + ScopeDelim + address + ScopeDelim + port
}
// MakeScopedAddressNodeID is like MakeAddressNodeID, but it always
// prefixes the ID witha scope.
func MakeScopedAddressNodeID(hostID, address string) string {
return hostID + ScopeDelim + address
func MakeScopedAddressNodeID(scope, address string) string {
return scope + ScopeDelim + address
}
// MakeProcessNodeID produces a process node ID from its composite parts.
@@ -174,14 +149,14 @@ func ParseNodeID(nodeID string) (hostID string, remainder string, ok bool) {
return fields[0], fields[1], true
}
// ParseEndpointNodeID produces the host ID, address, and port and remainder
// (typically an address) from an endpoint node ID. Note that hostID may be
// blank.
func ParseEndpointNodeID(endpointNodeID string) (hostID, address, port string, ok bool) {
// ParseEndpointNodeID produces the scope, address, and port and remainder.
// Note that hostID may be blank.
func ParseEndpointNodeID(endpointNodeID string) (scope, address, port string, ok bool) {
fields := strings.SplitN(endpointNodeID, ScopeDelim, 3)
if len(fields) != 3 {
return "", "", "", false
}
return fields[0], fields[1], fields[2], true
}
@@ -201,7 +176,8 @@ func ExtractHostID(m Node) string {
return hostID
}
func isLoopback(address string) bool {
// IsLoopback ascertains if an address comes from a loopback interface.
func IsLoopback(address string) bool {
ip := net.ParseIP(address)
return ip != nil && ip.IsLoopback()
}

View File

@@ -18,12 +18,12 @@ var (
unknownHostID = "" // by definition, we don't know it
unknownAddress = "172.16.93.112" // will be a pseudonode, no corresponding host
client54001EndpointNodeID = report.MakeEndpointNodeID(clientHostID, clientAddress, "54001") // i.e. curl
client54002EndpointNodeID = report.MakeEndpointNodeID(clientHostID, clientAddress, "54002") // also curl
server80EndpointNodeID = report.MakeEndpointNodeID(serverHostID, serverAddress, "80") // i.e. apache
unknown1EndpointNodeID = report.MakeEndpointNodeID(unknownHostID, unknownAddress, "10001")
unknown2EndpointNodeID = report.MakeEndpointNodeID(unknownHostID, unknownAddress, "10002")
unknown3EndpointNodeID = report.MakeEndpointNodeID(unknownHostID, unknownAddress, "10003")
client54001EndpointNodeID = report.MakeEndpointNodeID(clientHostID, "", clientAddress, "54001") // i.e. curl
client54002EndpointNodeID = report.MakeEndpointNodeID(clientHostID, "", clientAddress, "54002") // also curl
server80EndpointNodeID = report.MakeEndpointNodeID(serverHostID, "", serverAddress, "80") // i.e. apache
unknown1EndpointNodeID = report.MakeEndpointNodeID(unknownHostID, "", unknownAddress, "10001")
unknown2EndpointNodeID = report.MakeEndpointNodeID(unknownHostID, "", unknownAddress, "10002")
unknown3EndpointNodeID = report.MakeEndpointNodeID(unknownHostID, "", unknownAddress, "10003")
clientAddressNodeID = report.MakeAddressNodeID(clientHostID, clientAddress)
serverAddressNodeID = report.MakeAddressNodeID(serverHostID, serverAddress)
@@ -50,7 +50,8 @@ func TestEndpointNodeID(t *testing.T) {
}
for input, want := range map[string]struct{ name, address, port string }{
report.MakeEndpointNodeID("host.com", "1.2.3.4", "c"): {"", "1.2.3.4", "c"},
report.MakeEndpointNodeID("host.com", "namespaceid", "127.0.0.1", "c"): {"host.com-namespaceid", "127.0.0.1", "c"},
report.MakeEndpointNodeID("host.com", "", "1.2.3.4", "c"): {"", "1.2.3.4", "c"},
"a;b;c": {"a", "b", "c"},
} {
haveName, haveAddress, havePort, ok := report.ParseEndpointNodeID(input)

View File

@@ -57,15 +57,15 @@ var (
ClientHostNodeID = report.MakeHostNodeID(ClientHostID)
ServerHostNodeID = report.MakeHostNodeID(ServerHostID)
Client54001NodeID = report.MakeEndpointNodeID(ClientHostID, ClientIP, ClientPort54001) // curl (1)
Client54002NodeID = report.MakeEndpointNodeID(ClientHostID, ClientIP, ClientPort54002) // curl (2)
Server80NodeID = report.MakeEndpointNodeID(ServerHostID, ServerIP, ServerPort) // apache
UnknownClient1NodeID = report.MakeEndpointNodeID(ServerHostID, UnknownClient1IP, UnknownClient1Port) // we want to ensure two unknown clients, connnected
UnknownClient2NodeID = report.MakeEndpointNodeID(ServerHostID, UnknownClient2IP, UnknownClient2Port) // to the same server, are deduped.
UnknownClient3NodeID = report.MakeEndpointNodeID(ServerHostID, UnknownClient3IP, UnknownClient3Port) // Check this one isn't deduped
RandomClientNodeID = report.MakeEndpointNodeID(ServerHostID, RandomClientIP, RandomClientPort) // this should become an internet node
NonContainerNodeID = report.MakeEndpointNodeID(ServerHostID, ServerIP, NonContainerClientPort)
GoogleEndpointNodeID = report.MakeEndpointNodeID(ServerHostID, GoogleIP, GooglePort)
Client54001NodeID = report.MakeEndpointNodeID(ClientHostID, "", ClientIP, ClientPort54001) // curl (1)
Client54002NodeID = report.MakeEndpointNodeID(ClientHostID, "", ClientIP, ClientPort54002) // curl (2)
Server80NodeID = report.MakeEndpointNodeID(ServerHostID, "", ServerIP, ServerPort) // apache
UnknownClient1NodeID = report.MakeEndpointNodeID(ServerHostID, "", UnknownClient1IP, UnknownClient1Port) // we want to ensure two unknown clients, connnected
UnknownClient2NodeID = report.MakeEndpointNodeID(ServerHostID, "", UnknownClient2IP, UnknownClient2Port) // to the same server, are deduped.
UnknownClient3NodeID = report.MakeEndpointNodeID(ServerHostID, "", UnknownClient3IP, UnknownClient3Port) // Check this one isn't deduped
RandomClientNodeID = report.MakeEndpointNodeID(ServerHostID, "", RandomClientIP, RandomClientPort) // this should become an internet node
NonContainerNodeID = report.MakeEndpointNodeID(ServerHostID, "", ServerIP, NonContainerClientPort)
GoogleEndpointNodeID = report.MakeEndpointNodeID(ServerHostID, "", GoogleIP, GooglePort)
ClientProcess1NodeID = report.MakeProcessNodeID(ClientHostID, Client1PID)
ClientProcess2NodeID = report.MakeProcessNodeID(ClientHostID, Client2PID)