rbbcc 0.10.0 → 0.11.0.pre

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6550ee287fe7fb0e1e51112ed55345e2f7b314e9d581b8f28a166eec6f4542aa
4
- data.tar.gz: 9c826257aa21ad3ba3862eb7324683db36f9081d02b45b61a5c189ecb24f8836
3
+ metadata.gz: 1c5cbecb6f86bc56c109433915be6daea255c5578145372e7fef05e643c74f22
4
+ data.tar.gz: 73b4dcb08ed7882ffd905f44b3662e1f26fbc7075b9e832c29f44701304b1826
5
5
  SHA512:
6
- metadata.gz: 50cb035a4d0db91343599437ef2183b9a60eb138ee156ee406f2dd505e9725f8289f7ebdc6c6e04a07264d3eb6095a7a1c8b09a6b509840e6af03b767c38179d
7
- data.tar.gz: bc64019b6a2aa9d3be9c255e9f49c15f6313984e504b910306eff4ad7c410d478b5b106d24dd6ae489848c23e7ae274537d8b38fd59773e0a2f14518e601610f
6
+ metadata.gz: ce916936932a3a0bb00bf28b3bd67d55b2d2068e0d4d05308296d472306db0ffae3dd495a441189fdcfe0db0c29705a389f31781f0e3971f1ef349b1d0eb4a1f
7
+ data.tar.gz: 15da5d9cd65ec31dd5a3fd1b3b87ce76edf4c40805f2cb800bbb5178300ec003975aea1bff084e740a1a8820a8137701692c861188bd9b85cbd31a971caf5535
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rbbcc (0.10.0)
4
+ rbbcc (0.11.0.pre)
5
5
  fiddle
6
6
 
7
7
  GEM
@@ -23,6 +23,7 @@ GEM
23
23
  PLATFORMS
24
24
  aarch64-linux
25
25
  arm64-darwin-21
26
+ arm64-darwin-24
26
27
  arm64-darwin-25
27
28
 
28
29
  DEPENDENCIES
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # dns_blocker.rb Block DNS queries for a specified domain using TC/eBPF.
4
+ #
5
+ # Uses TC clsact qdisc and attaches a SCHED_CLS BPF program to the
6
+ # egress path. Because pyroute2 is unavailable in Ruby, the BPF
7
+ # program is pinned to /sys/fs/bpf and attached via the `tc` shell
8
+ # command.
9
+ #
10
+ # Usage (must be run as root):
11
+ # ruby dns_blocker.rb -i eth0 -d ruby-lang.org
12
+ #
13
+
14
+ require 'rbbcc'
15
+ require 'optparse'
16
+
17
+ include RbBCC
18
+
19
+ def domain_to_payload_check_code(domain)
20
+ # Convert a domain name to DNS wire format (length-prefixed labels)
21
+ # For example, "example.com" becomes "\\x07example\\x03com\\x00"
22
+ dns_expression = domain.split('.').map { |label| "#{label.length.chr}#{label}" }.join + "\x00"
23
+ c_check_code = dns_expression.chars.map.with_index { |c, i| "payload[offset+#{i}] == #{c.ord}" }.join(" &&\n ")
24
+ c_check_code
25
+ end
26
+
27
+ BPF_TEXT = ->(domain) {
28
+ <<~CLANG
29
+ // Some Hack :(
30
+ #define BPF_LOAD_ACQ -1
31
+ #define BPF_STORE_REL -2
32
+
33
+ #include <uapi/linux/bpf.h>
34
+ #include <uapi/linux/pkt_cls.h>
35
+ #include <linux/if_ether.h>
36
+ #include <linux/ip.h>
37
+ #include <linux/udp.h>
38
+
39
+ int block_dns(struct __sk_buff *skb) {
40
+ void *data = (void *)(long)skb->data;
41
+ void *data_end = (void *)(long)skb->data_end;
42
+
43
+ // Ethernet header check
44
+ struct ethhdr *eth = data;
45
+ if ((void *)(eth + 1) > data_end) return TC_ACT_OK;
46
+ if (eth->h_proto != bpf_htons(ETH_P_IP)) return TC_ACT_OK;
47
+
48
+ // IP header check
49
+ struct iphdr *ip = (void *)(eth + 1);
50
+ if ((void *)(ip + 1) > data_end) return TC_ACT_OK;
51
+ if (ip->protocol != IPPROTO_UDP) return TC_ACT_OK;
52
+
53
+ // UDP header check
54
+ struct udphdr *udp = (void *)ip + (ip->ihl * 4);
55
+ if ((void *)(udp + 1) > data_end) return TC_ACT_OK;
56
+
57
+ // Only care about port 53 (DNS) egress queries
58
+ if (udp->dest != bpf_htons(53)) return TC_ACT_OK;
59
+
60
+ // DNS payload boundary check: DNS header (12 bytes) + "example.com" wire format (13 bytes)
61
+ unsigned char *payload = (unsigned char *)(udp + 1);
62
+ if ((void *)(payload + 12 + 13) > data_end) return TC_ACT_OK;
63
+
64
+ // "example.com" in DNS wire format: \\x07example\\x03com\\x00
65
+ int offset = 12;
66
+ if (#{domain_to_payload_check_code(domain)}) {
67
+ bpf_trace_printk("Blocked DNS query for #{domain}\\n");
68
+ return TC_ACT_SHOT;
69
+ }
70
+
71
+ return TC_ACT_OK;
72
+ }
73
+ CLANG
74
+ }
75
+
76
+ PIN_PATH = "/sys/fs/bpf/dns_blocker_prog"
77
+
78
+ def setup_tc(interface)
79
+ # Run idempotently
80
+ system("sudo tc qdisc add dev #{interface} clsact 2>/dev/null")
81
+ end
82
+
83
+ def attach_tc(interface)
84
+ system(
85
+ "sudo `tc filter add dev #{interface} egress" +
86
+ " bpf pinned #{PIN_PATH} da",
87
+ exception: true
88
+ )
89
+ end
90
+
91
+ def cleanup_tc(interface)
92
+ system("sudo tc qdisc del dev #{interface} clsact", exception: true)
93
+ File.unlink(PIN_PATH) if File.exist?(PIN_PATH)
94
+ end
95
+
96
+ options = {domain: "example.com"}
97
+ OptionParser.new { |opts|
98
+ opts.banner = "Usage: #{$0} -i INTERFACE"
99
+ opts.on("-i", "--interface IFACE", "Network interface to monitor (e.g. eth0)") do |v|
100
+ options[:interface] = v
101
+ end
102
+ opts.on("-d", "--domain DOMAIN", "Domain to block (default: example.com)") do |v|
103
+ options[:domain] = v
104
+ end
105
+ }.parse!
106
+
107
+ iface = options[:interface] || abort("Error: Interface name is required")
108
+
109
+ # Clean up any leftover state from a previous run
110
+ cleanup_tc(iface)
111
+
112
+ puts "[*] Compiling BPF program..."
113
+ b = BCC.new(text: BPF_TEXT.call(options[:domain]))
114
+ fn = b.load_func("block_dns", BPF::SCHED_CLS)
115
+
116
+ # Pin the loaded BPF program so that `tc` can reference it by path
117
+ puts "[*] Pinning BPF program to #{PIN_PATH} ..."
118
+ BCC.pin!(fn, PIN_PATH)
119
+
120
+ # Set up clsact qdisc and attach the pinned program to egress
121
+ puts "[*] Attaching TC filter to #{iface} (egress) ..."
122
+ setup_tc(iface)
123
+ attach_tc(iface)
124
+
125
+ puts "[*] Blocking DNS queries for #{options[:domain]} on #{iface}. Press Ctrl+C to stop."
126
+ begin
127
+ b.trace_print
128
+ rescue Interrupt
129
+ puts "\n[*] Shutting down..."
130
+ ensure
131
+ cleanup_tc(iface)
132
+ puts "[*] Cleanup done."
133
+ end
data/lib/rbbcc/bcc.rb CHANGED
@@ -180,7 +180,7 @@ module RbBCC
180
180
  end
181
181
  orig_name = c.inspect
182
182
  c.define_singleton_method :inspect do
183
- orig_name.sub /(?=>$)/, " original_desc=#{desc.inspect}" rescue super
183
+ orig_name.sub(/(?=>$)/, " original_desc=#{desc.inspect}") rescue super
184
184
  end
185
185
  c
186
186
  end
@@ -218,6 +218,23 @@ module RbBCC
218
218
  fn[:sock] = sock
219
219
  fn
220
220
  end
221
+
222
+ #: (Integer | Hash[Symbol, untyped] fd, String path) -> String
223
+ def pin!(fd, path)
224
+ fd = fd[:fd] if fd.is_a?(Hash)
225
+ unless fd.is_a?(Integer) && fd >= 0
226
+ raise ArgumentError, "fd must exist and be a non-negative Integer"
227
+ end
228
+ unless path.is_a?(String) && !path.empty?
229
+ raise ArgumentError, "path must be a non-empty String"
230
+ end
231
+
232
+ res = Clib.bpf_obj_pin(fd, path)
233
+ if res < 0
234
+ raise SystemCallError.new("Failed to pin BPF object to %s" % path, Fiddle.last_error)
235
+ end
236
+ path
237
+ end
221
238
  end
222
239
 
223
240
  def initialize(text: "", src_file: nil, hdr_file: nil, debug: 0, cflags: [], usdt_contexts: [], allow_rlimit: 0, dev_name: nil)
@@ -279,7 +296,6 @@ module RbBCC
279
296
 
280
297
  def gen_args_from_usdt
281
298
  ptr = Clib.bcc_usdt_genargs(@usdt_contexts.map(&:context).pack('J*'), @usdt_contexts.size)
282
- code = ""
283
299
  if !ptr || ptr.null?
284
300
  return nil
285
301
  end
data/lib/rbbcc/clib.rb CHANGED
@@ -192,6 +192,7 @@ module RbBCC
192
192
 
193
193
  extern 'int bpf_open_raw_sock(const char *name)'
194
194
  extern 'int bpf_attach_socket(int sockfd, int progfd)'
195
+ extern 'int bpf_obj_pin(int fd, const char *pathname)'
195
196
  end
196
197
  end
197
198
 
data/lib/rbbcc/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module RbBCC
2
- VERSION = "0.10.0"
2
+ VERSION = "0.11.0.pre"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rbbcc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 0.11.0.pre
5
5
  platform: ruby
6
6
  authors:
7
7
  - Uchio Kondo
@@ -84,6 +84,7 @@ files:
84
84
  - examples/collectsyscall.rb
85
85
  - examples/dddos.rb
86
86
  - examples/disksnoop.rb
87
+ - examples/dns_blocker.rb
87
88
  - examples/example.gif
88
89
  - examples/extract_arg.rb
89
90
  - examples/hello_fields.rb
@@ -141,7 +142,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
141
142
  - !ruby/object:Gem::Version
142
143
  version: '0'
143
144
  requirements: []
144
- rubygems_version: 4.0.6
145
+ rubygems_version: 3.6.9
145
146
  specification_version: 4
146
147
  summary: BCC port for MRI
147
148
  test_files: []