redhound 2.0.0.rc1 → 2.0.0.rc2

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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -0
  3. data/README.md +1 -1
  4. data/docs/API.md +29 -1
  5. data/docs/MIGRATION.md +1 -1
  6. data/docs/PROTOCOLS.md +10 -1
  7. data/docs/USAGE.md +5 -1
  8. data/docs/VALIDATION.md +119 -3
  9. data/docs/json-schema.json +2 -2
  10. data/lib/redhound/analysis/flow_key.rb +7 -1
  11. data/lib/redhound/analysis/flow_table.rb +12 -5
  12. data/lib/redhound/analysis/ip_reassembler.rb +44 -11
  13. data/lib/redhound/analysis/stats.rb +14 -2
  14. data/lib/redhound/analysis/tcp_reassembler.rb +68 -16
  15. data/lib/redhound/analysis/tcp_stream.rb +7 -2
  16. data/lib/redhound/analysis.rb +30 -2
  17. data/lib/redhound/capture/bsd/bpf_device.rb +3 -0
  18. data/lib/redhound/capture/bsd/ifreq.rb +41 -0
  19. data/lib/redhound/capture/file_source.rb +135 -4
  20. data/lib/redhound/capture/interface.rb +5 -1
  21. data/lib/redhound/capture/linux/packet_socket.rb +53 -9
  22. data/lib/redhound/capture/linux/tpacket_v3.rb +5 -0
  23. data/lib/redhound/capture.rb +3 -2
  24. data/lib/redhound/cli/command.rb +1 -1
  25. data/lib/redhound/dissector.rb +18 -4
  26. data/lib/redhound/engine.rb +3 -0
  27. data/lib/redhound/field.rb +1 -1
  28. data/lib/redhound/file/format.rb +8 -1
  29. data/lib/redhound/file/pcap_writer.rb +10 -3
  30. data/lib/redhound/file/pcapng_reader.rb +19 -7
  31. data/lib/redhound/file/pcapng_writer.rb +22 -14
  32. data/lib/redhound/filter/bpf/vm.rb +3 -2
  33. data/lib/redhound/output/follow.rb +6 -1
  34. data/lib/redhound/output/summary.rb +29 -4
  35. data/lib/redhound/output/timestamp.rb +2 -2
  36. data/lib/redhound/output/tree.rb +2 -1
  37. data/lib/redhound/packet.rb +18 -4
  38. data/lib/redhound/protocols/data.rb +3 -1
  39. data/lib/redhound/protocols/dhcp.rb +29 -13
  40. data/lib/redhound/protocols/http.rb +5 -3
  41. data/lib/redhound/protocols/icmp.rb +55 -41
  42. data/lib/redhound/protocols/ipv6.rb +5 -2
  43. data/lib/redhound/protocols/ntp.rb +2 -4
  44. data/lib/redhound/protocols/tcp.rb +17 -9
  45. data/lib/redhound/protocols/tunnel.rb +2 -2
  46. data/lib/redhound/stream_dissector.rb +2 -2
  47. data/lib/redhound/version.rb +1 -1
  48. data/sig/generated/redhound/analysis/flow_table.rbs +6 -2
  49. data/sig/generated/redhound/analysis/ip_reassembler.rbs +12 -0
  50. data/sig/generated/redhound/analysis/stats.rbs +3 -0
  51. data/sig/generated/redhound/analysis/tcp_reassembler.rbs +12 -0
  52. data/sig/generated/redhound/capture/bsd/ifreq.rbs +15 -0
  53. data/sig/generated/redhound/capture/file_source.rbs +33 -0
  54. data/sig/generated/redhound/capture/linux/packet_socket.rbs +7 -0
  55. data/sig/generated/redhound/dissector.rbs +4 -0
  56. data/sig/generated/redhound/file/format.rbs +6 -0
  57. data/sig/generated/redhound/file/pcapng_reader.rbs +2 -2
  58. data/sig/generated/redhound/filter/bpf/vm.rbs +2 -0
  59. data/sig/generated/redhound/packet.rbs +5 -0
  60. data/sig/generated/redhound/protocols/data.rbs +2 -0
  61. data/sig/generated/redhound/protocols/dhcp.rbs +7 -4
  62. metadata +3 -1
@@ -25,12 +25,20 @@ module Redhound
25
25
  uint8 :type, 'icmp.type', enum: TYPES
26
26
  uint8 :code, 'icmp.code'
27
27
  uint16 :checksum, 'icmp.checksum', format: :hex
28
- uint16 :ident, 'icmp.ident'
29
- uint16 :seq, 'icmp.seq'
30
28
  end
31
29
  # @rbs (Context ctx, Layer layer) -> void
32
30
  def dissect(ctx, layer)
33
31
  layer.definitions[1] = layer.definitions[1].with(enum: CODES[layer[:type]])
32
+ if [0, 8, 13, 14, 17, 18].include?(layer[:type])
33
+ layer.add(:ident, 'icmp.ident', ctx.cursor.u16(4), offset: 4, length: 2)
34
+ layer.add(:seq, 'icmp.seq', ctx.cursor.u16(6), offset: 6, length: 2)
35
+ else
36
+ ctx.cursor.check(0, 8)
37
+ if layer[:type] == 3 && layer[:code] == 4
38
+ layer.add(:mtu, 'icmp.mtu', ctx.cursor.u16(6), offset: 6, length: 2)
39
+ end
40
+ end
41
+ layer.header_length, layer.payload_offset = 8, layer.offset + 8
34
42
  Checksum.verify(ctx, layer, ctx.cursor.bytes(0, ctx.cursor.remaining))
35
43
  end
36
44
  # @rbs (Context ctx, Layer layer) -> untyped
@@ -40,7 +48,12 @@ module Redhound
40
48
  Ipv4
41
49
  end
42
50
  # @rbs (Layer layer) -> String
43
- def summary(layer) = "ICMP type #{layer[:type]}, code #{layer[:code]}, id #{layer[:ident]}, seq #{layer[:seq]}"
51
+ def summary(layer)
52
+ text = "ICMP type #{layer[:type]}, code #{layer[:code]}"
53
+ text += ", id #{layer[:ident]}, seq #{layer[:seq]}" if layer[:ident]
54
+ text += ", mtu #{layer[:mtu]}" if layer[:mtu]
55
+ text
56
+ end
44
57
  end
45
58
 
46
59
  # @api private
@@ -74,29 +87,29 @@ module Redhound
74
87
  end
75
88
  cursor.check(0, length)
76
89
  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))
90
+ layer.add(:ident, 'icmpv6.echo.identifier', cursor.u16(4), offset: 4, length: 2)
91
+ layer.add(:seq, 'icmpv6.echo.sequence_number', cursor.u16(6), offset: 6, length: 2)
79
92
  elsif [135, 136, 137].include?(type)
80
93
  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
94
+ layer.add(:target, "icmpv6.nd.#{family}.target_address", cursor.bytes(8, 16), type: :ipv6, offset: 8, length: 16)
95
+ layer.add(:destination, 'icmpv6.rd.na.destination_address', cursor.bytes(24, 16), type: :ipv6, offset: 24, length: 16) if type == 137
83
96
  if type == 136
84
97
  flags = cursor.u32(4)
85
- layer.add(:flags, 'icmpv6.nd.na.flag', flags, format: :hex)
98
+ layer.add(:flags, 'icmpv6.nd.na.flag', flags, format: :hex, offset: 4, length: 4)
86
99
  { r: 0x80000000, s: 0x40000000, o: 0x20000000 }.each do |name, mask|
87
- layer.add(name, "icmpv6.nd.na.flag.#{name}", (flags & mask).positive?)
100
+ layer.add(name, "icmpv6.nd.na.flag.#{name}", (flags & mask).positive?, type: :boolean, offset: 4, length: 4)
88
101
  end
89
102
  end
90
103
  end
91
104
  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))
105
+ layer.add(:hop_limit, 'icmpv6.nd.ra.cur_hop_limit', cursor.u8(4), offset: 4, length: 1)
106
+ layer.add(:lifetime, 'icmpv6.nd.ra.router_lifetime', cursor.u16(6), offset: 6, length: 2)
94
107
  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))
108
+ layer.add(:flags, 'icmpv6.nd.ra.flag', flags, format: :hex, offset: 5, length: 1)
109
+ layer.add(:managed, 'icmpv6.nd.ra.flag.m', (flags & 0x80).positive?, type: :boolean, offset: 5, length: 1)
110
+ layer.add(:other, 'icmpv6.nd.ra.flag.o', (flags & 0x40).positive?, type: :boolean, offset: 5, length: 1)
111
+ layer.add(:reachable, 'icmpv6.nd.ra.reachable_time', cursor.u32(8), offset: 8, length: 4)
112
+ layer.add(:retrans, 'icmpv6.nd.ra.retrans_timer', cursor.u32(12), offset: 12, length: 4)
100
113
  end
101
114
  if type.between?(133, 137)
102
115
  pos = length
@@ -105,25 +118,25 @@ module Redhound
105
118
  len = cursor.u8(pos + 1) * 8
106
119
  return layer.diagnose(:error, :bad_length, 'NDP option has zero length') if len.zero?
107
120
  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)
121
+ layer.add(:"option_type_#{pos}", 'icmpv6.opt.type', opt, offset: pos, length: 1)
122
+ layer.add(:"option_length_#{pos}", 'icmpv6.opt.length', len / 8, offset: pos + 1, length: 1)
110
123
  if [1, 2].include?(opt) && len >= 8
111
- layer.add(:"link_address_#{pos}", 'icmpv6.opt.linkaddr', cursor.bytes(pos + 2, 6), type: :mac)
124
+ layer.add(:"link_address_#{pos}", 'icmpv6.opt.linkaddr', cursor.bytes(pos + 2, 6), type: :mac, offset: pos + 2, length: 6)
112
125
  elsif opt == 3 && len == 32
113
- layer.add(:"prefix_length_#{pos}", 'icmpv6.opt.prefix.length', cursor.u8(pos + 2))
126
+ layer.add(:"prefix_length_#{pos}", 'icmpv6.opt.prefix.length', cursor.u8(pos + 2), offset: pos + 2, length: 1)
114
127
  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)
128
+ layer.add(:"prefix_flags_#{pos}", 'icmpv6.opt.prefix.flag', flags, format: :hex, offset: pos + 3, length: 1)
129
+ layer.add(:"prefix_onlink_#{pos}", 'icmpv6.opt.prefix.flag.l', (flags & 0x80).positive?, type: :boolean, offset: pos + 3, length: 1)
130
+ layer.add(:"prefix_autonomous_#{pos}", 'icmpv6.opt.prefix.flag.a', (flags & 0x40).positive?, type: :boolean, offset: pos + 3, length: 1)
131
+ layer.add(:"prefix_valid_#{pos}", 'icmpv6.opt.prefix.valid_lifetime', cursor.u32(pos + 4), offset: pos + 4, length: 4)
132
+ layer.add(:"prefix_preferred_#{pos}", 'icmpv6.opt.prefix.preferred_lifetime', cursor.u32(pos + 8), offset: pos + 8, length: 4)
133
+ layer.add(:"prefix_#{pos}", 'icmpv6.opt.prefix', cursor.bytes(pos + 16, 16), type: :ipv6, offset: pos + 16, length: 16)
121
134
  elsif opt == 4 && len >= 8
122
- layer.add(:"redirected_#{pos}", 'icmpv6.opt.redirected_packet', cursor.bytes(pos + 8, len - 8), type: :bytes)
135
+ layer.add(:"redirected_#{pos}", 'icmpv6.opt.redirected_packet', cursor.bytes(pos + 8, len - 8), type: :bytes, offset: pos + 8, length: len - 8)
123
136
  elsif opt == 5 && len == 8
124
- layer.add(:"mtu_#{pos}", 'icmpv6.opt.mtu', cursor.u32(pos + 4))
137
+ layer.add(:"mtu_#{pos}", 'icmpv6.opt.mtu', cursor.u32(pos + 4), offset: pos + 4, length: 4)
125
138
  else
126
- layer.add(:"option_#{pos}", 'icmpv6.opt.data', cursor.bytes(pos + 2, len - 2), type: :bytes)
139
+ layer.add(:"option_#{pos}", 'icmpv6.opt.data', cursor.bytes(pos + 2, len - 2), type: :bytes, offset: pos + 2, length: len - 2)
127
140
  end
128
141
  pos += len
129
142
  end
@@ -152,34 +165,35 @@ module Redhound
152
165
  # @rbs (Context ctx, Layer layer) -> void
153
166
  def dissect(ctx, layer)
154
167
  cursor, type = ctx.cursor, layer[:type]
168
+ layer.definitions.reject! { |field| field.key == :response } if [0x12, 0x22].include?(type)
155
169
  cursor.check(0, 8)
156
170
  version = type == 0x22 || (type == 0x11 && cursor.remaining >= 12) ? 3 : (type == 0x12 || (type == 0x11 && layer[:response].zero?) ? 1 : 2)
157
171
  layer.add(:version, 'igmp.version', version)
158
172
  pos = 8
159
173
  if type == 0x22
160
174
  count = cursor.u16(6)
161
- layer.add(:records, 'igmp.num_grp_recs', count)
175
+ layer.add(:records, 'igmp.num_grp_recs', count, offset: 6, length: 2)
162
176
  count.times do |record|
163
177
  cursor.check(pos, 8)
164
178
  sources, auxiliary = cursor.u16(pos + 2), cursor.u8(pos + 1) * 4
165
179
  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?
180
+ layer.add(:"record_#{record}", 'igmp.record_type', cursor.u8(pos), offset: pos, length: 1)
181
+ layer.add(:"group_#{record}", 'igmp.maddr', cursor.u32(pos + 4), type: :ipv4, offset: pos + 4, length: 4)
182
+ layer.add(:"sources_#{record}", 'igmp.num_src', sources, offset: pos + 2, length: 2)
183
+ sources.times { |i| layer.add(:"source_#{record}_#{i}", 'igmp.saddr', cursor.u32(pos + 8 + i * 4), type: :ipv4, offset: pos + 8 + i * 4, length: 4) }
184
+ layer.add(:"auxiliary_#{record}", 'igmp.aux_data', cursor.bytes(pos + 8 + sources * 4, auxiliary), type: :bytes, offset: pos + 8 + sources * 4, length: auxiliary) if auxiliary.positive?
171
185
  pos += 8 + sources * 4 + auxiliary
172
186
  end
173
187
  else
174
- layer.add(:group, 'igmp.maddr', cursor.u32(4), type: :ipv4)
188
+ layer.add(:group, 'igmp.maddr', cursor.u32(4), type: :ipv4, offset: 4, length: 4)
175
189
  if version == 3
176
190
  sources = cursor.u16(10)
177
191
  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) }
192
+ layer.add(:suppress, 'igmp.s', (cursor.u8(8) & 8).positive?, type: :boolean, offset: 8, length: 1)
193
+ layer.add(:robustness, 'igmp.qrv', cursor.u8(8) & 7, offset: 8, length: 1)
194
+ layer.add(:interval, 'igmp.qqic', cursor.u8(9), offset: 9, length: 1)
195
+ layer.add(:sources, 'igmp.num_src', sources, offset: 10, length: 2)
196
+ sources.times { |i| layer.add(:"source_#{i}", 'igmp.saddr', cursor.u32(12 + i * 4), type: :ipv4, offset: 12 + i * 4, length: 4) }
183
197
  pos = 12 + sources * 4
184
198
  end
185
199
  end
@@ -23,8 +23,10 @@ module Redhound
23
23
  end
24
24
  # @rbs (Context ctx, Layer layer) -> void
25
25
  def dissect(ctx, layer)
26
+ ctx.extension_count = 0
26
27
  layer.diagnose(:error, :malformed, 'IPv6 version is not 6') if layer[:version] != 6
27
28
  layer.payload_end = layer.offset + 40 + layer[:plen]
29
+ layer.payload_offset = layer.payload_end if layer[:nxt] == 59
28
30
  end
29
31
  # @rbs (Context ctx, Layer layer) -> untyped
30
32
  def next_dissector(ctx, layer) = ctx.registry.lookup('ip.proto', layer[:nxt])
@@ -39,7 +41,7 @@ module Redhound
39
41
  ctx.extension_count += 1
40
42
  return layer.diagnose(:error, :malformed, 'more than 16 IPv6 extensions') if ctx.extension_count > 16
41
43
  parent = ctx.layers.last
42
- type = parent.protocol == :ipv6 ? parent[:nxt] : parent[:next]
44
+ type = parent.protocol == :ipv6 ? parent[:nxt] : parent.protocol == :ipv4 ? parent[:proto] : parent[:next]
43
45
  cursor = ctx.cursor
44
46
  nxt = cursor.u8(0)
45
47
  layer.add(:next, 'ipv6.nxt', nxt, length: 1)
@@ -49,11 +51,12 @@ module Redhound
49
51
  if type == 44
50
52
  flags = cursor.u16(2)
51
53
  layer.add(:fragment_offset, 'ipv6.fragment.offset', flags & 0xfff8, offset: 2, length: 2)
52
- layer.add(:more, 'ipv6.fragment.more', flags & 1)
54
+ layer.add(:more, 'ipv6.fragment.more', flags & 1, offset: 2, length: 2)
53
55
  layer.add(:identification, 'ipv6.fragment.id', cursor.u32(4), offset: 4, length: 4)
54
56
  end
55
57
  layer.header_length = length
56
58
  layer.payload_offset += length
59
+ layer.payload_offset = layer.payload_end if nxt == 59
57
60
  end
58
61
  # @rbs (Context ctx, Layer layer) -> untyped
59
62
  def next_dissector(ctx, layer)
@@ -17,8 +17,8 @@ module Redhound
17
17
  bit :mode, 'ntp.flags.mode', 3, enum: MODES
18
18
  end
19
19
  uint8 :stratum, 'ntp.stratum'
20
- uint8 :poll, 'ntp.ppoll'
21
- uint8 :precision, 'ntp.precision'
20
+ int8 :poll, 'ntp.ppoll'
21
+ int8 :precision, 'ntp.precision'
22
22
  uint32 :rootdelay, 'ntp.rootdelay'
23
23
  uint32 :rootdispersion, 'ntp.rootdispersion'
24
24
  uint32 :refid, 'ntp.refid'
@@ -30,8 +30,6 @@ module Redhound
30
30
 
31
31
  # @rbs (Context ctx, Layer layer) -> void
32
32
  def dissect(ctx, layer)
33
- layer.values[:poll] -= 256 if layer[:poll] >= 128
34
- layer.values[:precision] -= 256 if layer[:precision] >= 128
35
33
  layer.diagnose(:error, :malformed, 'invalid NTP version or mode') unless layer[:vn].between?(1, 4) && layer[:mode].between?(1, 7)
36
34
  if ctx.cursor.remaining > 48
37
35
  layer.add(:extension, 'ntp.extension', ctx.cursor.bytes(48, ctx.cursor.remaining - 48),
@@ -53,17 +53,25 @@ module Redhound
53
53
  return layer.diagnose(:error, :bad_length, 'truncated TCP option') if pos + 2 > hlen
54
54
  len = cursor.u8(pos + 1)
55
55
  return layer.diagnose(:error, :bad_length, 'invalid TCP option length') if len < 2 || pos + len > hlen
56
+ valid = case kind
57
+ when 2 then len == 4
58
+ when 3 then len == 3
59
+ when 4 then len == 2
60
+ when 5 then len >= 10 && (len - 2) % 8 == 0
61
+ when 8 then len == 10
62
+ when 34 then len == 2 || len.between?(6, 18)
63
+ else true
64
+ end
65
+ return layer.diagnose(:error, :bad_length, "invalid TCP option #{kind} length") unless valid
56
66
  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)
67
+ when 2 then layer.add(:mss, 'tcp.options.mss_val', cursor.u16(pos + 2), offset: pos + 2, length: 2)
68
+ when 3 then layer.add(:wscale, 'tcp.options.wscale.shift', cursor.u8(pos + 2), offset: pos + 2, length: 1)
69
+ when 4 then layer.add(:sack_permitted, 'tcp.options.sack_perm', true, type: :boolean, offset: pos, length: 2)
70
+ when 5 then layer.add(:sack, 'tcp.options.sack', cursor.bytes(pos + 2, len - 2), type: :bytes, offset: pos + 2, length: len - 2)
61
71
  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)
72
+ layer.add(:tsval, 'tcp.options.timestamp.tsval', cursor.u32(pos + 2), offset: pos + 2, length: 4)
73
+ layer.add(:tsecr, 'tcp.options.timestamp.tsecr', cursor.u32(pos + 6), offset: pos + 6, length: 4)
74
+ when 34 then layer.add(:tfo, 'tcp.options.tfo.cookie', cursor.bytes(pos + 2, len - 2), type: :bytes, offset: pos + 2, length: len - 2)
67
75
  end
68
76
  pos += len
69
77
  end
@@ -18,7 +18,7 @@ module Redhound
18
18
  return layer.diagnose(:error, :malformed, 'unsupported GRE version or routing') unless (flags & 0x4007).zero?
19
19
  len += 4 if flags & 0x8000 != 0
20
20
  if flags & 0x2000 != 0
21
- layer.add(:key, 'gre.key', ctx.cursor.u32(len))
21
+ layer.add(:key, 'gre.key', ctx.cursor.u32(len), offset: len, length: 4)
22
22
  len += 4
23
23
  end
24
24
  len += 4 if flags & 0x1000 != 0
@@ -38,7 +38,7 @@ module Redhound
38
38
  end
39
39
  # @rbs (Context ctx, Layer layer) -> void
40
40
  def dissect(ctx, layer)
41
- layer.add(:vni, 'vxlan.vni', layer[:vni_word] >> 8)
41
+ layer.add(:vni, 'vxlan.vni', layer[:vni_word] >> 8, offset: 4, length: 3)
42
42
  layer.diagnose(:error, :malformed, 'VXLAN I flag is unset') if layer[:flags] & 0x08000000 == 0
43
43
  end
44
44
  # @rbs (Context ctx, Layer layer) -> untyped
@@ -58,7 +58,7 @@ module Redhound
58
58
  end
59
59
 
60
60
  # @rbs () -> Integer
61
- def bytesize = @buffers.sum(&:bytesize) + @origins.sum { |origins| origins.size * 64 }
61
+ def bytesize = @buffers.sum { |buffer| buffer.empty? ? 0 : buffer.bytesize * 2 + 40 } + @origins.sum { |origins| origins.size * 128 }
62
62
 
63
63
  # @rbs (Integer direction, Integer length) -> Array[Integer]
64
64
  def consume_origins(direction, length)
@@ -108,7 +108,7 @@ module Redhound
108
108
  response = header.start_with?('HTTP/')
109
109
  status = response ? header.split(' ', 3)[1].to_i : 0
110
110
  return head_end if response && (head_response || status.between?(100, 199) || [204, 304].include?(status))
111
- lengths = header.scan(/\r\ncontent-length:[ \t]*([^\r\n]*)/in).flatten.flat_map { |value| value.split(',').map(&:strip) }
111
+ lengths = header.scan(/\r\ncontent-length:[ \t]*([^\r\n]*)/in).flatten.flat_map { |value| value.empty? ? [''] : value.split(',', -1).map(&:strip) }
112
112
  transfer = header.scan(/\r\ntransfer-encoding:[ \t]*([^\r\n]*)/in).flatten.join(',').downcase.split(',').map(&:strip)
113
113
  if (!lengths.empty? && !transfer.empty?) || lengths.uniq.size > 1 || lengths.any? { |value| !value.match?(/\A[0-9]+\z/n) }
114
114
  return head_end # The packet parser reports conflicting or invalid framing.
@@ -3,5 +3,5 @@
3
3
 
4
4
  module Redhound
5
5
  # Published gem version.
6
- VERSION = '2.0.0.rc1'
6
+ VERSION = '2.0.0.rc2'
7
7
  end
@@ -27,6 +27,10 @@ module Redhound
27
27
 
28
28
  attr_reader http_methods: untyped
29
29
 
30
+ attr_reader probes: untyped
31
+
32
+ attr_reader probe_disabled: untyped
33
+
30
34
  attr_accessor last_ns: untyped
31
35
 
32
36
  attr_accessor closed_ns: untyped
@@ -73,8 +77,8 @@ module Redhound
73
77
  # @rbs (Integer now_ns) -> void
74
78
  def expire: (Integer now_ns) -> void
75
79
 
76
- # @rbs () -> Flow?
77
- def evict: () -> Flow?
80
+ # @rbs (?Symbol? type) -> Flow?
81
+ def evict: (?Symbol? type) -> Flow?
78
82
 
79
83
  # @rbs (Flow flow) -> void
80
84
  def discard: (Flow flow) -> void
@@ -33,6 +33,18 @@ module Redhound
33
33
  # @rbs () -> Integer
34
34
  def bytesize: () -> Integer
35
35
 
36
+ # @rbs (untyped state) -> Integer
37
+ def state_bytesize: (untyped state) -> Integer
38
+
39
+ # @rbs (untyped key) -> untyped
40
+ def remove: (untyped key) -> untyped
41
+
42
+ # @rbs () -> untyped
43
+ def remove_oldest: () -> untyped
44
+
45
+ # @rbs () -> Integer
46
+ def finish: () -> Integer
47
+
36
48
  # @rbs () -> Integer
37
49
  def size: () -> Integer
38
50
  end
@@ -38,6 +38,9 @@ module Redhound
38
38
 
39
39
  # @rbs () -> Hash[Symbol, untyped]
40
40
  def to_h: () -> Hash[Symbol, untyped]
41
+
42
+ # @rbs (Integer interval) -> Hash[Symbol, Integer]
43
+ def interval_row: (Integer interval) -> Hash[Symbol, Integer]
41
44
  end
42
45
 
43
46
  # @api private
@@ -14,6 +14,18 @@ module Redhound
14
14
  # @rbs (Flow flow, Integer direction, TcpStream stream, untyped application, Packet? packet) -> Array[Layer]
15
15
  def deliver: (Flow flow, Integer direction, TcpStream stream, untyped application, Packet? packet) -> Array[Layer]
16
16
 
17
+ # @rbs (Flow flow, Integer direction, String data, Integer origin, Packet? packet) -> Array[Layer]
18
+ def application_data: (Flow flow, Integer direction, String data, Integer origin, Packet? packet) -> Array[Layer]
19
+
20
+ # @rbs (untyped klass, Flow flow, Integer direction, Layer layer) -> untyped
21
+ def create_application: (untyped klass, Flow flow, Integer direction, Layer layer) -> untyped
22
+
23
+ # @rbs (Flow flow, Integer direction, untyped application, Symbol method, *untyped arguments) -> Array[Layer]
24
+ def invoke: (Flow flow, Integer direction, untyped application, Symbol method, *untyped arguments) -> Array[Layer]
25
+
26
+ # @rbs (Flow flow, Integer direction, Symbol protocol, StandardError error) -> Layer
27
+ def application_error: (Flow flow, Integer direction, Symbol protocol, StandardError error) -> Layer
28
+
17
29
  # @rbs (Packet packet, Array[Layer] layers) -> void
18
30
  def attach: (Packet packet, Array[Layer] layers) -> void
19
31
 
@@ -0,0 +1,15 @@
1
+ # Generated from lib/redhound/capture/bsd/ifreq.rb with RBS::Inline
2
+
3
+ module Redhound
4
+ module Capture
5
+ module Bsd
6
+ # macOS interface metadata can be queried without opening a BPF device.
7
+ module Ifreq
8
+ SIOCGIFMTU: ::Integer
9
+
10
+ # @rbs (String name, index: Integer, flags: Integer, link_address: String?) -> Interface
11
+ def self.interface: (String name, index: Integer, flags: Integer, link_address: String?) -> Interface
12
+ end
13
+ end
14
+ end
15
+ end
@@ -5,6 +5,27 @@ module Redhound
5
5
  module Capture
6
6
  # @api private
7
7
  class FileSource < Source
8
+ class ReadStopped < StandardError
9
+ end
10
+
11
+ # @api private
12
+ class FileInput
13
+ # @rbs (::File io) -> void
14
+ def initialize: (::File io) -> void
15
+
16
+ # @rbs (Integer length) -> String?
17
+ def read: (Integer length) -> String?
18
+ end
19
+
20
+ # Nonregular inputs need bounded polling so Source#stop can interrupt an idle pipe.
21
+ class StreamInput
22
+ # @rbs (IO io, Source source) -> void
23
+ def initialize: (IO io, Source source) -> void
24
+
25
+ # @rbs (Integer length) -> String?
26
+ def read: (Integer length) -> String?
27
+ end
28
+
8
29
  attr_reader reader: untyped
9
30
 
10
31
  # @rbs (untyped input, ?filter: String?) -> void
@@ -18,6 +39,9 @@ module Redhound
18
39
  # @rbs (?timeout: Numeric?) -> Packet?
19
40
  def next_packet: (?timeout: Numeric?) -> Packet?
20
41
 
42
+ # @rbs (Filter::Program? program) -> void
43
+ def attach_filter: (Filter::Program? program) -> void
44
+
21
45
  # @rbs () -> Integer
22
46
  def linktype: () -> Integer
23
47
 
@@ -27,8 +51,17 @@ module Redhound
27
51
  # @rbs () -> Stats
28
52
  def stats: () -> Stats
29
53
 
54
+ # @rbs () -> void
55
+ def stop: () -> void
56
+
30
57
  # @rbs () -> void
31
58
  def close: () -> void
59
+
60
+ private
61
+
62
+ # A single bounded worker preserves partial records across caller timeouts.
63
+ # @rbs (Numeric? timeout) -> (Packet | nil | :wait_readable)
64
+ def read_packet: (Numeric? timeout) -> (Packet | nil | :wait_readable)
32
65
  end
33
66
  end
34
67
  end
@@ -28,6 +28,13 @@ module Redhound
28
28
  # @rbs () -> void
29
29
  def close: () -> void
30
30
 
31
+ # Positive filter returns select packets; the source applies its configured snaplen.
32
+ # @rbs (Array[[Integer, Integer, Integer, Integer]] instructions) -> Array[[Integer, Integer, Integer, Integer]]
33
+ def receive_filter: (Array[[ Integer, Integer, Integer, Integer ]] instructions) -> Array[[ Integer, Integer, Integer, Integer ]]
34
+
35
+ # @rbs () -> void
36
+ def warn_vlan_offload: () -> void
37
+
31
38
  # @rbs () -> void
32
39
  def close_resources: () -> void
33
40
 
@@ -25,6 +25,10 @@ module Redhound
25
25
  # Append a typed fixed-width field with optional format/enum metadata.
26
26
  def field: (Symbol type, Symbol key, String name, **untyped opts) -> void
27
27
 
28
+ # @rbs (Symbol key, String name, **untyped opts) -> void
29
+ # Append a signed 8-bit field.
30
+ def int8: (Symbol key, String name, **untyped opts) -> void
31
+
28
32
  # @rbs (Symbol key, String name, **untyped opts) -> void
29
33
  # Append an unsigned 8-bit field.
30
34
  def uint8: (Symbol key, String name, **untyped opts) -> void
@@ -7,6 +7,12 @@ module Redhound
7
7
  module Format
8
8
  MAX_RECORD: untyped
9
9
 
10
+ MAX_INTERFACES: ::Integer
11
+
12
+ MAX_INTERFACE_METADATA: untyped
13
+
14
+ MAX_OPTIONS: ::Integer
15
+
10
16
  PCAP_MAGICS: untyped
11
17
 
12
18
  PCAPNG_MAGIC: untyped
@@ -44,8 +44,8 @@ module Redhound
44
44
  # @rbs (Integer index) -> Capture::Interface
45
45
  def interface_at: (Integer index) -> Capture::Interface
46
46
 
47
- # @rbs (String body, Integer offset) -> Hash[Integer, Array[String]]
48
- def options: (String body, Integer offset) -> Hash[Integer, Array[String]]
47
+ # @rbs (String body, Integer offset, Array[Integer] allowed) -> Hash[Integer, Array[String]]
48
+ def options: (String body, Integer offset, Array[Integer] allowed) -> Hash[Integer, Array[String]]
49
49
 
50
50
  # @rbs (String body, Integer size) -> void
51
51
  def require_size: (String body, Integer size) -> void
@@ -7,6 +7,8 @@ module Redhound
7
7
  module BPF
8
8
  # @api private
9
9
  class VM
10
+ LOAD_FORMATS: untyped
11
+
10
12
  # @rbs (Array[[Integer, Integer, Integer, Integer]] instructions) -> void
11
13
  def initialize: (Array[[ Integer, Integer, Integer, Integer ]] instructions) -> void
12
14
 
@@ -73,5 +73,10 @@ module Redhound
73
73
  # @rbs () -> Hash[Symbol, untyped]
74
74
  # Return a JSON-compatible packet matching docs/json-schema.json.
75
75
  def to_h: () -> Hash[Symbol, untyped]
76
+
77
+ private
78
+
79
+ # @rbs (String? text) -> String?
80
+ def json_text: (String? text) -> String?
76
81
  end
77
82
  end
@@ -5,6 +5,8 @@ module Redhound
5
5
  module Protocols
6
6
  # @api private
7
7
  class Data < Dissector
8
+ LENGTH_FIELD: untyped
9
+
8
10
  # @rbs (Context ctx, Layer layer) -> void
9
11
  def dissect: (Context ctx, Layer layer) -> void
10
12
 
@@ -15,11 +15,14 @@ module Redhound
15
15
  # @rbs (Cursor cursor, Layer layer, Integer pos, Integer ending, untyped options) -> void
16
16
  def read_options: (Cursor cursor, Layer layer, Integer pos, Integer ending, untyped options) -> void
17
17
 
18
- # @rbs (Layer layer, Integer code, String value, Integer offset) -> void
19
- def decode_option: (Layer layer, Integer code, String value, Integer offset) -> void
18
+ # @rbs (Layer layer, Integer code, String value, untyped entries) -> void
19
+ def decode_option: (Layer layer, Integer code, String value, untyped entries) -> void
20
20
 
21
- # @rbs (Layer layer, String name, untyped value, Symbol type, Integer offset, Integer length) -> void
22
- def put: (Layer layer, String name, untyped value, Symbol type, Integer offset, Integer length) -> void
21
+ # @rbs (untyped entries, Integer logical) -> Integer
22
+ def wire_offset: (untyped entries, Integer logical) -> Integer
23
+
24
+ # @rbs (Layer layer, String name, untyped value, Symbol type, Integer offset, Integer length, ?untyped entries) -> void
25
+ def put: (Layer layer, String name, untyped value, Symbol type, Integer offset, Integer length, ?untyped entries) -> void
23
26
 
24
27
  # @rbs (Layer layer) -> String
25
28
  def summary: (Layer layer) -> String
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redhound
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.rc1
4
+ version: 2.0.0.rc2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yudai Takada
@@ -41,6 +41,7 @@ files:
41
41
  - lib/redhound/capture.rb
42
42
  - lib/redhound/capture/bsd/bpf_device.rb
43
43
  - lib/redhound/capture/bsd/constants.rb
44
+ - lib/redhound/capture/bsd/ifreq.rb
44
45
  - lib/redhound/capture/file_source.rb
45
46
  - lib/redhound/capture/interface.rb
46
47
  - lib/redhound/capture/linktype.rb
@@ -124,6 +125,7 @@ files:
124
125
  - sig/generated/redhound/capture.rbs
125
126
  - sig/generated/redhound/capture/bsd/bpf_device.rbs
126
127
  - sig/generated/redhound/capture/bsd/constants.rbs
128
+ - sig/generated/redhound/capture/bsd/ifreq.rbs
127
129
  - sig/generated/redhound/capture/file_source.rbs
128
130
  - sig/generated/redhound/capture/interface.rbs
129
131
  - sig/generated/redhound/capture/linktype.rbs