Carl 9 сар өмнө
parent
commit
e83885a277

+ 102 - 0
example/networking/net_monitor.php

@@ -0,0 +1,102 @@
+<?php
+
+if ($argc !== 2) {
+    echo "Usage: php {$argv[0]} <net_interface>\n";
+    echo "Example: php {$argv[0]} eno1\n";
+    exit(1);
+}
+
+$INTERFACE = $argv[1];
+define("OUTPUT_INTERVAL", 1);
+
+$bpf_text = <<<EOT
+#include <uapi/linux/ptrace.h>
+#include <net/sock.h>
+#include <bcc/proto.h>
+#include <linux/bpf.h>
+
+#define IP_TCP 6
+#define IP_UDP 17
+#define IP_ICMP 1
+#define ETH_HLEN 14
+
+BPF_PERF_OUTPUT(skb_events);
+BPF_HASH(packet_cnt, u64, long, 256);
+
+int packet_monitor(struct __sk_buff *skb) {
+    u8 *cursor = 0;
+    u32 saddr, daddr;
+    long* count = 0;
+    long one = 1;
+    u64 pass_value = 0;
+
+    struct ethernet_t *ethernet = cursor_advance(cursor, sizeof(*ethernet));
+    struct ip_t *ip = cursor_advance(cursor, sizeof(*ip));
+    if (ip->ver != 4)
+        return 0;
+    if (ip->nextp != IP_TCP)
+    {
+        if (ip -> nextp != IP_UDP)
+        {
+            if (ip -> nextp != IP_ICMP)
+                return 0;
+        }
+    }
+
+    saddr = ip -> src;
+    daddr = ip -> dst;
+
+    pass_value = saddr;
+    pass_value = pass_value << 32;
+    pass_value = pass_value + daddr;
+
+    count = packet_cnt.lookup(&pass_value);
+    if (count)  // check if this map exists
+        *count += 1;
+    else        // if the map for the key doesn't exist, create one
+        {
+            packet_cnt.update(&pass_value, &one);
+        }
+    return -1;
+}
+
+EOT;
+
+$b = new Bpf(["text" => $bpf_text]);
+$func = $b->load_func("packet_monitor", Bpf::SOCKET_FILTER);
+$b->attach_raw_socket($func, $INTERFACE);
+
+$packet_cnt = $b->get_table("packet_cnt");
+
+function decimal_to_ip($int) {
+    return long2ip($int);
+}
+
+while (true) {
+    sleep(OUTPUT_INTERVAL);
+
+    $items = $packet_cnt->values();
+    $time = date("Y-m-d H:i:s");
+
+    if (count($items) > 0) {
+        echo "\nCurrent packet stats:\n";
+    }
+
+    foreach ($items as $entry) {
+        $key = unpack("Q", $entry["key"])[1];
+        $val = unpack("l", $entry["value"])[1];
+
+        $src = ($key >> 32) & 0xFFFFFFFF;
+        $dst = $key & 0xFFFFFFFF;
+
+        echo sprintf(
+            "source: %s -> destination: %s count: %d time: %s\n",
+            decimal_to_ip($src),
+            decimal_to_ip($dst),
+            $val,
+            $time
+        );
+    }
+
+    $packet_cnt->clear();
+}

+ 1 - 1
example/tracing/biolatpcts.php

@@ -36,7 +36,7 @@ RAW_TRACEPOINT_PROBE(block_rq_complete)
 }
 EOT;
 
-$ebpf = new Ebpf($bpf_source);
+$ebpf = new Bpf(["text" => $bpf_source]);
 
 $cur_lat_100ms = $ebpf->lat_100ms;
 $cur_lat_1ms =$ebpf->lat_1ms;

+ 1 - 1
example/tracing/dddos.php

@@ -62,7 +62,7 @@ int detect_ddos(struct pt_regs *ctx, void *skb){
     return 0;
 }
 EOT;
-$b    = new Ebpf($prog);
+$b    = new Bpf(["text" => $prog]);
 
 $b->attach_kprobe("ip_rcv", "detect_ddos");
 

+ 69 - 0
example/tracing/disksnoop.php

@@ -0,0 +1,69 @@
+<?php
+
+define("REQ_WRITE", 1); // from include/linux/blk_types.h
+
+$prog = <<<EOT
+#include <uapi/linux/ptrace.h>
+#include <linux/blk-mq.h>
+
+BPF_HASH(start, struct request *);
+
+void trace_start(struct pt_regs *ctx, struct request *req) {
+    u64 ts = bpf_ktime_get_ns();
+    start.update(&req, &ts);
+}
+
+void trace_completion(struct pt_regs *ctx, struct request *req) {
+    u64 *tsp, delta;
+    tsp = start.lookup(&req);
+    if (tsp != 0) {
+        delta = bpf_ktime_get_ns() - *tsp;
+        bpf_trace_printk("%d %x %d\\n", req->__data_len,
+                         req->cmd_flags, delta / 1000);
+        start.delete(&req);
+    }
+}
+EOT;
+
+$b = new Bpf(["text" => $prog]);
+
+// attach trace_start
+if ($b->get_kprobe_functions("blk_start_request")) {
+    $b->attach_kprobe("blk_start_request", "trace_start");
+}
+$b->attach_kprobe("blk_mq_start_request", "trace_start");
+
+// attach trace_completion
+if ($b->get_kprobe_functions("__blk_account_io_done")) {
+    $b->attach_kprobe("__blk_account_io_done", "trace_completion");
+} elseif ($b->get_kprobe_functions("blk_account_io_done")) {
+    $b->attach_kprobe("blk_account_io_done", "trace_completion");
+} else {
+    $b->attach_kprobe("blk_mq_end_request", "trace_completion");
+}
+
+printf("%-18s %-2s %-7s %8s\n", "TIME(s)", "T", "BYTES", "LAT(ms)");
+
+$start = 0;
+
+while (true) {
+    $fields = $b->trace_fields();
+    list($task, $pid, $cpu, $flags, $ts, $msg) = $fields;
+
+    $parts = preg_split('/\s+/', $msg);
+
+    if (count($parts) < 3) continue;
+
+    list($bytes_s, $bflags_s, $us_s) = $parts;
+
+    $bflags = intval($bflags_s, 16);
+
+    if ($bflags & REQ_WRITE) {
+        $type_s = "W";
+    } else {
+        $type_s = "R";
+    }
+    $ms = intval($us_s) / 1000.0;
+
+    printf("%-18.9f %-2s %-7s %8.2f\n", $ts, $type_s, $bytes_s, $ms);
+}

+ 1 - 1
example/tracing/hello_fields.php

@@ -6,7 +6,7 @@ int hello(void *ctx) {
 }
 EOT;
 # load BPF program
-$ebpf = new Ebpf($prog);
+$ebpf = new Bpf(["text" => $prog]);
 $ebpf->attach_kprobe($ebpf->get_syscall_fnname("clone"),"hello");
 # header
 printf("%-18s %-16s %-6s %s\n", "TIME(s)", "COMM", "PID", "MESSAGE");

+ 1 - 1
example/tracing/hello_perf_output.php

@@ -24,7 +24,7 @@ int hello(struct pt_regs *ctx) {
 EOT;
 
 # load BPF program
-$b    = new Ebpf($prog);
+$b    = new Bpf(["text" => $prog]);
 $b->attach_kprobe($b->get_syscall_fnname("clone"), "hello");
 
 # header

+ 1 - 1
example/tracing/hello_perf_output_using_ns.php

@@ -32,7 +32,7 @@ $prog = str_replace("DEV", strval($dev), $prog);
 $prog = str_replace("INO", strval($ino), $prog);
 
 # load BPF program
-$b    = new Ebpf($prog);
+$b    = new Bpf(["text" => $prog]);
 $b->attach_kprobe($b->get_syscall_fnname("clone"), "hello");
 
 # header

+ 1 - 1
example/tracing/kvm_hypercall.php

@@ -37,7 +37,7 @@ EOT;
 
 
 # load BPF program
-$b = new Ebpf($prog);
+$b = new Bpf(["text" => $prog]);
 
 # header
 printf("%-18s %-16s %-6s %s\n", "TIME(s)", "COMM", "PID", "EVENT");

+ 1 - 1
example/tracing/mallocstacks.php

@@ -26,7 +26,7 @@ int alloc_enter(struct pt_regs *ctx, size_t size) {
 };
 EOT;
 
-$ebpf = new Ebpf($bpf_text);
+$ebpf = new Bpf(["text" => $bpf_text]);
 $ebpf->attach_uprobe("c", "malloc", "alloc_enter", ["pid" => $pid]);
 echo "Attaching to malloc in pid {$pid}, Ctrl+C to quit.\n";
 

+ 1 - 1
example/tracing/setuid_monitor.php

@@ -27,7 +27,7 @@ TRACEPOINT_PROBE(syscalls, sys_enter_setuid) {
 EOT;
 
 # load BPF program
-$b    = new Ebpf($prog);
+$b    = new Bpf(["text" => $prog]);
 
 # header
 printf("%-14s %-12s %-6s %s\n", "TIME(s)", "COMMAND", "PID", "UID");

+ 1 - 1
example/tracing/stacksnoop.php

@@ -42,7 +42,7 @@ void trace_stack(struct pt_regs *ctx) {
 }
 EOT;
 
-$b = new Ebpf($prog);
+$b = new Bpf(["text" => $prog]);
 $b->attach_kprobe($function, "trace_stack");
 
 $stack_traces = $b->get_table("stack_traces");

+ 1 - 1
example/tracing/strlen_count.php

@@ -24,7 +24,7 @@ int count(struct pt_regs *ctx) {
 };
 EOT;
 
-$b = new Ebpf($bpf_text);
+$b = new Bpf(["text" => $bpf_text]);
 $b->attach_uprobe("c", "strlen", "count");
 
 echo "Tracing strlen()... Hit Ctrl-C to end.\n";

+ 43 - 0
example/tracing/sync_timing.php

@@ -0,0 +1,43 @@
+<?php
+$prog = <<<EOT
+#include <uapi/linux/ptrace.h>
+
+BPF_HASH(last);
+
+int do_trace(struct pt_regs *ctx) {
+    u64 ts, *tsp, delta, key = 0;
+
+    // attempt to read stored timestamp
+    tsp = last.lookup(&key);
+    if (tsp != NULL) {
+        delta = bpf_ktime_get_ns() - *tsp;
+        if (delta < 1000000000) {
+            // output if time is less than 1 second
+            bpf_trace_printk("%d\\n", delta / 1000000);
+        }
+        last.delete(&key);
+    }
+
+    // update stored timestamp
+    ts = bpf_ktime_get_ns();
+    last.update(&key, &ts);
+    return 0;
+}
+EOT;
+
+$b = new Bpf(["text" => $prog]);
+
+$b->attach_kprobe($b->get_syscall_fnname("sync"), "do_trace");
+
+echo "Tracing for quick sync's... Ctrl-C to end\n";
+
+$start = 0;
+
+while (true) {
+    list($task, $pid, $cpu, $flags, $ts, $ms) =  $b->trace_fields();
+
+    if ($start === 0) $start = $ts;
+    $ts -= $start;
+
+    printf("At time %.2f s: multiple syncs detected, last %s ms ago\n", $ts, $ms);
+}

+ 1 - 1
example/tracing/tcpv4connect.php

@@ -49,7 +49,7 @@ int kretprobe__tcp_v4_connect(struct pt_regs *ctx)
 }
 EOT;
 
-$ebpf = new Ebpf($bpf_text);
+$ebpf = new Bpf(["text" => $bpf_text]);
 # header
 printf("%-6s %-12s %-16s %-16s %-4s\n", "PID", "COMM", "SADDR", "DADDR","DPORT");
 # format output

+ 1 - 1
example/tracing/tp.php

@@ -16,7 +16,7 @@ int test(struct trace_event_raw_sys_enter_rw__stub* ctx)
 }
 EOT;
 
-$ebpf = new Ebpf($bpf_text);
+$ebpf = new Bpf(["text" => $bpf_text]);
 $ebpf->attach_tracepoint("syscalls:sys_enter_write","test");
 # header
 printf("%-18s %-16s %-6s %s\n", "TIME(s)", "COMM", "PID", "MESSAGE");

+ 14 - 0
example/tracing/trace_fields.php

@@ -0,0 +1,14 @@
+<?php
+$prog = <<<EOT
+int hello(void *ctx) {
+    bpf_trace_printk("Hello, World!\\n");
+    return 0;
+}
+EOT;
+# load BPF program
+$b = new Bpf(["text" => $prog]);
+$b->attach_kprobe($b->get_syscall_fnname("clone"),"hello");
+# header
+echo sprintf("%s %s\n", "PID", "MESSAGE");
+# format output
+$b->trace_print("{1} {5}");

+ 1 - 1
example/tracing/trace_perf_output.php

@@ -27,7 +27,7 @@ int do_sys_clone(void *ctx) {
 }
 EOT;
 
-$ebpf = new Ebpf($prog);
+$ebpf = new Bpf(["text" => $prog]);
 $event_name = $ebpf->get_syscall_fnname("clone");
 $ebpf->attach_kprobe($event_name,"do_sys_clone");
 $ebpf->events->open_perf_buffer("trigger_alert_event");

+ 106 - 0
example/tracing/undump.php

@@ -0,0 +1,106 @@
+<?php
+$pid = null;
+$hexdump = false;
+
+foreach ($argv as $index => $arg) {
+    if ($arg === "-p" && isset($argv[$index + 1])) {
+        $pid = intval($argv[$index + 1]);
+    } elseif ($arg === "--hexdump") {
+        $hexdump = true;
+    }
+}
+
+$bpf_prog = <<<EOT
+#include <uapi/linux/ptrace.h>
+#include <net/sock.h>
+#include <bcc/proto.h>
+#include <linux/aio.h>
+#include <linux/socket.h>
+#include <linux/net.h>
+#include <linux/fs.h>
+#include <linux/mount.h>
+#include <linux/module.h>
+#include <net/sock.h>
+#include <net/af_unix.h>
+
+#define MAX_PKT 512
+struct recv_data_t {
+    u32 recv_len;
+    u8  pkt[MAX_PKT];
+};
+
+BPF_PERCPU_ARRAY(unix_data, struct recv_data_t, 1);
+BPF_PERF_OUTPUT(unix_recv_events);
+
+int trace_unix_stream_read_actor(struct pt_regs *ctx)
+{
+    u32 zero = 0;
+    int ret = PT_REGS_RC(ctx);
+    u64 pid_tgid = bpf_get_current_pid_tgid();
+    u32 pid = pid_tgid >> 32;
+    u32 tid = pid_tgid;
+
+    FILTER_PID
+
+    struct sk_buff *skb = (struct sk_buff *)PT_REGS_PARM1(ctx);
+    struct recv_data_t *data = unix_data.lookup(&zero);
+    if (!data)
+        return 0;
+
+    unsigned int data_len = skb->len;
+    if (data_len > MAX_PKT)
+        return 0;
+
+    void *iodata = (void *)skb->data;
+    data->recv_len = data_len;
+
+    bpf_probe_read(data->pkt, data_len, iodata);
+    unix_recv_events.perf_submit(ctx, data, data_len + sizeof(u32));
+
+    return 0;
+}
+EOT;
+
+if ($pid !== null) {
+    $filter = "if (pid != $pid) { return 0; }";
+    $bpf_prog = str_replace("FILTER_PID", $filter, $bpf_prog);
+} else {
+    $bpf_prog = str_replace("FILTER_PID", "", $bpf_prog);
+}
+// initialize BPF
+$b = new Bpf(["text" => $bpf_prog]);
+$b->attach_kprobe("unix_stream_read_actor", "trace_unix_stream_read_actor");
+
+echo $pid ? "Tracing PID $pid UNIX socket packets ... Hit Ctrl-C to end\n"
+          : "Tracing UNIX socket packets ... Hit Ctrl-C to end\n";
+
+function print_recv_pkg($cpu,$data,$size){
+    $recv_len = unpack("L", substr($data, 0, 4))[1];
+    $pkt = substr($data, 4, $recv_len);
+    global $pid;
+    global $hexdump;
+    if ($pid) {
+        echo "PID \033[1;31m$pid\033[0m ";
+    }
+
+    echo "Recv \033[1;31m$recv_len\033[0m bytes\n";
+    if ($hexdump) {
+        echo chunk_split(bin2hex($pkt), 32, "\n");
+    } else {
+        echo "    ";
+        for ($i = 0; $i < $recv_len; $i++) {
+            printf("%02x ", ord($pkt[$i]));
+            if (($i + 1) % 16 == 0) echo "\n    ";
+        }
+        echo "\n";
+    }
+}
+$b->unix_recv_events->open_perf_buffer("print_recv_pkg");
+
+while (true) {
+    try {
+        $b->perf_buffer_poll();
+    } catch (Exception $e) {
+        exit();
+    }
+}

+ 1 - 1
example/tracing/uprobe.php

@@ -9,7 +9,7 @@ int test(struct pt_regs *ctx)
 }
 EOT;
 
-$ebpf = new Ebpf($bpf_text);
+$ebpf = new Bpf(["text" => $bpf_text]);
 // $arr = array("pid"=>-1);
 $ebpf->attach_uprobe("/opt/github/phpcpp_helloworld/a.out","add","test",[]);
 // $ebpf->trace_print();

+ 30 - 0
example/tracing/urandomread-explicit.php

@@ -0,0 +1,30 @@
+<?php
+$bpf_text = <<<EOT
+#include <uapi/linux/ptrace.h>
+
+struct urandom_read_args {
+    u64 __unused__;
+    u32 got_bits;
+    u32 pool_left;
+    u32 input_left;
+};
+
+int printarg(struct urandom_read_args *args) {
+    bpf_trace_printk("%d\\n", args->got_bits);
+    return 0;
+}
+EOT;
+
+$b = new Bpf(["text" => $bpf_text]);
+$b->attach_tracepoint("random:urandom_read", "printarg");
+
+echo sprintf("%-18s %-16s %-6s %s\n", "TIME(s)", "COMM", "PID", "GOTBITS");
+
+while (true) {
+    try {
+        list($task, $pid, $cpu, $flags, $ts, $msg) = $ebpf->trace_fields();
+        printf("%-18.9f %-16s %-6d %s\n", $ts, $task, $pid, $msg);
+    } catch (Exception $e) {
+        break;
+    }
+}

+ 21 - 0
example/tracing/urandomread.php

@@ -0,0 +1,21 @@
+<?php
+$bpf_text = <<<EOT
+TRACEPOINT_PROBE(random, urandom_read) {
+    // args is from /sys/kernel/debug/tracing/events/random/urandom_read/format
+    bpf_trace_printk("%d\\n", args->got_bits);
+    return 0;
+}
+EOT;
+
+$b = new Bpf(["text" => $bpf_text]);
+
+echo sprintf("%-18s %-16s %-6s %s\n", "TIME(s)", "COMM", "PID", "GOTBITS");
+
+while (true) {
+    try {
+        list($task, $pid, $cpu, $flags, $ts, $msg) = $ebpf->trace_fields();
+        printf("%-18.9f %-16s %-6d %s\n", $ts, $task, $pid, $msg);
+    } catch (Exception $e) {
+        break;
+    }
+}

+ 10 - 0
function.cpp

@@ -0,0 +1,10 @@
+//
+// Created by Carl.Guo on 2025/4/17.
+//
+
+#include "function.h"
+
+Php::Value ProgFunc::__get(const Php::Value &name)
+{
+	return Php::Base::__get(name);
+}

+ 31 - 0
function.h

@@ -0,0 +1,31 @@
+//
+// Created by Carl.Guo on 2025/4/17.
+//
+
+#ifndef PHPCPP_HELLOWORLD_FUNCTION_H
+#define PHPCPP_HELLOWORLD_FUNCTION_H
+
+#include <iostream>
+#include <utility>
+#include <phpcpp.h>
+#include "bpf_module.h"
+#include "BPF.h"
+
+class ProgFunc : public Php::Base {
+
+public:
+	ebpf::BPF *bpf;
+	std::string _name;
+	int _fd;
+
+	ProgFunc(ebpf::BPF *bpf_instance, std::string name, int fd)
+			: bpf(bpf_instance), _name(std::move(name)), _fd(fd) {
+	}
+
+	virtual ~ProgFunc() = default;
+
+	Php::Value __get(const Php::Value &name);
+};
+
+
+#endif //PHPCPP_HELLOWORLD_FUNCTION_H

+ 157 - 24
main.cpp

@@ -7,11 +7,16 @@
 #include <unordered_set>
 #include <regex>
 #include <phpcpp.h>
-#include "bcc_common.h"
-#include "bpf_module.h"
-#include "libbpf.h"
 #include <BPF.h>
+#include <string>
+
+#include "main.h"
+#include "libbpf.h"
+#include "bpf_module.h"
+#include "bcc_common.h"
 #include "table.h"
+#include "function.h"
+
 
 #define TRACE_PIPE_PATH "/sys/kernel/debug/tracing/trace_pipe"
 
@@ -175,19 +180,15 @@ public:
 				continue;
 			}
 
-			// 过滤掉 NOKPROBE_SYMBOL() 定义的 _kbl_addr_*
 			if (func_name.rfind("_kbl_addr_", 0) == 0) {
 				continue;
 			}
-			// 过滤掉 perf 相关函数
 			if (func_name.rfind("__perf", 0) == 0 || func_name.rfind("perf_", 0) == 0) {
 				continue;
 			}
-			// 过滤掉 __SCT__ 前缀的静态函数
 			if (func_name.rfind("__SCT__", 0) == 0) {
 				continue;
 			}
-			// 过滤掉 GCC 8 编译生成的 .cold 函数
 			if (std::regex_match(func_name, cold_regex)) {
 				continue;
 			}
@@ -208,25 +209,46 @@ public:
 		auto res = get_kprobe_functions(fn);
 		Php::Array result;
 		for (const auto &item: res) {
-			result[result.size()] = item; // 将每个匹配的函数名添加到 Php::Array
+			result[result.size()] = item;
 		}
 		return result;
 	}
 
 	Php::Value php_test(Php::Parameters &params) {
+
 		std::string fn = params[0].stringValue();
-		Php::out  << fn << std::endl;
+		Php::out << fn << std::endl;
 		auto res = get_kprobe_functions(fn);
 		Php::Array result;
-		for (const auto &item : res) {
-			result[result.size()] = item; // 将每个匹配的函数名添加到 Php::Array
+		for (const auto &item: res) {
+			result[result.size()] = item;
 		}
 		return result;
 	}
 
 	void __construct(Php::Parameters &params) {
-		std::string bpf_code = params[0].stringValue();
-		auto init_res = bpf.init(bpf_code);
+		std::string source;
+		if (params.size() == 1 && params[0].isArray()) {
+			Php::Array opts = params[0];
+			if (opts.contains("text")) {
+				source = opts.get("text").stringValue();
+			} else if (opts.contains("src_file")) {
+				std::string path = opts.get("src_file");
+				std::ifstream f(path);
+				if (!f) {
+					throw Php::Exception("Failed to open BPF source file: " + path);
+				}
+				std::stringstream buffer;
+				buffer << f.rdbuf();
+				source = buffer.str();
+			} else {
+				throw Php::Exception("Ebpf expects either 'text' or 'src_file' as options.");
+			}
+		} else {
+			throw Php::Exception("Ebpf constructor requires an array with 'text' or 'src_file'.");
+		}
+
+		auto init_res = bpf.init(source);
 		if (!init_res.ok()) {
 			throw Php::Exception("init error: " + init_res.msg());
 		}
@@ -361,17 +383,54 @@ public:
 		}
 	}
 
-	[[noreturn]] static void php_trace_print() {
+	static void php_trace_print(Php::Parameters &params) {
 		std::ifstream pipe(TRACE_PIPE_PATH);
-		std::string line;
 		if (!pipe.is_open()) {
 			throw Php::Exception("Failed to open trace_pipe");
 		}
 
-		std::cout << "Press Ctrl+C to stop..." << std::endl;
+		std::string fmt;
+		if (params.size() > 0) {
+			fmt = params[0].stringValue();
+		}
+
+		std::string line;
 		while (true) {
-			if (std::getline(pipe, line)) {
+			if (!std::getline(pipe, line)) {
+				continue;
+			}
+			if (line.empty() || line.rfind("CPU:", 0) == 0) {
+				continue;
+			}
+			std::string task = line.substr(0, 16);
+			task.erase(0, task.find_first_not_of(" "));
+
+			std::istringstream iss(line.substr(17));
+			std::string pid, cpu, flags, ts, msg;
+			char delim;
+
+			if (!(iss >> pid >> delim >> cpu >> delim >> flags >> ts)) {
+				continue;
+			}
+
+			size_t sym_end = iss.str().find(": ", iss.tellg());
+			if (sym_end != std::string::npos) {
+				msg = iss.str().substr(sym_end + 2);
+			}
+
+			std::vector<std::string> fields = {task, pid, cpu, flags, ts, msg};
+			if (fmt.empty()) {
 				std::cout << line << std::endl;
+			} else {
+				std::string output = fmt;
+				std::regex pattern(R"(\{(\d+)\})");
+				std::smatch match;
+				while (std::regex_search(output, match, pattern)) {
+					int index = std::stoi(match[1]);
+					std::string replacement = (index >= 0 && index < (int) fields.size()) ? fields[index] : "";
+					output.replace(match.position(0), match.length(0), replacement);
+				}
+				std::cout << output << std::endl;
 			}
 		}
 	}
@@ -412,10 +471,10 @@ public:
 			Php::Array result;
 			result[0] = task;
 			result[1] = std::stoi(pid);
-			result[2] = std::stoi(cpu.substr(1, cpu.size() - 2)); // 去掉方括号
+			result[2] = std::stoi(cpu.substr(1, cpu.size() - 2));
 			result[3] = flags;
 			result[4] = std::stod(ts);
-			result[5] = msg;
+			result[5] = Php::Value(msg.c_str(), msg.size());
 			return result;
 		}
 		return Php::Value();
@@ -438,7 +497,8 @@ public:
 				return Php::Object("ProgArrayTable", new ProgArrayTable(&this->bpf, table_name));
 			case BPF_MAP_TYPE_PERF_EVENT_ARRAY:
 				if (_class_perf_event_obj.isNull()) {
-					_class_perf_event_obj = Php::Object("PerfEventArrayTable",new PerfEventArrayTable(&this->bpf, table_name));
+					_class_perf_event_obj = Php::Object("PerfEventArrayTable",
+					                                    new PerfEventArrayTable(&this->bpf, table_name));
 				}
 				return _class_perf_event_obj;
 			case BPF_MAP_TYPE_PERCPU_HASH:
@@ -506,6 +566,40 @@ public:
 		return bpf.get_syscall_fnname(name);
 	}
 
+	Php::Value php_load_func(Php::Parameters &params) {
+		std::string fn = params[0].stringValue();
+		auto prog_type = static_cast<bpf_prog_type>(params[1].numericValue());
+		int probe_fd;
+		auto res = bpf.load_func(fn, prog_type, probe_fd);
+		if (res.ok()) {
+			Php::Object prog_fn = Php::Object("BPFProgFunction", new ProgFunc(&this->bpf, fn, probe_fd));
+			prog_fn.set("name", fn);
+			prog_fn.set("fd", probe_fd);
+			return prog_fn;
+		} else {
+			throw Php::Exception(res.msg());
+		}
+	}
+
+	void php_attach_raw_socket(Php::Parameters &params) {
+		if (!params[0].isObject()) {
+			throw Php::Exception("First parameter must be a BPFProgFunction object.");
+		}
+		Php::Object prog_fn = params[0];
+		std::string interface = params[1].stringValue();
+		auto *prog = static_cast<ProgFunc *>(prog_fn.implementation());
+		if (prog == nullptr) {
+			throw Php::Exception("Invalid BPFProgFunction object.");
+		}
+		int sock = bpf_open_raw_sock(interface.c_str());
+		if (sock < 0) {
+			throw Php::Exception("Failed to open raw socket on interface: " + interface);
+		}
+		auto res = bpf_attach_socket(sock, prog->_fd);
+		if (res < 0) {
+			throw Php::Exception("Failed to attach BPF program to socket.");
+		}
+	}
 };
 
 std::string sanitize_str(std::string str, bool (*validator)(char),
@@ -540,11 +634,13 @@ std::string get_kprobe_event(const std::string &kernel_func,
 extern "C" {
 PHPCPP_EXPORT void *get_module() {
 	static Php::Extension extension("ebpf", "1.0.0");
-	Php::Class<EbpfExtension> ebpf_class("Ebpf");
-	ebpf_class.method<&EbpfExtension::php_trace_print>("trace_print");
+	Php::Class<EbpfExtension> ebpf_class("Bpf");
+	ebpf_class.method<&EbpfExtension::php_trace_print>("trace_print", {
+			Php::ByVal("fmt", Php::Type::String, false)
+	});
 	ebpf_class.method<&EbpfExtension::php_trace_fields>("trace_fields");
 	ebpf_class.method<&EbpfExtension::__construct>("__construct", {
-			Php::ByVal("bpf_code", Php::Type::String)
+			Php::ByVal("opt", Php::Type::Array)
 	});
 	ebpf_class.method<&EbpfExtension::php_attach_kprobe>("attach_kprobe", {
 			Php::ByVal("kernel_func", Php::Type::String),
@@ -601,11 +697,19 @@ PHPCPP_EXPORT void *get_module() {
 	ebpf_class.method<&EbpfExtension::php_get_kprobe_functions>("get_kprobe_functions", {
 			Php::ByVal("fn", Php::Type::String),
 	});
-	extension.add(std::move(ebpf_class));
+	ebpf_class.method<&EbpfExtension::php_load_func>("load_func", {
+			Php::ByVal("fn", Php::Type::String),
+			Php::ByVal("type", Php::Type::Numeric),
+	});
+	ebpf_class.method<&EbpfExtension::php_attach_raw_socket>("attach_raw_socket", {
+//			Php::ByVal("fn", Php::Type::Object),
+//			Php::ByVal("interface", Php::Type::String),
+	});
 
 	/*HashTable*/
 	Php::Class<HashTable> ebpf_hashtable_cls("HashTable");
 	ebpf_hashtable_cls.method<&HashTable::php_get_values>("values", {});
+	ebpf_hashtable_cls.method<&HashTable::php_clear>("clear",{});
 	/*ArrayTable*/
 	Php::Class<ArrayTable> ebpf_array_table_cls("ArrayTable");
 	ebpf_array_table_cls.method<&ArrayTable::php_get_value>("get_value", {
@@ -654,6 +758,34 @@ PHPCPP_EXPORT void *get_module() {
 	Php::Class<QueueStackTable> ebpf_queue_stack_table_cls("QueueStackTable");
 	/*RingBufTable*/
 	Php::Class<RingBufTable> ebpf_ringbuf_table_cls("RingBufTable");
+	/*ProgFunc*/
+	Php::Class<ProgFunc> ebpf_prog_func_cls("BPFProgFunction");
+	extension.add(std::move(ebpf_prog_func_cls));
+
+
+
+	/* const */
+	ebpf_class.constant("SOCKET_FILTER", BPFProgType::SOCKET_FILTER);
+	ebpf_class.constant("KPROBE", BPFProgType::KPROBE);
+	ebpf_class.constant("SCHED_CLS", BPFProgType::SCHED_CLS);
+	ebpf_class.constant("SCHED_ACT", BPFProgType::SCHED_ACT);
+	ebpf_class.constant("TRACEPOINT", BPFProgType::TRACEPOINT);
+	ebpf_class.constant("XDP", BPFProgType::XDP);
+	ebpf_class.constant("PERF_EVENT", BPFProgType::PERF_EVENT);
+	ebpf_class.constant("CGROUP_SKB", BPFProgType::CGROUP_SKB);
+	ebpf_class.constant("CGROUP_SOCK", BPFProgType::CGROUP_SOCK);
+	ebpf_class.constant("LWT_IN", BPFProgType::LWT_IN);
+	ebpf_class.constant("LWT_OUT", BPFProgType::LWT_OUT);
+	ebpf_class.constant("LWT_XMIT", BPFProgType::LWT_XMIT);
+	ebpf_class.constant("SOCK_OPS", BPFProgType::SOCK_OPS);
+	ebpf_class.constant("SK_SKB", BPFProgType::SK_SKB);
+	ebpf_class.constant("CGROUP_DEVICE", BPFProgType::CGROUP_DEVICE);
+	ebpf_class.constant("SK_MSG", BPFProgType::SK_MSG);
+	ebpf_class.constant("RAW_TRACEPOINT", BPFProgType::RAW_TRACEPOINT);
+	ebpf_class.constant("CGROUP_SOCK_ADDR", BPFProgType::CGROUP_SOCK_ADDR);
+	ebpf_class.constant("CGROUP_SOCKOPT", BPFProgType::CGROUP_SOCKOPT);
+	ebpf_class.constant("TRACING", BPFProgType::TRACING);
+	ebpf_class.constant("LSM", BPFProgType::LSM);
 
 	extension.add(std::move(ebpf_hashtable_cls));
 	extension.add(std::move(ebpf_array_table_cls));
@@ -673,6 +805,7 @@ PHPCPP_EXPORT void *get_module() {
 	extension.add(std::move(ebpf_map_in_maphash_table_cls));
 	extension.add(std::move(ebpf_queue_stack_table_cls));
 	extension.add(std::move(ebpf_ringbuf_table_cls));
+	extension.add(std::move(ebpf_class));
 	return extension;
 }
 }

+ 28 - 0
main.h

@@ -0,0 +1,28 @@
+#ifndef PHPCPP_HELLOWORLD_MAIN_H
+#define PHPCPP_HELLOWORLD_MAIN_H
+class BPFProgType {
+public:
+	static constexpr int SOCKET_FILTER     = 1;
+	static constexpr int KPROBE            = 2;
+	static constexpr int SCHED_CLS         = 3;
+	static constexpr int SCHED_ACT         = 4;
+	static constexpr int TRACEPOINT        = 5;
+	static constexpr int XDP               = 6;
+	static constexpr int PERF_EVENT        = 7;
+	static constexpr int CGROUP_SKB        = 8;
+	static constexpr int CGROUP_SOCK       = 9;
+	static constexpr int LWT_IN            = 10;
+	static constexpr int LWT_OUT           = 11;
+	static constexpr int LWT_XMIT          = 12;
+	static constexpr int SOCK_OPS          = 13;
+	static constexpr int SK_SKB            = 14;
+	static constexpr int CGROUP_DEVICE     = 15;
+	static constexpr int SK_MSG            = 16;
+	static constexpr int RAW_TRACEPOINT    = 17;
+	static constexpr int CGROUP_SOCK_ADDR  = 18;
+	static constexpr int CGROUP_SOCKOPT    = 25;
+	static constexpr int TRACING           = 26;
+	static constexpr int LSM               = 29;
+};
+
+#endif //PHPCPP_HELLOWORLD_MAIN_H

+ 5 - 0
table.cpp

@@ -96,6 +96,11 @@ Php::Value HashTable::php_get_values() {
 	return result;
 }
 
+void HashTable::php_clear() {
+	auto raw_table = bpf->get_table(tb_name);
+	raw_table.clear_table_non_atomic();
+}
+
 Php::Value PerCpuArrayTable::php_sum_value(Php::Parameters &param) {
 	auto index = param[0].numericValue();
 	std::vector<unsigned long> val;

+ 1 - 0
table.h

@@ -51,6 +51,7 @@ class HashTable : public BaseTable {
 public:
 	using BaseTable::BaseTable;
 	Php::Value php_get_values();
+	void php_clear();
 };
 
 class ArrayTable : public BaseTable {

+ 86 - 38
test.php

@@ -1,54 +1,102 @@
 <?php
+
+if ($argc !== 2) {
+    echo "Usage: php {$argv[0]} <net_interface>\n";
+    echo "Example: php {$argv[0]} eno1\n";
+    exit(1);
+}
+
+$INTERFACE = $argv[1];
+define("OUTPUT_INTERVAL", 1);
+
 $bpf_text = <<<EOT
 #include <uapi/linux/ptrace.h>
+#include <net/sock.h>
+#include <bcc/proto.h>
+#include <linux/bpf.h>
 
-struct key_t {
-    char c[80];
-};
-BPF_HASH(counts, struct key_t);
+#define IP_TCP 6
+#define IP_UDP 17
+#define IP_ICMP 1
+#define ETH_HLEN 14
 
-int count(struct pt_regs *ctx) {
-    if (!PT_REGS_PARM1(ctx))
-        return 0;
+BPF_PERF_OUTPUT(skb_events);
+BPF_HASH(packet_cnt, u64, long, 256);
 
-    struct key_t key = {};
-    u64 zero = 0, *val;
+int packet_monitor(struct __sk_buff *skb) {
+    u8 *cursor = 0;
+    u32 saddr, daddr;
+    long* count = 0;
+    long one = 1;
+    u64 pass_value = 0;
 
-    bpf_probe_read_user(&key.c, sizeof(key.c), (void *)PT_REGS_PARM1(ctx));
-    // could also use `counts.increment(key)`
-    val = counts.lookup_or_try_init(&key, &zero);
-    if (val) {
-      (*val)++;
+    struct ethernet_t *ethernet = cursor_advance(cursor, sizeof(*ethernet));
+    struct ip_t *ip = cursor_advance(cursor, sizeof(*ip));
+    if (ip->ver != 4)
+        return 0;
+    if (ip->nextp != IP_TCP)
+    {
+        if (ip -> nextp != IP_UDP)
+        {
+            if (ip -> nextp != IP_ICMP)
+                return 0;
+        }
     }
-    return 0;
-};
+
+    saddr = ip -> src;
+    daddr = ip -> dst;
+
+    pass_value = saddr;
+    pass_value = pass_value << 32;
+    pass_value = pass_value + daddr;
+
+    count = packet_cnt.lookup(&pass_value);
+    if (count)  // check if this map exists
+        *count += 1;
+    else        // if the map for the key doesn't exist, create one
+        {
+            packet_cnt.update(&pass_value, &one);
+        }
+    return -1;
+}
+
 EOT;
 
-$b = new Ebpf($bpf_text);
-$b->attach_uprobe("c", "strlen", "count");
+$b = new Bpf(["text" => $bpf_text]);
+$func = $b->load_func("packet_monitor", Bpf::SOCKET_FILTER);
+$b->attach_raw_socket($func, $INTERFACE);
 
-echo "Tracing strlen()... Hit Ctrl-C to end.\n";
-pcntl_signal(SIGINT, "signalHandler");
-pcntl_async_signals(true);
+$packet_cnt = $b->get_table("packet_cnt");
 
-# sleep until Ctrl-C
-while (true) {
-    sleep(99999999);
+function decimal_to_ip($int) {
+    return long2ip($int);
 }
 
-function signalHandler($signo)
-{
-    global $b;
-    switch ($signo) {
-        case SIGINT:
-            echo sprintf("%10s %s\n", "COUNT", "STRING");
-            $counts        = $b->get_table("counts");
-            $vals = $counts->values();
-            foreach ($vals as $v) {
-                $k = unpack("A80c", $v['key']);
-                $v = unpack("Qval", $v['value']);
-                printf("%10d \"%s\"\n", $v['val'],$k['c']);
-            }
-            exit(0);
+while (true) {
+    sleep(OUTPUT_INTERVAL);
+
+    $items = $packet_cnt->values();
+    $time = date("Y-m-d H:i:s");
+
+    if (count($items) > 0) {
+        echo "\nCurrent packet stats:\n";
     }
+
+    foreach ($items as $entry) {
+        $key = unpack("Q", $entry["key"])[1];
+        $val = unpack("l", $entry["value"])[1];
+
+        $src = ($key >> 32) & 0xFFFFFFFF;
+        $dst = $key & 0xFFFFFFFF;
+
+        echo sprintf(
+            "source: %s -> destination: %s count: %d time: %s\n",
+            decimal_to_ip($src),
+            decimal_to_ip($dst),
+            $val,
+            $time
+        );
+    }
+
+    $packet_cnt->clear();
 }