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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -0
- data/README.md +1 -1
- data/docs/API.md +29 -1
- data/docs/MIGRATION.md +1 -1
- data/docs/PROTOCOLS.md +10 -1
- data/docs/USAGE.md +5 -1
- data/docs/VALIDATION.md +119 -3
- data/docs/json-schema.json +2 -2
- data/lib/redhound/analysis/flow_key.rb +7 -1
- data/lib/redhound/analysis/flow_table.rb +12 -5
- data/lib/redhound/analysis/ip_reassembler.rb +44 -11
- data/lib/redhound/analysis/stats.rb +14 -2
- data/lib/redhound/analysis/tcp_reassembler.rb +68 -16
- data/lib/redhound/analysis/tcp_stream.rb +7 -2
- data/lib/redhound/analysis.rb +30 -2
- data/lib/redhound/capture/bsd/bpf_device.rb +3 -0
- data/lib/redhound/capture/bsd/ifreq.rb +41 -0
- data/lib/redhound/capture/file_source.rb +135 -4
- data/lib/redhound/capture/interface.rb +5 -1
- data/lib/redhound/capture/linux/packet_socket.rb +53 -9
- data/lib/redhound/capture/linux/tpacket_v3.rb +5 -0
- data/lib/redhound/capture.rb +3 -2
- data/lib/redhound/cli/command.rb +1 -1
- data/lib/redhound/dissector.rb +18 -4
- data/lib/redhound/engine.rb +3 -0
- data/lib/redhound/field.rb +1 -1
- data/lib/redhound/file/format.rb +8 -1
- data/lib/redhound/file/pcap_writer.rb +10 -3
- data/lib/redhound/file/pcapng_reader.rb +19 -7
- data/lib/redhound/file/pcapng_writer.rb +22 -14
- data/lib/redhound/filter/bpf/vm.rb +3 -2
- data/lib/redhound/output/follow.rb +6 -1
- data/lib/redhound/output/summary.rb +29 -4
- data/lib/redhound/output/timestamp.rb +2 -2
- data/lib/redhound/output/tree.rb +2 -1
- data/lib/redhound/packet.rb +18 -4
- data/lib/redhound/protocols/data.rb +3 -1
- data/lib/redhound/protocols/dhcp.rb +29 -13
- data/lib/redhound/protocols/http.rb +5 -3
- data/lib/redhound/protocols/icmp.rb +55 -41
- data/lib/redhound/protocols/ipv6.rb +5 -2
- data/lib/redhound/protocols/ntp.rb +2 -4
- data/lib/redhound/protocols/tcp.rb +17 -9
- data/lib/redhound/protocols/tunnel.rb +2 -2
- data/lib/redhound/stream_dissector.rb +2 -2
- data/lib/redhound/version.rb +1 -1
- data/sig/generated/redhound/analysis/flow_table.rbs +6 -2
- data/sig/generated/redhound/analysis/ip_reassembler.rbs +12 -0
- data/sig/generated/redhound/analysis/stats.rbs +3 -0
- data/sig/generated/redhound/analysis/tcp_reassembler.rbs +12 -0
- data/sig/generated/redhound/capture/bsd/ifreq.rbs +15 -0
- data/sig/generated/redhound/capture/file_source.rbs +33 -0
- data/sig/generated/redhound/capture/linux/packet_socket.rbs +7 -0
- data/sig/generated/redhound/dissector.rbs +4 -0
- data/sig/generated/redhound/file/format.rbs +6 -0
- data/sig/generated/redhound/file/pcapng_reader.rbs +2 -2
- data/sig/generated/redhound/filter/bpf/vm.rbs +2 -0
- data/sig/generated/redhound/packet.rbs +5 -0
- data/sig/generated/redhound/protocols/data.rbs +2 -0
- data/sig/generated/redhound/protocols/dhcp.rbs +7 -4
- 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)
|
|
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
|
-
|
|
21
|
-
|
|
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)
|
|
58
|
-
when 3 then layer.add(:wscale, 'tcp.options.wscale.shift', cursor.u8(pos + 2)
|
|
59
|
-
when 4 then layer.add(:sack_permitted, 'tcp.options.sack_perm', true, type: :boolean
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
|
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.
|
data/lib/redhound/version.rb
CHANGED
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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,
|
|
19
|
-
def decode_option: (Layer layer, Integer code, String value,
|
|
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 (
|
|
22
|
-
def
|
|
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.
|
|
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
|