ts-dissector.lua 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. function hasbit(x, p)
  2. return x % (p + p) >= p
  3. end
  4. tsdebug_ll = Proto("tsdebug", "Tailscale debug")
  5. PATH = ProtoField.string("tsdebug.PATH","PATH", base.ASCII)
  6. SNAT_IP_4 = ProtoField.ipv4("tsdebug.SNAT_IP_4", "Pre-NAT Source IPv4 address")
  7. SNAT_IP_6 = ProtoField.ipv4("tsdebug.SNAT_IP_6", "Pre-NAT Source IPv6 address")
  8. DNAT_IP_4 = ProtoField.ipv4("tsdebug.DNAT_IP_4", "Pre-NAT Dest IPv4 address")
  9. DNAT_IP_6 = ProtoField.ipv4("tsdebug.DNAT_IP_6", "Pre-NAT Dest IPv6 address")
  10. tsdebug_ll.fields = {PATH, SNAT_IP_4, SNAT_IP_6, DNAT_IP_4, DNAT_IP_6}
  11. function tsdebug_ll.dissector(buffer, pinfo, tree)
  12. pinfo.cols.protocol = tsdebug_ll.name
  13. packet_length = buffer:len()
  14. local offset = 0
  15. local subtree = tree:add(tsdebug_ll, buffer(), "Tailscale packet")
  16. -- -- Get path UINT16
  17. local path_id = buffer:range(offset, 2):le_uint()
  18. if path_id == 0 then subtree:add(PATH, "FromLocal")
  19. elseif path_id == 1 then subtree:add(PATH, "FromPeer")
  20. elseif path_id == 2 then subtree:add(PATH, "Synthesized (Inbound / ToLocal)")
  21. elseif path_id == 3 then subtree:add(PATH, "Synthesized (Outbound / ToPeer)")
  22. elseif path_id == 254 then subtree:add(PATH, "Disco frame")
  23. end
  24. offset = offset + 2
  25. -- -- Get SNAT address
  26. local snat_addr_len = buffer:range(offset, 1):le_uint()
  27. if snat_addr_len == 4 then subtree:add(SNAT_IP_4, buffer:range(offset + 1, snat_addr_len))
  28. elseif snat_addr_len > 0 then subtree:add(SNAT_IP_6, buffer:range(offset + 1, snat_addr_len))
  29. end
  30. offset = offset + 1 + snat_addr_len
  31. -- -- Get DNAT address
  32. local dnat_addr_len = buffer:range(offset, 1):le_uint()
  33. if dnat_addr_len == 4 then subtree:add(DNAT_IP_4, buffer:range(offset + 1, dnat_addr_len))
  34. elseif dnat_addr_len > 0 then subtree:add(DNAT_IP_6, buffer:range(offset + 1, dnat_addr_len))
  35. end
  36. offset = offset + 1 + dnat_addr_len
  37. -- -- Handover rest of data to lower-level dissector
  38. local data_buffer = buffer:range(offset, packet_length-offset):tvb()
  39. if path_id == 254 then
  40. Dissector.get("tsdisco"):call(data_buffer, pinfo, tree)
  41. else
  42. Dissector.get("ip"):call(data_buffer, pinfo, tree)
  43. end
  44. end
  45. -- Install the dissector on link-layer ID 147 (User-defined protocol 0)
  46. local eth_table = DissectorTable.get("wtap_encap")
  47. eth_table:add(wtap.USER0, tsdebug_ll)
  48. local ts_dissectors = DissectorTable.new("ts.proto", "Tailscale-specific dissectors", ftypes.STRING, base.NONE)
  49. --
  50. -- DISCO metadata dissector
  51. --
  52. tsdisco_meta = Proto("tsdisco", "Tailscale DISCO metadata")
  53. DISCO_IS_DERP = ProtoField.bool("tsdisco.IS_DERP","From DERP")
  54. DISCO_SRC_IP_4 = ProtoField.ipv4("tsdisco.SRC_IP_4", "Source IPv4 address")
  55. DISCO_SRC_IP_6 = ProtoField.ipv4("tsdisco.SRC_IP_6", "Source IPv6 address")
  56. DISCO_SRC_PORT = ProtoField.uint16("tsdisco.SRC_PORT","Source port", base.DEC)
  57. DISCO_DERP_PUB = ProtoField.bytes("tsdisco.DERP_PUB", "DERP public key", base.SPACE)
  58. tsdisco_meta.fields = {DISCO_IS_DERP, DISCO_SRC_PORT, DISCO_DERP_PUB, DISCO_SRC_IP_4, DISCO_SRC_IP_6}
  59. function tsdisco_meta.dissector(buffer, pinfo, tree)
  60. pinfo.cols.protocol = tsdisco_meta.name
  61. packet_length = buffer:len()
  62. local offset = 0
  63. local subtree = tree:add(tsdisco_meta, buffer(), "DISCO metadata")
  64. -- Parse flags
  65. local from_derp = hasbit(buffer(offset, 1):le_uint(), 0)
  66. subtree:add(DISCO_IS_DERP, from_derp) -- Flag bit 0
  67. offset = offset + 1
  68. -- Parse DERP public key
  69. if from_derp then
  70. subtree:add(DISCO_DERP_PUB, buffer(offset, 32))
  71. end
  72. offset = offset + 32
  73. -- Parse source port
  74. subtree:add(DISCO_SRC_PORT, buffer:range(offset, 2):le_uint())
  75. offset = offset + 2
  76. -- Parse source address
  77. local addr_len = buffer:range(offset, 2):le_uint()
  78. offset = offset + 2
  79. if addr_len == 4 then subtree:add(DISCO_SRC_IP_4, buffer:range(offset, addr_len))
  80. else subtree:add(DISCO_SRC_IP_6, buffer:range(offset, addr_len))
  81. end
  82. offset = offset + addr_len
  83. -- Handover to the actual disco frame dissector
  84. offset = offset + 2 -- skip over payload len
  85. local data_buffer = buffer:range(offset, packet_length-offset):tvb()
  86. Dissector.get("disco"):call(data_buffer, pinfo, tree)
  87. end
  88. ts_dissectors:add(1, tsdisco_meta)
  89. --
  90. -- DISCO frame dissector
  91. --
  92. tsdisco_frame = Proto("disco", "Tailscale DISCO frame")
  93. DISCO_TYPE = ProtoField.string("disco.TYPE", "Message type", base.ASCII)
  94. DISCO_VERSION = ProtoField.uint8("disco.VERSION","Protocol version", base.DEC)
  95. DISCO_TXID = ProtoField.bytes("disco.TXID", "Transaction ID", base.SPACE)
  96. DISCO_NODEKEY = ProtoField.bytes("disco.NODE_KEY", "Node key", base.SPACE)
  97. DISCO_PONG_SRC = ProtoField.ipv6("disco.PONG_SRC", "Pong source")
  98. DISCO_PONG_SRC_PORT = ProtoField.uint16("disco.PONG_SRC_PORT","Source port", base.DEC)
  99. DISCO_UNKNOWN = ProtoField.bytes("disco.UNKNOWN_DATA", "Trailing data", base.SPACE)
  100. tsdisco_frame.fields = {DISCO_TYPE, DISCO_VERSION, DISCO_TXID, DISCO_NODEKEY, DISCO_PONG_SRC, DISCO_PONG_SRC_PORT, DISCO_UNKNOWN}
  101. function tsdisco_frame.dissector(buffer, pinfo, tree)
  102. packet_length = buffer:len()
  103. local offset = 0
  104. local subtree = tree:add(tsdisco_frame, buffer(), "DISCO frame")
  105. -- Message type
  106. local message_type = buffer(offset, 1):le_uint()
  107. offset = offset + 1
  108. if message_type == 1 then subtree:add(DISCO_TYPE, "Ping")
  109. elseif message_type == 2 then subtree:add(DISCO_TYPE, "Pong")
  110. elseif message_type == 3 then subtree:add(DISCO_TYPE, "Call me maybe")
  111. end
  112. -- Message version
  113. local message_version = buffer(offset, 1):le_uint()
  114. offset = offset + 1
  115. subtree:add(DISCO_VERSION, message_version)
  116. -- TXID (Ping / Pong)
  117. if message_type == 1 or message_type == 2 then
  118. subtree:add(DISCO_TXID, buffer(offset, 12))
  119. offset = offset + 12
  120. end
  121. -- NodeKey (Ping)
  122. if message_type == 1 then
  123. subtree:add(DISCO_NODEKEY, buffer(offset, 32))
  124. offset = offset + 32
  125. end
  126. -- Src (Pong)
  127. if message_type == 2 then
  128. subtree:add(DISCO_PONG_SRC, buffer:range(offset, 16))
  129. offset = offset + 16
  130. end
  131. -- Src port (Pong)
  132. if message_type == 2 then
  133. subtree:add(DISCO_PONG_SRC_PORT, buffer(offset, 2):le_uint())
  134. offset = offset + 2
  135. end
  136. -- TODO(tom): Parse CallMeMaybe.MyNumber
  137. local trailing = buffer:range(offset, packet_length-offset)
  138. if trailing:len() > 0 then
  139. subtree:add(DISCO_UNKNOWN, trailing)
  140. end
  141. end
  142. ts_dissectors:add(2, tsdisco_frame)