cgproxy is work now

This commit is contained in:
fancy
2020-04-22 03:04:15 +08:00
commit 267aac32de
8 changed files with 367 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
build
.directory
.vscode
cgproxy2.sh

14
CMakeLists.txt Normal file
View File

@@ -0,0 +1,14 @@
cmake_minimum_required(VERSION 3.10)
project(cgproxy VERSION 1.0)
add_executable(cgattach cgattach.cpp)
install(TARGETS cgattach DESTINATION /usr/bin
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE SETUID)
install(FILES cgproxy.sh DESTINATION /usr/bin RENAME cgproxy
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
#install(FILES cgproxy2.sh DESTINATION /usr/bin RENAME cgproxy2
# PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
install(FILES cgproxy.service DESTINATION /usr/lib/systemd/system/)
install(FILES cgproxy.conf DESTINATION /etc/)
install(FILES cgroup-tproxy.sh DESTINATION /usr/share/cgproxy/scripts/)

80
cgattach.cpp Normal file
View File

@@ -0,0 +1,80 @@
#include <iostream>
#include <fstream>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <regex>
using namespace std;
void print_usage(){
fprintf(stderr, "usage: cgattach <pid> <cgroup>\n");
}
bool exist(string path){
struct stat st;
if (stat(path.c_str(),&st)!=-1){
return S_ISDIR(st.st_mode);
}
return false;
}
bool validate(string pid, string cgroup){
bool pid_v=regex_match(pid,regex("^[0-9]+$"));
bool cg_v=regex_match(cgroup,regex("^\\/[a-zA-Z0-9\\-_./@]+$"));
if (pid_v && cg_v) return true;
// cout<<pid_v<<" "<<cg_v<<endl;
print_usage();
exit(EXIT_FAILURE);
}
int main(int argc,char *argv[]){
setuid(0);
setgid(0);
if (getuid()!=0||getgid()!=0){
fprintf(stderr, "cgattach need setuid sticky bit\n");
exit(EXIT_FAILURE);
}
if (argc!=3){
print_usage();
exit(EXIT_FAILURE);
}
string pid=string(argv[1]);
string cgroup_target=string(argv[2]);
validate(pid, cgroup_target);
string cgroup_mount_point="/sys/fs/cgroup";
string cgroup_target_path=cgroup_mount_point+cgroup_target;
string cgroup_target_procs=cgroup_target_path+"/cgroup.procs"; // only support cgroup v2
// check if exist, we won't create it if not exist
if (!exist(cgroup_target_path)){
if (mkdir(cgroup_target_path.c_str(), S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)==0){
fprintf(stdout, "created cgroup %s success\n",cgroup_target.c_str());
}else{
fprintf(stderr, "created cgroup %s failed\n",cgroup_target.c_str());
exit(EXIT_FAILURE);
}
// fprintf(stderr, "cgroup %s not exist\n",cgroup_target.c_str());
// exit(EXIT_FAILURE);
}
// put pid to target cgroup
ofstream procs(cgroup_target_procs,ofstream::app);
if (!procs.is_open()){
fprintf(stderr, "open file %s failed\n",cgroup_target_procs.c_str());
exit(EXIT_FAILURE);
}
procs<<pid.c_str()<<endl;
procs.close();
// maybe there some write error, for example process pid not exist
if (!procs){
fprintf(stderr, "write %s to %s failed, maybe process %s not exist\n",pid.c_str(),cgroup_target_procs.c_str(),pid.c_str());
exit(EXIT_FAILURE);
}
return EXIT_SUCCESS;
}

22
cgproxy.conf Normal file
View File

@@ -0,0 +1,22 @@
## any process in this cgroup will be proxied
## must start with slash '/'
proxy_cgroup="/proxy.slice"
## listening port of another proxy process, for example v2ray
port=12345
## if you set to false, it's traffic won't go through proxy, but still can go direct to internet
enable_tcp=true
enable_udp=true
enable_ipv4=true
enable_ipv6=true
## v2ray outbound mark
## only useful if v2ray process is also in proxy_cgroup, for example, you want to proxy whole userspace,
## and v2ray is also running in userspace
## otherwise ignore this
v2ray_so_mark=255
## do not modify this if you don't known what you are doing
mark=2333
table=100

11
cgproxy.service Normal file
View File

@@ -0,0 +1,11 @@
[Unit]
Description=proxy cgroup
After=network.target
[Service]
ExecStart=sh /usr/share/cgproxy/scripts/cgroup-tproxy.sh --config=/etc/cgproxy.conf
ExecStop=sh /usr/share/cgproxy/scripts/cgroup-tproxy.sh stop
RemainAfterExit=1
[Install]
WantedBy=multi-user.target

12
cgproxy.sh Normal file
View File

@@ -0,0 +1,12 @@
#!/bin/bash
config="/etc/cgproxy.conf"
source $config
# test suid bit
if [ -u "$(which cgattach)" ]; then
cgattach $$ $proxy_cgroup
else
sudo cgattach $$ $proxy_cgroup
fi
$@

141
cgroup-tproxy.sh Normal file
View File

@@ -0,0 +1,141 @@
#!/bin/bash
print_help(){
cat << 'DOC'
#############################################################################
#
# 1. For now, linux default using cgroup v1 for compatibility
# this script need cgroup v2, you need enable cgroup v2 in your system.
#
# 2. Listening port is expected to accept iptables TPROXY, while REDIRECT
# will not work in this script, because REDIRECT only support tcp/ipv4
#
# 3. TPROXY need root or cap_net_admin capability whatever process is listening on port
# v2ray as example: sudo setcap cap_net_admin+ep /usr/lib/v2ray/v2ray
#
# 4. this script will proxy anything running in specific cgroup
#
# script usage:
# cgroup-tproxy.sh [--help|--config|stop]
# --config=FILE
# load config from file
# --help
# show help info
# stop
# clean then stop
#
# proxy usage:
# cgproxy <program>
#
#############################################################################
DOC
}
## any process in this cgroup will be proxied
proxy_cgroup="/user.slice/user-1000.slice/proxy.slice"
## some variables
port=12345
enable_tcp=true
enable_udp=true
enable_ipv4=true
enable_ipv6=true
## do not modify this if you don't known what you are doing
mark=2333
table=100
v2ray_so_mark=255
## cgroup things
# cgroup_mount_point=$(findmnt -t cgroup,cgroup2 -n -J|jq '.filesystems[0].target')
# cgroup_type=$(findmnt -t cgroup,cgroup2 -n -J|jq '.filesystems[0].fstype')
cgroup_mount_point="/sys/fs/cgroup"
cgroup_type="cgroup2"
cgroup_procs_file="cgroup.procs"
## parse parameter
for i in "$@"
do
case $i in
stop)
iptables -t mangle -F
iptables -t mangle -X TPROXY_PRE
iptables -t mangle -X TPROXY_OUT
ip6tables -t mangle -F
ip6tables -t mangle -X TPROXY_PRE
ip6tables -t mangle -X TPROXY_OUT
ip rule delete fwmark $mark lookup $table
ip route flush table $table
ip -6 rule delete fwmark $mark lookup $table
ip -6 route flush table $table
exit 0
;;
--config=*)
config=${i#*=}
source $config
shift
;;
--help)
print_help
exit 0
;;
esac
done
## TODO cgroup need to exists before using in iptables since 5.6.5, maybe it's bug
test -d $cgroup_mount_point$proxy_cgroup || mkdir $cgroup_mount_point$proxy_cgroup || exit -1;
## use TPROXY
#ipv4#
ip rule add fwmark $mark table $table
ip route add local default dev lo table $table
iptables -t mangle -N TPROXY_PRE
iptables -t mangle -A TPROXY_PRE -p udp -m mark --mark $mark -j TPROXY --on-ip 127.0.0.1 --on-port $port --tproxy-mark $mark
iptables -t mangle -A TPROXY_PRE -p tcp -m mark --mark $mark -j TPROXY --on-ip 127.0.0.1 --on-port $port --tproxy-mark $mark
iptables -t mangle -A PREROUTING -j TPROXY_PRE
iptables -t mangle -N TPROXY_OUT
iptables -t mangle -A TPROXY_OUT -p udp -o lo -j RETURN
iptables -t mangle -A TPROXY_OUT -p udp -m cgroup --path $proxy_cgroup -j MARK --set-mark $mark
iptables -t mangle -A TPROXY_OUT -p tcp -o lo -j RETURN
iptables -t mangle -A TPROXY_OUT -p tcp -m cgroup --path $proxy_cgroup -j MARK --set-mark $mark
iptables -t mangle -A OUTPUT -m mark ! --mark $v2ray_so_mark -j TPROXY_OUT
#ipv6#
ip -6 rule add fwmark $mark table $table
ip -6 route add local default dev lo table $table
ip6tables -t mangle -N TPROXY_PRE
ip6tables -t mangle -A TPROXY_PRE -p udp -m mark --mark $mark -j TPROXY --on-ip ::1 --on-port $port --tproxy-mark $mark
ip6tables -t mangle -A TPROXY_PRE -p tcp -m mark --mark $mark -j TPROXY --on-ip ::1 --on-port $port --tproxy-mark $mark
ip6tables -t mangle -A PREROUTING -j TPROXY_PRE
ip6tables -t mangle -N TPROXY_OUT
ip6tables -t mangle -A TPROXY_OUT -p udp -o lo -j RETURN
ip6tables -t mangle -A TPROXY_OUT -p udp -m cgroup --path $proxy_cgroup -j MARK --set-mark $mark
ip6tables -t mangle -A TPROXY_OUT -p tcp -o lo -j RETURN
ip6tables -t mangle -A TPROXY_OUT -p tcp -m cgroup --path $proxy_cgroup -j MARK --set-mark $mark
ip6tables -t mangle -A OUTPUT -m mark ! --mark $v2ray_so_mark -j TPROXY_OUT
## allow to disable, order is important
$enable_udp || iptables -t mangle -I TPROXY_OUT -p udp -j RETURN
$enable_udp || ip6tables -t mangle -I TPROXY_OUT -p udp -j RETURN
$enable_tcp || iptables -t mangle -I TPROXY_OUT -p tcp -j RETURN
$enable_tcp || ip6tables -t mangle -I TPROXY_OUT -p tcp -j RETURN
$enable_ipv4 || iptables -t mangle -I TPROXY_OUT -j RETURN
$enable_ipv6 || ip6tables -t mangle -I TPROXY_OUT -j RETURN
## create proxy prefix command for easy use
# cat << 'DOC' > /usr/bin/cgproxy
# !/usr/bin/bash
# systemd-run -q --slice proxy.slice --scope --user $@
# DOC
# chmod a+x /usr/bin/cgproxy
## message for user
cat << DOC
proxied cgroup: $proxy_cgroup
DOC
## tproxy need Root or cap_net_admin capability
# setcap cap_net_admin+ep /usr/lib/v2ray/v2ray

83
readme.md Normal file
View File

@@ -0,0 +1,83 @@
# Transparent Proxy with cgroup v2
## Introduction
cgproxy will **transparent** proxy anything running in specific cgroup. It resembles with *proxychains* and *tsock*, but without their disadvantages.
## Prerequest
- cgroup2
For now, linux default using cgroup v1 for compatibility, this project need cgroup v2, you need disable cgroup v1 and enable cgroup v2 in your system.
- TPROXY
A process listening on port (e.g. 12345) to accept iptables TPROXY, for example v2ray's dokodemo-door in tproxy mode.
## How to install
```bash
mkdir build && cd build && cmake .. && make && make install
```
It is alreay in archlinux AUR.
## How to use
- First enable service
```bash
sudo systemctl enable --now cgproxy.service
sudo systemctl status cgproxy.service
```
- Then prefix with cgproxy with you command, just like proxychains
```
cgproxy <CMD>
```
- For example, test proxy
```bash
cgproxy curl -vIs https://www.google.com
```
More config in `/etc/cgproxy.conf`:
```bash
## any process in this cgroup will be proxied
## must start with slash '/'
proxy_cgroup="/proxy.slice"
## listening port of another proxy process, for example v2ray
port=12345
## if you set to false, it's traffic won't go through proxy, but still can go direct to internet
enable_tcp=true
enable_udp=true
enable_ipv4=true
enable_ipv6=true
## v2ray outbound mark
## only useful if v2ray process is also in proxy_cgroup, for example, you want to proxy whole userspace,
## and v2ray is also running in userspace
## otherwise ignore this
v2ray_so_mark=255
## do not modify this if you don't known what you are doing
mark=2333
table=100
```
## NOTES
- `cgattach` attach pid to specific cgroup, and has *suid* bit set by default, be careful to use on multi-user server for securiry. To avoid this situation, you can remove the *suid* bit , then it will fallback to use *sudo*, with *visudo* you can restrict permission or set NOPASSWD for youself.
- TPROXY need root or cap_net_admin capability whatever process is listening on port,
v2ray as example: sudo setcap cap_net_admin+ep /usr/lib/v2ray/v2ray
## TIPS
- `systemd-cgls` to see the cgroup hierarchical tree.