33 Commits
v0.13 ... v0.15

Author SHA1 Message Date
springzfx
06da477373 bump version 2020-06-01 00:38:56 +08:00
springzfx
314ff64b8b remove uninitialized pid ref 2020-06-01 00:36:48 +08:00
springzfx
d1bf9620bf only process running program when execsnoop thread started 2020-05-30 15:54:58 +08:00
springzfx
b7ea8e441b execsnoop: leave alone if already in preserved cgroup
so make cgproxy and cgnoproxy command have highest priority
2020-05-30 15:30:47 +08:00
springzfx
b031aa8064 update readme 2020-05-30 03:52:22 +08:00
springzfx
8226db37e0 avoid unused variable and VLA 2020-05-30 03:46:36 +08:00
springzfx
5bbec4d1bf remove pthread.h, use CMAKE_DL_LIBS 2020-05-30 02:37:45 +08:00
springzfx
9b4ec44897 set timeout for thread start 2020-05-30 02:18:07 +08:00
springzfx
a1a1ed5bd9 linux-headers needed fot bpf init 2020-05-30 02:02:03 +08:00
springzfx
34e5d81329 change status signal place 2020-05-30 01:59:24 +08:00
springzfx
dfc688b5e5 linux-headers needed fot bpf init 2020-05-30 01:43:37 +08:00
springzfx
ec9609cec8 replace pthread with c++ thread 2020-05-30 01:43:30 +08:00
springzfx
cc83c1ae55 use deltype 2020-05-29 21:30:18 +08:00
springzfx
75751f4887 fix processing /proc/<pid>/cgroup 2020-05-29 17:43:12 +08:00
springzfx
af78ad2012 flush fprintf 2020-05-29 16:16:32 +08:00
springzfx
c32457a1aa robust detect already in correspond cgroup 2020-05-27 00:02:56 +08:00
springzfx
1d29828d1b remove path max len limit 2020-05-26 23:04:12 +08:00
springzfx
4fea0d39a2 add iptables output prefix 2020-05-26 00:10:14 +08:00
springzfx
badf282842 detect already in correspond cgroup 2020-05-26 00:05:38 +08:00
springzfx
41f856acd2 update deb depency 2020-05-25 19:37:23 +08:00
springzfx
45adf0a233 update man and readme 2020-05-25 19:20:56 +08:00
springzfx
1f4dd2fde2 update deb/rpm depency 2020-05-25 17:26:53 +08:00
springzfx
40bd709995 bump version 2020-05-25 17:22:40 +08:00
springzfx
221a75ae7b clang format 2020-05-25 17:13:47 +08:00
springzfx
16a341205f add with_execsnoop option 2020-05-25 16:53:34 +08:00
springzfx
076651b984 make execsnoop optional as module 2020-05-25 16:52:49 +08:00
springzfx
1c72a204a1 execsnoop as library 2020-05-25 16:37:57 +08:00
springzfx
f501c7e476 add execsnoop in c++ 2020-05-25 05:35:07 +08:00
springzfx
0ec9caefe1 fix [cgproxy --pid] not return early 2020-05-24 18:30:05 +08:00
springzfx
94b73b5103 execsnoop: add --debug arg 2020-05-24 18:27:33 +08:00
springzfx
c30df999b8 execsnoop: fix process path resolve 2020-05-24 01:11:09 +08:00
springzfx
932f2bbc94 updated readme 2020-05-23 16:28:35 +08:00
springzfx
1bbd1ab6ec updated readme 2020-05-23 16:25:20 +08:00
22 changed files with 600 additions and 267 deletions

View File

@@ -2,11 +2,15 @@ cmake_minimum_required(VERSION 3.10)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
project(cgproxy VERSION 0.13)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-result")
project(cgproxy VERSION 0.15)
add_compile_options(-Wall -Wextra -Wpedantic -Wno-unused-result -Wno-unused-parameter)
set(build_tools OFF)
set(build_test OFF)
# for clangd
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
option(with_execsnoop "enable program level proxy control feature, need bcc installed" ON)
option(build_tools OFF)
option(build_test OFF)
set(basic_permission OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
@@ -24,7 +28,6 @@ install(FILES cgnoproxy DESTINATION /usr/bin PERMISSIONS ${basic_permission})
install(FILES cgproxy.service DESTINATION /usr/lib/systemd/system/)
install(FILES config.json DESTINATION /etc/cgproxy/)
install(FILES cgroup-tproxy.sh DESTINATION /usr/share/cgproxy/scripts/ PERMISSIONS ${basic_permission})
install(FILES execsnoop.py DESTINATION /usr/share/cgproxy/scripts/ PERMISSIONS ${basic_permission})
install(FILES readme.md DESTINATION /usr/share/doc/cgproxy/)
# man pages

View File

@@ -31,7 +31,7 @@ DOC
}
## check root
[ ! $(id -u) -eq 0 ] && { >&2 echo "need root to modify iptables";exit -1; }
[ ! $(id -u) -eq 0 ] && { >&2 echo "iptables: need root to modify iptables";exit -1; }
## any process in this cgroup will be proxied
if [ -z ${cgroup_proxy+x} ]; then
@@ -73,7 +73,7 @@ cgroup_procs_file="cgroup.procs"
stop(){
iptables -t mangle -L TPROXY_PRE &> /dev/null || return
echo "cleaning tproxy iptables"
echo "iptables: cleaning tproxy iptables"
iptables -t mangle -D PREROUTING -j TPROXY_PRE
iptables -t mangle -D OUTPUT -j TPROXY_OUT
iptables -t mangle -F TPROXY_PRE
@@ -124,7 +124,7 @@ test -d $cgroup_mount_point$cgroup_proxy || mkdir $cgroup_mount_point$cgroup_
test -d $cgroup_mount_point$cgroup_noproxy || mkdir $cgroup_mount_point$cgroup_noproxy || exit -1;
echo "applying tproxy iptables"
echo "iptables: applying tproxy iptables"
## use TPROXY
#ipv4#
ip rule add fwmark $fwmark table $table
@@ -150,10 +150,10 @@ iptables -t mangle -A TPROXY_OUT -m connmark --mark $make_newin -j RETURN
iptables -t mangle -A TPROXY_OUT -m addrtype --dst-type LOCAL -j RETURN
iptables -t mangle -A TPROXY_OUT -m addrtype ! --dst-type UNICAST -j RETURN
for cg in ${cgroup_noproxy[@]}; do
iptables -t mangle -A TPROXY_OUT -m cgroup --path $cg -j RETURN
iptables -t mangle -A TPROXY_OUT -m cgroup --path $cg -j RETURN || { >&2 echo "iptables: $cg not exist, won't apply"; }
done
for cg in ${cgroup_proxy[@]}; do
iptables -t mangle -A TPROXY_OUT -m cgroup --path $cg -j MARK --set-mark $fwmark
iptables -t mangle -A TPROXY_OUT -m cgroup --path $cg -j MARK --set-mark $fwmark || { >&2 echo "iptables: $cg not exist, won't apply"; }
done
iptables -t mangle -A OUTPUT -j TPROXY_OUT
@@ -181,10 +181,10 @@ ip6tables -t mangle -A TPROXY_OUT -m connmark --mark $make_newin -j RETURN
ip6tables -t mangle -A TPROXY_OUT -m addrtype --dst-type LOCAL -j RETURN
ip6tables -t mangle -A TPROXY_OUT -m addrtype ! --dst-type UNICAST -j RETURN
for cg in ${cgroup_noproxy[@]}; do
ip6tables -t mangle -A TPROXY_OUT -m cgroup --path $cg -j RETURN
ip6tables -t mangle -A TPROXY_OUT -m cgroup --path $cg -j RETURN || { >&2 echo "iptables: $cg not exist, won't apply"; }
done
for cg in ${cgroup_proxy[@]}; do
ip6tables -t mangle -A TPROXY_OUT -m cgroup --path $cg -j MARK --set-mark $fwmark
ip6tables -t mangle -A TPROXY_OUT -m cgroup --path $cg -j MARK --set-mark $fwmark || { >&2 echo "iptables: $cg not exist, won't apply"; }
done
ip6tables -t mangle -A OUTPUT -j TPROXY_OUT
@@ -220,8 +220,8 @@ ip6tables -t mangle -I TPROXY_PRE -m addrtype ! --src-type LOCAL -m conntrack --
## message for user
cat << DOC
noproxy cgroup: ${cgroup_noproxy[@]}
proxied cgroup: ${cgroup_proxy[@]}
iptables: noproxy cgroup: ${cgroup_noproxy[@]}
iptables: proxied cgroup: ${cgroup_proxy[@]}
DOC
@@ -230,5 +230,5 @@ if $enable_gateway; then
ip6tables -t nat -A POSTROUTING -m owner ! --socket-exists -s fc00::/7 -j MASQUERADE # only masquerade ipv6 private address
sysctl -w net.ipv4.ip_forward=1
sysctl -w net.ipv6.conf.all.forwarding=1
echo "gateway enabled"
echo "ipatbles: gateway enabled"
fi

View File

@@ -1,135 +0,0 @@
#!/usr/bin/python
# This won't catch all new processes: an application may fork() but not exec().
from __future__ import print_function
import os, sys, signal, shutil
def eprint(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)
try:
from bcc import BPF
from bcc.utils import ArgString, printb
import bcc.utils as utils
except:
eprint("python-bcc not installed")
exit(0)
# define BPF program
bpf_text = """
#include <uapi/linux/ptrace.h>
#include <linux/sched.h>
#include <linux/fs.h>
#define ARGSIZE 256
struct data_t {
u32 pid; // PID as in the userspace term (i.e. task->tgid in kernel)
char path[ARGSIZE];
int retval;
};
BPF_PERF_OUTPUT(events);
int syscall__execve(struct pt_regs *ctx,
const char __user *filename,
const char __user *const __user *__argv,
const char __user *const __user *__envp)
{
// create data here and pass to submit_arg to save stack space (#555)
struct data_t data = {};
struct task_struct *task;
data.pid = bpf_get_current_pid_tgid() >> 32;
bpf_probe_read(data.path, sizeof(data.path), filename);
events.perf_submit(ctx, &data, sizeof(struct data_t));
return 0;
}
"""
def getRealPath(exec_path):
path=exec_path.strip()
if not path.startswith("/"):
path=shutil.which(path)
if path:
path=os.path.realpath(path)
if path and os.path.isfile(path):
return path
eprint("'{0}' can not be find".format(exec_path))
def getParam():
global exec_path_proxy, exec_path_noproxy
exec_path_str=os.getenv('program_proxy')
if exec_path_str:
paths=exec_path_str.split(':')
exec_path_proxy=[getRealPath(x) for x in paths]
print("program with proxy:", end =" ")
print(*exec_path_proxy,flush=True)
exec_path_str=os.getenv('program_noproxy')
if exec_path_str:
paths=exec_path_str.split(':')
exec_path_noproxy=[getRealPath(x) for x in paths]
print("program without proxy:", end =" ")
print(*exec_path_noproxy, flush=True)
def exit_gracefully(signum, frame):
eprint("execsnoop receive signal: {0}".format(signum),flush=True)
sys.exit(0)
def attach(pid, path, proxy=True):
if proxy:
print("proxy: %-6d %s" % (pid, path),flush=True)
os.system("/usr/bin/cgproxy --pid {0}".format(pid))
else:
print("noproxy: %-6d %s" % (pid, path),flush=True)
os.system("/usr/bin/cgproxy --pid {0} --noproxy".format(pid))
def processAlreadyRunning():
from subprocess import check_output
def get_pid(name):
try:
return map(int,check_output(["pidof",name]).split())
except:
return []
global exec_path_proxy, exec_path_noproxy
for path in exec_path_proxy:
for pid in get_pid(path):
attach(pid,path,True)
for path in exec_path_noproxy:
for pid in get_pid(path):
attach(pid,path,False)
signal.signal(signal.SIGINT, exit_gracefully)
signal.signal(signal.SIGHUP, exit_gracefully)
signal.signal(signal.SIGTERM, exit_gracefully)
show_ignore=False
exec_path_proxy=[]
exec_path_noproxy=[]
getParam()
processAlreadyRunning()
# initialize BPF
b = BPF(text=bpf_text)
execve_fnname = b.get_syscall_fnname("execve")
b.attach_kprobe(event=execve_fnname, fn_name="syscall__execve")
# process event
def print_event(cpu, data, size):
event = b["events"].event(data)
pid=event.pid
exec_path=event.path.decode('utf-8')
if (exec_path in exec_path_noproxy):
attach(pid, exec_path, False)
elif (exec_path in exec_path_proxy):
attach(pid, exec_path, True)
elif (show_ignore):
print("ignore: %-6d %s" % (pid, exec_path),flush=True)
# loop with callback to print_event
b["events"].open_perf_buffer(print_event)
while 1:
b.perf_buffer_poll()

View File

@@ -8,14 +8,14 @@ cgproxyd [--help] [--debug] [--execsnoop]
cgproxyd = cgproxy --daemon
.SH OPTIONS
.B --execsnoop
enable execsnoop to support program level proxy, need python-bcc installed to actually work
enable execsnoop to support program level proxy, need bcc installed to actually work
.SH CONFIGURATION
.I /etc/cgproxy/config.json
.br
.B port
tproxy listenning port
.br
program level proxy controll, need `python-bcc` installed to work:
program level proxy controll, need `bcc` installed to work:
.br
.RS
.B program_proxy

View File

@@ -7,7 +7,7 @@ set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "cgproxy will transparent proxy anything r
set(CPACK_DEBIAN_PACKAGE_NAME "cgproxy")
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "x86_64")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "systemd")
set(CPACK_DEBIAN_PACKAGE_SUGGESTS "python-bcc")
set(CPACK_DEBIAN_PACKAGE_SUGGESTS "libbpfcc")
set(CPACK_DEBIAN_PACKAGE_SECTION "network")
set(CPACK_DEBIAN_PACKAGE_PRIORITY "Optional")
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/springzfx/cgproxy")
@@ -17,7 +17,7 @@ set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_CURRENT_SOURCE_DIR}/postinst;${C
## rpm pack
set(CPACK_RPM_PACKAGE_ARCHITECTURE, "x86_64")
set(CPACK_RPM_PACKAGE_REQUIRES "systemd")
set(CPACK_RPM_PACKAGE_SUGGESTS "python-bcc")
set(CPACK_RPM_PACKAGE_SUGGESTS "bcc")
set(CPACK_RPM_PACKAGE_GROUP "network")
set(CPACK_RPM_PACKAGE_URL "https://github.com/springzfx/cgproxy")
set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/postinst")

View File

@@ -1,16 +1,22 @@
# Transparent Proxy with cgroup v2
# Transparent Proxy powered with cgroup v2
## Introduction
cgproxy will transparent proxy anything running in specific cgroup. It resembles with *proxychains* and *tsock*s in default setting.
cgproxy will transparent proxy anything running in specific cgroup. It resembles with *proxychains* and *tsock*s in default setting.
It aslo supports global transparent proxy and gateway proxy. See [Global transparent proxy](#global-transparent-proxy) and [Gateway proxy](#gateway-proxy).
Main feature:
- supports cgroup/program level proxy control.
- supports global transparent proxy and gateway proxy.
## Contents
<!--ts-->
* [Transparent Proxy with cgroup v2](#transparent-proxy-with-cgroup-v2)
* [Introduction](#introduction)
* [Prerequest](#prerequest)
@@ -103,7 +109,7 @@ Config file: **/etc/cgproxy/config.json**
- **port** tproxy listenning port
- program level proxy controll, need `python-bcc` installed to work
- program level proxy control, need `bcc` and `linux-headers` installed to work
- **program_proxy** program need to be proxied
- **program_noproxy** program that won't be proxied
@@ -152,8 +158,10 @@ sudo systemctl restart cgproxy.service
example: `cgnoproxy qv2ray`
- passive way, set it's cgroup in configuration, very useful for service
- passive way, persistent config
example: `"program_noproxy":["v2ray" ,"qv2ray"]`
example: `"cgroup_noproxy":["/system.slice/v2ray.service"]`
- Finally, restart cgproxy service, that's all

View File

@@ -3,12 +3,17 @@ find_package(nlohmann_json REQUIRED)
include_directories(${PROJECT_SOURCE_DIR})
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
if (with_execsnoop)
add_library(execsnoop MODULE execsnoop.cpp common.cpp)
target_link_libraries(execsnoop bcc)
install(TARGETS execsnoop DESTINATION /usr/lib/cgproxy/ PERMISSIONS ${basic_permission})
endif()
add_executable(main main.cpp
common.cpp config.cpp cgroup_attach.cpp
common.cpp config.cpp cgroup_attach.cpp
socket_client.cpp socket_server.cpp)
target_link_libraries(main nlohmann_json::nlohmann_json Threads::Threads)
target_link_libraries(main PRIVATE nlohmann_json::nlohmann_json Threads::Threads ${CMAKE_DL_LIBS})
set_target_properties(main PROPERTIES LINKER_LANGUAGE CXX)
set_target_properties(main PROPERTIES OUTPUT_NAME cgproxy)
install(TARGETS main DESTINATION /usr/bin PERMISSIONS ${basic_permission})
install(TARGETS main DESTINATION /usr/bin PERMISSIONS ${basic_permission})

View File

@@ -5,8 +5,8 @@
#include <nlohmann/json.hpp>
#include <unistd.h>
using json = nlohmann::json;
using namespace CGPROXY;
using namespace CGPROXY::CONFIG;
using namespace ::CGPROXY;
using namespace ::CGPROXY::CONFIG;
namespace CGPROXY::CGPROXY {
@@ -75,8 +75,10 @@ int main(int argc, char *argv[]) {
if (status == 1) error("maybe cgproxy.service not running");
exit(EXIT_FAILURE);
}
// if just attach pid, return here
if (attach_pid) return 0;
string s = join2str(argc - shift, argv + shift, ' ');
return system(s.c_str());
}
} // namespace CGPROXY::CGPROXY
} // namespace CGPROXY::CGPROXY

View File

@@ -4,9 +4,16 @@
#include "cgroup_attach.h"
#include "common.h"
#include "config.h"
#include "execsnoop.h"
#include "socket_server.h"
#include <algorithm>
#include <csignal>
#include <cstdlib>
#include <dlfcn.h>
#include <exception>
#include <fstream>
#include <functional>
#include <future>
#include <nlohmann/json.hpp>
#include <sched.h>
#include <sys/file.h>
@@ -17,17 +24,42 @@ using json = nlohmann::json;
using namespace ::CGPROXY::SOCKET;
using namespace ::CGPROXY::CONFIG;
using namespace ::CGPROXY::CGROUP;
// using namespace ::CGPROXY::EXECSNOOP;
namespace CGPROXY::EXECSNOOP {
bool loadExecsnoopLib() {
try {
info("loading %s", LIBEXECSNOOP_SO);
void *handle_dl = dlopen(LIBEXECSNOOP_SO, RTLD_NOW);
if (handle_dl == NULL) {
error("dlopen %s failed: %s", LIBEXECSNOOP_SO, dlerror());
return false;
}
_startThread = reinterpret_cast<startThread_t *>(dlsym(handle_dl, "startThread"));
if (_startThread == NULL) {
error("dlsym startThread func failed: %s", dlerror());
return false;
}
info("dlsym startThread func success");
return true;
} catch (exception &e) {
debug("exception: %s", e.what());
return false;
}
}
} // namespace CGPROXY::EXECSNOOP
namespace CGPROXY::CGPROXYD {
bool print_help = false;
bool enable_socketserver = true;
bool enable_execsnoop = false;
class cgproxyd {
thread_arg arg_t;
thread socketserver_thread;
thread execsnoop_thread;
Config config;
pthread_t socket_thread_id = -1;
pid_t exec_snoop_pid = -1;
static cgproxyd *instance;
static int handle_msg_static(char *msg) {
@@ -38,6 +70,65 @@ class cgproxyd {
return instance->handle_msg(msg);
}
static int handle_pid_static(int pid) {
if (!instance) {
error("no cgproxyd instance assigned");
return ERROR;
}
return instance->handle_pid(pid);
}
int handle_pid(int pid) {
unique_ptr<char[], decltype(&free)> path(
realpath(to_str("/proc/", pid, "/exe").c_str(), NULL), &free);
if (path == NULL) {
debug("execsnoop: pid %d live life too short", pid);
return 0;
}
debug("execsnoop: %d %s", pid, path.get());
vector<string> v;
v = config.program_noproxy;
if (find(v.begin(), v.end(), path.get()) != v.end()) {
string cg = getCgroup(pid);
if (cg.empty()) {
debug("execsnoop: cgroup get failed, ignore: %d %s", pid, path.get());
return 0;
}
if (belongToCgroup(cg, config.cgroup_proxy_preserved) ||
belongToCgroup(cg, config.cgroup_noproxy_preserved)) {
info("execsnoop: already in preserverd cgroup, leave alone: %d %s", pid,
path.get());
return 0;
}
if (!belongToCgroup(cg, config.cgroup_noproxy)) {
info("execsnoop; noproxy: %d %s", pid, path.get());
return attach(pid, config.cgroup_noproxy_preserved);
}
}
v = config.program_proxy;
if (find(v.begin(), v.end(), path.get()) != v.end()) {
string cg = getCgroup(pid);
if (cg.empty()) {
debug("execsnoop: cgroup get failed, ignore: %d %s", pid, path.get());
return 0;
}
if (belongToCgroup(cg, config.cgroup_proxy_preserved) ||
belongToCgroup(cg, config.cgroup_noproxy_preserved)) {
info("execsnoop: already in preserverd cgroup, leave alone: %d %s", pid,
path.get());
return 0;
}
if (!belongToCgroup(cg, config.cgroup_proxy)) {
info("execsnoop: proxied: %d %s", pid, path.get());
return attach(pid, config.cgroup_proxy_preserved);
}
}
return 0;
}
static void signalHandler(int signum) {
debug("Signal %d received.", signum);
if (!instance) {
@@ -78,62 +169,116 @@ class cgproxyd {
return MSG_ERROR;
}
int type, status;
int pid, cgroup_target;
int type, status, pid;
try {
type = j.at("type").get<int>();
switch (type) {
case MSG_TYPE_CONFIG_JSON:
status = config.loadFromJsonStr(j.at("data").dump());
if (status == SUCCESS) status = applyConfig(&config);
info("process received config json msg");
if (status == SUCCESS) status = applyConfig();
return status;
break;
case MSG_TYPE_CONFIG_PATH:
status = config.loadFromFile(j.at("data").get<string>());
if (status == SUCCESS) status = applyConfig(&config);
info("process received config path msg");
if (status == SUCCESS) status = applyConfig();
return status;
break;
case MSG_TYPE_PROXY_PID:
pid = j.at("data").get<int>();
info("process proxy pid msg: %d", pid);
status = attach(pid, config.cgroup_proxy_preserved);
return status;
break;
case MSG_TYPE_NOPROXY_PID:
pid = j.at("data").get<int>();
info("process noproxy pid msg: %d", pid);
status = attach(pid, config.cgroup_noproxy_preserved);
return status;
break;
default: return MSG_ERROR; break;
default:
error("unknown msg");
return MSG_ERROR;
break;
};
} catch (out_of_range &e) { return MSG_ERROR; } catch (exception &e) {
return ERROR;
}
}
pthread_t startSocketListeningThread() {
arg_t.handle_msg = &handle_msg_static;
pthread_t thread_id;
int status = pthread_create(&thread_id, NULL, &SocketServer::startThread, &arg_t);
if (status != 0) error("socket thread create failed");
return thread_id;
void startSocketListeningThread() {
promise<void> status;
future<void> status_f = status.get_future();
thread th(SOCKET::startThread, handle_msg_static, move(status));
socketserver_thread = move(th);
future_status fstatus = status_f.wait_for(chrono::seconds(THREAD_TIMEOUT));
if (fstatus == std::future_status::ready) {
info("socketserver thread started");
} else {
error("socketserver thread timeout, maybe failed");
}
}
void startExecSnoopProc() {
if (exec_snoop_pid != -1){
kill(exec_snoop_pid, SIGINT);
exec_snoop_pid=-1;
void startExecsnoopThread() {
if (!EXECSNOOP::loadExecsnoopLib() || EXECSNOOP::_startThread == NULL) {
error("execsnoop not ready to start, maybe bcc not installed");
return;
}
pid_t pid = fork();
if (pid == 0) {
execl(BPF_EXEC_SNOOP_START, (char *) NULL);
exit(0);
} else if (pid<0){
error("fork precess failed");
}else {
exec_snoop_pid = pid;
promise<void> status;
future<void> status_f = status.get_future();
thread th(EXECSNOOP::_startThread, handle_pid_static, move(status));
execsnoop_thread = move(th);
future_status fstatus = status_f.wait_for(chrono::seconds(THREAD_TIMEOUT));
if (fstatus == std::future_status::ready) {
info("execsnoop thread started");
processRunningProgram();
} else {
error("execsnoop thread timeout, maybe failed");
}
}
void processRunningProgram() {
debug("process running program");
for (auto &path : config.program_noproxy)
for (auto &pid : bash_pidof(path)) {
string cg = getCgroup(pid);
if (cg.empty()) {
debug("cgroup get failed, ignore: %d %s", pid, path.c_str());
continue;
}
if (belongToCgroup(cg, config.cgroup_proxy_preserved) ||
belongToCgroup(cg, config.cgroup_noproxy_preserved)) {
debug("already in preserverd cgroup, leave alone: %d %s", pid, path.c_str());
continue;
}
if (!belongToCgroup(cg, config.cgroup_noproxy)) {
int status = attach(pid, config.cgroup_noproxy_preserved);
if (status == 0) info("noproxy running process %d %s", pid, path.c_str());
}
}
for (auto &path : config.program_proxy)
for (auto &pid : bash_pidof(path)) {
string cg = getCgroup(pid);
if (cg.empty()) {
debug("cgroup get failed, ignore: %d %s", pid, path.c_str());
continue;
}
if (belongToCgroup(cg, config.cgroup_proxy_preserved) ||
belongToCgroup(cg, config.cgroup_noproxy_preserved)) {
debug("already in preserverd cgroup, leave alone: %d %s", pid, path.c_str());
continue;
}
if (!belongToCgroup(cg, config.cgroup_proxy)) {
int status = attach(pid, config.cgroup_proxy_preserved);
if (status == 0) info("proxied running process %d %s", pid, path.c_str());
}
}
}
void assignStaticInstance() { instance = this; }
public:
@@ -143,19 +288,24 @@ public:
signal(SIGTERM, &signalHandler);
signal(SIGHUP, &signalHandler);
config.loadFromFile(DEFAULT_CONFIG_FILE);
applyConfig(&config);
assignStaticInstance();
socket_thread_id = startSocketListeningThread();
pthread_join(socket_thread_id, NULL);
config.loadFromFile(DEFAULT_CONFIG_FILE);
applyConfig();
if (enable_socketserver) startSocketListeningThread();
if (enable_execsnoop) startExecsnoopThread();
if (socketserver_thread.joinable()) socketserver_thread.join();
if (execsnoop_thread.joinable()) execsnoop_thread.join();
return 0;
}
int applyConfig(Config *c) {
int applyConfig() {
system(TPROXY_IPTABLS_CLEAN);
c->toEnv();
config.print_summary();
config.toEnv();
system(TPROXY_IPTABLS_START);
if (enable_execsnoop) startExecSnoopProc();
// no need to track running status
return 0;
}
@@ -163,7 +313,6 @@ public:
void stop() {
debug("stopping");
system(TPROXY_IPTABLS_CLEAN);
// if (exec_snoop_pid != -1) kill(exec_snoop_pid, SIGINT);
unlock();
}
@@ -203,4 +352,4 @@ int main(int argc, char *argv[]) {
return d.start();
}
} // namespace CGPROXY::CGPROXYD
#endif
#endif

View File

@@ -13,10 +13,18 @@
namespace CGPROXY::CGROUP {
bool exist(string path) {
struct stat st;
if (stat(path.c_str(), &st) != -1) { return S_ISDIR(st.st_mode); }
return false;
string cgroup2_mount_point = get_cgroup2_mount_point();
string get_cgroup2_mount_point() {
stringstream buffer;
unique_ptr<FILE, decltype(&pclose)> fp(popen("findmnt -t cgroup2 -n -o TARGET", "r"),
&pclose);
if (!fp) return "";
char buf[READ_SIZE_MAX];
while (fgets(buf, READ_SIZE_MAX, fp.get()) != NULL) { buffer << buf; }
string s = buffer.str();
s.pop_back(); // remove newline character
return s;
}
bool validate(string pid, string cgroup) {
@@ -25,56 +33,45 @@ bool validate(string pid, string cgroup) {
if (pid_v && cg_v) return true;
error("attach paramater validate error");
return_error
}
string get_cgroup2_mount_point(int &status) {
char cgroup2_mount_point[100] = "";
FILE *fp = popen("findmnt -t cgroup2 -n -o TARGET", "r");
int count = fscanf(fp, "%s", cgroup2_mount_point);
fclose(fp);
if (count == 0) {
error("cgroup2 not supported");
status = -1;
return NULL;
}
status = 0;
return cgroup2_mount_point;
return_error;
}
int attach(const string pid, const string cgroup_target) {
if (getuid() != 0) {
error("need root to attach cgroup");
return_error
return_error;
}
debug("attaching %s to %s", pid.c_str(), cgroup_target.c_str());
int status;
if (!validate(pid, cgroup_target))
return_error string cgroup_mount_point = get_cgroup2_mount_point(status);
if (status != 0)
return_error string cgroup_target_path = cgroup_mount_point + cgroup_target;
if (!validate(pid, cgroup_target)) return_error;
if (cgroup2_mount_point.empty()) return_error;
string cgroup_target_path = cgroup2_mount_point + cgroup_target;
string cgroup_target_procs = cgroup_target_path + "/cgroup.procs";
// check if exist, we will create it if not exist
if (!exist(cgroup_target_path)) {
if (!dirExist(cgroup_target_path)) {
if (mkdir(cgroup_target_path.c_str(),
S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == 0) {
debug("created cgroup %s success", cgroup_target.c_str());
} else {
error("created cgroup %s failed, errno %d", cgroup_target.c_str(), errno);
return_error
return_error;
}
// error("cgroup %s not exist",cgroup_target.c_str());
// return_error
}
if (getCgroup(pid) == cgroup_target) {
debug("%s already in %s", pid.c_str(), cgroup_target.c_str());
return_success;
}
// put pid to target cgroup
ofstream procs(cgroup_target_procs, ofstream::app);
if (!procs.is_open()) {
error("open file %s failed", cgroup_target_procs.c_str());
return_error
return_error;
}
procs << pid.c_str() << endl;
procs.close();
@@ -83,13 +80,13 @@ int attach(const string pid, const string cgroup_target) {
if (!procs) {
error("write %s to %s failed, maybe process %s not exist", pid.c_str(),
cgroup_target_procs.c_str(), pid.c_str());
return_error
return_error;
}
return_success
return_success;
}
int attach(const int pid, const string cgroup_target) {
return attach(to_str(pid), cgroup_target);
}
} // namespace CGPROXY::CGROUP
} // namespace CGPROXY::CGROUP

View File

@@ -6,13 +6,12 @@
using namespace std;
namespace CGPROXY::CGROUP {
bool exist(string path);
extern string cgroup2_mount_point;
bool validate(string pid, string cgroup);
string get_cgroup2_mount_point(int &status);
string get_cgroup2_mount_point();
int attach(const string pid, const string cgroup_target);
int attach(const int pid, const string cgroup_target);
} // namespace CGPROXY::CGROUP
#endif
#endif

View File

@@ -1,7 +1,11 @@
#include "common.h"
#include <fstream>
#include <regex>
#include <sys/stat.h>
#include <unistd.h>
bool enable_debug = false;
bool enable_info = true;
string join2str(const vector<string> t, const char delm) {
string s;
@@ -18,6 +22,8 @@ string join2str(const int argc, char **argv, const char delm) {
return s;
}
bool startWith(string s, string prefix) { return s.rfind(prefix, 0) == 0; }
bool validCgroup(const string cgroup) {
return regex_match(cgroup, regex("^/[a-zA-Z0-9\\-_./@]*$"));
}
@@ -32,3 +38,87 @@ bool validCgroup(const vector<string> cgroup) {
bool validPid(const string pid) { return regex_match(pid, regex("^[0-9]+$")); }
bool validPort(const int port) { return port > 0; }
bool fileExist(const string &path) {
struct stat st;
return (stat(path.c_str(), &st) == 0 && S_ISREG(st.st_mode));
}
bool dirExist(const string &path) {
struct stat st;
return (stat(path.c_str(), &st) == 0 && S_ISDIR(st.st_mode));
}
vector<int> bash_pidof(const string &path) {
vector<int> pids;
unique_ptr<FILE, decltype(&pclose)> fp(popen(to_str("pidof ", path).c_str(), "r"),
&pclose);
if (!fp) return pids;
int pid;
while (fscanf(fp.get(), "%d", &pid) != EOF) { pids.push_back(pid); }
return pids;
}
string bash_which(const string &name) {
stringstream buffer;
unique_ptr<FILE, decltype(&pclose)> fp(popen(to_str("which ", name).c_str(), "r"),
&pclose);
if (!fp) return "";
char buf[READ_SIZE_MAX];
while (fgets(buf, READ_SIZE_MAX, fp.get()) != NULL) { buffer << buf; }
string s = buffer.str();
s.pop_back(); // remove newline character
return s;
}
string bash_readlink(const string &path) {
stringstream buffer;
unique_ptr<FILE, decltype(&pclose)> fp(popen(to_str("readlink -e ", path).c_str(), "r"),
&pclose);
if (!fp) return "";
char buf[READ_SIZE_MAX];
while (fgets(buf, READ_SIZE_MAX, fp.get()) != NULL) { buffer << buf; }
string s = buffer.str();
s.pop_back(); // remove newline character
return s;
}
string getRealExistPath(const string &name) {
if (name[0] == '/' && fileExist(name)) return name;
string path;
path = bash_which(name);
if (path.empty()) return "";
path = bash_readlink(path);
if (!fileExist(path)) return "";
return path;
}
bool belongToCgroup(string cg1, string cg2) { return startWith(cg1 + '/', cg2 + '/'); }
bool belongToCgroup(string cg1, vector<string> cg2) {
for (const auto &s : cg2) {
if (startWith(cg1 + '/', s + '/')) return true;
}
return false;
}
string getCgroup(const pid_t &pid) { return getCgroup(to_str(pid)); }
string getCgroup(const string &pid) {
string cgroup_f = to_str("/proc/", pid, "/cgroup");
if (!fileExist(cgroup_f)) return "";
string cgroup, line;
ifstream ifs(cgroup_f);
debug("prcessing file %s", cgroup_f.c_str());
while (ifs.good() && getline(ifs, line)) {
debug("process line: %s", line.c_str());
if (line[0] == '0') {
cgroup = line.substr(3);
debug("get cgroup of %s: %s", pid.c_str(), cgroup.c_str());
break;
}
}
ifs.close();
return cgroup;
}

View File

@@ -9,16 +9,19 @@ using namespace std;
#define TPROXY_IPTABLS_START "/usr/share/cgproxy/scripts/cgroup-tproxy.sh"
#define TPROXY_IPTABLS_CLEAN "/usr/share/cgproxy/scripts/cgroup-tproxy.sh stop"
#define BPF_EXEC_SNOOP_START "/usr/share/cgproxy/scripts/execsnoop.py"
#define LIBEXECSNOOP_SO "/usr/lib/cgproxy/libexecsnoop.so"
#define PID_LOCK_FILE "/var/run/cgproxyd.pid"
#define SOCKET_PATH "/tmp/cgproxy_unix_socket"
#define LISTEN_BACKLOG 64
#define DEFAULT_CONFIG_FILE "/etc/cgproxy/config.json"
#define READ_SIZE_MAX 128
#define CGROUP_PROXY_PRESVERED "/proxy.slice"
#define CGROUP_NOPROXY_PRESVERED "/noproxy.slice"
#define THREAD_TIMEOUT 5
#define MSG_TYPE_CONFIG_JSON 1
#define MSG_TYPE_CONFIG_PATH 2
#define MSG_TYPE_PROXY_PID 3
@@ -36,23 +39,34 @@ using namespace std;
#define FILE_ERROR 7
extern bool enable_debug;
extern bool enable_info;
#define error(...) \
{ \
fprintf(stderr, "error: "); \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, "\n"); \
fflush(stderr); \
}
#define debug(...) \
if (enable_debug) { \
fprintf(stderr, "debug: "); \
fprintf(stdout, "debug: "); \
fprintf(stdout, __VA_ARGS__); \
fprintf(stdout, "\n"); \
fflush(stdout); \
}
#define return_error return -1;
#define return_success return 0;
#define info(...) \
if (enable_info) { \
fprintf(stdout, "info: "); \
fprintf(stdout, __VA_ARGS__); \
fprintf(stdout, "\n"); \
fflush(stdout); \
}
#define return_error return -1
#define return_success return 0
template <typename... T> string to_str(T... args) {
stringstream ss;
@@ -64,10 +78,26 @@ template <typename... T> string to_str(T... args) {
string join2str(const vector<string> t, const char delm = ' ');
string join2str(const int argc, char **argv, const char delm = ' ');
bool startWith(string prefix);
bool validCgroup(const string cgroup);
bool validCgroup(const vector<string> cgroup);
bool validPid(const string pid);
bool validPort(const int port);
#endif
bool fileExist(const string &path);
bool dirExist(const string &path);
vector<int> bash_pidof(const string &path);
string bash_which(const string &name);
string bash_readlink(const string &path);
string getRealExistPath(const string &name);
/**
* whether cg1 belongs to cg2
*/
bool belongToCgroup(string cg1, string cg2);
bool belongToCgroup(string cg1, vector<string> cg2);
string getCgroup(const pid_t &pid);
string getCgroup(const string &pid);
#endif

View File

@@ -4,6 +4,7 @@
#include <iomanip>
#include <nlohmann/json.hpp>
#include <set>
#include <vector>
using json = nlohmann::json;
#define add2json(v) j[#v] = v;
@@ -20,7 +21,6 @@ using json = nlohmann::json;
namespace CGPROXY::CONFIG {
void Config::toEnv() {
mergeReserved();
setenv("program_proxy", join2str(program_proxy, ':').c_str(), 1);
setenv("program_noproxy", join2str(program_noproxy, ':').c_str(), 1);
setenv("cgroup_proxy", join2str(cgroup_proxy, ':').c_str(), 1);
@@ -89,6 +89,13 @@ int Config::loadFromJsonStr(const string js) {
tryassign(enable_udp);
tryassign(enable_ipv4);
tryassign(enable_ipv6);
// e.g. v2ray -> /usr/bin/v2ray -> /usr/lib/v2ray/v2ray
toRealProgramPath(program_noproxy);
toRealProgramPath(program_proxy);
mergeReserved();
return 0;
}
@@ -127,4 +134,26 @@ bool Config::validateJsonStr(const string js) {
return true;
}
void Config::print_summary() {
info("noproxy program: %s", join2str(program_noproxy).c_str());
info("proxied program: %s", join2str(program_proxy).c_str());
info("noproxy cgroup: %s", join2str(cgroup_noproxy).c_str());
info("proxied cgroup: %s", join2str(cgroup_proxy).c_str());
}
void Config::toRealProgramPath(vector<string> &v) {
vector<string> tmp;
for (auto &p : v) {
auto rpath = getRealExistPath(p);
if (!rpath.empty()) tmp.push_back(rpath);
else
error("%s not exist or broken link", p.c_str());
}
v = tmp;
}
#undef tryassign
#undef add2json
#undef merge
} // namespace CGPROXY::CONFIG

View File

@@ -13,8 +13,8 @@ public:
const string cgroup_proxy_preserved = CGROUP_PROXY_PRESVERED;
const string cgroup_noproxy_preserved = CGROUP_NOPROXY_PRESVERED;
vector<string> program_proxy;
vector<string> program_noproxy;
vector<string> program_proxy = {cgroup_proxy_preserved};
vector<string> program_noproxy = {cgroup_noproxy_preserved};
vector<string> cgroup_proxy;
vector<string> cgroup_noproxy;
bool enable_gateway = false;
@@ -30,10 +30,12 @@ public:
string toJsonStr();
int loadFromFile(const string f);
int loadFromJsonStr(const string js);
void print_summary();
private:
void mergeReserved();
bool validateJsonStr(const string js);
void toRealProgramPath(vector<string> &v);
};
} // namespace CGPROXY::CONFIG

104
src/execsnoop.cpp Normal file
View File

@@ -0,0 +1,104 @@
#include "execsnoop.h"
#include "bcc/BPF.h"
#include "common.h"
#include <bcc/libbpf.h>
#include <fstream>
#include <functional>
#include <iostream>
#include <string>
#include <unistd.h>
using namespace std;
namespace CGPROXY::EXECSNOOP {
const string BPF_PROGRAM = R"(
#include <linux/fs.h>
#include <linux/sched.h>
#include <uapi/linux/ptrace.h>
struct data_t {
int pid;
};
BPF_PERF_OUTPUT(events);
int syscall_execve(struct pt_regs *ctx,
const char __user *filename,
const char __user *const __user *__argv,
const char __user *const __user *__envp)
{
struct data_t data = {};
data.pid = bpf_get_current_pid_tgid();
events.perf_submit(ctx, &data, sizeof(struct data_t));
return 0;
}
int ret_syscall_execve(struct pt_regs *ctx){
struct data_t data = {};
data.pid = bpf_get_current_pid_tgid();
int retval = PT_REGS_RC(ctx);
if (retval==0)
events.perf_submit(ctx, &data, sizeof(struct data_t));
return 0;
}
)";
struct data_t {
int pid;
};
function<int(int)> callback = NULL;
promise<void> status;
void handle_events(void *cb_cookie, void *data, int data_size) {
auto event = static_cast<data_t *>(data);
int pid = event->pid;
if (callback) callback(pid);
}
int execsnoop() {
debug("starting execsnoop");
ebpf::BPF bpf;
auto init_res = bpf.init(BPF_PROGRAM);
if (init_res.code() != 0) {
error("bpf init failed, maybe linux-headers not installed");
std::cerr << init_res.msg() << std::endl;
return 1;
}
string execve_fnname = bpf.get_syscall_fnname("execve");
// auto attach_res = bpf.attach_kprobe(execve_fnname, "syscall_execve");
auto attach_res =
bpf.attach_kprobe(execve_fnname, "ret_syscall_execve", 0, BPF_PROBE_RETURN);
if (attach_res.code() != 0) {
std::cerr << attach_res.msg() << std::endl;
return 1;
}
auto open_res = bpf.open_perf_buffer("events", &handle_events);
if (open_res.code() != 0) {
std::cerr << open_res.msg() << std::endl;
return 1;
}
if (bpf.free_bcc_memory()) {
std::cerr << "Failed to free llvm/clang memory" << std::endl;
return 1;
}
status.set_value();
while (true) bpf.poll_perf_buffer("events");
return 0;
}
void startThread(function<int(int)> c, promise<void> _status) {
status = move(_status);
callback = c;
execsnoop();
}
} // namespace CGPROXY::EXECSNOOP

22
src/execsnoop.h Normal file
View File

@@ -0,0 +1,22 @@
#ifndef EXECSNOOP_HPP
#define EXECSNOOP_HPP 1
#include <functional>
#include <future>
#include <string>
using namespace std;
namespace CGPROXY::EXECSNOOP {
extern const string BPF_PROGRAM;
struct data_t;
extern function<int(int)> callback;
void handle_events(void *cb_cookie, void *data, int data_size);
int execsnoop();
extern "C" void startThread(function<int(int)> c, promise<void> _status);
typedef void startThread_t(function<int(int)>, promise<void>);
startThread_t *_startThread; // only for dlsym()
} // namespace CGPROXY::EXECSNOOP
#endif

View File

@@ -42,11 +42,7 @@ void send(const char *msg, int &status) {
}
void send(const string msg, int &status) {
int msg_len = msg.length();
char buff[msg_len];
msg.copy(buff, msg_len, 0);
buff[msg_len] = '\0';
send(buff, status);
send(msg.c_str(), status);
debug("return status: %d", status);
}

View File

@@ -1,6 +1,7 @@
#include "socket_server.h"
#include "common.h"
#include <filesystem>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <unistd.h>
@@ -9,7 +10,7 @@ namespace fs = std::filesystem;
namespace CGPROXY::SOCKET {
void SocketServer::socketListening(function<int(char *)> callback) {
void SocketServer::socketListening(function<int(char *)> callback, promise<void> status) {
debug("starting socket listening");
sfd = socket(AF_UNIX, SOCK_STREAM, 0);
@@ -26,6 +27,8 @@ void SocketServer::socketListening(function<int(char *)> callback) {
listen(sfd, LISTEN_BACKLOG);
chmod(SOCKET_PATH, S_IRWXU | S_IRWXG | S_IRWXO);
status.set_value();
while (true) {
close(cfd);
cfd = accept(sfd, NULL, NULL);
@@ -37,29 +40,28 @@ void SocketServer::socketListening(function<int(char *)> callback) {
flag = read(cfd, &msg_len, sizeof(int));
continue_if_error(flag, "read length");
// read msg
char msg[msg_len];
auto msg = (char *)malloc(msg_len + 1);
flag = read(cfd, msg, msg_len * sizeof(char));
continue_if_error(flag, "read msg");
msg[msg_len] = '\0';
// handle msg
int status = callback(msg);
free(msg);
// send back flag
flag = write(cfd, &status, sizeof(int));
continue_if_error(flag, "write back");
}
}
void *SocketServer::startThread(void *arg) {
thread_arg *p = (thread_arg *)arg;
SocketServer server;
server.socketListening(p->handle_msg);
return (void *)0;
}
SocketServer::~SocketServer() {
close(sfd);
close(cfd);
unlink(SOCKET_PATH);
}
void startThread(function<int(char *)> callback, promise<void> status) {
SocketServer server;
server.socketListening(callback, move(status));
}
} // namespace CGPROXY::SOCKET

View File

@@ -2,6 +2,7 @@
#define SOCKET_SERVER_H
#include <functional>
#include <future>
#include <stdlib.h>
#include <sys/un.h>
using namespace std;
@@ -14,20 +15,17 @@ namespace CGPROXY::SOCKET {
continue; \
}
struct thread_arg {
function<int(char *)> handle_msg;
};
class SocketServer {
public:
int sfd = -1, cfd = -1, flag = -1;
struct sockaddr_un unix_socket;
void socketListening(function<int(char *)> callback);
void socketListening(function<int(char *)> callback, promise<void> status);
~SocketServer();
static void *startThread(void *arg);
};
void startThread(function<int(char *)> callback, promise<void> status);
} // namespace CGPROXY::SOCKET
#endif

View File

@@ -1,4 +1,12 @@
include_directories(${PROJECT_SOURCE_DIR})
include_directories(${PROJECT_SOURCE_DIR}/src)
add_executable(cgattach cgattach.cpp ../src/cgroup_attach.cpp ../src/common.cpp)
install(TARGETS cgattach DESTINATION /usr/bin PERMISSIONS ${basic_permission})
add_executable(cgattach cgattach.cpp ../src/cgroup_attach.cpp ../src/common.cpp)
install(TARGETS cgattach DESTINATION /usr/bin PERMISSIONS ${basic_permission})
if (with_execsnoop)
add_executable(execsnoop_exec execsnoop.cpp ../src/common.cpp ../src/execsnoop.cpp)
set_target_properties(execsnoop_exec PROPERTIES OUTPUT_NAME execsnoop)
target_link_libraries(execsnoop_exec bcc)
install(TARGETS execsnoop_exec DESTINATION /usr/bin PERMISSIONS ${basic_permission})
endif()

24
tools/execsnoop.cpp Normal file
View File

@@ -0,0 +1,24 @@
#include "execsnoop.h"
#include "common.h"
#include <unistd.h>
using namespace std;
using namespace CGPROXY::EXECSNOOP;
#define PATH_MAX_LEN 128
int handle_pid(int pid) {
char path[PATH_MAX_LEN];
auto size = readlink(to_str("/proc/", pid, "/exe").c_str(), path, PATH_MAX_LEN);
if (size == -1) error("readlink: %s", to_str("/proc/", pid, "/exe").c_str());
path[size] = '\0';
info("%d %s", pid, path);
return 0;
}
int main() {
enable_debug = true;
enable_info = true;
callback = handle_pid;
execsnoop();
return 0;
}