redhound 1.0.0 → 2.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +35 -0
  3. data/README.md +97 -31
  4. data/docs/API.md +76 -0
  5. data/docs/FILTERS.md +37 -0
  6. data/docs/MIGRATION.md +23 -0
  7. data/docs/PROTOCOLS.md +45 -0
  8. data/docs/USAGE.md +82 -0
  9. data/docs/VALIDATION.md +59 -0
  10. data/docs/json-schema.json +64 -0
  11. data/exe/redhound +2 -1
  12. data/lib/redhound/analysis/flow_key.rb +53 -0
  13. data/lib/redhound/analysis/flow_table.rb +117 -0
  14. data/lib/redhound/analysis/ip_reassembler.rb +176 -0
  15. data/lib/redhound/analysis/stats.rb +138 -0
  16. data/lib/redhound/analysis/tcp_analysis.rb +43 -0
  17. data/lib/redhound/analysis/tcp_reassembler.rb +124 -0
  18. data/lib/redhound/analysis/tcp_stream.rb +151 -0
  19. data/lib/redhound/analysis.rb +129 -0
  20. data/lib/redhound/capture/bsd/bpf_device.rb +152 -0
  21. data/lib/redhound/capture/bsd/constants.rb +29 -0
  22. data/lib/redhound/capture/file_source.rb +83 -0
  23. data/lib/redhound/capture/interface.rb +66 -0
  24. data/lib/redhound/capture/linktype.rb +30 -0
  25. data/lib/redhound/capture/linux/constants.rb +37 -0
  26. data/lib/redhound/capture/linux/cooked.rb +18 -0
  27. data/lib/redhound/capture/linux/ifreq.rb +42 -0
  28. data/lib/redhound/capture/linux/packet_socket.rb +180 -0
  29. data/lib/redhound/capture/linux/sockaddr_ll.rb +33 -0
  30. data/lib/redhound/capture/linux/tpacket_v3.rb +132 -0
  31. data/lib/redhound/capture/source.rb +79 -0
  32. data/lib/redhound/capture/stats.rb +22 -0
  33. data/lib/redhound/capture.rb +69 -0
  34. data/lib/redhound/cli/command.rb +42 -0
  35. data/lib/redhound/cli/options.rb +133 -0
  36. data/lib/redhound/cli/privileges.rb +29 -0
  37. data/lib/redhound/cli/runner.rb +129 -0
  38. data/lib/redhound/context.rb +20 -0
  39. data/lib/redhound/cursor.rb +69 -0
  40. data/lib/redhound/diagnostic.rb +7 -0
  41. data/lib/redhound/dissector.rb +138 -0
  42. data/lib/redhound/engine.rb +66 -0
  43. data/lib/redhound/errors.rb +38 -0
  44. data/lib/redhound/field.rb +62 -0
  45. data/lib/redhound/file/format.rb +56 -0
  46. data/lib/redhound/file/pcap_reader.rb +43 -0
  47. data/lib/redhound/file/pcap_writer.rb +72 -0
  48. data/lib/redhound/file/pcapng_reader.rb +209 -0
  49. data/lib/redhound/file/pcapng_writer.rb +143 -0
  50. data/lib/redhound/file/rotating_writer.rb +98 -0
  51. data/lib/redhound/filter/analyzer.rb +197 -0
  52. data/lib/redhound/filter/bpf/assembler.rb +121 -0
  53. data/lib/redhound/filter/bpf/disassembler.rb +58 -0
  54. data/lib/redhound/filter/bpf/validator.rb +77 -0
  55. data/lib/redhound/filter/bpf/vm.rb +108 -0
  56. data/lib/redhound/filter/codegen.rb +413 -0
  57. data/lib/redhound/filter/lexer.rb +46 -0
  58. data/lib/redhound/filter/parser.rb +206 -0
  59. data/lib/redhound/filter/program.rb +55 -0
  60. data/lib/redhound/filter.rb +30 -0
  61. data/lib/redhound/layer.rb +75 -0
  62. data/lib/redhound/output/fields.rb +20 -0
  63. data/lib/redhound/output/follow.rb +59 -0
  64. data/lib/redhound/output/hexdump.rb +29 -0
  65. data/lib/redhound/output/json.rb +32 -0
  66. data/lib/redhound/output/summary.rb +59 -0
  67. data/lib/redhound/output/timestamp.rb +34 -0
  68. data/lib/redhound/output/tree.rb +24 -0
  69. data/lib/redhound/packet.rb +63 -0
  70. data/lib/redhound/protocols/arp.rb +39 -0
  71. data/lib/redhound/protocols/checksum.rb +38 -0
  72. data/lib/redhound/protocols/data.rb +21 -0
  73. data/lib/redhound/protocols/dhcp.rb +124 -0
  74. data/lib/redhound/protocols/dns.rb +268 -0
  75. data/lib/redhound/protocols/ethernet.rb +24 -0
  76. data/lib/redhound/protocols/http.rb +183 -0
  77. data/lib/redhound/protocols/icmp.rb +191 -0
  78. data/lib/redhound/protocols/ipv4.rb +56 -0
  79. data/lib/redhound/protocols/ipv6.rb +65 -0
  80. data/lib/redhound/protocols/link.rb +70 -0
  81. data/lib/redhound/protocols/llc.rb +37 -0
  82. data/lib/redhound/protocols/ntp.rb +47 -0
  83. data/lib/redhound/protocols/tcp.rb +80 -0
  84. data/lib/redhound/protocols/tls.rb +218 -0
  85. data/lib/redhound/protocols/tunnel.rb +48 -0
  86. data/lib/redhound/protocols/udp.rb +32 -0
  87. data/lib/redhound/protocols/vlan.rb +23 -0
  88. data/lib/redhound/reader.rb +8 -0
  89. data/lib/redhound/registry.rb +64 -0
  90. data/lib/redhound/stream_dissector.rb +168 -0
  91. data/lib/redhound/util/seq.rb +21 -0
  92. data/lib/redhound/version.rb +2 -1
  93. data/lib/redhound/writer.rb +25 -43
  94. data/lib/redhound.rb +81 -9
  95. data/sig/generated/redhound/analysis/flow_key.rbs +15 -0
  96. data/sig/generated/redhound/analysis/flow_table.rbs +98 -0
  97. data/sig/generated/redhound/analysis/ip_reassembler.rbs +40 -0
  98. data/sig/generated/redhound/analysis/stats.rbs +77 -0
  99. data/sig/generated/redhound/analysis/tcp_analysis.rbs +15 -0
  100. data/sig/generated/redhound/analysis/tcp_reassembler.rbs +27 -0
  101. data/sig/generated/redhound/analysis/tcp_stream.rbs +60 -0
  102. data/sig/generated/redhound/analysis.rbs +38 -0
  103. data/sig/generated/redhound/capture/bsd/bpf_device.rbs +39 -0
  104. data/sig/generated/redhound/capture/bsd/constants.rbs +40 -0
  105. data/sig/generated/redhound/capture/file_source.rbs +34 -0
  106. data/sig/generated/redhound/capture/interface.rbs +53 -0
  107. data/sig/generated/redhound/capture/linktype.rbs +20 -0
  108. data/sig/generated/redhound/capture/linux/constants.rbs +58 -0
  109. data/sig/generated/redhound/capture/linux/cooked.rbs +15 -0
  110. data/sig/generated/redhound/capture/linux/ifreq.rbs +18 -0
  111. data/sig/generated/redhound/capture/linux/packet_socket.rbs +57 -0
  112. data/sig/generated/redhound/capture/linux/sockaddr_ll.rbs +31 -0
  113. data/sig/generated/redhound/capture/linux/tpacket_v3.rbs +35 -0
  114. data/sig/generated/redhound/capture/source.rbs +44 -0
  115. data/sig/generated/redhound/capture/stats.rbs +25 -0
  116. data/sig/generated/redhound/capture.rbs +14 -0
  117. data/sig/generated/redhound/cli/command.rbs +12 -0
  118. data/sig/generated/redhound/cli/options.rbs +25 -0
  119. data/sig/generated/redhound/cli/privileges.rbs +12 -0
  120. data/sig/generated/redhound/cli/runner.rbs +27 -0
  121. data/sig/generated/redhound/context.rbs +27 -0
  122. data/sig/generated/redhound/cursor.rbs +59 -0
  123. data/sig/generated/redhound/diagnostic.rbs +21 -0
  124. data/sig/generated/redhound/dissector.rbs +113 -0
  125. data/sig/generated/redhound/engine.rbs +23 -0
  126. data/sig/generated/redhound/errors.rbs +56 -0
  127. data/sig/generated/redhound/field.rbs +74 -0
  128. data/sig/generated/redhound/file/format.rbs +33 -0
  129. data/sig/generated/redhound/file/pcap_reader.rbs +19 -0
  130. data/sig/generated/redhound/file/pcap_writer.rbs +38 -0
  131. data/sig/generated/redhound/file/pcapng_reader.rbs +54 -0
  132. data/sig/generated/redhound/file/pcapng_writer.rbs +50 -0
  133. data/sig/generated/redhound/file/rotating_writer.rbs +42 -0
  134. data/sig/generated/redhound/filter/analyzer.rbs +42 -0
  135. data/sig/generated/redhound/filter/bpf/assembler.rbs +39 -0
  136. data/sig/generated/redhound/filter/bpf/disassembler.rbs +18 -0
  137. data/sig/generated/redhound/filter/bpf/validator.rbs +30 -0
  138. data/sig/generated/redhound/filter/bpf/vm.rbs +26 -0
  139. data/sig/generated/redhound/filter/codegen.rbs +101 -0
  140. data/sig/generated/redhound/filter/lexer.rbs +15 -0
  141. data/sig/generated/redhound/filter/parser.rbs +65 -0
  142. data/sig/generated/redhound/filter/program.rbs +48 -0
  143. data/sig/generated/redhound/filter.rbs +10 -0
  144. data/sig/generated/redhound/layer.rbs +81 -0
  145. data/sig/generated/redhound/output/fields.rbs +15 -0
  146. data/sig/generated/redhound/output/follow.rbs +21 -0
  147. data/sig/generated/redhound/output/hexdump.rbs +15 -0
  148. data/sig/generated/redhound/output/json.rbs +18 -0
  149. data/sig/generated/redhound/output/summary.rbs +21 -0
  150. data/sig/generated/redhound/output/timestamp.rbs +21 -0
  151. data/sig/generated/redhound/output/tree.rbs +15 -0
  152. data/sig/generated/redhound/packet.rbs +77 -0
  153. data/sig/generated/redhound/protocols/arp.rbs +15 -0
  154. data/sig/generated/redhound/protocols/checksum.rbs +15 -0
  155. data/sig/generated/redhound/protocols/data.rbs +15 -0
  156. data/sig/generated/redhound/protocols/dhcp.rbs +28 -0
  157. data/sig/generated/redhound/protocols/dns.rbs +42 -0
  158. data/sig/generated/redhound/protocols/ethernet.rbs +15 -0
  159. data/sig/generated/redhound/protocols/http.rbs +36 -0
  160. data/sig/generated/redhound/protocols/icmp.rbs +41 -0
  161. data/sig/generated/redhound/protocols/ipv4.rbs +15 -0
  162. data/sig/generated/redhound/protocols/ipv6.rbs +24 -0
  163. data/sig/generated/redhound/protocols/link.rbs +33 -0
  164. data/sig/generated/redhound/protocols/llc.rbs +15 -0
  165. data/sig/generated/redhound/protocols/ntp.rbs +17 -0
  166. data/sig/generated/redhound/protocols/tcp.rbs +25 -0
  167. data/sig/generated/redhound/protocols/tls.rbs +51 -0
  168. data/sig/generated/redhound/protocols/tunnel.rbs +24 -0
  169. data/sig/generated/redhound/protocols/udp.rbs +18 -0
  170. data/sig/generated/redhound/protocols/vlan.rbs +12 -0
  171. data/sig/generated/redhound/reader.rbs +7 -0
  172. data/sig/generated/redhound/registry.rbs +45 -0
  173. data/sig/generated/redhound/stream_dissector.rbs +44 -0
  174. data/sig/generated/redhound/util/seq.rbs +20 -0
  175. data/sig/generated/redhound/version.rbs +1 -0
  176. data/sig/generated/redhound/writer.rbs +6 -19
  177. data/sig/generated/redhound.rbs +18 -0
  178. data/sig/shims/io.rbs +7 -0
  179. data/sig/shims/process.rbs +3 -0
  180. metadata +175 -55
  181. data/CODE_OF_CONDUCT.md +0 -132
  182. data/Dockerfile +0 -7
  183. data/Rakefile +0 -15
  184. data/Steepfile +0 -4
  185. data/docker-compose.yml +0 -9
  186. data/lib/redhound/analyzer.rb +0 -34
  187. data/lib/redhound/builder/packet_mreq.rb +0 -56
  188. data/lib/redhound/builder/socket.rb +0 -35
  189. data/lib/redhound/builder.rb +0 -4
  190. data/lib/redhound/command.rb +0 -67
  191. data/lib/redhound/l2/ether.rb +0 -68
  192. data/lib/redhound/l2/protocol.rb +0 -33
  193. data/lib/redhound/l2.rb +0 -4
  194. data/lib/redhound/l3/arp.rb +0 -114
  195. data/lib/redhound/l3/base.rb +0 -49
  196. data/lib/redhound/l3/ipv4.rb +0 -95
  197. data/lib/redhound/l3/ipv6.rb +0 -69
  198. data/lib/redhound/l3/protocol.rb +0 -173
  199. data/lib/redhound/l3/resolver.rb +0 -30
  200. data/lib/redhound/l3.rb +0 -8
  201. data/lib/redhound/l4/base.rb +0 -31
  202. data/lib/redhound/l4/icmp.rb +0 -71
  203. data/lib/redhound/l4/resolver.rb +0 -28
  204. data/lib/redhound/l4/udp.rb +0 -63
  205. data/lib/redhound/l4.rb +0 -6
  206. data/lib/redhound/receiver.rb +0 -45
  207. data/lib/redhound/resolver.rb +0 -22
  208. data/lib/redhound/source/socket.rb +0 -18
  209. data/lib/redhound/source.rb +0 -3
  210. data/rbs_collection.lock.yaml +0 -20
  211. data/rbs_collection.yaml +0 -17
  212. data/sig/generated/redhound/analyzer.rbs +0 -14
  213. data/sig/generated/redhound/builder/packet_mreq.rbs +0 -39
  214. data/sig/generated/redhound/builder/socket.rbs +0 -24
  215. data/sig/generated/redhound/command.rbs +0 -19
  216. data/sig/generated/redhound/l2/ether.rbs +0 -41
  217. data/sig/generated/redhound/l2/protocol.rbs +0 -15
  218. data/sig/generated/redhound/l3/arp.rbs +0 -57
  219. data/sig/generated/redhound/l3/base.rbs +0 -28
  220. data/sig/generated/redhound/l3/ipv4.rbs +0 -53
  221. data/sig/generated/redhound/l3/ipv6.rbs +0 -38
  222. data/sig/generated/redhound/l3/protocol.rbs +0 -16
  223. data/sig/generated/redhound/l3/resolver.rbs +0 -16
  224. data/sig/generated/redhound/l4/base.rbs +0 -19
  225. data/sig/generated/redhound/l4/icmp.rbs +0 -33
  226. data/sig/generated/redhound/l4/resolver.rbs +0 -16
  227. data/sig/generated/redhound/l4/udp.rbs +0 -36
  228. data/sig/generated/redhound/receiver.rbs +0 -19
  229. data/sig/generated/redhound/resolver.rbs +0 -11
  230. data/sig/generated/redhound/source/socket.rbs +0 -13
@@ -0,0 +1,191 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ module Redhound
5
+ # @api private
6
+ module Protocols
7
+ # @api private
8
+ class Icmp < Dissector
9
+ TYPES = { 0 => 'Echo Reply', 3 => 'Destination Unreachable', 4 => 'Source Quench', 5 => 'Redirect',
10
+ 8 => 'Echo Request', 9 => 'Router Advertisement', 10 => 'Router Solicitation', 11 => 'Time Exceeded',
11
+ 12 => 'Parameter Problem', 13 => 'Timestamp', 14 => 'Timestamp Reply', 17 => 'Address Mask Request', 18 => 'Address Mask Reply' }.freeze
12
+ CODES = {
13
+ 3 => { 0 => 'Network Unreachable', 1 => 'Host Unreachable', 2 => 'Protocol Unreachable', 3 => 'Port Unreachable',
14
+ 4 => 'Fragmentation Needed', 5 => 'Source Route Failed', 6 => 'Network Unknown', 7 => 'Host Unknown',
15
+ 8 => 'Source Host Isolated', 9 => 'Network Administratively Prohibited', 10 => 'Host Administratively Prohibited',
16
+ 11 => 'Network Unreachable for TOS', 12 => 'Host Unreachable for TOS', 13 => 'Administratively Prohibited',
17
+ 14 => 'Host Precedence Violation', 15 => 'Precedence Cutoff' },
18
+ 5 => { 0 => 'Redirect Network', 1 => 'Redirect Host', 2 => 'Redirect TOS Network', 3 => 'Redirect TOS Host' },
19
+ 11 => { 0 => 'TTL Expired', 1 => 'Reassembly Timeout' },
20
+ 12 => { 0 => 'Invalid Pointer', 1 => 'Missing Option', 2 => 'Invalid Length' }
21
+ }.freeze
22
+ protocol :icmp, name: 'Internet Control Message Protocol', short: 'ICMP'
23
+ dissects_on 'ip.proto', 1
24
+ header do
25
+ uint8 :type, 'icmp.type', enum: TYPES
26
+ uint8 :code, 'icmp.code'
27
+ uint16 :checksum, 'icmp.checksum', format: :hex
28
+ uint16 :ident, 'icmp.ident'
29
+ uint16 :seq, 'icmp.seq'
30
+ end
31
+ # @rbs (Context ctx, Layer layer) -> void
32
+ def dissect(ctx, layer)
33
+ layer.definitions[1] = layer.definitions[1].with(enum: CODES[layer[:type]])
34
+ Checksum.verify(ctx, layer, ctx.cursor.bytes(0, ctx.cursor.remaining))
35
+ end
36
+ # @rbs (Context ctx, Layer layer) -> untyped
37
+ def next_dissector(ctx, layer)
38
+ return nil unless [3, 4, 5, 11, 12].include?(layer[:type])
39
+ ctx.embedded = true
40
+ Ipv4
41
+ end
42
+ # @rbs (Layer layer) -> String
43
+ def summary(layer) = "ICMP type #{layer[:type]}, code #{layer[:code]}, id #{layer[:ident]}, seq #{layer[:seq]}"
44
+ end
45
+
46
+ # @api private
47
+ class Icmpv6 < Dissector
48
+ TYPES = { 1 => 'Destination Unreachable', 2 => 'Packet Too Big', 3 => 'Time Exceeded', 4 => 'Parameter Problem',
49
+ 128 => 'Echo Request', 129 => 'Echo Reply', 133 => 'Router Solicitation', 134 => 'Router Advertisement',
50
+ 135 => 'Neighbor Solicitation', 136 => 'Neighbor Advertisement', 137 => 'Redirect' }.freeze
51
+ CODES = {
52
+ 1 => { 0 => 'No Route', 1 => 'Administratively Prohibited', 2 => 'Beyond Source Scope', 3 => 'Address Unreachable',
53
+ 4 => 'Port Unreachable', 5 => 'Source Policy Failure', 6 => 'Reject Route', 7 => 'Source Routing Error' },
54
+ 3 => { 0 => 'Hop Limit Exceeded', 1 => 'Reassembly Timeout' },
55
+ 4 => { 0 => 'Invalid Header Field', 1 => 'Unknown Next Header', 2 => 'Unknown Option' }
56
+ }.freeze
57
+ protocol :icmpv6, name: 'ICMPv6 / Neighbor Discovery', short: 'ICMP6'
58
+ dissects_on 'ip.proto', 58
59
+ header do
60
+ uint8 :type, 'icmpv6.type', enum: TYPES
61
+ uint8 :code, 'icmpv6.code'
62
+ uint16 :checksum, 'icmpv6.checksum'
63
+ end
64
+ # @rbs (Context ctx, Layer layer) -> void
65
+ def dissect(ctx, layer)
66
+ cursor, type = ctx.cursor, layer[:type]
67
+ layer.definitions[1] = layer.definitions[1].with(enum: CODES[type])
68
+ length = case type
69
+ when 133 then 8
70
+ when 134 then 16
71
+ when 135, 136 then 24
72
+ when 137 then 40
73
+ else 8
74
+ end
75
+ cursor.check(0, length)
76
+ if [128, 129].include?(type)
77
+ layer.add(:ident, 'icmpv6.echo.identifier', cursor.u16(4))
78
+ layer.add(:seq, 'icmpv6.echo.sequence_number', cursor.u16(6))
79
+ elsif [135, 136, 137].include?(type)
80
+ family = { 135 => 'ns', 136 => 'na', 137 => 'rd' }.fetch(type)
81
+ layer.add(:target, "icmpv6.nd.#{family}.target_address", cursor.bytes(8, 16), type: :ipv6)
82
+ layer.add(:destination, 'icmpv6.rd.na.destination_address', cursor.bytes(24, 16), type: :ipv6) if type == 137
83
+ if type == 136
84
+ flags = cursor.u32(4)
85
+ layer.add(:flags, 'icmpv6.nd.na.flag', flags, format: :hex)
86
+ { r: 0x80000000, s: 0x40000000, o: 0x20000000 }.each do |name, mask|
87
+ layer.add(name, "icmpv6.nd.na.flag.#{name}", (flags & mask).positive?)
88
+ end
89
+ end
90
+ end
91
+ if type == 134
92
+ layer.add(:hop_limit, 'icmpv6.nd.ra.cur_hop_limit', cursor.u8(4))
93
+ layer.add(:lifetime, 'icmpv6.nd.ra.router_lifetime', cursor.u16(6))
94
+ flags = cursor.u8(5)
95
+ layer.add(:flags, 'icmpv6.nd.ra.flag', flags, format: :hex)
96
+ layer.add(:managed, 'icmpv6.nd.ra.flag.m', (flags & 0x80).positive?)
97
+ layer.add(:other, 'icmpv6.nd.ra.flag.o', (flags & 0x40).positive?)
98
+ layer.add(:reachable, 'icmpv6.nd.ra.reachable_time', cursor.u32(8))
99
+ layer.add(:retrans, 'icmpv6.nd.ra.retrans_timer', cursor.u32(12))
100
+ end
101
+ if type.between?(133, 137)
102
+ pos = length
103
+ while pos < cursor.remaining
104
+ opt = cursor.u8(pos)
105
+ len = cursor.u8(pos + 1) * 8
106
+ return layer.diagnose(:error, :bad_length, 'NDP option has zero length') if len.zero?
107
+ cursor.check(pos, len)
108
+ layer.add(:"option_type_#{pos}", 'icmpv6.opt.type', opt)
109
+ layer.add(:"option_length_#{pos}", 'icmpv6.opt.length', len / 8)
110
+ if [1, 2].include?(opt) && len >= 8
111
+ layer.add(:"link_address_#{pos}", 'icmpv6.opt.linkaddr', cursor.bytes(pos + 2, 6), type: :mac)
112
+ elsif opt == 3 && len == 32
113
+ layer.add(:"prefix_length_#{pos}", 'icmpv6.opt.prefix.length', cursor.u8(pos + 2))
114
+ flags = cursor.u8(pos + 3)
115
+ layer.add(:"prefix_flags_#{pos}", 'icmpv6.opt.prefix.flag', flags, format: :hex)
116
+ layer.add(:"prefix_onlink_#{pos}", 'icmpv6.opt.prefix.flag.l', (flags & 0x80).positive?)
117
+ layer.add(:"prefix_autonomous_#{pos}", 'icmpv6.opt.prefix.flag.a', (flags & 0x40).positive?)
118
+ layer.add(:"prefix_valid_#{pos}", 'icmpv6.opt.prefix.valid_lifetime', cursor.u32(pos + 4))
119
+ layer.add(:"prefix_preferred_#{pos}", 'icmpv6.opt.prefix.preferred_lifetime', cursor.u32(pos + 8))
120
+ layer.add(:"prefix_#{pos}", 'icmpv6.opt.prefix', cursor.bytes(pos + 16, 16), type: :ipv6)
121
+ elsif opt == 4 && len >= 8
122
+ layer.add(:"redirected_#{pos}", 'icmpv6.opt.redirected_packet', cursor.bytes(pos + 8, len - 8), type: :bytes)
123
+ elsif opt == 5 && len == 8
124
+ layer.add(:"mtu_#{pos}", 'icmpv6.opt.mtu', cursor.u32(pos + 4))
125
+ else
126
+ layer.add(:"option_#{pos}", 'icmpv6.opt.data', cursor.bytes(pos + 2, len - 2), type: :bytes)
127
+ end
128
+ pos += len
129
+ end
130
+ length = pos
131
+ end
132
+ layer.header_length, layer.payload_offset = length, layer.offset + length
133
+ Checksum.verify(ctx, layer, cursor.bytes(0, cursor.remaining), 58)
134
+ end
135
+ # @rbs (Context ctx, Layer layer) -> untyped
136
+ def next_dissector(ctx, layer)
137
+ return nil unless layer[:type] < 128
138
+ ctx.embedded = true
139
+ Ipv6
140
+ end
141
+ end
142
+
143
+ # @api private
144
+ class Igmp < Dissector
145
+ protocol :igmp, name: 'Internet Group Management Protocol', short: 'IGMP'
146
+ dissects_on 'ip.proto', 2
147
+ header do
148
+ uint8 :type, 'igmp.type'
149
+ uint8 :response, 'igmp.max_resp'
150
+ uint16 :checksum, 'igmp.checksum'
151
+ end
152
+ # @rbs (Context ctx, Layer layer) -> void
153
+ def dissect(ctx, layer)
154
+ cursor, type = ctx.cursor, layer[:type]
155
+ cursor.check(0, 8)
156
+ version = type == 0x22 || (type == 0x11 && cursor.remaining >= 12) ? 3 : (type == 0x12 || (type == 0x11 && layer[:response].zero?) ? 1 : 2)
157
+ layer.add(:version, 'igmp.version', version)
158
+ pos = 8
159
+ if type == 0x22
160
+ count = cursor.u16(6)
161
+ layer.add(:records, 'igmp.num_grp_recs', count)
162
+ count.times do |record|
163
+ cursor.check(pos, 8)
164
+ sources, auxiliary = cursor.u16(pos + 2), cursor.u8(pos + 1) * 4
165
+ cursor.check(pos, 8 + sources * 4 + auxiliary)
166
+ layer.add(:"record_#{record}", 'igmp.record_type', cursor.u8(pos))
167
+ layer.add(:"group_#{record}", 'igmp.maddr', cursor.u32(pos + 4), type: :ipv4)
168
+ layer.add(:"sources_#{record}", 'igmp.num_src', sources)
169
+ sources.times { |i| layer.add(:"source_#{record}_#{i}", 'igmp.saddr', cursor.u32(pos + 8 + i * 4), type: :ipv4) }
170
+ layer.add(:"auxiliary_#{record}", 'igmp.aux_data', cursor.bytes(pos + 8 + sources * 4, auxiliary), type: :bytes) if auxiliary.positive?
171
+ pos += 8 + sources * 4 + auxiliary
172
+ end
173
+ else
174
+ layer.add(:group, 'igmp.maddr', cursor.u32(4), type: :ipv4)
175
+ if version == 3
176
+ sources = cursor.u16(10)
177
+ cursor.check(8, 4 + sources * 4)
178
+ layer.add(:suppress, 'igmp.s', (cursor.u8(8) & 8).positive?)
179
+ layer.add(:robustness, 'igmp.qrv', cursor.u8(8) & 7)
180
+ layer.add(:interval, 'igmp.qqic', cursor.u8(9))
181
+ layer.add(:sources, 'igmp.num_src', sources)
182
+ sources.times { |i| layer.add(:"source_#{i}", 'igmp.saddr', cursor.u32(12 + i * 4), type: :ipv4) }
183
+ pos = 12 + sources * 4
184
+ end
185
+ end
186
+ layer.header_length, layer.payload_offset = pos, layer.offset + pos
187
+ Checksum.verify(ctx, layer, cursor.bytes(0, cursor.remaining))
188
+ end
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,56 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ module Redhound
5
+ # @api private
6
+ module Protocols
7
+ # @api private
8
+ class Ipv4 < Dissector
9
+ protocol :ipv4, name: 'Internet Protocol v4', short: 'IP'
10
+ dissects_on 'ethertype', 0x0800
11
+ dissects_on 'ip.proto', 4
12
+ header do
13
+ bits 8 do
14
+ bit :version, 'ip.version', 4
15
+ bit :hdr_len, 'ip.hdr_len', 4, scale: 4
16
+ end
17
+ bits 8 do
18
+ bit :dscp, 'ip.dsfield.dscp', 6
19
+ bit :ecn, 'ip.dsfield.ecn', 2
20
+ end
21
+ uint16 :len, 'ip.len'
22
+ uint16 :id, 'ip.id', format: :hex
23
+ bits 16 do
24
+ bit :reserved, 'ip.flags.rb', 1
25
+ bit :df, 'ip.flags.df', 1
26
+ bit :mf, 'ip.flags.mf', 1
27
+ bit :frag_offset, 'ip.frag_offset', 13, scale: 8
28
+ end
29
+ uint8 :ttl, 'ip.ttl'
30
+ uint8 :proto, 'ip.proto'
31
+ uint16 :checksum, 'ip.checksum', format: :hex
32
+ ipv4 :src, 'ip.src'
33
+ ipv4 :dst, 'ip.dst'
34
+ end
35
+ # @rbs (Context ctx, Layer layer) -> void
36
+ def dissect(ctx, layer)
37
+ hlen = layer[:hdr_len]
38
+ if layer[:version] != 4 || hlen < 20 || layer[:len] < hlen
39
+ layer.diagnose(:error, :bad_length, 'invalid IPv4 version or length')
40
+ return
41
+ end
42
+ ctx.cursor.check(0, hlen)
43
+ layer.header_length = hlen
44
+ layer.payload_offset = layer.offset + hlen
45
+ layer.payload_end = layer.offset + layer[:len]
46
+ layer.add(:options, 'ip.options', ctx.cursor.bytes(20, hlen - 20), type: :bytes, offset: 20, length: hlen - 20) if hlen > 20
47
+ Checksum.verify(ctx, layer, ctx.cursor.bytes(0, hlen))
48
+ end
49
+ # @rbs (Context ctx, Layer layer) -> untyped
50
+ def next_dissector(ctx, layer)
51
+ return nil unless layer[:frag_offset].zero?
52
+ ctx.registry.lookup('ip.proto', layer[:proto])
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,65 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ module Redhound
5
+ # @api private
6
+ module Protocols
7
+ # @api private
8
+ class Ipv6 < Dissector
9
+ protocol :ipv6, name: 'Internet Protocol v6', short: 'IP6'
10
+ dissects_on 'ethertype', 0x86dd
11
+ dissects_on 'ip.proto', 41
12
+ header do
13
+ bits 32 do
14
+ bit :version, 'ipv6.version', 4
15
+ bit :tclass, 'ipv6.tclass', 8
16
+ bit :flow, 'ipv6.flow', 20, format: :hex
17
+ end
18
+ uint16 :plen, 'ipv6.plen'
19
+ uint8 :nxt, 'ipv6.nxt'
20
+ uint8 :hlim, 'ipv6.hlim'
21
+ ipv6 :src, 'ipv6.src'
22
+ ipv6 :dst, 'ipv6.dst'
23
+ end
24
+ # @rbs (Context ctx, Layer layer) -> void
25
+ def dissect(ctx, layer)
26
+ layer.diagnose(:error, :malformed, 'IPv6 version is not 6') if layer[:version] != 6
27
+ layer.payload_end = layer.offset + 40 + layer[:plen]
28
+ end
29
+ # @rbs (Context ctx, Layer layer) -> untyped
30
+ def next_dissector(ctx, layer) = ctx.registry.lookup('ip.proto', layer[:nxt])
31
+ end
32
+
33
+ # @api private
34
+ class Ipv6Ext < Dissector
35
+ protocol :ipv6_ext, name: 'IPv6 extension', short: 'IP6 EXT'
36
+ dissects_on 'ip.proto', 0, 43, 44, 60, 51
37
+ # @rbs (Context ctx, Layer layer) -> void
38
+ def dissect(ctx, layer)
39
+ ctx.extension_count += 1
40
+ return layer.diagnose(:error, :malformed, 'more than 16 IPv6 extensions') if ctx.extension_count > 16
41
+ parent = ctx.layers.last
42
+ type = parent.protocol == :ipv6 ? parent[:nxt] : parent[:next]
43
+ cursor = ctx.cursor
44
+ nxt = cursor.u8(0)
45
+ layer.add(:next, 'ipv6.nxt', nxt, length: 1)
46
+ layer.add(:type, 'ipv6.extension.type', type)
47
+ length = type == 44 ? 8 : type == 51 ? (cursor.u8(1) + 2) * 4 : (cursor.u8(1) + 1) * 8
48
+ cursor.check(0, length)
49
+ if type == 44
50
+ flags = cursor.u16(2)
51
+ layer.add(:fragment_offset, 'ipv6.fragment.offset', flags & 0xfff8, offset: 2, length: 2)
52
+ layer.add(:more, 'ipv6.fragment.more', flags & 1)
53
+ layer.add(:identification, 'ipv6.fragment.id', cursor.u32(4), offset: 4, length: 4)
54
+ end
55
+ layer.header_length = length
56
+ layer.payload_offset += length
57
+ end
58
+ # @rbs (Context ctx, Layer layer) -> untyped
59
+ def next_dissector(ctx, layer)
60
+ return nil if layer[:fragment_offset] && layer[:fragment_offset] != 0
61
+ ctx.registry.lookup('ip.proto', layer[:next])
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,70 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ module Redhound
5
+ # @api private
6
+ module Protocols
7
+ # @api private
8
+ class Sll < Dissector
9
+ protocol :sll, name: 'Linux cooked capture', short: 'SLL'
10
+ dissects_on 'linktype', 113
11
+ header do
12
+ uint16 :pkttype, 'sll.pkttype'
13
+ uint16 :hatype, 'sll.hatype'
14
+ uint16 :halen, 'sll.halen'
15
+ uint64 :address, 'sll.src'
16
+ uint16 :type, 'sll.etype'
17
+ end
18
+ # @rbs (Context ctx, Layer layer) -> untyped
19
+ def next_dissector(ctx, layer) = ctx.registry.lookup('ethertype', layer[:type])
20
+ end
21
+ # @api private
22
+ class Sll2 < Dissector
23
+ protocol :sll2, name: 'Linux cooked capture v2', short: 'SLL2'
24
+ dissects_on 'linktype', 276
25
+ header do
26
+ uint16 :type, 'sll.etype'
27
+ uint16 :reserved, 'sll.reserved'
28
+ uint32 :ifindex, 'sll.ifindex'
29
+ uint16 :hatype, 'sll.hatype'
30
+ uint8 :pkttype, 'sll.pkttype'
31
+ uint8 :halen, 'sll.halen'
32
+ uint64 :address, 'sll.src'
33
+ end
34
+ # @rbs (Context ctx, Layer layer) -> untyped
35
+ def next_dissector(ctx, layer) = ctx.registry.lookup('ethertype', layer[:type])
36
+ end
37
+ # @api private
38
+ class Null < Dissector
39
+ protocol :null, name: 'BSD loopback', short: 'NULL'
40
+ dissects_on 'linktype', 0, 108
41
+ # @rbs (Context ctx, Layer layer) -> void
42
+ def dissect(ctx, layer)
43
+ bytes = ctx.cursor.bytes(0, 4)
44
+ family = bytes.unpack1(ctx.packet.linktype == 108 ? 'N' : 'L')
45
+ family = bytes.unpack1('N') if family > 255
46
+ layer.add(:family, 'null.family', family, length: 4)
47
+ layer.header_length = 4
48
+ layer.payload_offset += 4
49
+ end
50
+ # @rbs (Context ctx, Layer layer) -> untyped
51
+ def next_dissector(ctx, layer)
52
+ return Ipv4 if layer[:family] == 2
53
+ return Ipv6 if [10, 24, 28, 30].include?(layer[:family])
54
+ nil
55
+ end
56
+ end
57
+ # @api private
58
+ class Raw < Dissector
59
+ protocol :raw, name: 'Raw IP', short: 'RAW'
60
+ dissects_on 'linktype', 101, 228, 229
61
+ # @rbs (Context ctx, Layer layer) -> untyped
62
+ def next_dissector(ctx, layer)
63
+ case ctx.cursor.u8(0) >> 4
64
+ when 4 then Ipv4
65
+ when 6 then Ipv6
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,37 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ module Redhound
5
+ # @api private
6
+ module Protocols
7
+ # @api private
8
+ class Llc < Dissector
9
+ protocol :llc, name: 'Logical Link Control', short: 'LLC'
10
+ header do
11
+ uint8 :dsap, 'llc.dsap'
12
+ uint8 :ssap, 'llc.ssap'
13
+ uint8 :control, 'llc.control'
14
+ end
15
+ # @rbs (Context ctx, Layer layer) -> void
16
+ def dissect(ctx, layer)
17
+ if layer[:control] & 3 != 3
18
+ layer.values[:control] |= ctx.cursor.u8(3) << 8
19
+ layer.definitions[2] = layer.definitions[2].with(type: :uint16, length: 2)
20
+ layer.header_length = 4
21
+ layer.payload_offset = layer.offset + 4
22
+ return
23
+ end
24
+ return unless layer[:dsap] == 0xaa && layer[:ssap] == 0xaa && layer[:control] == 3
25
+ layer.add(:oui, 'llc.oui', ctx.cursor.bytes(3, 3), type: :bytes, offset: 3, length: 3)
26
+ layer.add(:type, 'llc.pid', ctx.cursor.u16(6), offset: 6, length: 2, format: :hex)
27
+ layer.header_length = 8
28
+ layer.payload_offset = layer.offset + 8
29
+ end
30
+ # @rbs (Context ctx, Layer layer) -> untyped
31
+ def next_dissector(ctx, layer)
32
+ return ctx.registry.lookup('ethertype', layer[:type]) if layer[:oui] == "\0\0\0".b
33
+ ctx.registry.lookup('llc.dsap', layer[:dsap])
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,47 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ module Redhound
5
+ # @api private
6
+ module Protocols
7
+ # @api private
8
+ class Ntp < Dissector
9
+ MODES = { 1 => 'symmetric active', 2 => 'symmetric passive', 3 => 'client', 4 => 'server',
10
+ 5 => 'broadcast', 6 => 'control', 7 => 'private' }.freeze
11
+ protocol :ntp, name: 'Network Time Protocol', short: 'NTP'
12
+ dissects_on 'udp.port', 123
13
+ header do
14
+ bits 8 do
15
+ bit :li, 'ntp.flags.li', 2
16
+ bit :vn, 'ntp.flags.vn', 3
17
+ bit :mode, 'ntp.flags.mode', 3, enum: MODES
18
+ end
19
+ uint8 :stratum, 'ntp.stratum'
20
+ uint8 :poll, 'ntp.ppoll'
21
+ uint8 :precision, 'ntp.precision'
22
+ uint32 :rootdelay, 'ntp.rootdelay'
23
+ uint32 :rootdispersion, 'ntp.rootdispersion'
24
+ uint32 :refid, 'ntp.refid'
25
+ uint64 :reftime, 'ntp.reftime'
26
+ uint64 :org, 'ntp.org'
27
+ uint64 :rec, 'ntp.rec'
28
+ uint64 :xmt, 'ntp.xmt'
29
+ end
30
+
31
+ # @rbs (Context ctx, Layer layer) -> void
32
+ def dissect(ctx, layer)
33
+ layer.values[:poll] -= 256 if layer[:poll] >= 128
34
+ layer.values[:precision] -= 256 if layer[:precision] >= 128
35
+ layer.diagnose(:error, :malformed, 'invalid NTP version or mode') unless layer[:vn].between?(1, 4) && layer[:mode].between?(1, 7)
36
+ if ctx.cursor.remaining > 48
37
+ layer.add(:extension, 'ntp.extension', ctx.cursor.bytes(48, ctx.cursor.remaining - 48),
38
+ type: :bytes, offset: 48, length: ctx.cursor.remaining - 48)
39
+ end
40
+ layer.payload_offset = layer.payload_end
41
+ end
42
+
43
+ # @rbs (Layer layer) -> String
44
+ def summary(layer) = "NTP v#{layer[:vn]} #{MODES[layer[:mode]]}, stratum #{layer[:stratum]}"
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,80 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ module Redhound
5
+ # @api private
6
+ module Protocols
7
+ # @api private
8
+ class Tcp < Dissector
9
+ FLAGS = { 0x80 => 'C', 0x40 => 'E', 0x20 => 'U', 0x10 => '.', 8 => 'P', 4 => 'R', 2 => 'S', 1 => 'F' }.freeze
10
+ FLAG_FIELDS = %w[fin syn reset push ack urg ecn cwr].each_with_index.map do |name, bit|
11
+ [FieldDefinition.new(:"flag_#{name}", "tcp.flags.#{name}", :boolean, 13, 1, 0, nil, nil, 1, nil, nil), 1 << bit]
12
+ end.freeze
13
+ protocol :tcp, name: 'Transmission Control Protocol', short: 'TCP'
14
+ dissects_on 'ip.proto', 6
15
+ header do
16
+ uint16 :srcport, 'tcp.srcport'
17
+ uint16 :dstport, 'tcp.dstport'
18
+ uint32 :seq, 'tcp.seq'
19
+ uint32 :ack, 'tcp.ack'
20
+ bits 16 do
21
+ bit :hdr_len, 'tcp.hdr_len', 4, scale: 4
22
+ bit :reserved, 'tcp.reserved', 3
23
+ bit :flags, 'tcp.flags', 9, format: :hex
24
+ end
25
+ uint16 :window, 'tcp.window_size_value'
26
+ uint16 :checksum, 'tcp.checksum', format: :hex
27
+ uint16 :urgent, 'tcp.urgent_pointer'
28
+ end
29
+ # @rbs (Context ctx, Layer layer) -> void
30
+ def dissect(ctx, layer)
31
+ hlen = layer[:hdr_len]
32
+ return layer.diagnose(:error, :bad_length, 'TCP data offset is less than 20') if hlen < 20
33
+ ctx.cursor.check(0, hlen)
34
+ layer.header_length, layer.payload_offset = hlen, layer.offset + hlen
35
+ FLAG_FIELDS.each do |definition, mask|
36
+ layer.definitions << definition
37
+ layer.values[definition.key] = (layer[:flags] & mask) != 0
38
+ end
39
+ options(ctx.cursor, layer, hlen)
40
+ layer.add(:length, 'tcp.len', ctx.cursor.remaining - hlen)
41
+ Checksum.verify(ctx, layer, ctx.cursor.bytes(0, ctx.cursor.remaining), 6)
42
+ end
43
+ # @rbs (Cursor cursor, Layer layer, Integer hlen) -> void
44
+ def options(cursor, layer, hlen)
45
+ pos = 20
46
+ while pos < hlen
47
+ kind = cursor.u8(pos)
48
+ break if kind.zero?
49
+ if kind == 1
50
+ pos += 1
51
+ next
52
+ end
53
+ return layer.diagnose(:error, :bad_length, 'truncated TCP option') if pos + 2 > hlen
54
+ len = cursor.u8(pos + 1)
55
+ return layer.diagnose(:error, :bad_length, 'invalid TCP option length') if len < 2 || pos + len > hlen
56
+ case kind
57
+ when 2 then layer.add(:mss, 'tcp.options.mss_val', cursor.u16(pos + 2)) if len == 4
58
+ when 3 then layer.add(:wscale, 'tcp.options.wscale.shift', cursor.u8(pos + 2)) if len == 3
59
+ when 4 then layer.add(:sack_permitted, 'tcp.options.sack_perm', true, type: :boolean) if len == 2
60
+ when 5 then layer.add(:sack, 'tcp.options.sack', cursor.bytes(pos + 2, len - 2), type: :bytes)
61
+ when 8
62
+ if len == 10
63
+ layer.add(:tsval, 'tcp.options.timestamp.tsval', cursor.u32(pos + 2))
64
+ layer.add(:tsecr, 'tcp.options.timestamp.tsecr', cursor.u32(pos + 6))
65
+ end
66
+ when 34 then layer.add(:tfo, 'tcp.options.tfo.cookie', cursor.bytes(pos + 2, len - 2), type: :bytes)
67
+ end
68
+ pos += len
69
+ end
70
+ end
71
+ # @rbs (Context ctx, Layer layer) -> untyped
72
+ def next_dissector(ctx, layer) = ctx.registry.by_port('tcp.port', layer[:srcport], layer[:dstport])
73
+ # @rbs (Layer layer) -> String
74
+ def summary(layer)
75
+ flags = FLAGS.filter_map { |bit, name| name if layer[:flags] & bit != 0 }.join
76
+ "TCP [#{flags}], seq #{layer[:seq]}, ack #{layer[:ack]}, win #{layer[:window]}, length #{layer[:length]}"
77
+ end
78
+ end
79
+ end
80
+ end