redhound 1.0.1 → 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 +51 -0
- data/README.md +97 -31
- data/docs/API.md +104 -0
- data/docs/FILTERS.md +37 -0
- data/docs/MIGRATION.md +23 -0
- data/docs/PROTOCOLS.md +54 -0
- data/docs/USAGE.md +86 -0
- data/docs/VALIDATION.md +175 -0
- data/docs/json-schema.json +64 -0
- data/exe/redhound +2 -1
- data/lib/redhound/analysis/flow_key.rb +59 -0
- data/lib/redhound/analysis/flow_table.rb +124 -0
- data/lib/redhound/analysis/ip_reassembler.rb +209 -0
- data/lib/redhound/analysis/stats.rb +150 -0
- data/lib/redhound/analysis/tcp_analysis.rb +43 -0
- data/lib/redhound/analysis/tcp_reassembler.rb +176 -0
- data/lib/redhound/analysis/tcp_stream.rb +156 -0
- data/lib/redhound/analysis.rb +157 -0
- data/lib/redhound/capture/bsd/bpf_device.rb +155 -0
- data/lib/redhound/capture/bsd/constants.rb +29 -0
- data/lib/redhound/capture/bsd/ifreq.rb +41 -0
- data/lib/redhound/capture/file_source.rb +214 -0
- data/lib/redhound/capture/interface.rb +70 -0
- data/lib/redhound/capture/linktype.rb +30 -0
- data/lib/redhound/capture/linux/constants.rb +37 -0
- data/lib/redhound/capture/linux/cooked.rb +18 -0
- data/lib/redhound/capture/linux/ifreq.rb +42 -0
- data/lib/redhound/capture/linux/packet_socket.rb +224 -0
- data/lib/redhound/capture/linux/sockaddr_ll.rb +33 -0
- data/lib/redhound/capture/linux/tpacket_v3.rb +137 -0
- data/lib/redhound/capture/source.rb +79 -0
- data/lib/redhound/capture/stats.rb +22 -0
- data/lib/redhound/capture.rb +70 -0
- data/lib/redhound/cli/command.rb +42 -0
- data/lib/redhound/cli/options.rb +133 -0
- data/lib/redhound/cli/privileges.rb +29 -0
- data/lib/redhound/cli/runner.rb +129 -0
- data/lib/redhound/context.rb +20 -0
- data/lib/redhound/cursor.rb +69 -0
- data/lib/redhound/diagnostic.rb +7 -0
- data/lib/redhound/dissector.rb +152 -0
- data/lib/redhound/engine.rb +69 -0
- data/lib/redhound/errors.rb +38 -0
- data/lib/redhound/field.rb +62 -0
- data/lib/redhound/file/format.rb +63 -0
- data/lib/redhound/file/pcap_reader.rb +43 -0
- data/lib/redhound/file/pcap_writer.rb +79 -0
- data/lib/redhound/file/pcapng_reader.rb +221 -0
- data/lib/redhound/file/pcapng_writer.rb +151 -0
- data/lib/redhound/file/rotating_writer.rb +98 -0
- data/lib/redhound/filter/analyzer.rb +197 -0
- data/lib/redhound/filter/bpf/assembler.rb +121 -0
- data/lib/redhound/filter/bpf/disassembler.rb +58 -0
- data/lib/redhound/filter/bpf/validator.rb +77 -0
- data/lib/redhound/filter/bpf/vm.rb +109 -0
- data/lib/redhound/filter/codegen.rb +413 -0
- data/lib/redhound/filter/lexer.rb +46 -0
- data/lib/redhound/filter/parser.rb +206 -0
- data/lib/redhound/filter/program.rb +55 -0
- data/lib/redhound/filter.rb +30 -0
- data/lib/redhound/layer.rb +75 -0
- data/lib/redhound/output/fields.rb +20 -0
- data/lib/redhound/output/follow.rb +64 -0
- data/lib/redhound/output/hexdump.rb +29 -0
- data/lib/redhound/output/json.rb +32 -0
- data/lib/redhound/output/summary.rb +84 -0
- data/lib/redhound/output/timestamp.rb +34 -0
- data/lib/redhound/output/tree.rb +25 -0
- data/lib/redhound/packet.rb +77 -0
- data/lib/redhound/protocols/arp.rb +39 -0
- data/lib/redhound/protocols/checksum.rb +38 -0
- data/lib/redhound/protocols/data.rb +23 -0
- data/lib/redhound/protocols/dhcp.rb +140 -0
- data/lib/redhound/protocols/dns.rb +268 -0
- data/lib/redhound/protocols/ethernet.rb +24 -0
- data/lib/redhound/protocols/http.rb +185 -0
- data/lib/redhound/protocols/icmp.rb +205 -0
- data/lib/redhound/protocols/ipv4.rb +56 -0
- data/lib/redhound/protocols/ipv6.rb +68 -0
- data/lib/redhound/protocols/link.rb +70 -0
- data/lib/redhound/protocols/llc.rb +37 -0
- data/lib/redhound/protocols/ntp.rb +45 -0
- data/lib/redhound/protocols/tcp.rb +88 -0
- data/lib/redhound/protocols/tls.rb +218 -0
- data/lib/redhound/protocols/tunnel.rb +48 -0
- data/lib/redhound/protocols/udp.rb +32 -0
- data/lib/redhound/protocols/vlan.rb +23 -0
- data/lib/redhound/reader.rb +8 -0
- data/lib/redhound/registry.rb +64 -0
- data/lib/redhound/stream_dissector.rb +168 -0
- data/lib/redhound/util/seq.rb +21 -0
- data/lib/redhound/version.rb +2 -1
- data/lib/redhound/writer.rb +25 -43
- data/lib/redhound.rb +81 -9
- data/sig/generated/redhound/analysis/flow_key.rbs +15 -0
- data/sig/generated/redhound/analysis/flow_table.rbs +102 -0
- data/sig/generated/redhound/analysis/ip_reassembler.rbs +52 -0
- data/sig/generated/redhound/analysis/stats.rbs +80 -0
- data/sig/generated/redhound/analysis/tcp_analysis.rbs +15 -0
- data/sig/generated/redhound/analysis/tcp_reassembler.rbs +39 -0
- data/sig/generated/redhound/analysis/tcp_stream.rbs +60 -0
- data/sig/generated/redhound/analysis.rbs +38 -0
- data/sig/generated/redhound/capture/bsd/bpf_device.rbs +39 -0
- data/sig/generated/redhound/capture/bsd/constants.rbs +40 -0
- data/sig/generated/redhound/capture/bsd/ifreq.rbs +15 -0
- data/sig/generated/redhound/capture/file_source.rbs +67 -0
- data/sig/generated/redhound/capture/interface.rbs +53 -0
- data/sig/generated/redhound/capture/linktype.rbs +20 -0
- data/sig/generated/redhound/capture/linux/constants.rbs +58 -0
- data/sig/generated/redhound/capture/linux/cooked.rbs +15 -0
- data/sig/generated/redhound/capture/linux/ifreq.rbs +18 -0
- data/sig/generated/redhound/capture/linux/packet_socket.rbs +64 -0
- data/sig/generated/redhound/capture/linux/sockaddr_ll.rbs +31 -0
- data/sig/generated/redhound/capture/linux/tpacket_v3.rbs +35 -0
- data/sig/generated/redhound/capture/source.rbs +44 -0
- data/sig/generated/redhound/capture/stats.rbs +25 -0
- data/sig/generated/redhound/capture.rbs +14 -0
- data/sig/generated/redhound/cli/command.rbs +12 -0
- data/sig/generated/redhound/cli/options.rbs +25 -0
- data/sig/generated/redhound/cli/privileges.rbs +12 -0
- data/sig/generated/redhound/cli/runner.rbs +27 -0
- data/sig/generated/redhound/context.rbs +27 -0
- data/sig/generated/redhound/cursor.rbs +59 -0
- data/sig/generated/redhound/diagnostic.rbs +21 -0
- data/sig/generated/redhound/dissector.rbs +117 -0
- data/sig/generated/redhound/engine.rbs +23 -0
- data/sig/generated/redhound/errors.rbs +56 -0
- data/sig/generated/redhound/field.rbs +74 -0
- data/sig/generated/redhound/file/format.rbs +39 -0
- data/sig/generated/redhound/file/pcap_reader.rbs +19 -0
- data/sig/generated/redhound/file/pcap_writer.rbs +38 -0
- data/sig/generated/redhound/file/pcapng_reader.rbs +54 -0
- data/sig/generated/redhound/file/pcapng_writer.rbs +50 -0
- data/sig/generated/redhound/file/rotating_writer.rbs +42 -0
- data/sig/generated/redhound/filter/analyzer.rbs +42 -0
- data/sig/generated/redhound/filter/bpf/assembler.rbs +39 -0
- data/sig/generated/redhound/filter/bpf/disassembler.rbs +18 -0
- data/sig/generated/redhound/filter/bpf/validator.rbs +30 -0
- data/sig/generated/redhound/filter/bpf/vm.rbs +28 -0
- data/sig/generated/redhound/filter/codegen.rbs +101 -0
- data/sig/generated/redhound/filter/lexer.rbs +15 -0
- data/sig/generated/redhound/filter/parser.rbs +65 -0
- data/sig/generated/redhound/filter/program.rbs +48 -0
- data/sig/generated/redhound/filter.rbs +10 -0
- data/sig/generated/redhound/layer.rbs +81 -0
- data/sig/generated/redhound/output/fields.rbs +15 -0
- data/sig/generated/redhound/output/follow.rbs +21 -0
- data/sig/generated/redhound/output/hexdump.rbs +15 -0
- data/sig/generated/redhound/output/json.rbs +18 -0
- data/sig/generated/redhound/output/summary.rbs +21 -0
- data/sig/generated/redhound/output/timestamp.rbs +21 -0
- data/sig/generated/redhound/output/tree.rbs +15 -0
- data/sig/generated/redhound/packet.rbs +82 -0
- data/sig/generated/redhound/protocols/arp.rbs +15 -0
- data/sig/generated/redhound/protocols/checksum.rbs +15 -0
- data/sig/generated/redhound/protocols/data.rbs +17 -0
- data/sig/generated/redhound/protocols/dhcp.rbs +31 -0
- data/sig/generated/redhound/protocols/dns.rbs +42 -0
- data/sig/generated/redhound/protocols/ethernet.rbs +15 -0
- data/sig/generated/redhound/protocols/http.rbs +36 -0
- data/sig/generated/redhound/protocols/icmp.rbs +41 -0
- data/sig/generated/redhound/protocols/ipv4.rbs +15 -0
- data/sig/generated/redhound/protocols/ipv6.rbs +24 -0
- data/sig/generated/redhound/protocols/link.rbs +33 -0
- data/sig/generated/redhound/protocols/llc.rbs +15 -0
- data/sig/generated/redhound/protocols/ntp.rbs +17 -0
- data/sig/generated/redhound/protocols/tcp.rbs +25 -0
- data/sig/generated/redhound/protocols/tls.rbs +51 -0
- data/sig/generated/redhound/protocols/tunnel.rbs +24 -0
- data/sig/generated/redhound/protocols/udp.rbs +18 -0
- data/sig/generated/redhound/protocols/vlan.rbs +12 -0
- data/sig/generated/redhound/reader.rbs +7 -0
- data/sig/generated/redhound/registry.rbs +45 -0
- data/sig/generated/redhound/stream_dissector.rbs +44 -0
- data/sig/generated/redhound/util/seq.rbs +20 -0
- data/sig/generated/redhound/version.rbs +1 -0
- data/sig/generated/redhound/writer.rbs +6 -19
- data/sig/generated/redhound.rbs +18 -0
- data/sig/shims/io.rbs +7 -0
- data/sig/shims/process.rbs +3 -0
- metadata +177 -55
- data/CODE_OF_CONDUCT.md +0 -132
- data/Dockerfile +0 -7
- data/Rakefile +0 -15
- data/Steepfile +0 -4
- data/docker-compose.yml +0 -9
- data/lib/redhound/analyzer.rb +0 -34
- data/lib/redhound/builder/packet_mreq.rb +0 -56
- data/lib/redhound/builder/socket.rb +0 -35
- data/lib/redhound/builder.rb +0 -4
- data/lib/redhound/command.rb +0 -67
- data/lib/redhound/l2/ether.rb +0 -68
- data/lib/redhound/l2/protocol.rb +0 -33
- data/lib/redhound/l2.rb +0 -4
- data/lib/redhound/l3/arp.rb +0 -114
- data/lib/redhound/l3/base.rb +0 -49
- data/lib/redhound/l3/ipv4.rb +0 -95
- data/lib/redhound/l3/ipv6.rb +0 -69
- data/lib/redhound/l3/protocol.rb +0 -173
- data/lib/redhound/l3/resolver.rb +0 -30
- data/lib/redhound/l3.rb +0 -9
- data/lib/redhound/l4/base.rb +0 -31
- data/lib/redhound/l4/icmp.rb +0 -71
- data/lib/redhound/l4/resolver.rb +0 -28
- data/lib/redhound/l4/udp.rb +0 -63
- data/lib/redhound/l4.rb +0 -6
- data/lib/redhound/receiver.rb +0 -45
- data/lib/redhound/resolver.rb +0 -22
- data/lib/redhound/source/socket.rb +0 -18
- data/lib/redhound/source.rb +0 -3
- data/rbs_collection.lock.yaml +0 -20
- data/rbs_collection.yaml +0 -17
- data/sig/generated/redhound/analyzer.rbs +0 -14
- data/sig/generated/redhound/builder/packet_mreq.rbs +0 -39
- data/sig/generated/redhound/builder/socket.rbs +0 -24
- data/sig/generated/redhound/command.rbs +0 -19
- data/sig/generated/redhound/l2/ether.rbs +0 -41
- data/sig/generated/redhound/l2/protocol.rbs +0 -15
- data/sig/generated/redhound/l3/arp.rbs +0 -57
- data/sig/generated/redhound/l3/base.rbs +0 -28
- data/sig/generated/redhound/l3/ipv4.rbs +0 -53
- data/sig/generated/redhound/l3/ipv6.rbs +0 -38
- data/sig/generated/redhound/l3/protocol.rbs +0 -16
- data/sig/generated/redhound/l3/resolver.rbs +0 -16
- data/sig/generated/redhound/l4/base.rbs +0 -19
- data/sig/generated/redhound/l4/icmp.rbs +0 -33
- data/sig/generated/redhound/l4/resolver.rbs +0 -16
- data/sig/generated/redhound/l4/udp.rbs +0 -36
- data/sig/generated/redhound/receiver.rbs +0 -19
- data/sig/generated/redhound/resolver.rbs +0 -11
- data/sig/generated/redhound/source/socket.rbs +0 -13
|
@@ -0,0 +1,205 @@
|
|
|
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
|
+
end
|
|
29
|
+
# @rbs (Context ctx, Layer layer) -> void
|
|
30
|
+
def dissect(ctx, layer)
|
|
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
|
|
42
|
+
Checksum.verify(ctx, layer, ctx.cursor.bytes(0, ctx.cursor.remaining))
|
|
43
|
+
end
|
|
44
|
+
# @rbs (Context ctx, Layer layer) -> untyped
|
|
45
|
+
def next_dissector(ctx, layer)
|
|
46
|
+
return nil unless [3, 4, 5, 11, 12].include?(layer[:type])
|
|
47
|
+
ctx.embedded = true
|
|
48
|
+
Ipv4
|
|
49
|
+
end
|
|
50
|
+
# @rbs (Layer layer) -> String
|
|
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
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @api private
|
|
60
|
+
class Icmpv6 < Dissector
|
|
61
|
+
TYPES = { 1 => 'Destination Unreachable', 2 => 'Packet Too Big', 3 => 'Time Exceeded', 4 => 'Parameter Problem',
|
|
62
|
+
128 => 'Echo Request', 129 => 'Echo Reply', 133 => 'Router Solicitation', 134 => 'Router Advertisement',
|
|
63
|
+
135 => 'Neighbor Solicitation', 136 => 'Neighbor Advertisement', 137 => 'Redirect' }.freeze
|
|
64
|
+
CODES = {
|
|
65
|
+
1 => { 0 => 'No Route', 1 => 'Administratively Prohibited', 2 => 'Beyond Source Scope', 3 => 'Address Unreachable',
|
|
66
|
+
4 => 'Port Unreachable', 5 => 'Source Policy Failure', 6 => 'Reject Route', 7 => 'Source Routing Error' },
|
|
67
|
+
3 => { 0 => 'Hop Limit Exceeded', 1 => 'Reassembly Timeout' },
|
|
68
|
+
4 => { 0 => 'Invalid Header Field', 1 => 'Unknown Next Header', 2 => 'Unknown Option' }
|
|
69
|
+
}.freeze
|
|
70
|
+
protocol :icmpv6, name: 'ICMPv6 / Neighbor Discovery', short: 'ICMP6'
|
|
71
|
+
dissects_on 'ip.proto', 58
|
|
72
|
+
header do
|
|
73
|
+
uint8 :type, 'icmpv6.type', enum: TYPES
|
|
74
|
+
uint8 :code, 'icmpv6.code'
|
|
75
|
+
uint16 :checksum, 'icmpv6.checksum'
|
|
76
|
+
end
|
|
77
|
+
# @rbs (Context ctx, Layer layer) -> void
|
|
78
|
+
def dissect(ctx, layer)
|
|
79
|
+
cursor, type = ctx.cursor, layer[:type]
|
|
80
|
+
layer.definitions[1] = layer.definitions[1].with(enum: CODES[type])
|
|
81
|
+
length = case type
|
|
82
|
+
when 133 then 8
|
|
83
|
+
when 134 then 16
|
|
84
|
+
when 135, 136 then 24
|
|
85
|
+
when 137 then 40
|
|
86
|
+
else 8
|
|
87
|
+
end
|
|
88
|
+
cursor.check(0, length)
|
|
89
|
+
if [128, 129].include?(type)
|
|
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)
|
|
92
|
+
elsif [135, 136, 137].include?(type)
|
|
93
|
+
family = { 135 => 'ns', 136 => 'na', 137 => 'rd' }.fetch(type)
|
|
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
|
|
96
|
+
if type == 136
|
|
97
|
+
flags = cursor.u32(4)
|
|
98
|
+
layer.add(:flags, 'icmpv6.nd.na.flag', flags, format: :hex, offset: 4, length: 4)
|
|
99
|
+
{ r: 0x80000000, s: 0x40000000, o: 0x20000000 }.each do |name, mask|
|
|
100
|
+
layer.add(name, "icmpv6.nd.na.flag.#{name}", (flags & mask).positive?, type: :boolean, offset: 4, length: 4)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
if type == 134
|
|
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)
|
|
107
|
+
flags = cursor.u8(5)
|
|
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)
|
|
113
|
+
end
|
|
114
|
+
if type.between?(133, 137)
|
|
115
|
+
pos = length
|
|
116
|
+
while pos < cursor.remaining
|
|
117
|
+
opt = cursor.u8(pos)
|
|
118
|
+
len = cursor.u8(pos + 1) * 8
|
|
119
|
+
return layer.diagnose(:error, :bad_length, 'NDP option has zero length') if len.zero?
|
|
120
|
+
cursor.check(pos, len)
|
|
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)
|
|
123
|
+
if [1, 2].include?(opt) && len >= 8
|
|
124
|
+
layer.add(:"link_address_#{pos}", 'icmpv6.opt.linkaddr', cursor.bytes(pos + 2, 6), type: :mac, offset: pos + 2, length: 6)
|
|
125
|
+
elsif opt == 3 && len == 32
|
|
126
|
+
layer.add(:"prefix_length_#{pos}", 'icmpv6.opt.prefix.length', cursor.u8(pos + 2), offset: pos + 2, length: 1)
|
|
127
|
+
flags = cursor.u8(pos + 3)
|
|
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)
|
|
134
|
+
elsif opt == 4 && len >= 8
|
|
135
|
+
layer.add(:"redirected_#{pos}", 'icmpv6.opt.redirected_packet', cursor.bytes(pos + 8, len - 8), type: :bytes, offset: pos + 8, length: len - 8)
|
|
136
|
+
elsif opt == 5 && len == 8
|
|
137
|
+
layer.add(:"mtu_#{pos}", 'icmpv6.opt.mtu', cursor.u32(pos + 4), offset: pos + 4, length: 4)
|
|
138
|
+
else
|
|
139
|
+
layer.add(:"option_#{pos}", 'icmpv6.opt.data', cursor.bytes(pos + 2, len - 2), type: :bytes, offset: pos + 2, length: len - 2)
|
|
140
|
+
end
|
|
141
|
+
pos += len
|
|
142
|
+
end
|
|
143
|
+
length = pos
|
|
144
|
+
end
|
|
145
|
+
layer.header_length, layer.payload_offset = length, layer.offset + length
|
|
146
|
+
Checksum.verify(ctx, layer, cursor.bytes(0, cursor.remaining), 58)
|
|
147
|
+
end
|
|
148
|
+
# @rbs (Context ctx, Layer layer) -> untyped
|
|
149
|
+
def next_dissector(ctx, layer)
|
|
150
|
+
return nil unless layer[:type] < 128
|
|
151
|
+
ctx.embedded = true
|
|
152
|
+
Ipv6
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# @api private
|
|
157
|
+
class Igmp < Dissector
|
|
158
|
+
protocol :igmp, name: 'Internet Group Management Protocol', short: 'IGMP'
|
|
159
|
+
dissects_on 'ip.proto', 2
|
|
160
|
+
header do
|
|
161
|
+
uint8 :type, 'igmp.type'
|
|
162
|
+
uint8 :response, 'igmp.max_resp'
|
|
163
|
+
uint16 :checksum, 'igmp.checksum'
|
|
164
|
+
end
|
|
165
|
+
# @rbs (Context ctx, Layer layer) -> void
|
|
166
|
+
def dissect(ctx, layer)
|
|
167
|
+
cursor, type = ctx.cursor, layer[:type]
|
|
168
|
+
layer.definitions.reject! { |field| field.key == :response } if [0x12, 0x22].include?(type)
|
|
169
|
+
cursor.check(0, 8)
|
|
170
|
+
version = type == 0x22 || (type == 0x11 && cursor.remaining >= 12) ? 3 : (type == 0x12 || (type == 0x11 && layer[:response].zero?) ? 1 : 2)
|
|
171
|
+
layer.add(:version, 'igmp.version', version)
|
|
172
|
+
pos = 8
|
|
173
|
+
if type == 0x22
|
|
174
|
+
count = cursor.u16(6)
|
|
175
|
+
layer.add(:records, 'igmp.num_grp_recs', count, offset: 6, length: 2)
|
|
176
|
+
count.times do |record|
|
|
177
|
+
cursor.check(pos, 8)
|
|
178
|
+
sources, auxiliary = cursor.u16(pos + 2), cursor.u8(pos + 1) * 4
|
|
179
|
+
cursor.check(pos, 8 + sources * 4 + auxiliary)
|
|
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?
|
|
185
|
+
pos += 8 + sources * 4 + auxiliary
|
|
186
|
+
end
|
|
187
|
+
else
|
|
188
|
+
layer.add(:group, 'igmp.maddr', cursor.u32(4), type: :ipv4, offset: 4, length: 4)
|
|
189
|
+
if version == 3
|
|
190
|
+
sources = cursor.u16(10)
|
|
191
|
+
cursor.check(8, 4 + sources * 4)
|
|
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) }
|
|
197
|
+
pos = 12 + sources * 4
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
layer.header_length, layer.payload_offset = pos, layer.offset + pos
|
|
201
|
+
Checksum.verify(ctx, layer, cursor.bytes(0, cursor.remaining))
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
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,68 @@
|
|
|
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
|
+
ctx.extension_count = 0
|
|
27
|
+
layer.diagnose(:error, :malformed, 'IPv6 version is not 6') if layer[:version] != 6
|
|
28
|
+
layer.payload_end = layer.offset + 40 + layer[:plen]
|
|
29
|
+
layer.payload_offset = layer.payload_end if layer[:nxt] == 59
|
|
30
|
+
end
|
|
31
|
+
# @rbs (Context ctx, Layer layer) -> untyped
|
|
32
|
+
def next_dissector(ctx, layer) = ctx.registry.lookup('ip.proto', layer[:nxt])
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @api private
|
|
36
|
+
class Ipv6Ext < Dissector
|
|
37
|
+
protocol :ipv6_ext, name: 'IPv6 extension', short: 'IP6 EXT'
|
|
38
|
+
dissects_on 'ip.proto', 0, 43, 44, 60, 51
|
|
39
|
+
# @rbs (Context ctx, Layer layer) -> void
|
|
40
|
+
def dissect(ctx, layer)
|
|
41
|
+
ctx.extension_count += 1
|
|
42
|
+
return layer.diagnose(:error, :malformed, 'more than 16 IPv6 extensions') if ctx.extension_count > 16
|
|
43
|
+
parent = ctx.layers.last
|
|
44
|
+
type = parent.protocol == :ipv6 ? parent[:nxt] : parent.protocol == :ipv4 ? parent[:proto] : parent[:next]
|
|
45
|
+
cursor = ctx.cursor
|
|
46
|
+
nxt = cursor.u8(0)
|
|
47
|
+
layer.add(:next, 'ipv6.nxt', nxt, length: 1)
|
|
48
|
+
layer.add(:type, 'ipv6.extension.type', type)
|
|
49
|
+
length = type == 44 ? 8 : type == 51 ? (cursor.u8(1) + 2) * 4 : (cursor.u8(1) + 1) * 8
|
|
50
|
+
cursor.check(0, length)
|
|
51
|
+
if type == 44
|
|
52
|
+
flags = cursor.u16(2)
|
|
53
|
+
layer.add(:fragment_offset, 'ipv6.fragment.offset', flags & 0xfff8, offset: 2, length: 2)
|
|
54
|
+
layer.add(:more, 'ipv6.fragment.more', flags & 1, offset: 2, length: 2)
|
|
55
|
+
layer.add(:identification, 'ipv6.fragment.id', cursor.u32(4), offset: 4, length: 4)
|
|
56
|
+
end
|
|
57
|
+
layer.header_length = length
|
|
58
|
+
layer.payload_offset += length
|
|
59
|
+
layer.payload_offset = layer.payload_end if nxt == 59
|
|
60
|
+
end
|
|
61
|
+
# @rbs (Context ctx, Layer layer) -> untyped
|
|
62
|
+
def next_dissector(ctx, layer)
|
|
63
|
+
return nil if layer[:fragment_offset] && layer[:fragment_offset] != 0
|
|
64
|
+
ctx.registry.lookup('ip.proto', layer[:next])
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
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,45 @@
|
|
|
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
|
+
int8 :poll, 'ntp.ppoll'
|
|
21
|
+
int8 :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.diagnose(:error, :malformed, 'invalid NTP version or mode') unless layer[:vn].between?(1, 4) && layer[:mode].between?(1, 7)
|
|
34
|
+
if ctx.cursor.remaining > 48
|
|
35
|
+
layer.add(:extension, 'ntp.extension', ctx.cursor.bytes(48, ctx.cursor.remaining - 48),
|
|
36
|
+
type: :bytes, offset: 48, length: ctx.cursor.remaining - 48)
|
|
37
|
+
end
|
|
38
|
+
layer.payload_offset = layer.payload_end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @rbs (Layer layer) -> String
|
|
42
|
+
def summary(layer) = "NTP v#{layer[:vn]} #{MODES[layer[:mode]]}, stratum #{layer[:stratum]}"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
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
|
+
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
|
|
66
|
+
case kind
|
|
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)
|
|
71
|
+
when 8
|
|
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)
|
|
75
|
+
end
|
|
76
|
+
pos += len
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
# @rbs (Context ctx, Layer layer) -> untyped
|
|
80
|
+
def next_dissector(ctx, layer) = ctx.registry.by_port('tcp.port', layer[:srcport], layer[:dstport])
|
|
81
|
+
# @rbs (Layer layer) -> String
|
|
82
|
+
def summary(layer)
|
|
83
|
+
flags = FLAGS.filter_map { |bit, name| name if layer[:flags] & bit != 0 }.join
|
|
84
|
+
"TCP [#{flags}], seq #{layer[:seq]}, ack #{layer[:ack]}, win #{layer[:window]}, length #{layer[:length]}"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|