record_test.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. //go:build !ts_omit_netlog && !ts_omit_logtail
  4. package netlog
  5. import (
  6. "net/netip"
  7. "testing"
  8. "time"
  9. jsonv2 "github.com/go-json-experiment/json"
  10. "github.com/go-json-experiment/json/jsontext"
  11. "github.com/google/go-cmp/cmp"
  12. "github.com/google/go-cmp/cmp/cmpopts"
  13. "tailscale.com/tailcfg"
  14. "tailscale.com/types/ipproto"
  15. "tailscale.com/types/netlogtype"
  16. "tailscale.com/util/must"
  17. )
  18. func addr(s string) netip.Addr {
  19. if s == "" {
  20. return netip.Addr{}
  21. }
  22. return must.Get(netip.ParseAddr(s))
  23. }
  24. func addrPort(s string) netip.AddrPort {
  25. if s == "" {
  26. return netip.AddrPort{}
  27. }
  28. return must.Get(netip.ParseAddrPort(s))
  29. }
  30. func prefix(s string) netip.Prefix {
  31. if p, err := netip.ParsePrefix(s); err == nil {
  32. return p
  33. }
  34. a := addr(s)
  35. return netip.PrefixFrom(a, a.BitLen())
  36. }
  37. func conn(proto ipproto.Proto, src, dst string) netlogtype.Connection {
  38. return netlogtype.Connection{Proto: proto, Src: addrPort(src), Dst: addrPort(dst)}
  39. }
  40. func counts(txP, txB, rxP, rxB uint64) netlogtype.Counts {
  41. return netlogtype.Counts{TxPackets: txP, TxBytes: txB, RxPackets: rxP, RxBytes: rxB}
  42. }
  43. func TestToMessage(t *testing.T) {
  44. rec := record{
  45. selfNode: nodeUser{NodeView: (&tailcfg.Node{
  46. ID: 123456,
  47. StableID: "n123456CNTL",
  48. Name: "src.tail123456.ts.net.",
  49. Addresses: []netip.Prefix{prefix("100.1.2.3")},
  50. Tags: []string{"tag:src"},
  51. }).View()},
  52. start: time.Now(),
  53. end: time.Now().Add(5 * time.Second),
  54. seenNodes: map[netip.Addr]nodeUser{
  55. addr("100.1.2.4"): {NodeView: (&tailcfg.Node{
  56. ID: 123457,
  57. StableID: "n123457CNTL",
  58. Name: "dst1.tail123456.ts.net.",
  59. Addresses: []netip.Prefix{prefix("100.1.2.4")},
  60. Tags: []string{"tag:dst1"},
  61. }).View()},
  62. addr("100.1.2.5"): {NodeView: (&tailcfg.Node{
  63. ID: 123458,
  64. StableID: "n123458CNTL",
  65. Name: "dst2.tail123456.ts.net.",
  66. Addresses: []netip.Prefix{prefix("100.1.2.5")},
  67. Tags: []string{"tag:dst2"},
  68. }).View()},
  69. },
  70. virtConns: map[netlogtype.Connection]countsType{
  71. conn(0x1, "100.1.2.3:1234", "100.1.2.4:80"): {Counts: counts(12, 34, 56, 78), connType: virtualTraffic},
  72. conn(0x1, "100.1.2.3:1234", "100.1.2.5:80"): {Counts: counts(23, 45, 78, 790), connType: virtualTraffic},
  73. conn(0x6, "172.16.1.1:80", "100.1.2.4:1234"): {Counts: counts(91, 54, 723, 621), connType: subnetTraffic},
  74. conn(0x6, "172.16.1.2:443", "100.1.2.5:1234"): {Counts: counts(42, 813, 3, 1823), connType: subnetTraffic},
  75. conn(0x6, "172.16.1.3:80", "100.1.2.6:1234"): {Counts: counts(34, 52, 78, 790), connType: subnetTraffic},
  76. conn(0x6, "100.1.2.3:1234", "12.34.56.78:80"): {Counts: counts(11, 110, 10, 100), connType: exitTraffic},
  77. conn(0x6, "100.1.2.4:1234", "23.34.56.78:80"): {Counts: counts(423, 1, 6, 123), connType: exitTraffic},
  78. conn(0x6, "100.1.2.4:1234", "23.34.56.78:443"): {Counts: counts(22, 220, 20, 200), connType: exitTraffic},
  79. conn(0x6, "100.1.2.5:1234", "45.34.56.78:80"): {Counts: counts(33, 330, 30, 300), connType: exitTraffic},
  80. conn(0x6, "100.1.2.6:1234", "67.34.56.78:80"): {Counts: counts(44, 440, 40, 400), connType: exitTraffic},
  81. conn(0x6, "42.54.72.42:555", "18.42.7.1:777"): {Counts: counts(44, 440, 40, 400)},
  82. },
  83. physConns: map[netlogtype.Connection]netlogtype.Counts{
  84. conn(0, "100.1.2.4:0", "4.3.2.1:1234"): counts(12, 34, 56, 78),
  85. conn(0, "100.1.2.5:0", "4.3.2.10:1234"): counts(78, 56, 34, 12),
  86. },
  87. }
  88. rec.seenNodes[rec.selfNode.toNode().Addresses[0]] = rec.selfNode
  89. got := rec.toMessage(false, false)
  90. want := netlogtype.Message{
  91. NodeID: rec.selfNode.StableID(),
  92. Start: rec.start,
  93. End: rec.end,
  94. SrcNode: rec.selfNode.toNode(),
  95. DstNodes: []netlogtype.Node{
  96. rec.seenNodes[addr("100.1.2.4")].toNode(),
  97. rec.seenNodes[addr("100.1.2.5")].toNode(),
  98. },
  99. VirtualTraffic: []netlogtype.ConnectionCounts{
  100. {Connection: conn(0x1, "100.1.2.3:1234", "100.1.2.4:80"), Counts: counts(12, 34, 56, 78)},
  101. {Connection: conn(0x1, "100.1.2.3:1234", "100.1.2.5:80"), Counts: counts(23, 45, 78, 790)},
  102. },
  103. SubnetTraffic: []netlogtype.ConnectionCounts{
  104. {Connection: conn(0x6, "172.16.1.1:80", "100.1.2.4:1234"), Counts: counts(91, 54, 723, 621)},
  105. {Connection: conn(0x6, "172.16.1.2:443", "100.1.2.5:1234"), Counts: counts(42, 813, 3, 1823)},
  106. {Connection: conn(0x6, "172.16.1.3:80", "100.1.2.6:1234"), Counts: counts(34, 52, 78, 790)},
  107. },
  108. ExitTraffic: []netlogtype.ConnectionCounts{
  109. {Connection: conn(0x6, "42.54.72.42:555", "18.42.7.1:777"), Counts: counts(44, 440, 40, 400)},
  110. {Connection: conn(0x6, "100.1.2.3:1234", "12.34.56.78:80"), Counts: counts(11, 110, 10, 100)},
  111. {Connection: conn(0x6, "100.1.2.4:1234", "23.34.56.78:80"), Counts: counts(423, 1, 6, 123)},
  112. {Connection: conn(0x6, "100.1.2.4:1234", "23.34.56.78:443"), Counts: counts(22, 220, 20, 200)},
  113. {Connection: conn(0x6, "100.1.2.5:1234", "45.34.56.78:80"), Counts: counts(33, 330, 30, 300)},
  114. {Connection: conn(0x6, "100.1.2.6:1234", "67.34.56.78:80"), Counts: counts(44, 440, 40, 400)},
  115. },
  116. PhysicalTraffic: []netlogtype.ConnectionCounts{
  117. {Connection: conn(0, "100.1.2.4:0", "4.3.2.1:1234"), Counts: counts(12, 34, 56, 78)},
  118. {Connection: conn(0, "100.1.2.5:0", "4.3.2.10:1234"), Counts: counts(78, 56, 34, 12)},
  119. },
  120. }
  121. if d := cmp.Diff(got, want, cmpopts.EquateComparable(netip.Addr{}, netip.AddrPort{})); d != "" {
  122. t.Errorf("toMessage(false, false) mismatch (-got +want):\n%s", d)
  123. }
  124. got = rec.toMessage(true, false)
  125. want.SrcNode = netlogtype.Node{}
  126. want.DstNodes = nil
  127. if d := cmp.Diff(got, want, cmpopts.EquateComparable(netip.Addr{}, netip.AddrPort{})); d != "" {
  128. t.Errorf("toMessage(true, false) mismatch (-got +want):\n%s", d)
  129. }
  130. got = rec.toMessage(true, true)
  131. want.ExitTraffic = []netlogtype.ConnectionCounts{
  132. {Connection: conn(0, "", ""), Counts: counts(44+44, 440+440, 40+40, 400+400)},
  133. {Connection: conn(0, "100.1.2.3:0", ""), Counts: counts(11, 110, 10, 100)},
  134. {Connection: conn(0, "100.1.2.4:0", ""), Counts: counts(423+22, 1+220, 6+20, 123+200)},
  135. {Connection: conn(0, "100.1.2.5:0", ""), Counts: counts(33, 330, 30, 300)},
  136. }
  137. if d := cmp.Diff(got, want, cmpopts.EquateComparable(netip.Addr{}, netip.AddrPort{})); d != "" {
  138. t.Errorf("toMessage(true, true) mismatch (-got +want):\n%s", d)
  139. }
  140. }
  141. func TestToNode(t *testing.T) {
  142. tests := []struct {
  143. node *tailcfg.Node
  144. user *tailcfg.UserProfile
  145. want netlogtype.Node
  146. }{
  147. {},
  148. {
  149. node: &tailcfg.Node{
  150. StableID: "n123456CNTL",
  151. Name: "test.tail123456.ts.net.",
  152. Addresses: []netip.Prefix{prefix("100.1.2.3")},
  153. Tags: []string{"tag:dupe", "tag:test", "tag:dupe"},
  154. User: 12345, // should be ignored
  155. },
  156. want: netlogtype.Node{
  157. NodeID: "n123456CNTL",
  158. Name: "test.tail123456.ts.net",
  159. Addresses: []netip.Addr{addr("100.1.2.3")},
  160. Tags: []string{"tag:dupe", "tag:test"},
  161. },
  162. },
  163. {
  164. node: &tailcfg.Node{
  165. StableID: "n123456CNTL",
  166. Addresses: []netip.Prefix{prefix("100.1.2.3")},
  167. User: 12345,
  168. },
  169. want: netlogtype.Node{
  170. NodeID: "n123456CNTL",
  171. Addresses: []netip.Addr{addr("100.1.2.3")},
  172. },
  173. },
  174. {
  175. node: &tailcfg.Node{
  176. StableID: "n123456CNTL",
  177. Addresses: []netip.Prefix{prefix("100.1.2.3")},
  178. Hostinfo: (&tailcfg.Hostinfo{OS: "linux"}).View(),
  179. User: 12345,
  180. },
  181. user: &tailcfg.UserProfile{
  182. ID: 12345,
  183. LoginName: "user@domain",
  184. },
  185. want: netlogtype.Node{
  186. NodeID: "n123456CNTL",
  187. Addresses: []netip.Addr{addr("100.1.2.3")},
  188. OS: "linux",
  189. User: "user@domain",
  190. },
  191. },
  192. }
  193. for _, tt := range tests {
  194. nu := nodeUser{tt.node.View(), tt.user.View()}
  195. got := nu.toNode()
  196. b := must.Get(jsonv2.Marshal(got))
  197. if len(b) > nu.jsonLen() {
  198. t.Errorf("jsonLen = %v, want >= %d", nu.jsonLen(), len(b))
  199. }
  200. if d := cmp.Diff(got, tt.want, cmpopts.EquateComparable(netip.Addr{})); d != "" {
  201. t.Errorf("toNode mismatch (-got +want):\n%s", d)
  202. }
  203. }
  204. }
  205. func FuzzQuotedLen(f *testing.F) {
  206. for _, s := range quotedLenTestdata {
  207. f.Add(s)
  208. }
  209. f.Fuzz(func(t *testing.T, s string) {
  210. testQuotedLen(t, s)
  211. })
  212. }
  213. func TestQuotedLen(t *testing.T) {
  214. for _, s := range quotedLenTestdata {
  215. testQuotedLen(t, s)
  216. }
  217. }
  218. var quotedLenTestdata = []string{
  219. "", // empty string
  220. func() string {
  221. b := make([]byte, 128)
  222. for i := range b {
  223. b[i] = byte(i)
  224. }
  225. return string(b)
  226. }(), // all ASCII
  227. "�", // replacement rune
  228. "\xff", // invalid UTF-8
  229. "ʕ◔ϖ◔ʔ", // Unicode gopher
  230. }
  231. func testQuotedLen(t *testing.T, in string) {
  232. got := jsonQuotedLen(in)
  233. b, _ := jsontext.AppendQuote(nil, in)
  234. want := len(b)
  235. if got != want {
  236. t.Errorf("jsonQuotedLen(%q) = %v, want %v", in, got, want)
  237. }
  238. }