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,224 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Redhound
|
|
5
|
+
# @api private
|
|
6
|
+
module Capture
|
|
7
|
+
# @api private
|
|
8
|
+
module Linux
|
|
9
|
+
# @api private
|
|
10
|
+
class PacketSocket < Source
|
|
11
|
+
attr_reader :socket, :linktype, :interfaces
|
|
12
|
+
|
|
13
|
+
# @rbs (interface: String | Integer | Interface, ?snaplen: Integer, ?promiscuous: bool, ?buffer_size: Integer?, ?direction: Symbol, ?filter: String?) -> void
|
|
14
|
+
def initialize(interface:, snaplen: 262_144, promiscuous: true, buffer_size: nil, direction: :inout, filter: nil)
|
|
15
|
+
super()
|
|
16
|
+
raise ArgumentError, 'snaplen must be between 1 and 16 MiB' unless snaplen.between?(1, File::Format::MAX_RECORD)
|
|
17
|
+
raise ArgumentError, 'direction must be in, out or inout' unless %i[in out inout].include?(direction)
|
|
18
|
+
|
|
19
|
+
@interface = interface.is_a?(Interface) ? interface : Interface.find(interface)
|
|
20
|
+
@interfaces = [@interface]
|
|
21
|
+
@snaplen, @direction = snaplen, direction
|
|
22
|
+
@linktype = @interface.linktype
|
|
23
|
+
@cooked = @linktype == 276
|
|
24
|
+
@socket = ::Socket.new(Constants::AF_PACKET, @cooked ? ::Socket::SOCK_DGRAM : ::Socket::SOCK_RAW, 0)
|
|
25
|
+
@interface_drops = interface_dropped
|
|
26
|
+
setup_receive_buffer(buffer_size)
|
|
27
|
+
attach_filter(filter ? Filter.compile(filter, linktype: @linktype, snaplen:, live: true) : nil)
|
|
28
|
+
if promiscuous && @interface.name == 'any'
|
|
29
|
+
warn 'redhound: promiscuous mode is unavailable on any'
|
|
30
|
+
elsif promiscuous
|
|
31
|
+
@socket.setsockopt(Constants::SOL_PACKET, Constants::PACKET_ADD_MEMBERSHIP, [@interface.index, Constants::PACKET_MR_PROMISC, 0, ''.b].pack('i S S a8'))
|
|
32
|
+
end
|
|
33
|
+
@socket.bind(SockaddrLL.new(index: @interface.index).to_sockaddr)
|
|
34
|
+
prime_timestamp
|
|
35
|
+
warn_vlan_offload
|
|
36
|
+
if direction == :in
|
|
37
|
+
begin
|
|
38
|
+
@socket.setsockopt(Constants::SOL_PACKET, Constants::PACKET_IGNORE_OUTGOING, [1].pack('i'))
|
|
39
|
+
rescue Errno::ENOPROTOOPT, Errno::EINVAL
|
|
40
|
+
# Older kernels are filtered by packet direction below.
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
rescue Errno::EPERM, Errno::EACCES => error
|
|
44
|
+
close_resources
|
|
45
|
+
raise PermissionDenied, "capture requires root or CAP_NET_RAW: #{error.message}"
|
|
46
|
+
rescue StandardError
|
|
47
|
+
close_resources
|
|
48
|
+
raise
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @rbs (?timeout: Numeric?) -> Packet?
|
|
52
|
+
def next_packet(timeout: nil)
|
|
53
|
+
deadline = deadline_for(timeout)
|
|
54
|
+
polled = false
|
|
55
|
+
until @stopped || @closed
|
|
56
|
+
return nil if polled && deadline && Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
|
|
57
|
+
polled = true
|
|
58
|
+
result = @socket.recvfrom_nonblock(Constants::RECEIVE_SIZE, exception: false)
|
|
59
|
+
if result == :wait_readable
|
|
60
|
+
return nil unless wait_readable(@socket, deadline)
|
|
61
|
+
|
|
62
|
+
next
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
data, address_info = result
|
|
66
|
+
address = SockaddrLL.parse(address_info.to_sockaddr)
|
|
67
|
+
next unless accept_direction?(address)
|
|
68
|
+
|
|
69
|
+
original_length = data.bytesize
|
|
70
|
+
data = Cooked.header(address) + data if @cooked
|
|
71
|
+
original_length += 20 if @cooked
|
|
72
|
+
timestamp_ns = timestamp
|
|
73
|
+
meta = { pkttype: address.pkttype, csum_not_ready: address.hatype == 772 || address.pkttype == Constants::PACKET_OUTGOING }
|
|
74
|
+
meta[:possible_truncation] = true if original_length >= Constants::RECEIVE_SIZE + (@cooked ? 20 : 0)
|
|
75
|
+
@number += 1
|
|
76
|
+
@capture_stats.captured += 1
|
|
77
|
+
return Packet.new(data.byteslice(0, @snaplen), timestamp_ns:, original_length:, linktype: @linktype,
|
|
78
|
+
interface: packet_interface(address), direction: address.pkttype == Constants::PACKET_OUTGOING ? :out : :in, number: @number, meta:)
|
|
79
|
+
end
|
|
80
|
+
nil
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# @rbs (untyped program) -> void
|
|
84
|
+
def attach_filter(program)
|
|
85
|
+
instructions = program ? program.instructions : [[0x06, 0, 0, receive_snaplen]] #: Array[[Integer, Integer, Integer, Integer]]
|
|
86
|
+
Filter::BPF::Validator.validate!(instructions)
|
|
87
|
+
instructions = receive_filter(instructions)
|
|
88
|
+
packed = instructions.map { |instruction| instruction.pack('S C C L') }.join
|
|
89
|
+
@socket.setsockopt(::Socket::SOL_SOCKET, Constants::SO_ATTACH_FILTER, [instructions.size, packed].pack('S x6 P'))
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# @rbs () -> Stats
|
|
93
|
+
def stats
|
|
94
|
+
unless @closed
|
|
95
|
+
counters = @socket.getsockopt(Constants::SOL_PACKET, Constants::PACKET_STATISTICS).data.unpack('L*')
|
|
96
|
+
@capture_stats.received += counters.fetch(0, 0)
|
|
97
|
+
@capture_stats.dropped += counters.fetch(1, 0)
|
|
98
|
+
@capture_stats.freeze_count += counters.fetch(2, 0)
|
|
99
|
+
@capture_stats.if_dropped = [interface_dropped - @interface_drops, 0].max
|
|
100
|
+
end
|
|
101
|
+
@capture_stats
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# @rbs () -> void
|
|
105
|
+
def close
|
|
106
|
+
return if @closed
|
|
107
|
+
|
|
108
|
+
begin
|
|
109
|
+
stats
|
|
110
|
+
ensure
|
|
111
|
+
super
|
|
112
|
+
close_resources
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
protected
|
|
117
|
+
|
|
118
|
+
# Positive filter returns select packets; the source applies its configured snaplen.
|
|
119
|
+
# @rbs (Array[[Integer, Integer, Integer, Integer]] instructions) -> Array[[Integer, Integer, Integer, Integer]]
|
|
120
|
+
def receive_filter(instructions)
|
|
121
|
+
unless instructions.any? { |instruction| instruction[0] == 0x16 }
|
|
122
|
+
return instructions.map do |code, jt, jf, k|
|
|
123
|
+
[code, jt, jf, code == 0x06 && k.positive? ? receive_snaplen : k]
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
assembler = Filter::BPF::Assembler.new
|
|
128
|
+
labels = instructions.map { assembler.label }
|
|
129
|
+
accepted, rejected = assembler.label, assembler.label
|
|
130
|
+
instructions.each_with_index do |(code, jt, jf, k), index|
|
|
131
|
+
assembler.mark(labels.fetch(index))
|
|
132
|
+
if code == 0x16
|
|
133
|
+
assembler.branch(0x15, 0, rejected, accepted)
|
|
134
|
+
elsif code == 5
|
|
135
|
+
assembler.jump(labels.fetch(index + 1 + k))
|
|
136
|
+
elsif Filter::BPF::Validator::JUMPS.include?(code)
|
|
137
|
+
assembler.branch(code, k, labels.fetch(index + 1 + jt), labels.fetch(index + 1 + jf))
|
|
138
|
+
else
|
|
139
|
+
assembler.emit(code, code == 6 && k.positive? ? receive_snaplen : k)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
assembler.mark(accepted)
|
|
143
|
+
assembler.emit(6, receive_snaplen)
|
|
144
|
+
assembler.mark(rejected)
|
|
145
|
+
assembler.emit(6, 0)
|
|
146
|
+
result = assembler.assemble
|
|
147
|
+
Filter::BPF::Validator.validate!(result)
|
|
148
|
+
result
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# @rbs () -> void
|
|
152
|
+
def warn_vlan_offload
|
|
153
|
+
return unless instance_of?(PacketSocket) && [1, 276].include?(@linktype) && !@interface.loopback?
|
|
154
|
+
|
|
155
|
+
name = @interface.name == 'any' ? '<interface>' : @interface.name
|
|
156
|
+
warn "redhound: socket capture cannot restore stripped VLAN tags; disable receive VLAN offload with ethtool -K #{name} rxvlan off on the receiving interface"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# @rbs () -> void
|
|
160
|
+
def close_resources
|
|
161
|
+
@socket.close if @socket && !@socket.closed?
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# @rbs () -> void
|
|
165
|
+
def prime_timestamp
|
|
166
|
+
@socket.ioctl(Constants::SIOCGSTAMPNS, "\0" * 16)
|
|
167
|
+
rescue Errno::ENOENT, Errno::ENOTTY, Errno::EINVAL
|
|
168
|
+
nil
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# @rbs () -> Integer
|
|
172
|
+
def receive_snaplen = Constants::RECEIVE_SIZE
|
|
173
|
+
|
|
174
|
+
# @rbs (Integer? buffer_size) -> void
|
|
175
|
+
def setup_receive_buffer(buffer_size)
|
|
176
|
+
return unless buffer_size
|
|
177
|
+
raise ArgumentError, 'buffer size must be positive' unless buffer_size.positive?
|
|
178
|
+
|
|
179
|
+
@socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_RCVBUF, [buffer_size].pack('i'))
|
|
180
|
+
begin
|
|
181
|
+
@socket.setsockopt(::Socket::SOL_SOCKET, Constants::SO_RCVBUFFORCE, [buffer_size].pack('i'))
|
|
182
|
+
rescue Errno::EPERM, Errno::EACCES, Errno::ENOPROTOOPT => error
|
|
183
|
+
warn "redhound: SO_RCVBUFFORCE unavailable (#{error.message}); using SO_RCVBUF with the system limit"
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# @rbs (SockaddrLL address) -> bool
|
|
188
|
+
def accept_direction?(address)
|
|
189
|
+
outgoing = address.pkttype == Constants::PACKET_OUTGOING
|
|
190
|
+
return false if outgoing && address.hatype == 772 && @direction != :out
|
|
191
|
+
|
|
192
|
+
@direction == :inout || (@direction == :out) == outgoing
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# @rbs (SockaddrLL address) -> Interface
|
|
196
|
+
def packet_interface(address)
|
|
197
|
+
return @interface unless @interface.name == 'any'
|
|
198
|
+
|
|
199
|
+
@any_interfaces ||= Interface.all.to_h { |interface| [interface.index, interface] }
|
|
200
|
+
@any_interfaces.fetch(address.index) { Interface.new(name: "if#{address.index}", index: address.index, linktype: 276) }
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# @rbs () -> Integer
|
|
204
|
+
def interface_dropped
|
|
205
|
+
return 0 if @interface.name == 'any'
|
|
206
|
+
|
|
207
|
+
::File.read("/sys/class/net/#{@interface.name}/statistics/rx_dropped").to_i
|
|
208
|
+
rescue Errno::ENOENT, Errno::EACCES
|
|
209
|
+
0
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# @rbs () -> Integer
|
|
213
|
+
def timestamp
|
|
214
|
+
buffer = "\0" * 16
|
|
215
|
+
@socket.ioctl(Constants::SIOCGSTAMPNS, buffer)
|
|
216
|
+
seconds, nanos = buffer.unpack('q q') #: [Integer, Integer]
|
|
217
|
+
seconds * 1_000_000_000 + nanos
|
|
218
|
+
rescue Errno::ENOENT, Errno::ENOTTY, Errno::EINVAL
|
|
219
|
+
Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Redhound
|
|
5
|
+
# @api private
|
|
6
|
+
module Capture
|
|
7
|
+
# @api private
|
|
8
|
+
module Linux
|
|
9
|
+
# @api private
|
|
10
|
+
class SockaddrLL
|
|
11
|
+
attr_reader :protocol, :index, :hatype, :pkttype, :address
|
|
12
|
+
|
|
13
|
+
# @rbs (?protocol: Integer, ?index: Integer, ?hatype: Integer, ?pkttype: Integer, ?address: String) -> void
|
|
14
|
+
def initialize(protocol: Constants::ETH_P_ALL, index: 0, hatype: 0, pkttype: 0, address: ''.b)
|
|
15
|
+
@protocol, @index, @hatype, @pkttype, @address = protocol, index, hatype, pkttype, address.byteslice(0, 8)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @rbs () -> String
|
|
19
|
+
def to_sockaddr
|
|
20
|
+
[Constants::AF_PACKET, @protocol, @index, @hatype, @pkttype, @address.bytesize, @address].pack('S n l S C C a8')
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @rbs (String bytes) -> SockaddrLL
|
|
24
|
+
def self.parse(bytes)
|
|
25
|
+
raise CaptureError, 'truncated sockaddr_ll' if bytes.bytesize < 20
|
|
26
|
+
|
|
27
|
+
_family, protocol, index, hatype, pkttype, length, address = bytes.unpack('S n l S C C a8') #: [Integer, Integer, Integer, Integer, Integer, Integer, String]
|
|
28
|
+
new(protocol:, index:, hatype:, pkttype:, address: address.byteslice(0, [length, 8].min) || ''.b)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Redhound
|
|
5
|
+
# @api private
|
|
6
|
+
module Capture
|
|
7
|
+
# @api private
|
|
8
|
+
module Linux
|
|
9
|
+
# @api private
|
|
10
|
+
class TPacketV3 < PacketSocket
|
|
11
|
+
# @rbs (?timeout: Numeric?) -> Packet?
|
|
12
|
+
def next_packet(timeout: nil)
|
|
13
|
+
deadline = deadline_for(timeout)
|
|
14
|
+
polled = false
|
|
15
|
+
until @stopped || @closed
|
|
16
|
+
return nil if polled && deadline && Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
|
|
17
|
+
polled = true
|
|
18
|
+
unless @pending_count.positive?
|
|
19
|
+
base = @current_block * @block_size
|
|
20
|
+
unless (u32(base + 8) & Constants::TP_STATUS_USER).positive?
|
|
21
|
+
return nil unless wait_readable(@socket, deadline)
|
|
22
|
+
next
|
|
23
|
+
end
|
|
24
|
+
@block_end = base + u32(base + 20)
|
|
25
|
+
@pending_count = u32(base + 12)
|
|
26
|
+
@packet_offset = base + u32(base + 16)
|
|
27
|
+
if @pending_count.zero?
|
|
28
|
+
release_block
|
|
29
|
+
next
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
offset = @packet_offset
|
|
34
|
+
check_ring_bounds(offset, 68)
|
|
35
|
+
next_offset, seconds, nanos, caplen, original_length, status = @ring.get_string(offset, 24).unpack('L6')
|
|
36
|
+
mac_offset = @ring.get_value(:u16, offset + 24)
|
|
37
|
+
check_ring_bounds(offset + mac_offset, caplen)
|
|
38
|
+
data = @ring.get_string(offset + mac_offset, caplen)
|
|
39
|
+
address = SockaddrLL.parse(@ring.get_string(offset + 48, 20))
|
|
40
|
+
meta = { pkttype: address.pkttype, csum_not_ready: (status & Constants::TP_STATUS_CSUMNOTREADY).positive?,
|
|
41
|
+
csum_valid: (status & Constants::TP_STATUS_CSUM_VALID).positive? }
|
|
42
|
+
if (status & Constants::TP_STATUS_VLAN_VALID).positive?
|
|
43
|
+
tci = u32(offset + 32) & 0xffff
|
|
44
|
+
tpid = (status & Constants::TP_STATUS_VLAN_TPID_VALID).positive? ? @ring.get_value(:u16, offset + 36) : 0x8100
|
|
45
|
+
meta[:vlan_tci], meta[:vlan_tpid] = tci, tpid
|
|
46
|
+
if !@cooked && @linktype == 1
|
|
47
|
+
original_length += 4
|
|
48
|
+
data = data.byteslice(0, 12) + [tpid, tci].pack('n2') + data.byteslice(12..) if data.bytesize >= 12
|
|
49
|
+
elsif @cooked
|
|
50
|
+
data = [tci, address.protocol].pack('n2') + data
|
|
51
|
+
original_length += 4
|
|
52
|
+
address = SockaddrLL.new(protocol: tpid, index: address.index, hatype: address.hatype,
|
|
53
|
+
pkttype: address.pkttype, address: address.address)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
if @cooked
|
|
57
|
+
data = Cooked.header(address) + data
|
|
58
|
+
original_length += 20
|
|
59
|
+
end
|
|
60
|
+
@pending_count -= 1
|
|
61
|
+
if @pending_count.zero?
|
|
62
|
+
release_block
|
|
63
|
+
else
|
|
64
|
+
raise CaptureError, 'invalid ring packet offset' if next_offset < 68
|
|
65
|
+
@packet_offset += next_offset
|
|
66
|
+
end
|
|
67
|
+
next unless accept_direction?(address)
|
|
68
|
+
|
|
69
|
+
@number += 1
|
|
70
|
+
@capture_stats.captured += 1
|
|
71
|
+
return Packet.new(data.byteslice(0, @snaplen), timestamp_ns: seconds * 1_000_000_000 + nanos,
|
|
72
|
+
original_length:, linktype: @linktype, interface: packet_interface(address),
|
|
73
|
+
direction: address.pkttype == Constants::PACKET_OUTGOING ? :out : :in, number: @number, meta:)
|
|
74
|
+
end
|
|
75
|
+
nil
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
protected
|
|
79
|
+
|
|
80
|
+
# @rbs () -> void
|
|
81
|
+
def close_resources
|
|
82
|
+
begin
|
|
83
|
+
@ring.free if @ring
|
|
84
|
+
ensure
|
|
85
|
+
@ring = nil
|
|
86
|
+
super
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# @rbs () -> Integer
|
|
91
|
+
def receive_snaplen = @cooked ? [@snaplen - 20, 1].max : @snaplen
|
|
92
|
+
|
|
93
|
+
# @rbs (Integer? buffer_size) -> void
|
|
94
|
+
def setup_receive_buffer(buffer_size)
|
|
95
|
+
raise ArgumentError, 'buffer size must be positive' if buffer_size && !buffer_size.positive?
|
|
96
|
+
|
|
97
|
+
@block_size = 1 << 20
|
|
98
|
+
@block_size *= 2 while @block_size < @snaplen + 128
|
|
99
|
+
@block_count = buffer_size ? [(buffer_size.to_f / @block_size).ceil, 1].max : 8
|
|
100
|
+
frame_size = 2048
|
|
101
|
+
request = [@block_size, @block_count, frame_size, @block_size * @block_count / frame_size, 50, 0, 0].pack('L7')
|
|
102
|
+
@socket.setsockopt(Constants::SOL_PACKET, Constants::PACKET_VERSION, [Constants::TPACKET_V3].pack('i'))
|
|
103
|
+
@socket.setsockopt(Constants::SOL_PACKET, Constants::PACKET_RX_RING, request)
|
|
104
|
+
experimental = Warning[:experimental]
|
|
105
|
+
begin
|
|
106
|
+
Warning[:experimental] = false
|
|
107
|
+
@ring = IO::Buffer.map(@socket, @block_size * @block_count, 0)
|
|
108
|
+
rescue ArgumentError => error
|
|
109
|
+
raise UnsupportedPlatform, "Ruby #{RUBY_VERSION} IO::Buffer.map cannot map a packet socket: #{error.message}"
|
|
110
|
+
ensure
|
|
111
|
+
Warning[:experimental] = experimental
|
|
112
|
+
end
|
|
113
|
+
@current_block, @pending_count, @packet_offset, @block_end = 0, 0, 0, 0
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
private
|
|
117
|
+
|
|
118
|
+
# @rbs (Integer offset) -> Integer
|
|
119
|
+
def u32(offset) = @ring.get_value(:u32, offset)
|
|
120
|
+
|
|
121
|
+
# @rbs (Integer offset, Integer length) -> void
|
|
122
|
+
def check_ring_bounds(offset, length)
|
|
123
|
+
unless offset >= @current_block * @block_size && length >= 0 && offset + length <= @block_end && @block_end <= (@current_block + 1) * @block_size
|
|
124
|
+
raise CaptureError, 'invalid ring packet bounds'
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# @rbs () -> void
|
|
129
|
+
def release_block
|
|
130
|
+
@ring.set_value(:u32, @current_block * @block_size + 8, 0)
|
|
131
|
+
@current_block = (@current_block + 1) % @block_count
|
|
132
|
+
@pending_count = 0
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Redhound
|
|
5
|
+
# @api private
|
|
6
|
+
module Capture
|
|
7
|
+
# @api private
|
|
8
|
+
class Source
|
|
9
|
+
include Enumerable #[Packet]
|
|
10
|
+
|
|
11
|
+
# @rbs () -> void
|
|
12
|
+
def initialize
|
|
13
|
+
@stopped = false
|
|
14
|
+
@closed = false
|
|
15
|
+
@number = 0
|
|
16
|
+
@capture_stats = Stats.new
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @rbs () { (Packet) -> void } -> self
|
|
20
|
+
# @rbs () -> Enumerator[Packet, self]
|
|
21
|
+
def each_packet
|
|
22
|
+
return enum_for(:each_packet) unless block_given?
|
|
23
|
+
|
|
24
|
+
until @stopped || @closed
|
|
25
|
+
packet = next_packet(timeout: 0.1)
|
|
26
|
+
yield packet if packet
|
|
27
|
+
end
|
|
28
|
+
self
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
alias each each_packet
|
|
32
|
+
|
|
33
|
+
# @rbs (?timeout: Numeric?) -> Packet?
|
|
34
|
+
def next_packet(timeout: nil)
|
|
35
|
+
raise NotImplementedError
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @rbs () -> Stats
|
|
39
|
+
def stats = @capture_stats
|
|
40
|
+
|
|
41
|
+
# @rbs () -> bool
|
|
42
|
+
def stopped? = @stopped
|
|
43
|
+
|
|
44
|
+
# @rbs () -> void
|
|
45
|
+
def stop
|
|
46
|
+
@stopped = true
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @rbs () -> void
|
|
50
|
+
def close
|
|
51
|
+
stop
|
|
52
|
+
@closed = true
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
# @rbs (IO io, Numeric? deadline) -> bool
|
|
58
|
+
def wait_readable(io, deadline)
|
|
59
|
+
until @stopped || @closed
|
|
60
|
+
remaining = deadline && deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
61
|
+
return !!IO.select([io], nil, nil, 0) if remaining && remaining <= 0
|
|
62
|
+
return true if IO.select([io], nil, nil, remaining ? [remaining, 0.1].min : 0.1)
|
|
63
|
+
end
|
|
64
|
+
false
|
|
65
|
+
rescue IOError, Errno::EBADF
|
|
66
|
+
return false if @closed || @stopped
|
|
67
|
+
|
|
68
|
+
raise
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @rbs (Numeric? timeout) -> Numeric?
|
|
72
|
+
def deadline_for(timeout)
|
|
73
|
+
raise ArgumentError, 'timeout must be nonnegative' if timeout && timeout.negative?
|
|
74
|
+
|
|
75
|
+
timeout && Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Redhound
|
|
5
|
+
# @api private
|
|
6
|
+
module Capture
|
|
7
|
+
# @api private
|
|
8
|
+
class Stats
|
|
9
|
+
attr_accessor :received, :dropped, :if_dropped, :freeze_count, :captured
|
|
10
|
+
|
|
11
|
+
# @rbs (?received: Integer, ?dropped: Integer, ?if_dropped: Integer, ?freeze_count: Integer, ?captured: Integer) -> void
|
|
12
|
+
def initialize(received: 0, dropped: 0, if_dropped: 0, freeze_count: 0, captured: 0)
|
|
13
|
+
@received, @dropped, @if_dropped, @freeze_count, @captured = received, dropped, if_dropped, freeze_count, captured
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @rbs () -> Hash[Symbol, Integer]
|
|
17
|
+
def to_h
|
|
18
|
+
{ received: @received, dropped: @dropped, if_dropped: @if_dropped, freeze_count: @freeze_count, captured: @captured }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative 'capture/linktype'
|
|
5
|
+
require_relative 'capture/interface'
|
|
6
|
+
require_relative 'capture/stats'
|
|
7
|
+
require_relative 'capture/source'
|
|
8
|
+
require_relative 'capture/linux/constants'
|
|
9
|
+
require_relative 'capture/linux/sockaddr_ll'
|
|
10
|
+
require_relative 'capture/linux/ifreq'
|
|
11
|
+
require_relative 'capture/linux/cooked'
|
|
12
|
+
require_relative 'capture/linux/packet_socket'
|
|
13
|
+
require_relative 'capture/linux/tpacket_v3'
|
|
14
|
+
require_relative 'capture/bsd/constants'
|
|
15
|
+
require_relative 'capture/bsd/ifreq'
|
|
16
|
+
require_relative 'capture/bsd/bpf_device'
|
|
17
|
+
require_relative 'file/format'
|
|
18
|
+
require_relative 'file/pcap_reader'
|
|
19
|
+
require_relative 'file/pcapng_reader'
|
|
20
|
+
require_relative 'file/pcap_writer'
|
|
21
|
+
require_relative 'file/pcapng_writer'
|
|
22
|
+
require_relative 'file/rotating_writer'
|
|
23
|
+
require_relative 'capture/file_source'
|
|
24
|
+
require_relative 'reader'
|
|
25
|
+
|
|
26
|
+
module Redhound
|
|
27
|
+
# @api private
|
|
28
|
+
module Capture
|
|
29
|
+
# @rbs (interface: String | Integer | Interface, ?backend: Symbol | String, ?snaplen: Integer, ?promiscuous: bool, ?buffer_size: Integer?, ?direction: Symbol, ?filter: String?) -> Source
|
|
30
|
+
# @rbs [T] (interface: String | Integer | Interface, ?backend: Symbol | String, ?snaplen: Integer, ?promiscuous: bool, ?buffer_size: Integer?, ?direction: Symbol, ?filter: String?) { (Source) -> T } -> T
|
|
31
|
+
def self.open(interface:, backend: :auto, snaplen: 262_144, promiscuous: true, buffer_size: nil, direction: :inout, filter: nil)
|
|
32
|
+
options = { snaplen:, promiscuous:, buffer_size:, direction:, filter: } #: Hash[Symbol, untyped]
|
|
33
|
+
backend = backend.to_sym
|
|
34
|
+
source = if RUBY_PLATFORM.include?('linux')
|
|
35
|
+
case backend
|
|
36
|
+
when :socket then Linux::PacketSocket.new(interface:, **options)
|
|
37
|
+
when :ring then Linux::TPacketV3.new(interface:, **options)
|
|
38
|
+
when :auto
|
|
39
|
+
if RUBY_PLATFORM.start_with?('x86_64-', 'aarch64-', 'arm64-')
|
|
40
|
+
begin
|
|
41
|
+
Linux::TPacketV3.new(interface:, **options)
|
|
42
|
+
rescue SystemCallError, IO::Buffer::AccessError, IO::Buffer::AllocationError, NotImplementedError, UnsupportedPlatform => error
|
|
43
|
+
warn "redhound: ring backend unavailable (#{error.message}); using socket"
|
|
44
|
+
Linux::PacketSocket.new(interface:, **options)
|
|
45
|
+
end
|
|
46
|
+
else
|
|
47
|
+
Linux::PacketSocket.new(interface:, **options)
|
|
48
|
+
end
|
|
49
|
+
else raise ArgumentError, "invalid Linux capture backend: #{backend}"
|
|
50
|
+
end
|
|
51
|
+
elsif RUBY_PLATFORM.include?('darwin')
|
|
52
|
+
raise ArgumentError, "invalid macOS capture backend: #{backend}" unless %i[auto bpf].include?(backend)
|
|
53
|
+
|
|
54
|
+
Bsd::BpfDevice.new(interface:, **options)
|
|
55
|
+
else
|
|
56
|
+
raise UnsupportedPlatform, "unsupported capture platform: #{RUBY_PLATFORM}"
|
|
57
|
+
end
|
|
58
|
+
return source unless block_given?
|
|
59
|
+
|
|
60
|
+
begin
|
|
61
|
+
yield source
|
|
62
|
+
ensure
|
|
63
|
+
source.close
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @rbs () -> Array[Interface]
|
|
68
|
+
def self.interfaces = Interface.all
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Redhound
|
|
5
|
+
# @api private
|
|
6
|
+
module CLI
|
|
7
|
+
# @api private
|
|
8
|
+
class Command
|
|
9
|
+
# @rbs (Array[String] argv, ?out: untyped, ?err: untyped) -> Integer
|
|
10
|
+
def run(argv, out: $stdout, err: $stderr)
|
|
11
|
+
options = Options.new(argv)
|
|
12
|
+
values = options.values
|
|
13
|
+
values[:plugins].each { |path| require ::File.expand_path(path) }
|
|
14
|
+
if values[:help]
|
|
15
|
+
out.puts(options.parser)
|
|
16
|
+
elsif values[:version]
|
|
17
|
+
out.puts("Redhound #{VERSION}")
|
|
18
|
+
elsif values[:list_interfaces]
|
|
19
|
+
Capture.interfaces.each { |i| out.puts(i.to_s) }
|
|
20
|
+
elsif values[:list_protocols]
|
|
21
|
+
Registry.default.protocols.each_value do |klass|
|
|
22
|
+
fields = klass.compiled_header&.definitions&.map(&:name) || []
|
|
23
|
+
out.puts("#{klass.protocol_id}: #{klass.protocol_name} #{fields.join(' ')}")
|
|
24
|
+
end
|
|
25
|
+
else
|
|
26
|
+
Runner.new(values, out: out, err: err).run
|
|
27
|
+
end
|
|
28
|
+
0
|
|
29
|
+
rescue ConfigurationError, FilterError, ArgumentError => e
|
|
30
|
+
err.puts("redhound: #{e.message}")
|
|
31
|
+
if e.is_a?(FilterSyntaxError) && e.expression && e.position
|
|
32
|
+
err.puts(" #{e.expression}", " #{' ' * e.position}^")
|
|
33
|
+
end
|
|
34
|
+
2
|
|
35
|
+
rescue Error, SystemCallError, IOError, SocketError, LoadError => e
|
|
36
|
+
err.puts("redhound: #{e.message}")
|
|
37
|
+
err.puts(e.backtrace) if values && (values[:debug] || ENV['REDHOUND_DEBUG'] == '1')
|
|
38
|
+
1
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|