redhound 1.0.0 → 2.0.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +35 -0
- data/README.md +97 -31
- data/docs/API.md +76 -0
- data/docs/FILTERS.md +37 -0
- data/docs/MIGRATION.md +23 -0
- data/docs/PROTOCOLS.md +45 -0
- data/docs/USAGE.md +82 -0
- data/docs/VALIDATION.md +59 -0
- data/docs/json-schema.json +64 -0
- data/exe/redhound +2 -1
- data/lib/redhound/analysis/flow_key.rb +53 -0
- data/lib/redhound/analysis/flow_table.rb +117 -0
- data/lib/redhound/analysis/ip_reassembler.rb +176 -0
- data/lib/redhound/analysis/stats.rb +138 -0
- data/lib/redhound/analysis/tcp_analysis.rb +43 -0
- data/lib/redhound/analysis/tcp_reassembler.rb +124 -0
- data/lib/redhound/analysis/tcp_stream.rb +151 -0
- data/lib/redhound/analysis.rb +129 -0
- data/lib/redhound/capture/bsd/bpf_device.rb +152 -0
- data/lib/redhound/capture/bsd/constants.rb +29 -0
- data/lib/redhound/capture/file_source.rb +83 -0
- data/lib/redhound/capture/interface.rb +66 -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 +180 -0
- data/lib/redhound/capture/linux/sockaddr_ll.rb +33 -0
- data/lib/redhound/capture/linux/tpacket_v3.rb +132 -0
- data/lib/redhound/capture/source.rb +79 -0
- data/lib/redhound/capture/stats.rb +22 -0
- data/lib/redhound/capture.rb +69 -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 +138 -0
- data/lib/redhound/engine.rb +66 -0
- data/lib/redhound/errors.rb +38 -0
- data/lib/redhound/field.rb +62 -0
- data/lib/redhound/file/format.rb +56 -0
- data/lib/redhound/file/pcap_reader.rb +43 -0
- data/lib/redhound/file/pcap_writer.rb +72 -0
- data/lib/redhound/file/pcapng_reader.rb +209 -0
- data/lib/redhound/file/pcapng_writer.rb +143 -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 +108 -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 +59 -0
- data/lib/redhound/output/hexdump.rb +29 -0
- data/lib/redhound/output/json.rb +32 -0
- data/lib/redhound/output/summary.rb +59 -0
- data/lib/redhound/output/timestamp.rb +34 -0
- data/lib/redhound/output/tree.rb +24 -0
- data/lib/redhound/packet.rb +63 -0
- data/lib/redhound/protocols/arp.rb +39 -0
- data/lib/redhound/protocols/checksum.rb +38 -0
- data/lib/redhound/protocols/data.rb +21 -0
- data/lib/redhound/protocols/dhcp.rb +124 -0
- data/lib/redhound/protocols/dns.rb +268 -0
- data/lib/redhound/protocols/ethernet.rb +24 -0
- data/lib/redhound/protocols/http.rb +183 -0
- data/lib/redhound/protocols/icmp.rb +191 -0
- data/lib/redhound/protocols/ipv4.rb +56 -0
- data/lib/redhound/protocols/ipv6.rb +65 -0
- data/lib/redhound/protocols/link.rb +70 -0
- data/lib/redhound/protocols/llc.rb +37 -0
- data/lib/redhound/protocols/ntp.rb +47 -0
- data/lib/redhound/protocols/tcp.rb +80 -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 +98 -0
- data/sig/generated/redhound/analysis/ip_reassembler.rbs +40 -0
- data/sig/generated/redhound/analysis/stats.rbs +77 -0
- data/sig/generated/redhound/analysis/tcp_analysis.rbs +15 -0
- data/sig/generated/redhound/analysis/tcp_reassembler.rbs +27 -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/file_source.rbs +34 -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 +57 -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 +113 -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 +33 -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 +26 -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 +77 -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 +15 -0
- data/sig/generated/redhound/protocols/dhcp.rbs +28 -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 +175 -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 -8
- 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,413 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
module Redhound
|
|
5
|
+
# @api private
|
|
6
|
+
module Filter
|
|
7
|
+
# @api private
|
|
8
|
+
class CodeGen
|
|
9
|
+
LINKTYPES = { ethernet: 1, raw: 101, null: 0, loop: 108, linux_sll: 113, sll: 113,
|
|
10
|
+
linux_sll2: 276, sll2: 276, ipv4: 228, ipv6: 229 }.freeze
|
|
11
|
+
LAYOUTS = { 1 => [14, 12], 113 => [16, 14], 276 => [20, 0], 0 => [4, 0],
|
|
12
|
+
108 => [4, 0], 101 => [0, nil], 12 => [0, nil], 228 => [0, nil], 229 => [0, nil] }.freeze
|
|
13
|
+
ALU = { '+' => 0x00, '-' => 0x10, '*' => 0x20, '/' => 0x30, '|' => 0x40,
|
|
14
|
+
'&' => 0x50, '<<' => 0x60, '>>' => 0x70, '%' => 0x90, '^' => 0xa0 }.freeze
|
|
15
|
+
|
|
16
|
+
# @rbs (Integer linktype, Integer snaplen, bool live, String expression) -> void
|
|
17
|
+
def initialize(linktype, snaplen, live, expression)
|
|
18
|
+
@linktype = linktype
|
|
19
|
+
@snaplen = snaplen
|
|
20
|
+
@live = live
|
|
21
|
+
@expression = expression
|
|
22
|
+
@assembler = BPF::Assembler.new
|
|
23
|
+
@shift = 0
|
|
24
|
+
@dynamic_vlan = false
|
|
25
|
+
@vlan_count = 0
|
|
26
|
+
@network, @ethertype = LAYOUTS.fetch(linktype) { raise FilterError, "unsupported filter linktype #{linktype}" }
|
|
27
|
+
@cooked_live = live && linktype == 276
|
|
28
|
+
@network = 0 if @cooked_live
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @rbs (untyped ast) -> Program
|
|
32
|
+
def compile(ast)
|
|
33
|
+
if @live && @linktype == 1 && includes_vlan?(ast)
|
|
34
|
+
@assembler.emit(0x00, 0)
|
|
35
|
+
@assembler.emit(0x02, 15)
|
|
36
|
+
end
|
|
37
|
+
accepted = @assembler.label
|
|
38
|
+
rejected = @assembler.label
|
|
39
|
+
emit(ast, accepted, rejected)
|
|
40
|
+
@assembler.mark(accepted)
|
|
41
|
+
@assembler.emit(6, @snaplen)
|
|
42
|
+
@assembler.mark(rejected)
|
|
43
|
+
@assembler.emit(6, 0)
|
|
44
|
+
Program.new(@assembler.assemble, linktype: @linktype, cooked_live: @cooked_live)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
# @rbs (untyped ast) -> bool
|
|
50
|
+
def includes_vlan?(ast)
|
|
51
|
+
return ast[3] == :vlan if ast[0] == :primitive
|
|
52
|
+
return includes_vlan?(ast[1]) if ast[0] == :not
|
|
53
|
+
return includes_vlan?(ast[1]) || includes_vlan?(ast[2]) if %i[and or].include?(ast[0])
|
|
54
|
+
false
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @rbs (untyped node, Symbol yes, Symbol no) -> void
|
|
58
|
+
def emit(node, yes, no)
|
|
59
|
+
case node[0]
|
|
60
|
+
when :true then @assembler.jump(yes)
|
|
61
|
+
when :false then @assembler.jump(no)
|
|
62
|
+
when :not then emit(node[1], no, yes)
|
|
63
|
+
when :and, :or
|
|
64
|
+
middle = @assembler.label
|
|
65
|
+
emit(node[1], node[0] == :and ? middle : yes, node[0] == :and ? no : middle)
|
|
66
|
+
@assembler.mark(middle)
|
|
67
|
+
emit(node[2], yes, no)
|
|
68
|
+
when :primitive
|
|
69
|
+
if node[3] == :vlan
|
|
70
|
+
vlan(node[4], yes, no)
|
|
71
|
+
else
|
|
72
|
+
emit(primitive(node), yes, no)
|
|
73
|
+
end
|
|
74
|
+
when :test
|
|
75
|
+
_, offset, size, operation, value, mode = node
|
|
76
|
+
load(offset, size, mode)
|
|
77
|
+
compare(operation, value, yes, no)
|
|
78
|
+
when :relation
|
|
79
|
+
guards = access_protocols(node[1]) + access_protocols(node[3])
|
|
80
|
+
guards.uniq.each do |protocol|
|
|
81
|
+
next if protocol == 'ether'
|
|
82
|
+
following = @assembler.label
|
|
83
|
+
guard = if %w[ip ip6 arp rarp].include?(protocol)
|
|
84
|
+
family(protocol)
|
|
85
|
+
elsif protocol == 'icmp6'
|
|
86
|
+
conjunction(family('ip6'), next_protocol(6, 58))
|
|
87
|
+
else
|
|
88
|
+
conjunction(family('ip'), next_protocol(4, Analyzer::PROTOCOL_NUMBERS.fetch(protocol)), fragment_guard)
|
|
89
|
+
end
|
|
90
|
+
emit(guard, following, no)
|
|
91
|
+
@assembler.mark(following)
|
|
92
|
+
end
|
|
93
|
+
arithmetic(node[1], 0)
|
|
94
|
+
if node[3][0] == :number
|
|
95
|
+
compare(node[2], node[3][1], yes, no)
|
|
96
|
+
else
|
|
97
|
+
@assembler.emit(2, 0)
|
|
98
|
+
arithmetic(node[3], 1)
|
|
99
|
+
@assembler.emit(7)
|
|
100
|
+
@assembler.emit(0x60, 0)
|
|
101
|
+
compare(node[2], 0, yes, no, true)
|
|
102
|
+
end
|
|
103
|
+
else raise FilterError, "invalid filter AST #{node[0]}"
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# @rbs (untyped node) -> untyped
|
|
108
|
+
def primitive(node)
|
|
109
|
+
_, proto, direction, type, value, position = node
|
|
110
|
+
case type
|
|
111
|
+
when :protocol
|
|
112
|
+
return @linktype == 1 ? [:true] : [:false] if proto == 'ether'
|
|
113
|
+
return family(proto) if %w[ip ip6 arp rarp].include?(proto)
|
|
114
|
+
number = Analyzer::PROTOCOL_NUMBERS.fetch(proto)
|
|
115
|
+
return protocol(number, proto == 'icmp' || proto == 'igmp' ? [4] : proto == 'icmp6' ? [6] : [4, 6])
|
|
116
|
+
when :proto
|
|
117
|
+
return test(0xfffff000, 4, :eq, value, :absolute) if proto == 'ether' && @cooked_live
|
|
118
|
+
return test(@ethertype.to_i + @shift, 2, :eq, value) if proto == 'ether' && @ethertype && ![0, 108].include?(@linktype)
|
|
119
|
+
error('ether proto is unavailable for this linktype', position) if proto == 'ether'
|
|
120
|
+
versions = proto == 'ip' ? [4] : proto == 'ip6' ? [6] : [4, 6]
|
|
121
|
+
return protocol(value, versions)
|
|
122
|
+
when :host, :net
|
|
123
|
+
return ethernet_host(value.first, direction, position) if proto == 'ether'
|
|
124
|
+
return addresses(value, proto, direction)
|
|
125
|
+
when :port, :portrange then ports(value, direction)
|
|
126
|
+
when :greater, :less then [:relation, [:len], type == :greater ? '>=' : '<=', [:number, value]]
|
|
127
|
+
when :broadcast, :multicast then broadcast(proto, type, position)
|
|
128
|
+
else raise FilterError, "unsupported filter primitive #{type}"
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# @rbs (String name) -> untyped
|
|
133
|
+
def family(name)
|
|
134
|
+
value = Analyzer::ETHER_TYPES.fetch(name)
|
|
135
|
+
return test(0xfffff000, 4, :eq, value, :absolute) if @cooked_live
|
|
136
|
+
if @ethertype && ![0, 108].include?(@linktype)
|
|
137
|
+
test(@ethertype + @shift, 2, :eq, value)
|
|
138
|
+
elsif [0, 108].include?(@linktype)
|
|
139
|
+
return [:false] unless %w[ip ip6].include?(name)
|
|
140
|
+
values = name == 'ip' ? [2] : [24, 28, 30]
|
|
141
|
+
if @linktype.zero?
|
|
142
|
+
values.map! do |v|
|
|
143
|
+
word = [v].pack('L').unpack1('N') #: Integer
|
|
144
|
+
word
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
disjunction(*values.map { |v| test(0, 4, :eq, v, :absolute) })
|
|
148
|
+
elsif name == 'ip' || name == 'ip6'
|
|
149
|
+
version = name == 'ip' ? 4 : 6
|
|
150
|
+
return [:false] if (@linktype == 228 && version != 4) || (@linktype == 229 && version != 6)
|
|
151
|
+
[:relation, [:binary, '&', [:read, 0, 1, :absolute], [:number, 0xf0]], '=', [:number, version << 4]]
|
|
152
|
+
else
|
|
153
|
+
[:false]
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# @rbs (Integer number, Array[Integer] versions) -> untyped
|
|
158
|
+
def protocol(number, versions)
|
|
159
|
+
disjunction(*versions.map do |version|
|
|
160
|
+
guard = next_protocol(version, number)
|
|
161
|
+
if version == 6
|
|
162
|
+
fragment = conjunction(next_protocol(6, 44), test(network + 40, 1, :eq, number))
|
|
163
|
+
guard = disjunction(guard, fragment)
|
|
164
|
+
end
|
|
165
|
+
conjunction(family(version == 4 ? 'ip' : 'ip6'), guard)
|
|
166
|
+
end)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# @rbs (Integer version, Integer number) -> untyped
|
|
170
|
+
def next_protocol(version, number) = test(network + (version == 4 ? 9 : 6), 1, :eq, number)
|
|
171
|
+
|
|
172
|
+
# @rbs () -> untyped
|
|
173
|
+
def fragment_guard = test(network + 6, 2, :unset, 0x1fff)
|
|
174
|
+
|
|
175
|
+
# @rbs () -> Integer
|
|
176
|
+
def network = @network + @shift
|
|
177
|
+
|
|
178
|
+
# @rbs (Integer offset, Integer size, Symbol operation, Integer value, ?Symbol? mode) -> untyped
|
|
179
|
+
def test(offset, size, operation, value, mode = nil) = [:test, offset, size, operation, value, mode]
|
|
180
|
+
|
|
181
|
+
# @rbs (*untyped nodes) -> untyped
|
|
182
|
+
def conjunction(*nodes) = nodes.reduce { |left, right| [:and, left, right] } || [:true]
|
|
183
|
+
|
|
184
|
+
# @rbs (*untyped nodes) -> untyped
|
|
185
|
+
def disjunction(*nodes) = nodes.reduce { |left, right| [:or, left, right] } || [:false]
|
|
186
|
+
|
|
187
|
+
# @rbs (Symbol direction, untyped src, untyped dst) -> untyped
|
|
188
|
+
def directional(direction, src, dst)
|
|
189
|
+
return src if direction == :src
|
|
190
|
+
return dst if direction == :dst
|
|
191
|
+
direction == :both ? conjunction(src, dst) : disjunction(src, dst)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# @rbs (untyped values, String? proto, Symbol direction) -> untyped
|
|
195
|
+
def addresses(values, proto, direction)
|
|
196
|
+
disjunction(*values.map do |version, address, mask|
|
|
197
|
+
families = proto ? [proto] : version == 4 ? %w[ip arp rarp] : ['ip6']
|
|
198
|
+
disjunction(*families.map do |name|
|
|
199
|
+
src = network + (version == 6 ? 8 : %w[arp rarp].include?(name) ? 14 : 12)
|
|
200
|
+
dst = network + (version == 6 ? 24 : %w[arp rarp].include?(name) ? 24 : 16)
|
|
201
|
+
conjunction(family(name), directional(direction, address_test(src, version, address, mask), address_test(dst, version, address, mask)))
|
|
202
|
+
end)
|
|
203
|
+
end)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# @rbs (Integer offset, Integer version, Integer address, Integer mask) -> untyped
|
|
207
|
+
def address_test(offset, version, address, mask)
|
|
208
|
+
count = version == 6 ? 4 : 1
|
|
209
|
+
conjunction(*(0...count).filter_map do |index|
|
|
210
|
+
shift = (count - index - 1) * 32
|
|
211
|
+
word_mask = (mask >> shift) & 0xffffffff
|
|
212
|
+
next if word_mask.zero?
|
|
213
|
+
expected = (address >> shift) & word_mask
|
|
214
|
+
read = [:read, offset + index * 4, 4, nil]
|
|
215
|
+
read = [:binary, '&', read, [:number, word_mask]] unless word_mask == 0xffffffff
|
|
216
|
+
[:relation, read, '=', [:number, expected]]
|
|
217
|
+
end)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# @rbs (String mac, Symbol direction, Integer position) -> untyped
|
|
221
|
+
def ethernet_host(mac, direction, position)
|
|
222
|
+
error('Ethernet addresses are unavailable for this linktype', position) unless @linktype == 1
|
|
223
|
+
parts = mac.unpack('Nn') #: [Integer, Integer]
|
|
224
|
+
src = conjunction(test(6, 4, :eq, parts[0], :absolute), test(10, 2, :eq, parts[1], :absolute))
|
|
225
|
+
dst = conjunction(test(0, 4, :eq, parts[0], :absolute), test(4, 2, :eq, parts[1], :absolute))
|
|
226
|
+
directional(direction, src, dst)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# @rbs (untyped ranges, Symbol direction) -> untyped
|
|
230
|
+
def ports(ranges, direction)
|
|
231
|
+
disjunction(*(ranges.flat_map do |number, limits|
|
|
232
|
+
[4, 6].map do |version|
|
|
233
|
+
guard = conjunction(family(version == 4 ? 'ip' : 'ip6'), next_protocol(version, number))
|
|
234
|
+
guard = conjunction(guard, fragment_guard) if version == 4
|
|
235
|
+
src = port_test(version, 0, limits)
|
|
236
|
+
dst = port_test(version, 2, limits)
|
|
237
|
+
conjunction(guard, directional(direction, src, dst))
|
|
238
|
+
end
|
|
239
|
+
end))
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# @rbs (Integer version, Integer offset, [Integer, Integer] limits) -> untyped
|
|
243
|
+
def port_test(version, offset, limits)
|
|
244
|
+
mode = version == 4 ? :transport : nil
|
|
245
|
+
location = network + (version == 4 ? offset : 40 + offset)
|
|
246
|
+
low, high = limits
|
|
247
|
+
return test(location, 2, :eq, low, mode) if low == high
|
|
248
|
+
conjunction(test(location, 2, :ge, low, mode), test(location, 2, :le, high, mode))
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# @rbs (String? proto, Symbol type, Integer position) -> untyped
|
|
252
|
+
def broadcast(proto, type, position)
|
|
253
|
+
if !proto || proto == 'ether'
|
|
254
|
+
error('link broadcast/multicast requires Ethernet', position) unless @linktype == 1
|
|
255
|
+
return type == :broadcast ? conjunction(test(0, 4, :eq, 0xffffffff, :absolute), test(4, 2, :eq, 0xffff, :absolute)) : test(0, 1, :set, 1, :absolute)
|
|
256
|
+
end
|
|
257
|
+
if proto == 'ip6'
|
|
258
|
+
return conjunction(family('ip6'), test(network + 24, 1, :eq, 0xff))
|
|
259
|
+
end
|
|
260
|
+
condition = type == :broadcast ? disjunction(test(network + 16, 4, :eq, 0), test(network + 16, 4, :eq, 0xffffffff)) : test(network + 16, 1, :ge, 0xe0)
|
|
261
|
+
conjunction(family('ip'), condition)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# @rbs (Integer? id, Symbol yes, Symbol no) -> void
|
|
265
|
+
def vlan(id, yes, no)
|
|
266
|
+
error('VLAN filtering requires Ethernet', 0) unless @linktype == 1
|
|
267
|
+
tag_offset = 12 + @shift
|
|
268
|
+
condition = disjunction(*[0x8100, 0x88a8, 0x9100].map { |type| test(tag_offset, 2, :eq, type) })
|
|
269
|
+
condition = conjunction(condition, [:relation, [:binary, '&', [:read, tag_offset + 2, 2, nil], [:number, 0xfff]], '=', [:number, id]]) if id
|
|
270
|
+
if @live && @vlan_count.zero?
|
|
271
|
+
inline = @assembler.label
|
|
272
|
+
metadata = @assembler.label
|
|
273
|
+
@assembler.emit(0x20, 0xfffff030)
|
|
274
|
+
@assembler.branch(0x15, 1, metadata, inline)
|
|
275
|
+
@assembler.mark(metadata)
|
|
276
|
+
if id
|
|
277
|
+
@assembler.emit(0x20, 0xfffff02c)
|
|
278
|
+
@assembler.emit(0x54, 0xfff)
|
|
279
|
+
@assembler.branch(0x15, id, yes, no)
|
|
280
|
+
else
|
|
281
|
+
@assembler.jump(yes)
|
|
282
|
+
end
|
|
283
|
+
@assembler.mark(inline)
|
|
284
|
+
@assembler.emit(0x00, 4)
|
|
285
|
+
@assembler.emit(0x02, 15)
|
|
286
|
+
emit(condition, yes, no)
|
|
287
|
+
@dynamic_vlan = true
|
|
288
|
+
elsif @dynamic_vlan
|
|
289
|
+
matched = @assembler.label
|
|
290
|
+
emit(condition, matched, no)
|
|
291
|
+
@assembler.mark(matched)
|
|
292
|
+
@assembler.emit(0x60, 15)
|
|
293
|
+
@assembler.emit(0x04, 4)
|
|
294
|
+
@assembler.emit(0x02, 15)
|
|
295
|
+
@assembler.jump(yes)
|
|
296
|
+
else
|
|
297
|
+
emit(condition, yes, no)
|
|
298
|
+
@shift += 4
|
|
299
|
+
end
|
|
300
|
+
@vlan_count += 1
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# @rbs (Integer offset, Integer size, Symbol? mode) -> void
|
|
304
|
+
def load(offset, size, mode)
|
|
305
|
+
opcode = { 4 => 0x20, 2 => 0x28, 1 => 0x30 }.fetch(size)
|
|
306
|
+
if mode == :transport
|
|
307
|
+
transport_index
|
|
308
|
+
@assembler.emit(opcode + 0x20, offset)
|
|
309
|
+
elsif @dynamic_vlan && mode != :absolute
|
|
310
|
+
@assembler.emit(0x61, 15)
|
|
311
|
+
@assembler.emit(opcode + 0x20, offset)
|
|
312
|
+
else
|
|
313
|
+
@assembler.emit(opcode, offset)
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# @rbs () -> void
|
|
318
|
+
def transport_index
|
|
319
|
+
if @dynamic_vlan
|
|
320
|
+
@assembler.emit(0x61, 15)
|
|
321
|
+
@assembler.emit(0x50, network)
|
|
322
|
+
@assembler.emit(0x54, 15)
|
|
323
|
+
@assembler.emit(0x64, 2)
|
|
324
|
+
@assembler.emit(0x0c)
|
|
325
|
+
@assembler.emit(7)
|
|
326
|
+
else
|
|
327
|
+
@assembler.emit(0xb1, network)
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# @rbs (untyped operation, Integer value, Symbol yes, Symbol no, ?bool from_x) -> void
|
|
332
|
+
def compare(operation, value, yes, no, from_x = false)
|
|
333
|
+
code = case operation.to_s
|
|
334
|
+
when 'eq', '=', '==' then 0x15
|
|
335
|
+
when 'ne', '!=' then yes, no = no, yes; 0x15
|
|
336
|
+
when 'gt', '>' then 0x25
|
|
337
|
+
when 'ge', '>=' then 0x35
|
|
338
|
+
when 'lt', '<' then yes, no = no, yes; 0x35
|
|
339
|
+
when 'le', '<=' then yes, no = no, yes; 0x25
|
|
340
|
+
when 'set' then 0x45
|
|
341
|
+
when 'unset' then yes, no = no, yes; 0x45
|
|
342
|
+
else raise FilterError, "unknown comparison #{operation}"
|
|
343
|
+
end
|
|
344
|
+
@assembler.branch(code | (from_x ? 8 : 0), value, yes, no)
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# @rbs (untyped node) -> Array[String]
|
|
348
|
+
def access_protocols(node)
|
|
349
|
+
case node[0]
|
|
350
|
+
when :access then [node[1]] + access_protocols(node[2])
|
|
351
|
+
when :binary then access_protocols(node[2]) + access_protocols(node[3])
|
|
352
|
+
when :neg then access_protocols(node[1])
|
|
353
|
+
else []
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
# @rbs (untyped node, Integer depth) -> void
|
|
358
|
+
def arithmetic(node, depth)
|
|
359
|
+
error('arithmetic expression requires more than 14 scratch registers', 0) if depth >= 14
|
|
360
|
+
case node[0]
|
|
361
|
+
when :number then @assembler.emit(0x00, node[1])
|
|
362
|
+
when :len
|
|
363
|
+
@assembler.emit(0x80)
|
|
364
|
+
@assembler.emit(0x04, 20) if @cooked_live
|
|
365
|
+
when :read then load(node[1], node[2], node[3])
|
|
366
|
+
when :neg then arithmetic(node[1], depth); @assembler.emit(0x84)
|
|
367
|
+
when :binary
|
|
368
|
+
arithmetic(node[2], depth)
|
|
369
|
+
if node[3][0] == :number
|
|
370
|
+
@assembler.emit(ALU.fetch(node[1]) | 4, node[3][1])
|
|
371
|
+
else
|
|
372
|
+
@assembler.emit(2, depth)
|
|
373
|
+
arithmetic(node[3], depth + 1)
|
|
374
|
+
@assembler.emit(7)
|
|
375
|
+
@assembler.emit(0x60, depth)
|
|
376
|
+
@assembler.emit(ALU.fetch(node[1]) | 12)
|
|
377
|
+
end
|
|
378
|
+
when :access then accessor(node, depth)
|
|
379
|
+
else raise FilterError, "invalid arithmetic AST #{node[0]}"
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# @rbs (untyped node, Integer depth) -> void
|
|
384
|
+
def accessor(node, depth)
|
|
385
|
+
_, protocol, offset, size = node
|
|
386
|
+
transport = !%w[ether ip ip6 arp rarp icmp6].include?(protocol)
|
|
387
|
+
base = protocol == 'ether' ? 0 : network + (protocol == 'icmp6' ? 40 : 0)
|
|
388
|
+
if offset[0] == :number
|
|
389
|
+
load(base + offset[1], size, transport ? :transport : protocol == 'ether' ? :absolute : nil)
|
|
390
|
+
else
|
|
391
|
+
arithmetic(offset, depth)
|
|
392
|
+
@assembler.emit(2, depth)
|
|
393
|
+
if transport
|
|
394
|
+
transport_index
|
|
395
|
+
elsif @dynamic_vlan && protocol != 'ether'
|
|
396
|
+
@assembler.emit(0x61, 15)
|
|
397
|
+
else
|
|
398
|
+
@assembler.emit(0x01, 0)
|
|
399
|
+
end
|
|
400
|
+
@assembler.emit(0x60, depth)
|
|
401
|
+
@assembler.emit(0x0c)
|
|
402
|
+
@assembler.emit(7)
|
|
403
|
+
@assembler.emit({ 4 => 0x40, 2 => 0x48, 1 => 0x50 }.fetch(size), base)
|
|
404
|
+
end
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
# @rbs (String message, Integer position) -> bot
|
|
408
|
+
def error(message, position)
|
|
409
|
+
raise FilterSyntaxError.new(message, expression: @expression, position: position)
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
module Redhound
|
|
5
|
+
# @api private
|
|
6
|
+
module Filter
|
|
7
|
+
# @api private
|
|
8
|
+
class Lexer
|
|
9
|
+
# @rbs (String expression) -> void
|
|
10
|
+
def initialize(expression)
|
|
11
|
+
@expression = expression
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# @rbs () -> Array[[String, Integer]]
|
|
15
|
+
def tokens
|
|
16
|
+
result = [] #: Array[[String, Integer]]
|
|
17
|
+
position = 0
|
|
18
|
+
brackets = 0
|
|
19
|
+
while position < @expression.length
|
|
20
|
+
tail = @expression[position..]
|
|
21
|
+
if (space = /\A\s+/.match(tail))
|
|
22
|
+
position += space[0].length
|
|
23
|
+
next
|
|
24
|
+
end
|
|
25
|
+
patterns = [ /\A(?:&&|\|\||!=|==|>=|<=|<<|>>)/ ]
|
|
26
|
+
if brackets.zero?
|
|
27
|
+
patterns += [ /\A(?:[a-fA-F0-9]{1,2}:){5}[a-fA-F0-9]{1,2}/,
|
|
28
|
+
/\A(?=[a-fA-F0-9:.]*:)[a-fA-F0-9:.]+(?:\/\d+)?/,
|
|
29
|
+
/\A\d+(?:\.\d+){1,3}(?:\/\d+)?/ ]
|
|
30
|
+
end
|
|
31
|
+
patterns += [ /\A(?:0[xX][\da-fA-F]+|\d+)/,
|
|
32
|
+
/\A\\?[a-zA-Z_][a-zA-Z0-9_.-]*/, /\A[()\[\]:+*\/%&|^!<>=-]/ ]
|
|
33
|
+
token = patterns.filter_map { |pattern| pattern.match(tail)&.[](0) }.first
|
|
34
|
+
unless token
|
|
35
|
+
raise FilterSyntaxError.new("unexpected character #{tail[0].inspect}", expression: @expression, position: position)
|
|
36
|
+
end
|
|
37
|
+
result << [token, position]
|
|
38
|
+
brackets += 1 if token == '['
|
|
39
|
+
brackets -= 1 if token == ']'
|
|
40
|
+
position += token.length
|
|
41
|
+
end
|
|
42
|
+
result << ['', @expression.length]
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
module Redhound
|
|
5
|
+
# @api private
|
|
6
|
+
module Filter
|
|
7
|
+
# ASTs stay private; primitive operands are resolved before code generation.
|
|
8
|
+
# @api private
|
|
9
|
+
class Parser
|
|
10
|
+
PROTOCOLS = %w[ether ip ip6 arp rarp tcp udp sctp icmp icmp6 igmp].freeze
|
|
11
|
+
TYPES = %w[host net port portrange proto].freeze
|
|
12
|
+
COMPARISONS = %w[= == != > >= < <=].freeze
|
|
13
|
+
PRECEDENCE = { '|' => 1, '^' => 2, '&' => 3, '<<' => 4, '>>' => 4, '+' => 5, '-' => 5, '*' => 6, '/' => 6, '%' => 6 }.freeze
|
|
14
|
+
CONSTANTS = { 'tcpflags' => 13, 'tcp-fin' => 1, 'tcp-syn' => 2, 'tcp-rst' => 4,
|
|
15
|
+
'tcp-push' => 8, 'tcp-ack' => 16, 'tcp-urg' => 32, 'tcp-ece' => 64, 'tcp-cwr' => 128,
|
|
16
|
+
'icmptype' => 0, 'icmpcode' => 1, 'icmp-echoreply' => 0, 'icmp-echo' => 8,
|
|
17
|
+
'icmp-unreach' => 3, 'icmp-timxceed' => 11 }.freeze
|
|
18
|
+
|
|
19
|
+
# @rbs (String expression) -> void
|
|
20
|
+
def initialize(expression)
|
|
21
|
+
@expression = expression
|
|
22
|
+
@tokens = Lexer.new(expression).tokens
|
|
23
|
+
@index = 0
|
|
24
|
+
@qualifier = nil #: untyped
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @rbs () -> untyped
|
|
28
|
+
def parse
|
|
29
|
+
return [:true] if peek.empty?
|
|
30
|
+
ast = expression
|
|
31
|
+
error("unexpected token #{peek.inspect}") unless peek.empty?
|
|
32
|
+
ast
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
# libpcap gives AND and OR the same precedence, left associative.
|
|
38
|
+
# @rbs () -> untyped
|
|
39
|
+
def expression
|
|
40
|
+
node = unary
|
|
41
|
+
while %w[and && or ||].include?(peek)
|
|
42
|
+
operation = %w[and &&].include?(take) ? :and : :or
|
|
43
|
+
node = [operation, node, unary]
|
|
44
|
+
end
|
|
45
|
+
node
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @rbs () -> untyped
|
|
49
|
+
def unary
|
|
50
|
+
if %w[not !].include?(peek)
|
|
51
|
+
take
|
|
52
|
+
return [:not, unary]
|
|
53
|
+
end
|
|
54
|
+
if peek == '(' || peek == '-' || numeric?(peek) || CONSTANTS.key?(peek)
|
|
55
|
+
saved_index = @index
|
|
56
|
+
begin
|
|
57
|
+
return relation
|
|
58
|
+
rescue FilterSyntaxError
|
|
59
|
+
@index = saved_index
|
|
60
|
+
end
|
|
61
|
+
if peek == '('
|
|
62
|
+
take
|
|
63
|
+
node = expression
|
|
64
|
+
expect(')')
|
|
65
|
+
return node
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
return relation if peek == 'len' || @tokens[@index + 1]&.first == '['
|
|
69
|
+
|
|
70
|
+
primitive
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @rbs () -> untyped
|
|
74
|
+
def primitive
|
|
75
|
+
position = @tokens[@index][1]
|
|
76
|
+
if %w[vlan greater less broadcast multicast].include?(peek)
|
|
77
|
+
kind = take.to_sym
|
|
78
|
+
value = %i[broadcast multicast].include?(kind) || (kind == :vlan && !numeric?(peek)) ? nil : number
|
|
79
|
+
@qualifier = nil
|
|
80
|
+
return [:primitive, nil, :either, kind, value, position]
|
|
81
|
+
end
|
|
82
|
+
proto = PROTOCOLS.include?(peek) ? take : nil
|
|
83
|
+
if proto && %w[broadcast multicast].include?(peek)
|
|
84
|
+
@qualifier = nil
|
|
85
|
+
return [:primitive, proto, :either, take.to_sym, nil, position]
|
|
86
|
+
end
|
|
87
|
+
direction = nil
|
|
88
|
+
if %w[src dst].include?(peek)
|
|
89
|
+
direction = take.to_sym
|
|
90
|
+
if %w[and or].include?(peek) && %w[src dst].include?(@tokens[@index + 1]&.first)
|
|
91
|
+
operation = take
|
|
92
|
+
other = take
|
|
93
|
+
error('direction must combine src and dst') if other == direction.to_s
|
|
94
|
+
direction = operation == 'and' ? :both : :either
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
type = TYPES.include?(peek) ? take.to_sym : nil
|
|
98
|
+
if proto && !direction && !type
|
|
99
|
+
@qualifier = nil
|
|
100
|
+
return [:primitive, proto, :either, :protocol, nil, position]
|
|
101
|
+
end
|
|
102
|
+
explicit = proto || direction || type
|
|
103
|
+
qualifier = explicit ? [proto, direction || :either, type || :host] : @qualifier
|
|
104
|
+
qualifier ||= [nil, :either, :host]
|
|
105
|
+
@qualifier = qualifier
|
|
106
|
+
if peek == '('
|
|
107
|
+
take
|
|
108
|
+
node = expression
|
|
109
|
+
expect(')')
|
|
110
|
+
return node
|
|
111
|
+
end
|
|
112
|
+
value = take
|
|
113
|
+
error('expected a filter operand', position) if value.empty? || %w[and or && || )].include?(value)
|
|
114
|
+
if qualifier[2] == :portrange && peek == '-'
|
|
115
|
+
take
|
|
116
|
+
value += '-' + take
|
|
117
|
+
elsif qualifier[2] == :net && peek == 'mask'
|
|
118
|
+
take
|
|
119
|
+
value += ' mask ' + take
|
|
120
|
+
end
|
|
121
|
+
[:primitive, *qualifier, value, position]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# @rbs () -> untyped
|
|
125
|
+
def relation
|
|
126
|
+
left = arithmetic
|
|
127
|
+
error('expected an arithmetic comparison') unless COMPARISONS.include?(peek)
|
|
128
|
+
operator = take
|
|
129
|
+
right = arithmetic
|
|
130
|
+
@qualifier = nil
|
|
131
|
+
[:relation, left, operator, right]
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# @rbs (?Integer minimum) -> untyped
|
|
135
|
+
def arithmetic(minimum = 0)
|
|
136
|
+
position = @tokens[@index][1]
|
|
137
|
+
value = take
|
|
138
|
+
node = if value == '('
|
|
139
|
+
expression = arithmetic
|
|
140
|
+
expect(')')
|
|
141
|
+
expression
|
|
142
|
+
elsif value == '-'
|
|
143
|
+
[:neg, arithmetic(7)]
|
|
144
|
+
elsif value == 'len'
|
|
145
|
+
[:len]
|
|
146
|
+
elsif CONSTANTS.key?(value)
|
|
147
|
+
[:number, CONSTANTS.fetch(value)]
|
|
148
|
+
elsif numeric?(value)
|
|
149
|
+
[:number, integer(value)]
|
|
150
|
+
elsif PROTOCOLS.include?(value) && peek == '['
|
|
151
|
+
take
|
|
152
|
+
offset = arithmetic
|
|
153
|
+
size = peek == ':' ? (take; number) : 1
|
|
154
|
+
expect(']')
|
|
155
|
+
error('byte accessor width must be 1, 2 or 4', position) unless [1, 2, 4].include?(size)
|
|
156
|
+
[:access, value, offset, size]
|
|
157
|
+
else
|
|
158
|
+
error("invalid arithmetic operand #{value.inspect}", position)
|
|
159
|
+
end
|
|
160
|
+
while PRECEDENCE.fetch(peek, -1) >= minimum
|
|
161
|
+
operation = take
|
|
162
|
+
node = [:binary, operation, node, arithmetic(PRECEDENCE.fetch(operation) + 1)]
|
|
163
|
+
end
|
|
164
|
+
node
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# @rbs (String value) -> bool
|
|
168
|
+
def numeric?(value) = /\A(?:0[xX][\da-fA-F]+|\d+)\z/.match?(value)
|
|
169
|
+
|
|
170
|
+
# @rbs (String value) -> Integer
|
|
171
|
+
def integer(value)
|
|
172
|
+
Integer(value, value.start_with?('0') && value.length > 1 ? 0 : 10)
|
|
173
|
+
rescue ArgumentError
|
|
174
|
+
error("invalid integer #{value.inspect}")
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# @rbs () -> Integer
|
|
178
|
+
def number
|
|
179
|
+
value = take
|
|
180
|
+
error("expected a number, got #{value.inspect}") unless numeric?(value)
|
|
181
|
+
integer(value)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# @rbs () -> String
|
|
185
|
+
def peek = @tokens[@index][0]
|
|
186
|
+
|
|
187
|
+
# @rbs () -> String
|
|
188
|
+
def take
|
|
189
|
+
token = @tokens[@index][0]
|
|
190
|
+
@index += 1 unless token.empty?
|
|
191
|
+
token
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# @rbs (String value) -> void
|
|
195
|
+
def expect(value)
|
|
196
|
+
error("expected #{value.inspect}, got #{peek.inspect}") unless peek == value
|
|
197
|
+
take
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# @rbs (String message, ?Integer? position) -> bot
|
|
201
|
+
def error(message, position = nil)
|
|
202
|
+
raise FilterSyntaxError.new(message, expression: @expression, position: position || @tokens[@index][1])
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|