Module: Vivarium
- Defined in:
- lib/vivarium.rb,
lib/vivarium/logger.rb,
lib/vivarium/version.rb
Defined Under Namespace
Classes: Daemon, Error, Event, Logger, MapStore, ObservationSession
Constant Summary
collapse
- PIN_DIR =
ENV.fetch("VIVARIUM_BPF_PIN_DIR", "/sys/fs/bpf/vivarium")
- CONFIG_ROOT_TARGETS_PIN =
File.join(PIN_DIR, "config_root_targets")
- CONFIG_SPAWNED_TARGETS_PIN =
File.join(PIN_DIR, "config_spawned_targets")
- CONFIG_TARGETS_PIN =
CONFIG_ROOT_TARGETS_PIN
- EVENT_INVOKED_PIN =
File.join(PIN_DIR, "event_invoked")
- EVENT_WRITE_POS_PIN =
File.join(PIN_DIR, "event_write_pos")
- EVENT_NAME_SIZE =
16
- EVENT_PAYLOAD_SIZE =
256
- EVENT_TS_SIZE =
8
- EVENT_STRUCT_SIZE =
288
- EVENT_TS_OFFSET =
0
- EVENT_PID_OFFSET =
8
- EVENT_NAME_OFFSET =
12
- EVENT_PAYLOAD_OFFSET =
28
- EVENT_CAPACITY =
1024
- VERSION =
"0.1.2"
Class Attribute Summary collapse
Class Method Summary
collapse
-
.build_observe_tracepoint(store, logger) ⇒ Object
-
.decode_bad_socket_payload(raw_payload) ⇒ Object
-
.decode_dns_qname(raw_payload) ⇒ Object
-
.decode_odd_socket_payload(raw_payload) ⇒ Object
-
.decode_sock_connect_payload(raw_payload) ⇒ Object
-
.filter_internal_frames? ⇒ Boolean
-
.observe(pin_dir: bpf_pin_dir, logger: nil, dest: $stdout, format: :human) ⇒ Object
-
.render_event_payload(event) ⇒ Object
-
.run_daemon!(argv = ARGV) ⇒ Object
-
.scoped_observe(pin_dir:, logger:, dest:, format:) ⇒ Object
-
.socket_const_name(prefix, value) ⇒ Object
-
.top_observe(pin_dir: bpf_pin_dir, logger: nil, dest: $stdout, format: :human) ⇒ Object
Class Attribute Details
.bpf_pin_dir ⇒ Object
37
38
39
|
# File 'lib/vivarium.rb', line 37
def bpf_pin_dir
@bpf_pin_dir || PIN_DIR
end
|
Class Method Details
.build_observe_tracepoint(store, logger) ⇒ Object
836
837
838
839
840
841
842
843
844
845
|
# File 'lib/vivarium.rb', line 836
def self.build_observe_tracepoint(store, logger)
TracePoint.new(:return, :c_return) do |tp|
events = store.drain_events
next if events.empty?
stack = caller_locations(2, 16)
stack = stack.reject { |loc| loc.path.to_s.include?("vivarium") } if filter_internal_frames?
logger.log(events, tp, stack)
end
end
|
.decode_bad_socket_payload(raw_payload) ⇒ Object
142
143
144
|
# File 'lib/vivarium.rb', line 142
def self.decode_bad_socket_payload(raw_payload)
decode_odd_socket_payload(raw_payload)
end
|
.decode_dns_qname(raw_payload) ⇒ Object
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
|
# File 'lib/vivarium.rb', line 73
def self.decode_dns_qname(raw_payload)
bytes = raw_payload.to_s.b.bytes
labels = []
idx = 0
while idx < bytes.length
length = bytes[idx]
break if length.nil? || length.zero?
break if length > 63
idx += 1
break if (idx + length) > bytes.length
label = bytes[idx, length].pack("C*")
labels << label
idx += length
end
return "" if labels.empty?
labels.join(".")
end
|
.decode_odd_socket_payload(raw_payload) ⇒ Object
117
118
119
120
121
122
123
124
125
126
127
128
|
# File 'lib/vivarium.rb', line 117
def self.decode_odd_socket_payload(raw_payload)
bytes = raw_payload.to_s.b
return "" if bytes.bytesize < 6
family = bytes[0, 2].unpack1("S<")
type = bytes[2, 2].unpack1("S<")
protocol = bytes[4, 2].unpack1("S<")
family_name = socket_const_name("AF_", family)
type_name = socket_const_name("SOCK_", type)
protocol_name = socket_const_name("IPPROTO_", protocol)
"family=#{family}(#{family_name}) type=#{type}(#{type_name}) protocol=#{protocol}(#{protocol_name})"
end
|
.decode_sock_connect_payload(raw_payload) ⇒ Object
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
|
# File 'lib/vivarium.rb', line 96
def self.decode_sock_connect_payload(raw_payload)
bytes = raw_payload.to_s.b
return "" if bytes.bytesize < 20
family = bytes[0, 2].unpack1("S<")
port = bytes[2, 2].unpack1("n")
addr = bytes[4, 16]
case family
when 2 ipv4 = addr[0, 4].bytes.join(".")
"#{ipv4}:#{port} (#{socket_const_name("AF_", family)})"
when 10 words = addr.unpack("n8")
ipv6 = words.map { |w| format("%x", w) }.join(":")
"[#{ipv6}]:#{port} (#{socket_const_name("AF_", family)})"
else
"family=#{family}(#{socket_const_name("AF_", family)}) port=#{port}"
end
end
|
.filter_internal_frames? ⇒ Boolean
847
848
849
850
851
852
|
# File 'lib/vivarium.rb', line 847
def self.filter_internal_frames?
value = ENV["VIVARIUM_FILTER_INTERNAL_FRAMES"]
return true if value.nil?
!%w[0 false off no].include?(value.strip.downcase)
end
|
.observe(pin_dir: bpf_pin_dir, logger: nil, dest: $stdout, format: :human) ⇒ Object
799
800
801
802
803
|
# File 'lib/vivarium.rb', line 799
def self.observe(pin_dir: bpf_pin_dir, logger: nil, dest: $stdout, format: :human)
return scoped_observe(pin_dir: pin_dir, logger: logger, dest: dest, format: format) { yield } if block_given?
top_observe(pin_dir: pin_dir, logger: logger, dest: dest, format: format)
end
|
.render_event_payload(event) ⇒ Object
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
|
# File 'lib/vivarium.rb', line 146
def self.render_event_payload(event)
case event.event_name
when "dns_req"
decoded = decode_dns_qname(event.payload)
decoded.empty? ? event.payload.inspect : decoded
when "sock_connect"
decoded = decode_sock_connect_payload(event.payload)
decoded.empty? ? event.payload.inspect : decoded
when "odd_socket"
decoded = decode_odd_socket_payload(event.payload)
decoded.empty? ? event.payload.inspect : decoded
else
event.payload.inspect
end
end
|
.run_daemon!(argv = ARGV) ⇒ Object
854
855
856
857
858
859
860
861
862
|
# File 'lib/vivarium.rb', line 854
def self.run_daemon!(argv = ARGV)
options = { pin_dir: bpf_pin_dir }
OptionParser.new do |opts|
opts.banner = "Usage: vivariumd [--pin-dir PATH]"
opts.on("--pin-dir PATH", "Pinned map directory") { |v| options[:pin_dir] = v }
end.parse!(argv)
Daemon.new(pin_dir: options[:pin_dir]).run
end
|
.scoped_observe(pin_dir:, logger:, dest:, format:) ⇒ Object
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
|
# File 'lib/vivarium.rb', line 820
def self.scoped_observe(pin_dir:, logger:, dest:, format:)
logger ||= Logger.new(dest: dest, format: format)
store = MapStore.new(pin_dir: pin_dir)
pid = Process.pid
store.register_pid(pid)
logger.info("scoped observing with pid=#{pid}")
tracer = build_observe_tracepoint(store, logger)
tracer.enable
yield
ensure
tracer&.disable
store&.unregister_pid(pid)
end
|
.socket_const_name(prefix, value) ⇒ Object
130
131
132
133
134
135
136
137
138
139
140
|
# File 'lib/vivarium.rb', line 130
def self.socket_const_name(prefix, value)
return "UNKNOWN" unless defined?(Socket)
key = Socket.constants.find do |name|
name.to_s.start_with?(prefix) && Socket.const_get(name) == value
rescue NameError
false
end
key ? key.to_s : "UNKNOWN"
end
|
.top_observe(pin_dir: bpf_pin_dir, logger: nil, dest: $stdout, format: :human) ⇒ Object
805
806
807
808
809
810
811
812
813
814
815
816
817
818
|
# File 'lib/vivarium.rb', line 805
def self.top_observe(pin_dir: bpf_pin_dir, logger: nil, dest: $stdout, format: :human)
logger ||= Logger.new(dest: dest, format: format)
store = MapStore.new(pin_dir: pin_dir)
pid = Process.pid
store.register_pid(pid)
logger.info("top-level observing with pid=#{pid}")
tracer = build_observe_tracepoint(store, logger)
tracer.enable
session = ObservationSession.new(store: store, pid: pid, tracer: tracer)
at_exit { session.stop }
session
end
|