connections.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. package detailed
  2. import (
  3. "fmt"
  4. "sort"
  5. "strconv"
  6. "github.com/weaveworks/scope/probe/endpoint"
  7. "github.com/weaveworks/scope/render"
  8. "github.com/weaveworks/scope/report"
  9. )
  10. const (
  11. portKey = "port"
  12. portLabel = "Port"
  13. countKey = "count"
  14. countLabel = "Count"
  15. remoteKey = "remote"
  16. remoteLabel = "Remote"
  17. number = "number"
  18. )
  19. // Exported for testing
  20. var (
  21. NormalColumns = []Column{
  22. {ID: portKey, Label: portLabel, Datatype: report.Number},
  23. {ID: countKey, Label: countLabel, Datatype: report.Number, DefaultSort: true},
  24. }
  25. InternetColumns = []Column{
  26. {ID: remoteKey, Label: remoteLabel},
  27. {ID: portKey, Label: portLabel, Datatype: report.Number},
  28. {ID: countKey, Label: countLabel, Datatype: report.Number, DefaultSort: true},
  29. }
  30. )
  31. // ConnectionsSummary is the table of connection to/form a node
  32. type ConnectionsSummary struct {
  33. ID string `json:"id"`
  34. TopologyID string `json:"topologyId"`
  35. Label string `json:"label"`
  36. Columns []Column `json:"columns"`
  37. Connections []Connection `json:"connections"`
  38. }
  39. // Connection is a row in the connections table.
  40. type Connection struct {
  41. ID string `json:"id"` // ID of this element in the UI. Must be unique for a given ConnectionsSummary.
  42. NodeID string `json:"nodeId"` // ID of a node in the topology. Optional, must be set if linkable is true.
  43. Label string `json:"label"`
  44. LabelMinor string `json:"labelMinor,omitempty"`
  45. Metadata []report.MetadataRow `json:"metadata,omitempty"`
  46. }
  47. type connectionsByID []Connection
  48. func (s connectionsByID) Len() int { return len(s) }
  49. func (s connectionsByID) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
  50. func (s connectionsByID) Less(i, j int) bool { return s[i].ID < s[j].ID }
  51. // Intermediate type used as a key to dedupe rows
  52. type connection struct {
  53. remoteNodeID string
  54. remoteAddr, localAddr string // for internet nodes only
  55. port string // destination port
  56. }
  57. type connectionCounters struct {
  58. counted map[string]struct{}
  59. counts map[connection]int
  60. }
  61. func newConnectionCounters() *connectionCounters {
  62. return &connectionCounters{counted: map[string]struct{}{}, counts: map[connection]int{}}
  63. }
  64. func (c *connectionCounters) add(dns report.DNSRecords, outgoing bool, localNode, remoteNode, localEndpoint, remoteEndpoint report.Node) {
  65. // We identify connections by their source endpoint, pre-NAT, to
  66. // ensure we only count them once.
  67. srcEndpoint, dstEndpoint := remoteEndpoint, localEndpoint
  68. if outgoing {
  69. srcEndpoint, dstEndpoint = localEndpoint, remoteEndpoint
  70. }
  71. connectionID := srcEndpoint.ID
  72. if copySrcEndpointID, _, ok := srcEndpoint.Latest.LookupEntry(endpoint.CopyOf); ok {
  73. connectionID = copySrcEndpointID
  74. }
  75. if _, ok := c.counted[connectionID]; ok {
  76. return
  77. }
  78. conn := connection{remoteNodeID: remoteNode.ID}
  79. var ok bool
  80. if _, _, conn.port, ok = report.ParseEndpointNodeID(dstEndpoint.ID); !ok {
  81. return
  82. }
  83. // For internet nodes we break out individual addresses
  84. if conn.remoteAddr, ok = internetAddr(dns, remoteNode, remoteEndpoint); !ok {
  85. return
  86. }
  87. if conn.localAddr, ok = internetAddr(dns, localNode, localEndpoint); !ok {
  88. return
  89. }
  90. count := 1
  91. if countStr, _, ok := srcEndpoint.Latest.LookupEntry(report.ConnectionCount); ok {
  92. if i, err := strconv.Atoi(countStr); err == nil {
  93. count = i
  94. }
  95. }
  96. c.counted[connectionID] = struct{}{}
  97. c.counts[conn] += count
  98. }
  99. func internetAddr(dns report.DNSRecords, node report.Node, ep report.Node) (string, bool) {
  100. if !render.IsInternetNode(node) {
  101. return "", true
  102. }
  103. _, addr, _, ok := report.ParseEndpointNodeID(ep.ID)
  104. if !ok {
  105. return "", false
  106. }
  107. if name, found := dns.FirstMatch(ep.ID, func(string) bool { return true }); found {
  108. // we show the "most important" name only, since we don't have
  109. // space for more
  110. addr = fmt.Sprintf("%s (%s)", name, addr)
  111. }
  112. return addr, true
  113. }
  114. func (c *connectionCounters) rows(r report.Report, ns report.Nodes, includeLocal bool) []Connection {
  115. output := []Connection{}
  116. for row, count := range c.counts {
  117. // Use MakeBasicNodeSummary to render the id and label of this node
  118. summary, _ := MakeBasicNodeSummary(r, ns[row.remoteNodeID])
  119. connection := Connection{
  120. ID: fmt.Sprintf("%s-%s-%s-%s", row.remoteNodeID, row.remoteAddr, row.localAddr, row.port),
  121. NodeID: summary.ID,
  122. Label: summary.Label,
  123. LabelMinor: summary.LabelMinor,
  124. }
  125. if row.remoteAddr != "" {
  126. connection.Label = row.remoteAddr
  127. connection.LabelMinor = ""
  128. }
  129. if includeLocal {
  130. connection.Metadata = append(connection.Metadata,
  131. report.MetadataRow{
  132. ID: remoteKey,
  133. Value: row.localAddr,
  134. })
  135. }
  136. connection.Metadata = append(connection.Metadata,
  137. report.MetadataRow{
  138. ID: portKey,
  139. Value: row.port,
  140. },
  141. report.MetadataRow{
  142. ID: countKey,
  143. Value: strconv.Itoa(count),
  144. },
  145. )
  146. output = append(output, connection)
  147. }
  148. sort.Sort(connectionsByID(output))
  149. return output
  150. }
  151. func incomingConnectionsSummary(topologyID string, r report.Report, n report.Node, ns report.Nodes) ConnectionsSummary {
  152. localEndpointIDs, localEndpointIDCopies := endpointChildIDsAndCopyMapOf(n)
  153. counts := newConnectionCounters()
  154. // For each node which has an edge TO me
  155. for _, node := range ns {
  156. if !node.Adjacency.Contains(n.ID) {
  157. continue
  158. }
  159. for _, remoteEndpoint := range endpointChildrenOf(node) {
  160. for _, localEndpointID := range remoteEndpoint.Adjacency.Intersection(localEndpointIDs) {
  161. localEndpointID = canonicalEndpointID(localEndpointIDCopies, localEndpointID)
  162. counts.add(r.DNS, false, n, node, r.Endpoint.Nodes[localEndpointID], remoteEndpoint)
  163. }
  164. }
  165. }
  166. columnHeaders := NormalColumns
  167. if render.IsInternetNode(n) {
  168. columnHeaders = InternetColumns
  169. }
  170. return ConnectionsSummary{
  171. ID: "incoming-connections",
  172. TopologyID: topologyID,
  173. Label: "Inbound",
  174. Columns: columnHeaders,
  175. Connections: counts.rows(r, ns, render.IsInternetNode(n)),
  176. }
  177. }
  178. func outgoingConnectionsSummary(topologyID string, r report.Report, n report.Node, ns report.Nodes) ConnectionsSummary {
  179. localEndpoints := endpointChildrenOf(n)
  180. counts := newConnectionCounters()
  181. // For each node which has an edge FROM me
  182. for _, id := range n.Adjacency {
  183. node, ok := ns[id]
  184. if !ok {
  185. continue
  186. }
  187. remoteEndpointIDs, remoteEndpointIDCopies := endpointChildIDsAndCopyMapOf(node)
  188. for _, localEndpoint := range localEndpoints {
  189. for _, remoteEndpointID := range localEndpoint.Adjacency.Intersection(remoteEndpointIDs) {
  190. remoteEndpointID = canonicalEndpointID(remoteEndpointIDCopies, remoteEndpointID)
  191. counts.add(r.DNS, true, n, node, localEndpoint, r.Endpoint.Nodes[remoteEndpointID])
  192. }
  193. }
  194. }
  195. columnHeaders := NormalColumns
  196. if render.IsInternetNode(n) {
  197. columnHeaders = InternetColumns
  198. }
  199. return ConnectionsSummary{
  200. ID: "outgoing-connections",
  201. TopologyID: topologyID,
  202. Label: "Outbound",
  203. Columns: columnHeaders,
  204. Connections: counts.rows(r, ns, render.IsInternetNode(n)),
  205. }
  206. }
  207. func endpointChildrenOf(n report.Node) []report.Node {
  208. result := []report.Node{}
  209. n.Children.ForEach(func(child report.Node) {
  210. if child.Topology == report.Endpoint {
  211. result = append(result, child)
  212. }
  213. })
  214. return result
  215. }
  216. func endpointChildIDsAndCopyMapOf(n report.Node) (report.IDList, map[string]string) {
  217. ids := report.MakeIDList()
  218. copies := map[string]string{}
  219. n.Children.ForEach(func(child report.Node) {
  220. if child.Topology == report.Endpoint {
  221. ids = ids.Add(child.ID)
  222. if copyID, _, ok := child.Latest.LookupEntry(endpoint.CopyOf); ok {
  223. copies[child.ID] = copyID
  224. }
  225. }
  226. })
  227. return ids, copies
  228. }
  229. // canonicalEndpointID returns the original endpoint ID of which id is
  230. // a "copy_of" (due to NATing), or, if the id is not a copy, the id
  231. // itself.
  232. //
  233. // This is used for determining a unique destination endpoint ID for a
  234. // connection, removing any arbitrariness in the destination port we
  235. // are associating with the connection when it is encountered multiple
  236. // times in the topology (with different destination endpoints, due to
  237. // DNATing).
  238. func canonicalEndpointID(copies map[string]string, id string) string {
  239. if original, ok := copies[id]; ok {
  240. return original
  241. }
  242. return id
  243. }