Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move DNS name mapping from endpoint to report #3061

Merged
merged 2 commits into from
Feb 20, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions probe/endpoint/connection_tracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,23 +219,33 @@ func (t *connectionTracker) addConnection(rpt *report.Report, incoming bool, ft
)
rpt.Endpoint.AddNode(fromNode.WithAdjacent(toNode.ID))
rpt.Endpoint.AddNode(toNode)
t.addDNS(rpt, ft.fromAddr)
t.addDNS(rpt, ft.toAddr)
}

func (t *connectionTracker) makeEndpointNode(namespaceID string, addr string, port uint16, extra map[string]string) report.Node {
portStr := strconv.Itoa(int(port))
node := report.MakeNodeWith(report.MakeEndpointNodeID(t.conf.HostID, namespaceID, addr, portStr), nil)
if names := t.conf.DNSSnooper.CachedNamesForIP(addr); len(names) > 0 {
node = node.WithSet(SnoopedDNSNames, report.MakeStringSet(names...))
}
if names, err := t.reverseResolver.get(addr); err == nil && len(names) > 0 {
node = node.WithSet(ReverseDNSNames, report.MakeStringSet(names...))
}
if extra != nil {
node = node.WithLatests(extra)
}
return node
}

// Add DNS record for address to report, if not already there
func (t *connectionTracker) addDNS(rpt *report.Report, addr string) {
if _, found := rpt.DNS[addr]; !found {
forward := t.conf.DNSSnooper.CachedNamesForIP(addr)
record := report.DNSRecord{
Forward: report.MakeStringSet(forward...),
}
if names, err := t.reverseResolver.get(addr); err == nil && len(names) > 0 {
record.Reverse = report.MakeStringSet(names...)
}
rpt.DNS[addr] = record
}
}

func (t *connectionTracker) Stop() error {
if t.ebpfTracker != nil {
t.ebpfTracker.stop()
Expand Down
3 changes: 1 addition & 2 deletions probe/endpoint/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ func newReverseResolver() *reverseResolver {
}

// get the reverse resolution for an IP address if already in the cache, a
// gcache.NotFoundKeyError error otherwise. Note: it returns one of the
// possible names that can be obtained for that IP.
// gcache.NotFoundKeyError error otherwise.
func (r *reverseResolver) get(address string) ([]string, error) {
val, err := r.cache.Get(address)
if hostnames, ok := val.([]string); err == nil && ok {
Expand Down
14 changes: 7 additions & 7 deletions render/detailed/connections.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func newConnectionCounters() *connectionCounters {
return &connectionCounters{counted: map[string]struct{}{}, counts: map[connection]int{}}
}

func (c *connectionCounters) add(outgoing bool, localNode, remoteNode, localEndpoint, remoteEndpoint report.Node) {
func (c *connectionCounters) add(dns report.DNSRecords, outgoing bool, localNode, remoteNode, localEndpoint, remoteEndpoint report.Node) {
// We identify connections by their source endpoint, pre-NAT, to
// ensure we only count them once.
srcEndpoint, dstEndpoint := remoteEndpoint, localEndpoint
Expand All @@ -94,26 +94,26 @@ func (c *connectionCounters) add(outgoing bool, localNode, remoteNode, localEndp
return
}
// For internet nodes we break out individual addresses
if conn.remoteAddr, ok = internetAddr(remoteNode, remoteEndpoint); !ok {
if conn.remoteAddr, ok = internetAddr(dns, remoteNode, remoteEndpoint); !ok {
return
}
if conn.localAddr, ok = internetAddr(localNode, localEndpoint); !ok {
if conn.localAddr, ok = internetAddr(dns, localNode, localEndpoint); !ok {
return
}

c.counted[connectionID] = struct{}{}
c.counts[conn]++
}

func internetAddr(node report.Node, ep report.Node) (string, bool) {
func internetAddr(dns report.DNSRecords, node report.Node, ep report.Node) (string, bool) {
if !render.IsInternetNode(node) {
return "", true
}
_, addr, _, ok := report.ParseEndpointNodeID(ep.ID)
if !ok {
return "", false
}
if name, found := render.DNSFirstMatch(ep, func(string) bool { return true }); found {
if name, found := dns.FirstMatch(ep.ID, func(string) bool { return true }); found {
// we show the "most important" name only, since we don't have
// space for more
addr = fmt.Sprintf("%s (%s)", name, addr)
Expand Down Expand Up @@ -171,7 +171,7 @@ func incomingConnectionsSummary(topologyID string, r report.Report, n report.Nod
for _, remoteEndpoint := range endpointChildrenOf(node) {
for _, localEndpointID := range remoteEndpoint.Adjacency.Intersection(localEndpointIDs) {
localEndpointID = canonicalEndpointID(localEndpointIDCopies, localEndpointID)
counts.add(false, n, node, r.Endpoint.Nodes[localEndpointID], remoteEndpoint)
counts.add(r.DNS, false, n, node, r.Endpoint.Nodes[localEndpointID], remoteEndpoint)
}
}
}
Expand Down Expand Up @@ -203,7 +203,7 @@ func outgoingConnectionsSummary(topologyID string, r report.Report, n report.Nod
for _, localEndpoint := range localEndpoints {
for _, remoteEndpointID := range localEndpoint.Adjacency.Intersection(remoteEndpointIDs) {
remoteEndpointID = canonicalEndpointID(remoteEndpointIDCopies, remoteEndpointID)
counts.add(true, n, node, localEndpoint, r.Endpoint.Nodes[remoteEndpointID])
counts.add(r.DNS, true, n, node, localEndpoint, r.Endpoint.Nodes[remoteEndpointID])
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion render/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func (e mapEndpoints) Render(rpt report.Report) Nodes {
// Nodes without a hostid are mapped to pseudo nodes, if
// possible.
if _, ok := n.Latest.Lookup(report.HostNodeID); !ok {
if id, ok := pseudoNodeID(n, local); ok {
if id, ok := pseudoNodeID(rpt, n, local); ok {
ret.addChild(n, id, Pseudo)
continue
}
Expand Down
31 changes: 4 additions & 27 deletions render/id.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package render
import (
"strings"

"github.com/weaveworks/scope/probe/endpoint"
"github.com/weaveworks/scope/report"
)

Expand Down Expand Up @@ -62,13 +61,13 @@ func NewDerivedPseudoNode(id string, node report.Node) report.Node {
return output
}

func pseudoNodeID(n report.Node, local report.Networks) (string, bool) {
func pseudoNodeID(rpt report.Report, n report.Node, local report.Networks) (string, bool) {
_, addr, _, ok := report.ParseEndpointNodeID(n.ID)
if !ok {
return "", false
}

if id, ok := externalNodeID(n, addr, local); ok {
if id, ok := externalNodeID(rpt, n, addr, local); ok {
return id, ok
}

Expand All @@ -78,11 +77,11 @@ func pseudoNodeID(n report.Node, local report.Networks) (string, bool) {
}

// figure out if a node should be considered external and returns an ID which can be used to create a pseudo node
func externalNodeID(n report.Node, addr string, local report.Networks) (string, bool) {
func externalNodeID(rpt report.Report, n report.Node, addr string, local report.Networks) (string, bool) {
// First, check if it's a known service and emit a a specific node if it
// is. This needs to be done before checking IPs since known services can
// live in the same network, see https://github.com/weaveworks/scope/issues/2163
if hostname, found := DNSFirstMatch(n, isKnownService); found {
if hostname, found := rpt.DNS.FirstMatch(n.ID, isKnownService); found {
return ServiceNodeIDPrefix + hostname, true
}

Expand All @@ -101,25 +100,3 @@ func externalNodeID(n report.Node, addr string, local report.Networks) (string,
// The node is not external
return "", false
}

// DNSFirstMatch returns the first DNS name where match() returns
// true, from a prioritized list of snooped and reverse-resolved DNS
// names associated with node n.
func DNSFirstMatch(n report.Node, match func(name string) bool) (string, bool) {
// we rely on Sets being sorted, to make selection for display more
// deterministic
// prioritize snooped names
snoopedNames, _ := n.Sets.Lookup(endpoint.SnoopedDNSNames)
for _, hostname := range snoopedNames {
if match(hostname) {
return hostname, true
}
}
reverseNames, _ := n.Sets.Lookup(endpoint.ReverseDNSNames)
for _, hostname := range reverseNames {
if match(hostname) {
return hostname, true
}
}
return "", false
}
60 changes: 60 additions & 0 deletions report/dns.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package report

// DNSRecord contains names that an IP address maps to
type DNSRecord struct {
Forward StringSet `json:"forward,omitempty"`
Reverse StringSet `json:"reverse,omitempty"`
}

// DNSRecords contains all address->name mappings for a report
type DNSRecords map[string]DNSRecord

// Copy makes a copy of the DNSRecords
func (r DNSRecords) Copy() DNSRecords {
cp := make(DNSRecords, len(r))
for k, v := range r {
cp[k] = v
}
return cp
}

// Merge merges the other object into this one, and returns the result object.
// The original is not modified.
func (r DNSRecords) Merge(other DNSRecords) DNSRecords {
if len(other) > len(r) {
r, other = other, r
}
cp := r.Copy()
for k, v := range other {
if v2, ok := cp[k]; ok {
cp[k] = DNSRecord{
Forward: v.Forward.Merge(v2.Forward),
Reverse: v.Reverse.Merge(v2.Reverse),
}
} else {
cp[k] = v
}
}
return cp
}

// FirstMatch returns the first DNS name where match() returns true
func (r DNSRecords) FirstMatch(id string, match func(name string) bool) (string, bool) {
_, addr, _, ok := ParseEndpointNodeID(id)
if !ok {
return "", false
}
// we rely on StringSets being sorted, to make selection deterministic
// prioritize forward names
for _, hostname := range r[addr].Forward {
if match(hostname) {
return hostname, true
}
}
for _, hostname := range r[addr].Reverse {
if match(hostname) {
return hostname, true
}
}
return "", false
}
35 changes: 34 additions & 1 deletion report/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ type Report struct {
// their status endpoints. Edges are present.
Overlay Topology

DNS DNSRecords

// Sampling data for this report.
Sampling Sampling

Expand Down Expand Up @@ -242,6 +244,8 @@ func MakeReport() Report {
WithShape(Heptagon).
WithLabel("service", "services"),

DNS: DNSRecords{},

Sampling: Sampling{},
Window: 0,
Plugins: xfer.MakePluginSpecs(),
Expand All @@ -252,6 +256,7 @@ func MakeReport() Report {
// Copy returns a value copy of the report.
func (r Report) Copy() Report {
newReport := Report{
DNS: r.DNS.Copy(),
Sampling: r.Sampling,
Window: r.Window,
Plugins: r.Plugins.Copy(),
Expand All @@ -267,6 +272,7 @@ func (r Report) Copy() Report {
// original is not modified.
func (r Report) Merge(other Report) Report {
newReport := r.Copy()
newReport.DNS = newReport.DNS.Merge(other.DNS)
newReport.Sampling = newReport.Sampling.Merge(other.Sampling)
newReport.Window = newReport.Window + other.Window
newReport.Plugins = newReport.Plugins.Merge(other.Plugins)
Expand Down Expand Up @@ -371,7 +377,7 @@ func (r Report) Validate() error {
//
// This for now creates node's LatestControls from Controls.
func (r Report) Upgrade() Report {
return r.upgradeLatestControls().upgradePodNodes().upgradeNamespaces()
return r.upgradeLatestControls().upgradePodNodes().upgradeNamespaces().upgradeDNSRecords()
}

func (r Report) upgradeLatestControls() Report {
Expand Down Expand Up @@ -469,6 +475,33 @@ func (r Report) upgradeNamespaces() Report {
return r
}

func (r Report) upgradeDNSRecords() Report {
if len(r.DNS) > 0 {
return r
}
dns := make(DNSRecords)
for endpointID, endpoint := range r.Endpoint.Nodes {
_, addr, _, ok := ParseEndpointNodeID(endpointID)
snoopedNames, foundS := endpoint.Sets.Lookup(SnoopedDNSNames)
reverseNames, foundR := endpoint.Sets.Lookup(ReverseDNSNames)
if ok && (foundS || foundR) {
// Add address and names to report-level map
if existing, found := dns[addr]; found {
// Optimise the expected case that they are equal
if existing.Forward.Equal(snoopedNames) && existing.Reverse.Equal(reverseNames) {
continue
}
// Not equal - merge this node's data into existing data,
snoopedNames = snoopedNames.Merge(existing.Forward)
reverseNames = reverseNames.Merge(existing.Reverse)
}
dns[addr] = DNSRecord{Forward: snoopedNames, Reverse: reverseNames}
}
}
r.DNS = dns
return r
}

// BackwardCompatible returns a new backward-compatible report.
//
// This for now creates node's Controls from LatestControls.
Expand Down
13 changes: 13 additions & 0 deletions report/string_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,19 @@ func (s StringSet) Intersection(b StringSet) StringSet {
return result
}

// Equal returns true if a and b have the same contents
func (s StringSet) Equal(b StringSet) bool {
if len(s) != len(b) {
return false
}
for i := range s {
if s[i] != b[i] {
return false
}
}
return true
}

// Add adds the strings to the StringSet. Add is the only valid way to grow a
// StringSet. Add returns the StringSet to enable chaining.
func (s StringSet) Add(strs ...string) StringSet {
Expand Down