rbbcc 0.11.6 → 0.11.8

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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/lib/rbbcc/table.rb +8 -7
  4. data/lib/rbbcc/version.rb +1 -1
  5. data/rbbcc.gemspec +5 -2
  6. metadata +2 -62
  7. data/docs/README.md +0 -7
  8. data/docs/answers/01-hello-world.rb +0 -16
  9. data/docs/answers/02-sys_sync.rb +0 -18
  10. data/docs/answers/03-hello_fields.rb +0 -33
  11. data/docs/answers/04-sync_timing.rb +0 -46
  12. data/docs/answers/05-sync_count.rb +0 -54
  13. data/docs/answers/06-disksnoop.rb +0 -71
  14. data/docs/answers/07-hello_perf_output.rb +0 -59
  15. data/docs/answers/08-sync_perf_output.rb +0 -60
  16. data/docs/answers/09-bitehist.rb +0 -32
  17. data/docs/answers/10-disklatency.rb +0 -51
  18. data/docs/answers/11-vfsreadlat.c +0 -46
  19. data/docs/answers/11-vfsreadlat.rb +0 -66
  20. data/docs/answers/12-urandomread.rb +0 -38
  21. data/docs/answers/13-disksnoop_fixed.rb +0 -108
  22. data/docs/answers/14-strlen_count.rb +0 -46
  23. data/docs/answers/15-nodejs_http_server.rb +0 -44
  24. data/docs/answers/16-task_switch.c +0 -23
  25. data/docs/answers/16-task_switch.rb +0 -17
  26. data/docs/answers/node-server.js +0 -11
  27. data/docs/getting_started.md +0 -154
  28. data/docs/projects_using_rbbcc.md +0 -43
  29. data/docs/tutorial_bcc_ruby_developer.md +0 -774
  30. data/docs/tutorial_bcc_ruby_developer_japanese.md +0 -770
  31. data/examples/bitehist.rb +0 -46
  32. data/examples/charty/Gemfile +0 -11
  33. data/examples/charty/Gemfile.lock +0 -48
  34. data/examples/charty/bitehist-unicode.rb +0 -87
  35. data/examples/collectsyscall.rb +0 -182
  36. data/examples/dddos.rb +0 -112
  37. data/examples/disksnoop.rb +0 -73
  38. data/examples/dns_blocker.rb +0 -134
  39. data/examples/example.gif +0 -0
  40. data/examples/extract_arg.rb +0 -26
  41. data/examples/hello_fields.rb +0 -32
  42. data/examples/hello_perf_output.rb +0 -64
  43. data/examples/hello_ring_buffer.rb +0 -64
  44. data/examples/hello_world.rb +0 -6
  45. data/examples/kvm_hypercall.rb +0 -69
  46. data/examples/lsm_sockblock.rb +0 -141
  47. data/examples/mallocstack.rb +0 -63
  48. data/examples/networking/http_filter/http-parse-simple.c +0 -114
  49. data/examples/networking/http_filter/http-parse-simple.rb +0 -85
  50. data/examples/pin_maps_a.rb +0 -88
  51. data/examples/pin_maps_b.rb +0 -71
  52. data/examples/py-orig/sockblock.py +0 -119
  53. data/examples/ringbuf_pin_a.rb +0 -51
  54. data/examples/ringbuf_pin_b.rb +0 -29
  55. data/examples/ruby_usdt.rb +0 -105
  56. data/examples/sbrk_trace.rb +0 -204
  57. data/examples/ssl_http_trace.rb +0 -274
  58. data/examples/syscalluname.rb +0 -39
  59. data/examples/table.rb +0 -15
  60. data/examples/tools/bashreadline.rb +0 -83
  61. data/examples/tools/execsnoop.rb +0 -229
  62. data/examples/tools/runqlat.rb +0 -148
  63. data/examples/urandomread-explicit.rb +0 -58
  64. data/examples/urandomread.rb +0 -39
  65. data/examples/usdt-test.rb +0 -6
  66. data/examples/usdt.rb +0 -26
data/examples/bitehist.rb DELETED
@@ -1,46 +0,0 @@
1
- #!/usr/bin/env ruby
2
- #
3
- # bitehist.rb, ported from bitehist.py. See license on that file
4
- #
5
- # Written as a basic example of using histograms to show a distribution.
6
- #
7
- # A Ctrl-C will print the gathered histogram then exit.
8
- #
9
-
10
- require 'rbbcc'
11
- include RbBCC
12
-
13
- b = BCC.new(text: <<CLANG)
14
- #include <uapi/linux/ptrace.h>
15
- #include <linux/blkdev.h>
16
-
17
- BPF_HISTOGRAM(dist);
18
- BPF_HISTOGRAM(dist_linear);
19
-
20
- int kprobe__blk_account_io_done(struct pt_regs *ctx, struct request *req)
21
- {
22
- dist.increment(bpf_log2l(req->__data_len / 1024));
23
- dist_linear.increment(req->__data_len / 1024);
24
- return 0;
25
- }
26
- CLANG
27
-
28
- puts "Tracing... Hit Ctrl-C to end."
29
-
30
- loop do
31
- begin
32
- sleep 0.1
33
- rescue Interrupt
34
- puts
35
- break
36
- end
37
- end
38
-
39
- puts "log2 histogram"
40
- puts "~~~~~~~~~~~~~~"
41
- b["dist"].print_log2_hist("kbytes")
42
-
43
- puts
44
- puts "linear histogram"
45
- puts "~~~~~~~~~~~~~~~~"
46
- b["dist_linear"].print_linear_hist("kbytes")
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source "https://rubygems.org"
4
-
5
- git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
6
-
7
- gem "rbbcc", path: '../../'
8
- gem "charty", path: '/usr/local/ghq/github.com/udzura/charty'
9
- gem "unicode_plot"
10
- gem "numo"
11
- gem "matplotlib"
@@ -1,48 +0,0 @@
1
- PATH
2
- remote: ../..
3
- specs:
4
- rbbcc (0.2.0)
5
-
6
- PATH
7
- remote: /usr/local/ghq/github.com/udzura/charty
8
- specs:
9
- charty (0.2.2)
10
- red-colors
11
-
12
- GEM
13
- remote: https://rubygems.org/
14
- specs:
15
- enumerable-statistics (2.0.1)
16
- matplotlib (1.1.0)
17
- pycall (>= 1.0.0)
18
- numo (0.1.1)
19
- numo-fftw (~> 0.1.1)
20
- numo-gnuplot (~> 0.2.4)
21
- numo-gsl (~> 0.1.1)
22
- numo-linalg (~> 0.1.0)
23
- numo-narray (~> 0.9.1)
24
- numo-fftw (0.1.1)
25
- numo-narray (~> 0.9.0)
26
- numo-gnuplot (0.2.4)
27
- numo-gsl (0.1.2)
28
- numo-narray (>= 0.9.0.8)
29
- numo-linalg (0.1.5)
30
- numo-narray (>= 0.9.1.4)
31
- numo-narray (0.9.1.6)
32
- pycall (1.3.0)
33
- red-colors (0.1.1)
34
- unicode_plot (0.0.4)
35
- enumerable-statistics (>= 2.0.1)
36
-
37
- PLATFORMS
38
- ruby
39
-
40
- DEPENDENCIES
41
- charty!
42
- matplotlib
43
- numo
44
- rbbcc!
45
- unicode_plot
46
-
47
- BUNDLED WITH
48
- 2.0.2
@@ -1,87 +0,0 @@
1
- #!/usr/bin/env ruby
2
- #
3
- # bitehist.rb, ported from bitehist.py. See license on that file
4
- #
5
- # Written as a basic example of using histograms to show a distribution.
6
- #
7
- # A Ctrl-C will print the gathered histogram then exit.
8
- #
9
-
10
- require 'rbbcc'
11
- include RbBCC
12
-
13
- b = BCC.new(text: <<CLANG)
14
- #include <uapi/linux/ptrace.h>
15
- #include <linux/blkdev.h>
16
-
17
- BPF_HISTOGRAM(dist);
18
- BPF_HISTOGRAM(dist_linear);
19
-
20
- int kprobe__blk_account_io_completion(struct pt_regs *ctx, struct request *req)
21
- {
22
- dist.increment(bpf_log2l(req->__data_len / 1024));
23
- dist_linear.increment(req->__data_len / 1024);
24
- return 0;
25
- }
26
- CLANG
27
-
28
- puts "Tracing... Hit Ctrl-C to end."
29
-
30
- loop do
31
- begin
32
- sleep 0.1
33
- rescue Interrupt
34
- puts
35
- break
36
- end
37
- end
38
-
39
- require 'unicode_plot'
40
- def log2hist_unicode(table)
41
- strip_leading_zero = true
42
- vals = Array.new($log2_index_max) { 0 }
43
- data = {}
44
- table.each_pair do |k, v|
45
- vals[k.to_bcc_value] = v.to_bcc_value
46
- end
47
-
48
- log2_dist_max = 64
49
- idx_max = -1
50
- val_max = 0
51
-
52
- vals.each_with_index do |v, i|
53
- idx_max = i if v > 0
54
- val_max = v if v > val_max
55
- end
56
-
57
- (1...(idx_max + 1)).each do |i|
58
- low = (1 << i) >> 1
59
- high = (1 << i)
60
- if (low == high)
61
- low -= 1
62
- end
63
- val = vals[i]
64
-
65
- if strip_leading_zero
66
- if val
67
- data["[#{low}, #{high})"] = val
68
- strip_leading_zero = false
69
- end
70
- else
71
- data["[#{low}, #{high})"] = val
72
- end
73
- end
74
-
75
- unless data.empty?
76
- puts UnicodePlot.barplot(ylabel: "kbytes", data: data, title: "log2 histogram", color: :magenta).render
77
- else
78
- puts "No sample found."
79
- end
80
- end
81
-
82
- log2hist_unicode b["dist"]
83
-
84
- # puts
85
- # puts "linear histogram"
86
- # puts "~~~~~~~~~~~~~~~~"
87
- # b["dist_linear"].print_linear_hist("kbytes")
@@ -1,182 +0,0 @@
1
- #!/usr/bin/env ruby
2
- #
3
- # Example to use complecated structure in BPF Map key:
4
- # This program collects and shows raw syscall usage summary.
5
- #
6
- # Usage:
7
- # bundle exec ruby examples/collectsyscall.rb
8
- #
9
- # Output example:
10
- # Collecting syscalls...
11
- # ^C
12
- # PID=1098(maybe: gmain) --->
13
- # inotify_add_watch 4 0.019 ms
14
- # poll 1 0.000 ms
15
- #
16
- # PID=1114(maybe: dbus-daemon) --->
17
- # stat 12 0.021 ms
18
- # openat 3 0.015 ms
19
- # getdents 2 0.013 ms
20
- # recvmsg 2 0.006 ms
21
- # sendmsg 1 0.008 ms
22
- # close 1 0.002 ms
23
- # fstat 1 0.002 ms
24
- # epoll_wait 1 0.000 ms
25
- #
26
- # PID=1175(maybe: memcached) --->
27
- # epoll_wait 3 2012.455 ms
28
- #
29
- # PID=1213(maybe: redis-server) --->
30
- # read 64 0.736 ms
31
- # epoll_wait 32 3782.098 ms
32
- # openat 32 1.149 ms
33
- # getpid 32 0.074 ms
34
- # close 32 0.045 ms
35
- # ....
36
-
37
- require 'rbbcc'
38
- include RbBCC
39
-
40
- $pid = nil
41
- $comm = nil
42
-
43
- if ARGV.size == 2 &&
44
- ARGV[0] == '-p'
45
- $pid = ARGV[1].to_i
46
- elsif ARGV.size == 2 &&
47
- ARGV[0] == '-c'
48
- $comm = ARGV[1]
49
- elsif ARGV[0] == '-h' ||
50
- ARGV[0] == '--help'
51
- $stderr.puts "Usage: #{$0} [-p PID]"
52
- exit 1
53
- end
54
-
55
- SYSCALL_MAP = `ausyscall --dump`
56
- .lines
57
- .map{|l| l.chomp.split }
58
- .each_with_object(Hash.new) {|(k, v), ha| ha[k.to_i] = v }
59
-
60
- # if no ausyscall(8) then shows number itself
61
- # it is included in auditd package (e.g. Ubuntu)
62
- def to_name(nr)
63
- SYSCALL_MAP[nr] || nr.to_s
64
- end
65
-
66
- prog = <<BPF
67
- #include <uapi/linux/ptrace.h>
68
-
69
- struct key_t {
70
- u32 pid;
71
- u64 syscall_nr;
72
- };
73
- struct leaf_t{
74
- u64 count;
75
- u64 elapsed_ns;
76
- u64 enter_ns;
77
- char comm[16];
78
- };
79
- BPF_HASH(store, struct key_t, struct leaf_t);
80
-
81
- TRACEPOINT_PROBE(raw_syscalls, sys_enter) {
82
- struct key_t key = {0};
83
- struct leaf_t initial = {0}, *val_;
84
- char comm[16];
85
-
86
- // key.pid = bpf_get_current_pid_tgid();
87
- key.pid = (bpf_get_current_pid_tgid() >> 32);
88
- key.syscall_nr = args->id;
89
-
90
- DO_FILTER_BY_PID
91
- bpf_get_current_comm(&comm, sizeof(comm));
92
- DO_FILTER_BY_COMM
93
-
94
- val_ = store.lookup_or_try_init(&key, &initial);
95
- if (val_) {
96
- struct leaf_t val = *val_;
97
- val.count++;
98
- val.enter_ns = bpf_ktime_get_ns();
99
- bpf_get_current_comm(&val.comm, sizeof(val.comm));
100
- store.update(&key, &val);
101
- }
102
- return 0;
103
- }
104
-
105
- TRACEPOINT_PROBE(raw_syscalls, sys_exit) {
106
- struct key_t key = {0};
107
- struct leaf_t *val_;
108
-
109
- key.pid = (bpf_get_current_pid_tgid() >> 32);
110
- key.syscall_nr = args->id;
111
-
112
- val_ = store.lookup(&key);
113
- if (val_) {
114
- struct leaf_t val = *val_;
115
- if(val.enter_ns) {
116
- u64 delta = bpf_ktime_get_ns() - val.enter_ns;
117
- val.enter_ns = 0;
118
- val.elapsed_ns += delta;
119
- }
120
- store.update(&key, &val);
121
- }
122
- return 0;
123
- }
124
- BPF
125
-
126
- if $pid
127
- prog.sub!('DO_FILTER_BY_PID', <<~FILTER)
128
- if (key.pid != #{$pid}) return 0;
129
- FILTER
130
- else
131
- prog.sub!('DO_FILTER_BY_PID', '')
132
- end
133
-
134
- if $comm
135
- prog.sub!('DO_FILTER_BY_COMM', <<~FILTER)
136
- int idx;
137
- int matched = 1;
138
- char *needle = "#{$comm}";
139
- for(idx=0;idx<sizeof(comm);++idx) {
140
- if (comm[idx] != needle[idx]) matched = 0;
141
- if (!needle[idx]) break;
142
- if (!comm[idx]) break;
143
- }
144
- if(!matched) return 0;
145
- FILTER
146
- else
147
- prog.sub!('DO_FILTER_BY_COMM', '')
148
- end
149
-
150
- b = BCC.new(text: prog)
151
-
152
- puts "Collecting syscalls..."
153
- begin
154
- sleep(99999999)
155
- rescue Interrupt
156
- puts
157
- end
158
-
159
- info_by_pids = {}
160
- comms = {}
161
- store = b.get_table("store")
162
- store.items.each do |k, v|
163
- #require 'pry'; binding.pry
164
- count, elapsed_ns, _dummy, comm = v[0, 40].unpack("Q Q Q Z16")
165
- info_by_pids[k.pid] ||= {}
166
- info_by_pids[k.pid][k.syscall_nr] = {
167
- name: to_name(k.syscall_nr),
168
- count: count,
169
- elapsed_ms: elapsed_ns / 1000000.0
170
- }
171
- comms[k.pid] ||= comm
172
- end
173
-
174
- pids = info_by_pids.keys.sort
175
- pids.each do |pid|
176
- puts "PID=#{pid}(maybe: #{comms[pid]}) --->"
177
- i = info_by_pids[pid]
178
- i.to_a.sort_by {|k, v| [-v[:count], -v[:elapsed_ms]] }.each do |nr, record|
179
- puts "\t%<name>-22s %<count>7d %<elapsed_ms>10.3f ms" % record
180
- end
181
- puts
182
- end
data/examples/dddos.rb DELETED
@@ -1,112 +0,0 @@
1
- #!/usr/bin/env ruby
2
- #
3
- # dddos.rb DDOS dectection system, ported from python code.
4
- #
5
- # Written as a basic tracing example of using ePBF
6
- # to detect a potential DDOS attack against a system.
7
- #
8
- # See Copyright and License on bcc project:
9
- # examples/tracing/dddos.py
10
- #
11
- # 14-Jan-2019 Jugurtha BELKALEM Created dddos.py.
12
-
13
- require 'rbbcc'
14
- include RbBCC
15
-
16
- prog = """
17
- #include <linux/skbuff.h>
18
- #include <uapi/linux/ip.h>
19
-
20
- #define MAX_NB_PACKETS 1000
21
- #define LEGAL_DIFF_TIMESTAMP_PACKETS 1000000
22
-
23
- BPF_HASH(rcv_packets);
24
-
25
- struct detectionPackets {
26
- u64 nb_ddos_packets;
27
- };
28
-
29
- BPF_PERF_OUTPUT(events);
30
-
31
- int detect_ddos(struct pt_regs *ctx, void *skb){
32
- struct detectionPackets detectionPacket = {};
33
-
34
- // Used to count number of received packets
35
- u64 rcv_packets_nb_index = 0, rcv_packets_nb_inter=1, *rcv_packets_nb_ptr;
36
-
37
- // Used to measure elapsed time between 2 successive received packets
38
- u64 rcv_packets_ts_index = 1, rcv_packets_ts_inter=0, *rcv_packets_ts_ptr;
39
-
40
- /* The algorithm analyses packets received by ip_rcv function
41
- * and measures the difference in reception time between each packet.
42
- * DDOS flooders send millions of packets such that difference of
43
- * timestamp between 2 successive packets is so small
44
- * (which is not like regular applications behaviour).
45
- * This script looks for this difference in time and if it sees
46
- * more than MAX_NB_PACKETS succesive packets with a difference
47
- * of timestamp between each one of them less than
48
- * LEGAL_DIFF_TIMESTAMP_PACKETS ns,
49
- * ------------------ It Triggers an ALERT -----------------
50
- * Those settings must be adapted depending on regular network traffic
51
- * -------------------------------------------------------------------
52
- * Important: this is a rudimentary intrusion detection system, one can
53
- * test a real case attack using hping3. However; if regular network
54
- * traffic increases above predefined detection settings, a false
55
- * positive alert will be triggered (an example would be the
56
- case of large file downloads).
57
- */
58
- rcv_packets_nb_ptr = rcv_packets.lookup(&rcv_packets_nb_index);
59
- rcv_packets_ts_ptr = rcv_packets.lookup(&rcv_packets_ts_index);
60
- if(rcv_packets_nb_ptr != 0 && rcv_packets_ts_ptr != 0){
61
- rcv_packets_nb_inter = *rcv_packets_nb_ptr;
62
- rcv_packets_ts_inter = bpf_ktime_get_ns() - *rcv_packets_ts_ptr;
63
- if(rcv_packets_ts_inter < LEGAL_DIFF_TIMESTAMP_PACKETS){
64
- rcv_packets_nb_inter++;
65
- } else {
66
- rcv_packets_nb_inter = 0;
67
- }
68
- if(rcv_packets_nb_inter > MAX_NB_PACKETS){
69
- detectionPacket.nb_ddos_packets = rcv_packets_nb_inter;
70
- events.perf_submit(ctx, &detectionPacket, sizeof(detectionPacket));
71
- }
72
- }
73
- rcv_packets_ts_inter = bpf_ktime_get_ns();
74
- rcv_packets.update(&rcv_packets_nb_index, &rcv_packets_nb_inter);
75
- rcv_packets.update(&rcv_packets_ts_index, &rcv_packets_ts_inter);
76
- return 0;
77
- }
78
- """
79
-
80
- # Loads eBPF program
81
- b = BCC.new(text: prog)
82
-
83
- # Attach kprobe to kernel function and sets detect_ddos as kprobe handler
84
- b.attach_kprobe(event: "ip_rcv", fn_name: "detect_ddos")
85
-
86
- DetectionTimestamp = \
87
- Fiddle::Importer.struct(["unsigned long long nb_ddos_packets"])
88
-
89
- # Show message when ePBF stats
90
- puts("DDOS detector started ... Hit Ctrl-C to end!")
91
-
92
- puts("%-26s %-10s" % ["TIME(s)", "MESSAGE"])
93
-
94
- trigger_alert_event = lambda { |cpu, data, size|
95
- # data is raw Fiddle::Pointer instance
96
- event = DetectionTimestamp.malloc
97
- Fiddle::Pointer.new(event.to_ptr)[0, DetectionTimestamp.size] = data[0, DetectionTimestamp.size]
98
- puts("%-26s %s %d" % [
99
- Time.now,
100
- "DDOS Attack => nb of packets up to now : ",
101
- event.nb_ddos_packets])
102
- }
103
-
104
- # loop with callback to trigger_alert_event
105
- b["events"].open_perf_buffer(&trigger_alert_event)
106
- loop do
107
- begin
108
- b.perf_buffer_poll
109
- rescue Interrupt
110
- exit
111
- end
112
- end
@@ -1,73 +0,0 @@
1
- #!/usr/bin/env ruby
2
- #
3
- # disksnoop.rb Trace block device I/O: basic version of iosnoop.
4
- # For Linux, uses BCC, eBPF. Embedded C.
5
- # This is ported from original disksnoop.py
6
- #
7
- # Written as a basic example of tracing latency.
8
- #
9
- # Licensed under the Apache License, Version 2.0 (the "License")
10
- #
11
- # 11-Aug-2015 Brendan Gregg Created disksnoop.py
12
-
13
- require 'rbbcc'
14
- include RbBCC
15
-
16
- REQ_WRITE = 1 # from include/linux/blk_types.h
17
-
18
- # load BPF program
19
- b = BCC.new(text: <<CLANG)
20
- #include <uapi/linux/ptrace.h>
21
- #include <linux/blkdev.h>
22
-
23
- BPF_HASH(start, struct request *);
24
-
25
- void trace_start(struct pt_regs *ctx, struct request *req) {
26
- // stash start timestamp by request ptr
27
- u64 ts = bpf_ktime_get_ns();
28
-
29
- start.update(&req, &ts);
30
- }
31
-
32
- void trace_completion(struct pt_regs *ctx, struct request *req) {
33
- u64 *tsp, delta;
34
-
35
- tsp = start.lookup(&req);
36
- if (tsp != 0) {
37
- delta = bpf_ktime_get_ns() - *tsp;
38
- bpf_trace_printk("%d %x %d\\n", req->__data_len,
39
- req->cmd_flags, delta / 1000);
40
- start.delete(&req);
41
- }
42
- }
43
- CLANG
44
-
45
- if BCC.get_kprobe_functions('blk_start_request')
46
- b.attach_kprobe(event: "blk_start_request", fn_name: "trace_start")
47
- end
48
- b.attach_kprobe(event: "blk_mq_start_request", fn_name: "trace_start")
49
- b.attach_kprobe(event: "blk_account_io_completion", fn_name: "trace_completion")
50
-
51
- # header
52
- puts("%-18s %-2s %-7s %8s" % ["TIME(s)", "T", "BYTES", "LAT(ms)"])
53
-
54
- # format output
55
- loop do
56
- begin
57
- task, pid, cpu, flags, ts, msg = b.trace_fields
58
- bytes_s, bflags_s, us_s = msg.split
59
-
60
- if (bflags_s.to_i(16) & REQ_WRITE).nonzero?
61
- type_s = "W"
62
- elsif bytes_s == "0" # see blk_fill_rwbs() for logic
63
- type_s = "M"
64
- else
65
- type_s = "R"
66
- end
67
- ms = us_s.to_i.to_f / 1000
68
-
69
- puts("%-18.9f %-2s %-7s %8.2f" % [ts, type_s, bytes_s, ms])
70
- rescue Interrupt
71
- exit
72
- end
73
- end
@@ -1,134 +0,0 @@
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
- # Run idempotently
93
- system("sudo tc qdisc del dev #{interface} clsact 2>/dev/null")
94
- File.unlink(PIN_PATH) if File.exist?(PIN_PATH)
95
- end
96
-
97
- options = {domain: "example.com"}
98
- OptionParser.new { |opts|
99
- opts.banner = "Usage: #{$0} -i INTERFACE"
100
- opts.on("-i", "--interface IFACE", "Network interface to monitor (e.g. eth0)") do |v|
101
- options[:interface] = v
102
- end
103
- opts.on("-d", "--domain DOMAIN", "Domain to block (default: example.com)") do |v|
104
- options[:domain] = v
105
- end
106
- }.parse!
107
-
108
- iface = options[:interface] || abort("Error: Interface name is required")
109
-
110
- # Clean up any leftover state from a previous run
111
- cleanup_tc(iface)
112
-
113
- puts "[*] Compiling BPF program..."
114
- b = BCC.new(text: BPF_TEXT.call(options[:domain]))
115
- fn = b.load_func("block_dns", BPF::SCHED_CLS)
116
-
117
- # Pin the loaded BPF program so that `tc` can reference it by path
118
- puts "[*] Pinning BPF program to #{PIN_PATH} ..."
119
- BCC.pin!(fn, PIN_PATH)
120
-
121
- # Set up clsact qdisc and attach the pinned program to egress
122
- puts "[*] Attaching TC filter to #{iface} (egress) ..."
123
- setup_tc(iface)
124
- attach_tc(iface)
125
-
126
- puts "[*] Blocking DNS queries for #{options[:domain]} on #{iface}. Press Ctrl+C to stop."
127
- begin
128
- b.trace_print
129
- rescue Interrupt
130
- puts "\n[*] Shutting down..."
131
- ensure
132
- cleanup_tc(iface)
133
- puts "[*] Cleanup done."
134
- end
data/examples/example.gif DELETED
Binary file