13 Commits
v0.12 ... v0.13

Author SHA1 Message Date
springzfx
fa7d877de5 bump version 2020-05-23 15:15:05 +08:00
springzfx
3475001ca3 update deb//rpm depency 2020-05-23 15:14:11 +08:00
springzfx
0b25b5263a update man and readme 2020-05-23 14:41:12 +08:00
springzfx
388ba6a4c8 execsnoop: fix get_pid 2020-05-23 13:49:09 +08:00
springzfx
5dbce18f95 python-bcc optional 2020-05-23 13:44:59 +08:00
springzfx
792a156647 add execsnoop 2020-05-23 13:29:11 +08:00
springzfx
92abcb1851 add --pid option 2020-05-23 03:30:46 +08:00
springzfx
a73b697cab use exec 2020-05-22 01:21:53 +08:00
springzfx
aace8c3d31 add tips that service may not running 2020-05-21 20:50:44 +08:00
springzfx
665e02ceaa readme: add iptables version requirement 2020-05-21 20:24:49 +08:00
fancy
bfe3289201 use DynamicUser in v2ray.service 2020-05-21 14:41:58 +08:00
fancy
2c8625c110 add man page 2020-05-20 00:42:00 +08:00
fancy
ba0b780adf update readme 2020-05-19 15:01:25 +08:00
17 changed files with 357 additions and 35 deletions

View File

@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.10)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
project(cgproxy VERSION 0.12)
project(cgproxy VERSION 0.13)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-result")
set(build_tools OFF)
@@ -23,5 +23,20 @@ install(FILES cgproxyd DESTINATION /usr/bin PERMISSIONS ${basic_permission})
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/)
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
set(man_gz
${PROJECT_BINARY_DIR}/cgproxyd.1.gz
${PROJECT_BINARY_DIR}/cgproxy.1.gz
${PROJECT_BINARY_DIR}/cgnoproxy.1.gz
)
add_custom_target(man
COMMAND gzip -fk cgproxyd.1 cgproxy.1 cgnoproxy.1
COMMAND mv *.gz ${PROJECT_BINARY_DIR}
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/man
)
add_dependencies(main man)
install(FILES ${man_gz} DESTINATION /usr/share/man/man1/)

View File

@@ -1,2 +1,2 @@
#!/bin/sh
/usr/bin/cgproxy --noproxy $@
exec /usr/bin/cgproxy --noproxy $@

View File

@@ -4,7 +4,7 @@ After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/cgproxyd
ExecStart=/usr/bin/cgproxyd --execsnoop
[Install]
WantedBy=multi-user.target

View File

@@ -1,2 +1,2 @@
#!/bin/sh
/usr/bin/cgproxy --daemon $@
exec /usr/bin/cgproxy --daemon $@

View File

@@ -1,11 +1,13 @@
{
"port": 12345,
"program_noproxy": ["v2ray", "qv2ray"],
"program_proxy": [],
"cgroup_noproxy": ["/system.slice/v2ray.service"],
"cgroup_proxy": [],
"enable_dns": true,
"enable_gateway": false,
"enable_ipv4": true,
"enable_ipv6": true,
"enable_tcp": true,
"enable_dns": true,
"enable_udp": true,
"port": 12345
"enable_tcp": true,
"enable_ipv4": true,
"enable_ipv6": true
}

135
execsnoop.py Normal file
View File

@@ -0,0 +1,135 @@
#!/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()

16
man/cgnoproxy.1 Normal file
View File

@@ -0,0 +1,16 @@
.\" Manpage for cgproxyd
.TH man 1 "19 May 2020" "1.0" "cgnoproxy man page"
.SH NAME
cgnoproxy \- Run program without proxy
.SH SYNOPSIS
cgnoproxy --help
cgnoproxy [--debug] <CMD>
cgnoproxy [--debug] --pid <PID>
.SH ALIAS
cgnoproxy = cgproxy --noproxy
.SH DESCRIPTION
cgnoproxy send current running process pid or specified pid to cgproxyd through unix socket, then pid is attached to non-proxied cgroup
.SH EXAMPLES
cgnoproxy sudo v2ray -config config_file
.SH SEE ALSO
cgproxyd(1), cgproxy(1), cgnoproxy(1)

14
man/cgproxy.1 Normal file
View File

@@ -0,0 +1,14 @@
.\" Manpage for cgproxyd
.TH man 1 "19 May 2020" "1.0" "cgproxy man page"
.SH NAME
cgproxy \- Run program with proxy
.SH SYNOPSIS
cgproxy --help
cgproxy [--debug] <CMD>
cgproxy [--debug] --pid <PID>
.SH DESCRIPTION
cgproxy send current running process pid or specified pid to cgproxyd through unix socket, then pid is attached to proxied cgroup
.SH EXAMPLES
cgproxy curl -vI https://www.google.com
.SH SEE ALSO
cgproxyd(1), cgproxy(1), cgnoproxy(1)

54
man/cgproxyd.1 Normal file
View File

@@ -0,0 +1,54 @@
.\" Manpage for cgproxyd
.TH man 1 "19 May 2020" "1.0" "cgproxyd man page"
.SH NAME
cgproxyd \- Start a daemon with unix socket to accept control from cgproxy/cgnoproxy
.SH SYNOPSIS
cgproxyd [--help] [--debug] [--execsnoop]
.SH ALIAS
cgproxyd = cgproxy --daemon
.SH OPTIONS
.B --execsnoop
enable execsnoop to support program level proxy, need python-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:
.br
.RS
.B program_proxy
program need to be proxied
.br
.B program_noproxy
program that won't be proxied
.RE
.br
cgroup level proxy control:
.br
.RS
.B cgroup_noproxy
cgroup array that no need to proxy, /noproxy.slice is preserved.
.br
.B cgroup_proxy
cgroup array that need to proxy, /proxy.slice is preserved.
.RE
.br
.B enable_gateway
enable gateway proxy for local devices.
.br
.B enable_dns
enable dns to go to proxy.
.br
.B enable_tcp
.br
.B enable_udp
.br
.B enable_ipv4
.br
.B enable_ipv6
.br
.SH SEE ALSO
cgproxyd(1), cgproxy(1), cgnoproxy(1)

View File

@@ -7,6 +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_SECTION "network")
set(CPACK_DEBIAN_PACKAGE_PRIORITY "Optional")
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/springzfx/cgproxy")
@@ -16,6 +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_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

@@ -6,7 +6,7 @@
## Introduction
cgproxy will transparent proxy anything running in specific cgroup. It resembles with *proxychains* and *tsock*, but without their disadvantages, and more powerfull.
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).
@@ -40,6 +40,12 @@ It aslo supports global transparent proxy and gateway proxy. See [Global transpa
A process listening on port (e.g. 12345) to accept iptables TPROXY, for example v2ray's dokodemo-door in tproxy mode.
- Iptables
Iptables version should be at least 1.6.0, run `iptables --version` to check.
ubuntu 16.04, debian 9, fedora 27 and later are desired
## How to install
```bash
@@ -81,28 +87,51 @@ Config file: **/etc/cgproxy/config.json**
```json
{
"port": 12345,
"program_noproxy": ["v2ray", "qv2ray"],
"program_proxy": [ ],
"cgroup_noproxy": ["/system.slice/v2ray.service"],
"cgroup_proxy": [],
"enable_dns": true,
"cgroup_proxy": [ ],
"enable_gateway": false,
"enable_ipv4": true,
"enable_ipv6": true,
"enable_tcp": true,
"enable_dns": true,
"enable_udp": true,
"port": 12345
"enable_tcp": true,
"enable_ipv4": true,
"enable_ipv6": true
}
```
- **port** tproxy listenning port
- **cgroup_noproxy** cgroup array that no need to proxy, `/noproxy.slice` is preserved
- **cgroup_proxy** cgroup array that need to proxy, `/proxy.slice` is preserved
- program level proxy controll, need `python-bcc` installed to work
- **program_proxy** program need to be proxied
- **program_noproxy** program that won't be proxied
- cgroup level proxy control:
- **cgroup_noproxy** cgroup array that no need to proxy, `/noproxy.slice` is preserved
- **cgroup_proxy** cgroup array that need to proxy, `/proxy.slice` is preserved
- **enable_gateway** enable gateway proxy for local devices
- **enable_dns** enable dns to go to proxy
- **enable_tcp**
- **enable_udp**
- **enable_ipv4**
- **enable_ipv6**
- options priority
```
program_noproxy > program_proxy > cgroup_noproxy > cgroup_proxy
enable_ipv6 > enable_ipv4 > enable_tcp > enable_udp > enable_dns
```
**Note**: cgroup in configuration need to be exist, otherwise ignored
If you changed config, remember to restart service
@@ -140,16 +169,21 @@ sudo systemctl restart cgproxy.service
- `cgnoproxy` run program wihout proxy, very useful in global transparent proxy
```bash
cgnoproxy [--debug] <CMD>
cgnoproxy [--debug] <CMD>
cgnoproxy [--debug] --pid <PID>
```
- `cgattach` attach specific process pid to specific cgroup which will create if not exist , cgroup can be only one level down exist cgroup, otherwise created fail.
- `cgattach` attach specific process pid to specific cgroup which will create if not exist , cgroup can be only one level down exist cgroup, otherwise created fail.
You need to set `set(build_tools ON)` in *CmakeLists.txt* to build this.
```bash
cgattch <pid> <cgroup>
# example
cgattch 9999 /proxy.slice
```
- For more detail command usage, see `man cgproxyd` `man cgproxy` `man cgnoproxy`
## NOTES

View File

@@ -11,6 +11,8 @@ using namespace CGPROXY::CONFIG;
namespace CGPROXY::CGPROXY {
bool print_help = false, proxy = true;
bool attach_pid = false;
string arg_pid;
inline void print_usage() {
if (proxy) {
cout << "Run program with proxy" << endl;
@@ -22,14 +24,24 @@ inline void print_usage() {
}
}
void processArgs(const int argc, char *argv[], int &shift) {
for (int i = 1; i < argc; i++) {
bool processArgs(const int argc, char *argv[], int &shift) {
int i;
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "--pid") == 0) {
attach_pid = true;
i++;
if (i == argc) return false;
arg_pid = argv[i];
if (!validPid(arg_pid)) return false;
continue;
}
if (strcmp(argv[i], "--noproxy") == 0) { proxy = false; }
if (strcmp(argv[i], "--debug") == 0) { enable_debug = true; }
if (strcmp(argv[i], "--help") == 0) { print_help = true; }
if (argv[i][0] != '-') { break; }
shift += 1;
}
shift = i;
return true;
}
void send_pid(const pid_t pid, bool proxy, int &status) {
@@ -40,23 +52,27 @@ void send_pid(const pid_t pid, bool proxy, int &status) {
}
int main(int argc, char *argv[]) {
int shift = 1;
processArgs(argc, argv, shift);
int shift = -1;
if (!processArgs(argc, argv, shift)) {
error("parameter error");
exit(EXIT_FAILURE);
}
if (print_help) {
print_usage();
exit(0);
}
if (argc == shift) {
if (!attach_pid && argc == shift) {
error("no program specified");
exit(EXIT_FAILURE);
}
int status = -1;
send_pid(getpid(), proxy, status);
send_pid(attach_pid ? stoi(arg_pid) : getpid(), proxy, status);
if (status != 0) {
error("attach process failed");
if (status == 1) error("maybe cgproxy.service not running");
exit(EXIT_FAILURE);
}

View File

@@ -8,6 +8,7 @@
#include <csignal>
#include <fstream>
#include <nlohmann/json.hpp>
#include <sched.h>
#include <sys/file.h>
#include <unistd.h>
@@ -19,10 +20,14 @@ using namespace ::CGPROXY::CGROUP;
namespace CGPROXY::CGPROXYD {
bool print_help = false;
bool enable_execsnoop = false;
class cgproxyd {
thread_arg arg_t;
Config config;
pthread_t socket_thread_id = -1;
pid_t exec_snoop_pid = -1;
static cgproxyd *instance;
static int handle_msg_static(char *msg) {
@@ -40,7 +45,7 @@ class cgproxyd {
} else {
instance->stop();
}
exit(signum);
exit(0);
}
// single process instance
@@ -113,6 +118,22 @@ class cgproxyd {
return thread_id;
}
void startExecSnoopProc() {
if (exec_snoop_pid != -1){
kill(exec_snoop_pid, SIGINT);
exec_snoop_pid=-1;
}
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;
}
}
void assignStaticInstance() { instance = this; }
public:
@@ -134,6 +155,7 @@ public:
system(TPROXY_IPTABLS_CLEAN);
c->toEnv();
system(TPROXY_IPTABLS_START);
if (enable_execsnoop) startExecSnoopProc();
// no need to track running status
return 0;
}
@@ -141,6 +163,7 @@ public:
void stop() {
debug("stopping");
system(TPROXY_IPTABLS_CLEAN);
// if (exec_snoop_pid != -1) kill(exec_snoop_pid, SIGINT);
unlock();
}
@@ -149,8 +172,6 @@ public:
cgproxyd *cgproxyd::instance = NULL;
bool print_help = false;
void print_usage() {
cout << "Start a daemon with unix socket to accept control" << endl;
cout << "Usage: cgproxyd [--help] [--debug]" << endl;
@@ -161,6 +182,7 @@ void processArgs(const int argc, char *argv[]) {
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--debug") == 0) { enable_debug = true; }
if (strcmp(argv[i], "--help") == 0) { print_help = true; }
if (strcmp(argv[i], "--execsnoop") == 0) { enable_execsnoop = true; }
if (argv[i][0] != '-') { break; }
}
}

View File

@@ -7,8 +7,9 @@
#include <vector>
using namespace std;
#define TPROXY_IPTABLS_START "sh /usr/share/cgproxy/scripts/cgroup-tproxy.sh"
#define TPROXY_IPTABLS_CLEAN "sh /usr/share/cgproxy/scripts/cgroup-tproxy.sh stop"
#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 PID_LOCK_FILE "/var/run/cgproxyd.pid"
#define SOCKET_PATH "/tmp/cgproxy_unix_socket"

View File

@@ -21,6 +21,8 @@ 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);
setenv("cgroup_noproxy", join2str(cgroup_noproxy, ':').c_str(), 1);
setenv("enable_gateway", to_str(enable_gateway).c_str(), 1);
@@ -43,6 +45,8 @@ int Config::saveToFile(const string f) {
string Config::toJsonStr() {
json j;
add2json(program_proxy);
add2json(program_noproxy);
add2json(cgroup_proxy);
add2json(cgroup_noproxy);
add2json(enable_gateway);
@@ -74,6 +78,8 @@ int Config::loadFromJsonStr(const string js) {
return PARAM_ERROR;
}
json j = json::parse(js);
tryassign(program_proxy);
tryassign(program_noproxy);
tryassign(cgroup_proxy);
tryassign(cgroup_noproxy);
tryassign(enable_gateway);
@@ -96,6 +102,7 @@ bool Config::validateJsonStr(const string js) {
bool status = true;
const set<string> boolset = {"enable_gateway", "enable_dns", "enable_tcp",
"enable_udp", "enable_ipv4", "enable_ipv6"};
const set<string> allowset = {"program_proxy", "program_noproxy"};
for (auto &[key, value] : j.items()) {
if (key == "cgroup_proxy" || key == "cgroup_noproxy") {
if (value.is_string() && !validCgroup((string)value)) status = false;
@@ -106,6 +113,8 @@ bool Config::validateJsonStr(const string js) {
if (!validPort(value)) status = false;
} else if (boolset.find(key) != boolset.end()) {
if (!value.is_boolean()) status = false;
} else if (allowset.find(key) != allowset.end()) {
} else {
error("unknown key: %s", key.c_str());
return false;

View File

@@ -13,6 +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> cgroup_proxy;
vector<string> cgroup_noproxy;
bool enable_gateway = false;

View File

@@ -7,7 +7,7 @@ Wants=network-online.target
[Service]
Type=exec
ExecStart=/usr/lib/v2ray/v2ray -config /etc/v2ray/config.json
User=nobody
DynamicUser=yes
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
NoNewPrivileges=yes
Restart=on-failure
@@ -15,4 +15,4 @@ Restart=on-failure
RestartPreventExitStatus=23
[Install]
WantedBy=multi-user.target
WantedBy=multi-user.target