80 Commits
v0.05 ... v0.14

Author SHA1 Message Date
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
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
fancy
1fa0d51e1d bump version 2020-05-18 21:34:30 +08:00
fancy
aedebf4e31 merge to one executable 2020-05-18 21:34:30 +08:00
fancy
051742eef1 rename .hpp to .h 2020-05-18 21:34:30 +08:00
fancy
619fcaae8e build library 2020-05-18 21:34:30 +08:00
fancy
78c10e3e3e bump version 2020-05-18 21:34:30 +08:00
fancy
99b29195f2 spearete package pack 2020-05-18 21:34:30 +08:00
fancy
53fccbe72d single cgproxyd process only 2020-05-18 21:34:30 +08:00
fancy
db4757316a fix print usage and clang format 2020-05-18 21:34:30 +08:00
fancy
3a4e62b3c2 remove no-need depency 2020-05-18 21:34:30 +08:00
fancy
4525d83a53 reorganize project 2020-05-18 21:34:30 +08:00
fancy
c5ec1027ad clang format 2020-05-17 17:32:12 +08:00
fancy
ee3a5d0fa2 merge from dev 2020-05-16 16:05:56 +08:00
fancy
f2210f9bda updated readme 2020-05-16 15:17:40 +08:00
fancy
e86ea01f6e updated readme 2020-05-16 15:12:39 +08:00
fancy
bc9f6d4d4e clean from v3.x, add json dependency 2020-05-16 14:35:59 +08:00
fancy
6de88897b2 add todo comment 2020-05-16 14:22:04 +08:00
fancy
39275452da fix precess args 2020-05-16 14:12:44 +08:00
fancy
a16cefbfb2 handle signal, robust iptables clean 2020-05-16 14:07:07 +08:00
fancy
2adba75b3e cgproxyd as class 2020-05-16 11:53:15 +08:00
fancy
fbcc499ba8 rename .h to .hpp 2020-05-16 10:49:47 +08:00
fancy
1c16f57193 now based on unix socket and json config 2020-05-16 10:29:34 +08:00
fancy
87cd5a6d99 missing one file 2020-05-16 00:17:37 +08:00
fancy
138fa698be update readme 2020-05-16 00:12:03 +08:00
fancy
ffea0fb2b9 add local aur build 2020-05-16 00:12:03 +08:00
fancy
696fcb6b4e update readme 2020-05-16 00:12:03 +08:00
fancy
9b7d6804f7 update readme 2020-05-16 00:12:03 +08:00
fancy
ad362998d8 allow array input for cgroup_proxy and cgroup_noproxy 2020-05-16 00:11:25 +08:00
fancy
2b5ff745ac core socket feature 2020-05-16 00:07:04 +08:00
fancy
b2b3168463 update readme 2020-05-14 16:16:45 +08:00
fancy
2838ffbb70 bump version 2020-05-14 14:28:08 +08:00
fancy
749fe38ca8 add local aur build 2020-05-14 14:21:40 +08:00
fancy
c0668fd8d2 use macro 2020-05-14 13:57:39 +08:00
fancy
06ae0b9fc5 update readme 2020-05-14 12:07:12 +08:00
fancy
4e04dcf84a update readme 2020-05-14 04:53:23 +08:00
fancy
c0e9ea24c1 allow array input for cgroup_proxy and cgroup_noproxy 2020-05-14 04:47:29 +08:00
fancy
b5701d8b49 allow array input for cgroup_proxy and cgroup_noproxy 2020-05-14 04:39:02 +08:00
fancy
4e37bccc1a add basic unix socket control 2020-05-13 23:43:28 +08:00
fancy
f8e0abbb55 check root, and check iptables before clean 2020-05-13 23:42:05 +08:00
fancy
6c24c68831 only masquerade ipv6 private address 2020-05-08 11:47:51 +08:00
fancy
d3b2dc0465 small fix 2020-05-07 00:22:08 +08:00
fancy
4be7be2083 example: rename to keep order 2020-05-06 02:42:44 +08:00
fancy
25f94968ae install readme.md to doc 2020-05-05 20:14:26 +08:00
fancy
3b4b67df33 small change 2020-05-05 19:30:35 +08:00
fancy
31ae519193 v2ray.service without root 2020-05-05 00:32:09 +08:00
fancy
7f0ebe9d35 remove mark_noproxy, and other small change 2020-05-04 20:41:50 +08:00
fancy
236c08172b example: update readme 2020-05-03 18:29:08 +08:00
fancy
c07ae13030 example: add aliyun doh 2020-05-03 17:00:34 +08:00
fancy
d5ea832b4f change timeout, and dns no need to proxy 2020-05-03 15:05:56 +08:00
fancy
aa5ca6f204 update example, limit to localhost 2020-05-02 08:01:19 +08:00
fancy
a80187f947 optimize function get_cgroup2_mount_point 2020-05-02 04:15:54 +08:00
fancy
dca895c7cc readme update 2020-05-02 03:45:36 +08:00
fancy
08097a54d7 readme update 2020-05-02 03:42:22 +08:00
fancy
bce568d802 readme update 2020-05-02 03:40:32 +08:00
fancy
98c07a31af readme update 2020-05-02 03:37:58 +08:00
fancy
916c11d280 update config example 2020-05-01 17:51:53 +08:00
fancy
72579bc84a typo fix 2020-05-01 13:55:06 +08:00
58 changed files with 1919 additions and 453 deletions

2
.gitignore vendored
View File

@@ -1,5 +1,7 @@
build
.directory
.vscode
.clangd
v2ray_config/proxy
v2ray_config/06_outbounds_proxy.json
aur-*

View File

@@ -1,51 +1,42 @@
cmake_minimum_required(VERSION 3.10)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
project(cgproxy VERSION 3.5)
add_executable(cgattach cgattach.cpp)
project(cgproxy VERSION 0.14)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-result")
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 cgnoproxy.sh DESTINATION /usr/bin
RENAME cgnoproxy
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
# install(FILES run_in_cgroup.sh DESTINATION /usr/bin
# RENAME run_in_cgroup
# PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
option(with_execsnoop "enable program level proxy control feature, need bcc installed" ON)
option(build_tools OFF)
option(build_test OFF)
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/)
set(basic_permission OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
add_subdirectory(src)
add_subdirectory(pack)
if (build_tools)
add_subdirectory(tools)
endif()
if (build_test)
add_subdirectory(test)
endif()
## package for deb and rpm
set(CPACK_GENERATOR "DEB;RPM")
set(CPACK_PACKAGE_NAME "cgproxy")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "cgproxy will transparent proxy anything running in specific cgroup.It aslo supports global transparent proxy and gateway proxy")
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/ PERMISSIONS ${basic_permission})
install(FILES readme.md DESTINATION /usr/share/doc/cgproxy/)
## deb pack
set(CPACK_DEBIAN_PACKAGE_NAME "cgproxy")
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "x86_64")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "systemd")
set(CPACK_DEBIAN_PACKAGE_SECTION "network")
set(CPACK_DEBIAN_PACKAGE_PRIORITY "Optional")
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/springzfx/cgproxy")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "springzfx@gmail.com")
set(CONTROL_DIR ${CMAKE_SOURCE_DIR}/control)
set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CONTROL_DIR}/postinst;${CONTROL_DIR}/prerm")
## rpm pack
set(CPACK_RPM_PACKAGE_ARCHITECTURE, "x86_64")
set(CPACK_RPM_PACKAGE_REQUIRES "systemd")
set(CPACK_RPM_PACKAGE_GROUP "network")
set(CPACK_RPM_PACKAGE_URL "https://github.com/springzfx/cgproxy")
set(CONTROL_DIR ${CMAKE_SOURCE_DIR}/control)
set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${CONTROL_DIR}/postinst")
set(CPACK_RPM_PRE_UNINSTALL_SCRIPT_FILE "${CONTROL_DIR}/prerm")
include(CPack)
# 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/)

137
_clang-format Normal file
View File

@@ -0,0 +1,137 @@
---
Language: Cpp
# BasedOnStyle: LLVM
AccessModifierOffset: -2
AlignAfterOpenBracket: Align
AlignConsecutiveMacros: false
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Right
AlignOperands: true
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: Always
AllowShortCaseLabelsOnASingleLine: true
AllowShortFunctionsOnASingleLine: All
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Always
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: MultiLine
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 90
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DeriveLineEnding: true
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
SortPriority: 0
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3
SortPriority: 0
- Regex: '.*'
Priority: 1
SortPriority: 0
IncludeIsMainRegex: '(Test)?$'
IncludeIsMainSourceRegex: ''
IndentCaseLabels: false
IndentGotoLabels: true
IndentPPDirectives: None
IndentWidth: 2
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: true
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 2
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Right
ReflowComments: true
SortIncludes: true
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceBeforeSquareBrackets: false
Standard: Latest
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 8
UseCRLF: false
UseTab: Never
...

View File

@@ -1,94 +0,0 @@
#include <errno.h>
#include <fstream>
#include <iostream>
#include <regex>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
using namespace std;
void print_usage() { fprintf(stdout, "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;
fprintf(stderr, "paramater validate error\n");
print_usage();
exit(EXIT_FAILURE);
}
string get_cgroup2_mount_point(){
char cgroup2_mount_point[100];
FILE* fp = popen("findmnt -t cgroup2 -n |cut -d' ' -f 1", "r");
fscanf(fp,"%s",&cgroup2_mount_point);
fclose(fp);
return cgroup2_mount_point;
}
int main(int argc, char *argv[]) {
setuid(0);
setgid(0);
if (getuid() != 0 || getgid() != 0) {
fprintf(stderr, "cgattach need suid sticky bit or run with root\n");
exit(EXIT_FAILURE);
}
if (argc != 3) {
fprintf(stderr, "only need 2 paramaters\n");
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_mount_point = get_cgroup2_mount_point();
string cgroup_target_path = cgroup_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 (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, errno %d\n",
cgroup_target.c_str(), errno);
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 may 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;
}

2
cgnoproxy Normal file
View File

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

View File

@@ -1,16 +0,0 @@
#!/bin/bash
config="/etc/cgproxy.conf"
source $config
# test suid bit
if [ -u "$(which cgattach)" ]; then
cgattach $$ $cgroup_noproxy && attached=1
else
sudo cgattach $$ $cgroup_noproxy && attached=1
fi
# test attach success or not
[[ -z "$attached" ]] && echo "attach error" && exit 1
exec "$@"

View File

@@ -1,34 +0,0 @@
# see how to configure
# https://github.com/springzfx/cgproxy
########################################################################
## cgroup transparent proxy
## any process in cgroup_proxy will be proxied, and cgroup_noproxy the opposite
## cgroup must start with slash '/'
# cgroup_proxy="/"
cgroup_proxy="/proxy.slice"
# cgroup_noproxy="/system.slice/v2ray.service"
cgroup_noproxy="/noproxy.slice"
########################################################################
## allow as gateway for local network
enable_gateway=false
########################################################################
## 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
enable_dns=true
########################################################################
## do not modify this if you don't known what you are doing
table=100
fwmark=0x01
mark_noproxy=0xff
mark_newin=0x02

View File

@@ -1,11 +1,10 @@
[Unit]
Description=proxy cgroup
Description=cgproxy service
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
Type=simple
ExecStart=/usr/bin/cgproxyd --execsnoop
[Install]
WantedBy=multi-user.target

View File

@@ -1,16 +0,0 @@
#!/bin/bash
config="/etc/cgproxy.conf"
source $config
# test suid bit
if [ -u "$(which cgattach)" ]; then
cgattach $$ $cgroup_proxy && attached=1
else
sudo cgattach $$ $cgroup_proxy && attached=1
fi
# test attach success or not
[[ -z "$attached" ]] && echo "attach error" && exit 1
exec "$@"

2
cgproxyd Normal file
View File

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

View File

@@ -30,68 +30,86 @@ cat << 'DOC'
DOC
}
## check root
[ ! $(id -u) -eq 0 ] && { >&2 echo "need root to modify iptables";exit -1; }
## any process in this cgroup will be proxied
cgroup_proxy="/proxy.slice"
cgroup_noproxy="/noproxy.slice"
if [ -z ${cgroup_proxy+x} ]; then
cgroup_proxy="/proxy.slice"
else
IFS=':' read -r -a cgroup_proxy <<< "$cgroup_proxy"
fi
## any process in this cgroup will not be proxied
if [ -z ${cgroup_noproxy+x} ]; then
cgroup_noproxy="/noproxy.slice"
else
IFS=':' read -r -a cgroup_noproxy <<< "$cgroup_noproxy"
fi
# allow as gateway for local network
enable_gateway=false
[ -z ${enable_gateway+x} ] && enable_gateway=false
## some variables
port=12345
enable_tcp=true
enable_udp=true
enable_ipv4=true
enable_ipv6=true
enable_dns=true
[ -z ${port+x} ] && port=12345
## some options
[ -z ${enable_dns+x} ] && enable_dns=true
[ -z ${enable_tcp+x} ] && enable_tcp=true
[ -z ${enable_udp+x} ] && enable_udp=true
[ -z ${enable_ipv4+x} ] && enable_ipv4=true
[ -z ${enable_ipv6+x} ] && enable_ipv6=true
## do not modify this if you don't known what you are doing
table=100
fwmark=0x01
mark_noproxy=0xff
make_newin=0x02
## 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=$(findmnt -t cgroup2 -n |cut -d' ' -f 1)
cgroup_mount_point=$(findmnt -t cgroup2 -n -o TARGET)
cgroup_type="cgroup2"
cgroup_procs_file="cgroup.procs"
stop(){
iptables -t mangle -L TPROXY_PRE &> /dev/null || return
echo "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
iptables -t mangle -F TPROXY_OUT
iptables -t mangle -F TPROXY_ENT
iptables -t mangle -X TPROXY_PRE
iptables -t mangle -X TPROXY_OUT
iptables -t mangle -X TPROXY_ENT
ip6tables -t mangle -D PREROUTING -j TPROXY_PRE
ip6tables -t mangle -D OUTPUT -j TPROXY_OUT
ip6tables -t mangle -F TPROXY_PRE
ip6tables -t mangle -F TPROXY_OUT
ip6tables -t mangle -F TPROXY_ENT
ip6tables -t mangle -X TPROXY_PRE
ip6tables -t mangle -X TPROXY_OUT
ip6tables -t mangle -X TPROXY_ENT
ip rule delete fwmark $fwmark lookup $table
ip route flush table $table
ip -6 rule delete fwmark $fwmark lookup $table
ip -6 route flush table $table
## may not exist, just ignore, and tracking their existence is not reliable
iptables -t nat -D POSTROUTING -m owner ! --socket-exists -j MASQUERADE &> /dev/null
ip6tables -t nat -D POSTROUTING -m owner ! --socket-exists -s fc00::/7 -j MASQUERADE &> /dev/null
}
## parse parameter
for i in "$@"
do
case $i in
stop)
iptables -t mangle -D PREROUTING -j TPROXY_PRE
iptables -t mangle -D OUTPUT -j TPROXY_OUT
iptables -t mangle -F TPROXY_PRE
iptables -t mangle -F TPROXY_OUT
iptables -t mangle -F TPROXY_ENT
iptables -t mangle -X TPROXY_PRE
iptables -t mangle -X TPROXY_OUT
iptables -t mangle -X TPROXY_ENT
ip6tables -t mangle -D PREROUTING -j TPROXY_PRE
ip6tables -t mangle -D OUTPUT -j TPROXY_OUT
ip6tables -t mangle -F TPROXY_PRE
ip6tables -t mangle -F TPROXY_OUT
ip6tables -t mangle -F TPROXY_ENT
ip6tables -t mangle -X TPROXY_PRE
ip6tables -t mangle -X TPROXY_OUT
ip6tables -t mangle -X TPROXY_ENT
ip rule delete fwmark $fwmark lookup $table
ip route flush table $table
ip -6 rule delete fwmark $fwmark lookup $table
ip -6 route flush table $table
## may not exist, just ignore, and tracking their existence is not reliable
iptables -t nat -D POSTROUTING -m owner ! --socket-exists -j MASQUERADE &> /dev/null
ip6tables -t nat -D POSTROUTING -m owner ! --socket-exists -j MASQUERADE &> /dev/null
stop
exit 0
;;
--config=*)
config=${i#*=}
source $config
shift
;;
--help)
print_help
@@ -101,16 +119,19 @@ esac
done
## TODO cgroup need to exists before using in iptables since 5.6.5, maybe it's bug
## only create the first one in arrary
test -d $cgroup_mount_point$cgroup_proxy || mkdir $cgroup_mount_point$cgroup_proxy || exit -1;
test -d $cgroup_mount_point$cgroup_noproxy || mkdir $cgroup_mount_point$cgroup_noproxy || exit -1;
echo "applying tproxy iptables"
## use TPROXY
#ipv4#
ip rule add fwmark $fwmark table $table
ip route add local default dev lo table $table
iptables -t mangle -N TPROXY_ENT
iptables -t mangle -A TPROXY_ENT -p tcp -j TPROXY --on-ip 127.0.0.1 --on-port $port --tproxy-mark $fwmark
iptables -t mangle -A TPROXY_ENT -p udp -j TPROXY --on-ip 127.0.0.1 --on-port $port --tproxy-mark $fwmark
iptables -t mangle -A TPROXY_ENT -p tcp -j TPROXY --on-ip localhost --on-port $port --tproxy-mark $fwmark
iptables -t mangle -A TPROXY_ENT -p udp -j TPROXY --on-ip localhost --on-port $port --tproxy-mark $fwmark
iptables -t mangle -N TPROXY_PRE
iptables -t mangle -A TPROXY_PRE -m socket --transparent -j MARK --set-mark $fwmark
@@ -119,76 +140,78 @@ iptables -t mangle -A TPROXY_PRE -p icmp -j RETURN
iptables -t mangle -A TPROXY_PRE -p udp --dport 53 -j TPROXY_ENT
iptables -t mangle -A TPROXY_PRE -p tcp --dport 53 -j TPROXY_ENT
iptables -t mangle -A TPROXY_PRE -m addrtype --dst-type LOCAL -j RETURN
iptables -t mangle -A TPROXY_PRE -m pkttype --pkt-type broadcast -j RETURN
iptables -t mangle -A TPROXY_PRE -m pkttype --pkt-type multicast -j RETURN
iptables -t mangle -A TPROXY_PRE -m addrtype ! --dst-type UNICAST -j RETURN
iptables -t mangle -A TPROXY_PRE -j TPROXY_ENT
iptables -t mangle -A PREROUTING -j TPROXY_PRE
iptables -t mangle -N TPROXY_OUT
iptables -t mangle -A TPROXY_OUT -o lo -j RETURN
iptables -t mangle -A TPROXY_OUT -p icmp -j RETURN
iptables -t mangle -A TPROXY_OUT -m connmark --mark $make_newin -j RETURN
iptables -t mangle -A TPROXY_OUT -m pkttype --pkt-type broadcast -j RETURN
iptables -t mangle -A TPROXY_OUT -m pkttype --pkt-type multicast -j RETURN
iptables -t mangle -A TPROXY_OUT -m mark --mark $mark_noproxy -j RETURN
iptables -t mangle -A TPROXY_OUT -m cgroup --path $cgroup_noproxy -j RETURN
iptables -t mangle -A TPROXY_OUT -m cgroup --path $cgroup_proxy -j MARK --set-mark $fwmark
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
done
for cg in ${cgroup_proxy[@]}; do
iptables -t mangle -A TPROXY_OUT -m cgroup --path $cg -j MARK --set-mark $fwmark
done
iptables -t mangle -A OUTPUT -j TPROXY_OUT
#ipv6#
ip -6 rule add fwmark $fwmark table $table
ip -6 route add local default dev lo table $table
ip6tables -t mangle -N TPROXY_ENT
ip6tables -t mangle -A TPROXY_ENT -p tcp -j TPROXY --on-ip ::1 --on-port $port --tproxy-mark $fwmark
ip6tables -t mangle -A TPROXY_ENT -p udp -j TPROXY --on-ip ::1 --on-port $port --tproxy-mark $fwmark
ip6tables -t mangle -A TPROXY_ENT -p tcp -j TPROXY --on-ip localhost --on-port $port --tproxy-mark $fwmark
ip6tables -t mangle -A TPROXY_ENT -p udp -j TPROXY --on-ip localhost --on-port $port --tproxy-mark $fwmark
ip6tables -t mangle -N TPROXY_PRE
ip6tables -t mangle -A TPROXY_PRE -m socket --transparent -j MARK --set-mark $fwmark
ip6tables -t mangle -A TPROXY_PRE -m socket --transparent -j RETURN
ip6tables -t mangle -A TPROXY_PRE -p icmp -j RETURN
ip6tables -t mangle -A TPROXY_PRE -p icmpv6 -j RETURN
ip6tables -t mangle -A TPROXY_PRE -p udp --dport 53 -j TPROXY_ENT
ip6tables -t mangle -A TPROXY_PRE -p tcp --dport 53 -j TPROXY_ENT
ip6tables -t mangle -A TPROXY_PRE -m addrtype --dst-type LOCAL -j RETURN
ip6tables -t mangle -A TPROXY_PRE -m pkttype --pkt-type broadcast -j RETURN
ip6tables -t mangle -A TPROXY_PRE -m pkttype --pkt-type multicast -j RETURN
ip6tables -t mangle -A TPROXY_PRE -m addrtype ! --dst-type UNICAST -j RETURN
ip6tables -t mangle -A TPROXY_PRE -j TPROXY_ENT
ip6tables -t mangle -A PREROUTING -j TPROXY_PRE
ip6tables -t mangle -N TPROXY_OUT
ip6tables -t mangle -A TPROXY_OUT -o lo -j RETURN
ip6tables -t mangle -A TPROXY_OUT -p icmp -j RETURN
ip6tables -t mangle -A TPROXY_OUT -m pkttype --pkt-type broadcast -j RETURN
ip6tables -t mangle -A TPROXY_OUT -m pkttype --pkt-type multicast -j RETURN
ip6tables -t mangle -A TPROXY_OUT -p icmpv6 -j RETURN
ip6tables -t mangle -A TPROXY_OUT -m connmark --mark $make_newin -j RETURN
ip6tables -t mangle -A TPROXY_OUT -m mark --mark $mark_noproxy -j RETURN
ip6tables -t mangle -A TPROXY_OUT -m cgroup --path $cgroup_noproxy -j RETURN
ip6tables -t mangle -A TPROXY_OUT -m cgroup --path $cgroup_proxy -j MARK --set-mark $fwmark
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
done
for cg in ${cgroup_proxy[@]}; do
ip6tables -t mangle -A TPROXY_OUT -m cgroup --path $cg -j MARK --set-mark $fwmark
done
ip6tables -t mangle -A OUTPUT -j TPROXY_OUT
## allow to disable, order is important
$enable_dns || iptables -t mangle -I TPROXY_OUT -p udp --dport 53 -j RETURN
$enable_dns || ip6tables -t mangle -I TPROXY_OUT -p udp --dport 53 -j RETURN
$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
$enable_dns || iptables -t mangle -I TPROXY_OUT -p udp --dport 53 -j RETURN
$enable_dns || ip6tables -t mangle -I TPROXY_OUT -p udp --dport 53 -j RETURN
$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
if $enable_gateway; then
$enable_dns || iptables -t mangle -I TPROXY_PRE -p udp --dport 53 -j RETURN
$enable_dns || ip6tables -t mangle -I TPROXY_PRE -p udp --dport 53 -j RETURN
$enable_udp || iptables -t mangle -I TPROXY_PRE -p udp -j RETURN
$enable_udp || ip6tables -t mangle -I TPROXY_PRE -p udp -j RETURN
$enable_tcp || iptables -t mangle -I TPROXY_PRE -p tcp -j RETURN
$enable_tcp || ip6tables -t mangle -I TPROXY_PRE -p tcp -j RETURN
$enable_ipv4 || iptables -t mangle -I TPROXY_PRE -j RETURN
$enable_ipv6 || ip6tables -t mangle -I TPROXY_PRE -j RETURN
$enable_dns || iptables -t mangle -I TPROXY_PRE -p udp --dport 53 -j RETURN
$enable_dns || ip6tables -t mangle -I TPROXY_PRE -p udp --dport 53 -j RETURN
$enable_udp || iptables -t mangle -I TPROXY_PRE -p udp -j RETURN
$enable_udp || ip6tables -t mangle -I TPROXY_PRE -p udp -j RETURN
$enable_tcp || iptables -t mangle -I TPROXY_PRE -p tcp -j RETURN
$enable_tcp || ip6tables -t mangle -I TPROXY_PRE -p tcp -j RETURN
$enable_ipv4 || iptables -t mangle -I TPROXY_PRE -j RETURN
$enable_ipv6 || ip6tables -t mangle -I TPROXY_PRE -j RETURN
fi
## do not handle local device connection through tproxy if gateway is not enabled
$enable_gateway || iptables -t mangle -I TPROXY_PRE -m addrtype ! --src-type LOCAL -m addrtype ! --dst-type LOCAL -j RETURN
$enable_gateway || ip6tables -t mangle -I TPROXY_PRE -m addrtype ! --src-type LOCAL -m addrtype ! --dst-type LOCAL -j RETURN
$enable_gateway || iptables -t mangle -I TPROXY_PRE -m addrtype ! --src-type LOCAL -j RETURN
$enable_gateway || ip6tables -t mangle -I TPROXY_PRE -m addrtype ! --src-type LOCAL -j RETURN
## make sure following rules are the first in chain TPROXY_PRE to mark new incoming connection or gateway proxy connection
## so must put at last to insert first
@@ -197,14 +220,14 @@ 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
noproxy cgroup: ${cgroup_noproxy[@]}
proxied cgroup: ${cgroup_proxy[@]}
DOC
if $enable_gateway; then
iptables -t nat -A POSTROUTING -m owner ! --socket-exists -j MASQUERADE
ip6tables -t nat -A POSTROUTING -m owner ! --socket-exists -j MASQUERADE
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"

13
config.json Normal file
View File

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

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)

26
pack/CMakeLists.txt Normal file
View File

@@ -0,0 +1,26 @@
## package for deb and rpm
set(CPACK_GENERATOR "DEB;RPM")
set(CPACK_PACKAGE_NAME "cgproxy")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "cgproxy will transparent proxy anything running in specific cgroup.It aslo supports global transparent proxy and gateway proxy")
## deb pack
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")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "springzfx@gmail.com")
set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_CURRENT_SOURCE_DIR}/postinst;${CMAKE_CURRENT_SOURCE_DIR}/prerm")
## 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")
set(CPACK_RPM_PRE_UNINSTALL_SCRIPT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/prerm")
include(CPack)

187
readme.md
View File

@@ -1,12 +1,19 @@
# 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*, 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)
Main feature:
- supports cgroup/program level proxy control.
- supports global transparent proxy and gateway proxy.
## Contents
<!--ts-->
@@ -14,7 +21,8 @@ It aslo supports global transparent proxy and gateway proxy. See [Global transpa
* [Introduction](#introduction)
* [Prerequest](#prerequest)
* [How to install](#how-to-install)
* [How to use](#how-to-use)
* [Default usage](#default-usage)
* [Configuration](#configuration)
* [Global transparent proxy](#global-transparent-proxy)
* [Gateway proxy](#gateway-proxy)
* [Other useful tools provided in this project](#other-useful-tools-provided-in-this-project)
@@ -22,7 +30,7 @@ It aslo supports global transparent proxy and gateway proxy. See [Global transpa
* [TIPS](#tips)
* [Licences](#licences)
<!-- Added by: fancy, at: Thu 23 Apr 2020 01:23:57 PM HKT -->
<!-- Added by: fancy, at: Sat 16 May 2020 03:12:07 PM HKT -->
<!--te-->
@@ -36,7 +44,13 @@ It aslo supports global transparent proxy and gateway proxy. See [Global transpa
- TPROXY
A process listening on port (e.g. 12345) to accept iptables TPROXY, for example v2ray's dokodemo-door in tproxy mode.
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
@@ -44,67 +58,88 @@ It aslo supports global transparent proxy and gateway proxy. See [Global transpa
mkdir build && cd build && cmake .. && make && make install
```
- It is alreay in [archlinux AUR](https://aur.archlinux.org/packages/cgproxy/).
- It is alreay in [archlinux AUR](https://aur.archlinux.org/packages/?K=cgproxy).
- DEB and RPM are packaged in [release page](https://github.com/springzfx/cgproxy/releases).
## How to use
## Default usage
- First enable service
- First enable and start service
```bash
sudo systemctl enable --now cgproxy.service
sudo systemctl status cgproxy.service
```
- Then prefix with cgproxy with your command, just like proxychains
```
cgproxy <CMD>
```bash
cgproxy [--debug] <CMD>
```
- For example, test proxy
```bash
cgproxy curl -vIs https://www.google.com
cgproxy curl -vI https://www.google.com
```
<details>
<summary>More config in `/etc/cgproxy.conf` (click to expand)</summary>
- To completely stop
```
sudo systemctl disable --now cgproxy.service
```
```bash
########################################################################
## cgroup transparent proxy
## any process in cgroup_proxy will be proxied, and cgroup_noproxy the opposite
## cgroup must start with slash '/'
# cgroup_proxy="/"
cgroup_proxy="/proxy.slice"
cgroup_noproxy="/noproxy.slice"
## Configuration
########################################################################
## allow as gateway for local network
enable_gateway=false
Config file: **/etc/cgproxy/config.json**
########################################################################
## 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
enable_dns=true
########################################################################
## do not modify this if you don't known what you are doing
table=100
fwmark=0x01
mark_noproxy=0xff
mark_newin=0x02
```json
{
"port": 12345,
"program_noproxy": ["v2ray", "qv2ray"],
"program_proxy": [ ],
"cgroup_noproxy": ["/system.slice/v2ray.service"],
"cgroup_proxy": [ ],
"enable_gateway": false,
"enable_dns": true,
"enable_udp": true,
"enable_tcp": true,
"enable_ipv4": true,
"enable_ipv6": true
}
```
</details>
- **port** tproxy listenning port
- program level proxy control, 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
```bash
@@ -113,67 +148,67 @@ sudo systemctl restart cgproxy.service
## Global transparent proxy
- First, set **cgroup_proxy="/"** in `/etc/cgproxy.conf`, this will proxy all connection
- Set `"cgroup_proxy":["/"]` in configuration, this will proxy all connection
- Then, run your proxy software in cgroup_noproxy to allow direct to internet
```bash
cgnoproxy <PROXY PROGRAM>
# qv2ray as example
cgnoproxy qv2ray
# v2ray as example
cgnoproxy sudo v2ray --config config_file
```
- Finally, restart service `sudo systemctl restart cgproxy.service`, that's all
- Allow your proxy program (v2ray) direct to internet to avoid loop. Two ways:
- active way, run command
example: `cgnoproxy sudo v2ray -config config_file`
example: `cgnoproxy qv2ray`
- passive way, persistent config
example: `"program_noproxy":["v2ray" ,"qv2ray"]`
example: `"cgroup_noproxy":["/system.slice/v2ray.service"]`
- Finally, restart cgproxy service, that's all
## Gateway proxy
- Set **enable_gateway=true** in `/etc/cgproxy.conf` and restart service
- Run your proxy software in cgroup_noproxy to allow direct to internet as above. This is necessary when you use global transparent proxy the same time.
- Other device set this host as gateway, and set public dns if necessary
- Set `"enable_gateway":true` in configuration
- And allow your proxy software (v2ray) direct to internet if necessary, described above
- Other device set this host as gateway, and set public dns if need
## Other useful tools provided in this project
- `cgnoproxy` run program wihout proxy, very useful in global transparent proxy
```bash
cgnoproxy <CMD>
cgnoproxy [--debug] <CMD>
cgnoproxy [--debug] --pid <PID>
```
- `run_in_cgroup` run command in 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.
```bash
run_in_cgroup --cgroup=CGROUP <COMMAND>
# example
run_in_cgroup --cgroup=/mycgroup.slice ping 127.0.0.1
```
You need to set `set(build_tools ON)` in *CmakeLists.txt* to build this.
- `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.
```bash
cgattch <pid> <cgroup>
# example
cgattch 9999 /proxy.slice
```
- For more detail command usage, see `man cgproxyd` `man cgproxy` `man cgnoproxy`
## 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.
- v2ray TPROXY need root or special permission
- v2ray TPROXY need root or special permission, use [service](https://github.com/springzfx/cgproxy/blob/v3.x/v2ray_config/v2ray.service) or
```bash
sudo setcap "cap_net_admin,cap_net_bind_service=ep" /usr/lib/v2ray/v2ray
```
- Why not outbound mark solution, because in v2ray [when `"localhost"` is used, out-going DNS traffic is not controlled by V2Ray](https://www.v2fly.org/en/configuration/dns.html), so no mark at all, that's pitty.
- Why not outbound mark solution, because in v2ray [when `"localhost"` is used, out-going DNS traffic is not controlled by V2Ray](https://www.v2fly.org/en/configuration/dns.html), so no mark at all, that's pity.
## TIPS
- `systemd-cgls` to see the cgroup hierarchical tree.
- v2ray full config exmaple in [v2ray_buid](https://github.com/springzfx/cgproxy/tree/master/v2ray_buid), more to see [v2ray multi-file config](https://www.v2fly.org/chapter_02/multiple_config.html)
- Qv2ray config example
- Check cgroup2 support `findmnt -t cgroup2`
- Offer you v2ray service and full config exmaple in [v2ray_config](https://github.com/springzfx/cgproxy/tree/master/v2ray_config)
- Offer you qv2ray config example
![Qv2ray config example](https://i.loli.net/2020/04/28/bdQBzUD37FOgfvt.png)

View File

@@ -1,50 +0,0 @@
#!/bin/bash
print_help(){
cat << 'DOC'
usage:
run_in_cgroup --cgroup=CGROUP <COMMAND>
run_in_cgroup --help
note:
CGROUP must start will slash '/' , and no special character
example:
run_in_cgroup --cggroup=/mycgroup.slice ping 127.0.0.1
DOC
}
## parse parameter
for i in "$@"
do
case $i in
--cgroup=*)
cgroup=${i#*=}
shift
;;
--help)
print_help
exit 0
shift
;;
-*)
shift
;;
*)
break
;;
esac
done
[[ -z "$cgroup" ]] && print_help && exit 1
[[ -z "$@" ]] && print_help && exit 1
# test suid bit
if [ -u "$(which cgattach)" ]; then
cgattach $$ $cgroup && attached=1
else
sudo cgattach $$ $cgroup && attached=1
fi
# test attach success or not
[[ -z "$attached" ]] && print_help && exit 1
exec "$@"

19
src/CMakeLists.txt Normal file
View File

@@ -0,0 +1,19 @@
find_package(Threads REQUIRED)
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
socket_client.cpp socket_server.cpp)
target_link_libraries(main PRIVATE nlohmann_json::nlohmann_json Threads::Threads dl)
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})

84
src/cgproxy.hpp Normal file
View File

@@ -0,0 +1,84 @@
#include "common.h"
#include "config.h"
#include "socket_client.h"
#include <cstdlib>
#include <nlohmann/json.hpp>
#include <unistd.h>
using json = nlohmann::json;
using namespace CGPROXY;
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;
cout << "Usage: cgproxy [--help] [--debug] <CMD>" << endl;
} else {
cout << "Run program without proxy" << endl;
cout << "Usage: cgpnoroxy [--help] [--debug] <CMD>" << endl;
cout << "Alias: cgnoproxy = cgproxy --noproxy" << endl;
}
}
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 = i;
return true;
}
void send_pid(const pid_t pid, bool proxy, int &status) {
json j;
j["type"] = proxy ? MSG_TYPE_PROXY_PID : MSG_TYPE_NOPROXY_PID;
j["data"] = pid;
SOCKET::send(j.dump(), status);
}
int main(int argc, char *argv[]) {
int shift = -1;
if (!processArgs(argc, argv, shift)) {
error("parameter error");
exit(EXIT_FAILURE);
}
if (print_help) {
print_usage();
exit(0);
}
if (!attach_pid && argc == shift) {
error("no program specified");
exit(EXIT_FAILURE);
}
int status = -1;
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);
}
// 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

305
src/cgproxyd.hpp Normal file
View File

@@ -0,0 +1,305 @@
#ifndef CGPROXYD_HPP
#define CGPROXYD_HPP
#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 <nlohmann/json.hpp>
#include <pthread.h>
#include <sched.h>
#include <sys/file.h>
#include <unistd.h>
using namespace std;
using json = nlohmann::json;
using namespace ::CGPROXY::SOCKET;
using namespace ::CGPROXY::CONFIG;
using namespace ::CGPROXY::CGROUP;
// using namespace ::CGPROXY::EXECSNOOP;
namespace CGPROXY::EXECSNOOP {
typedef void *(*startThread_t)(void *arg);
startThread_t _startThread;
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 failed: %s", dlerror());
return false;
}
info("dlsym startThread success");
return true;
} catch (exception &e) { return false; }
}
} // namespace CGPROXY::EXECSNOOP
namespace CGPROXY::CGPROXYD {
bool print_help = false;
bool enable_socketserver = true;
bool enable_execsnoop = false;
class cgproxyd {
SOCKET::thread_arg socketserver_thread_arg;
pthread_t socket_thread_id = THREAD_UNDEF;
EXECSNOOP::thread_arg execsnoop_thread_arg;
pthread_t execsnoop_thread_id = THREAD_UNDEF;
Config config;
static cgproxyd *instance;
static int handle_msg_static(char *msg) {
if (!instance) {
error("no cgproxyd instance assigned");
return ERROR;
}
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) {
auto path = realpath(to_str("/proc/", pid, "/exe").c_str(), NULL);
if (path == NULL) {
debug("pid %d live life too short", pid);
return 0;
}
debug("execsnoop: %d %s", pid, path);
vector<string> v;
v = config.program_noproxy;
if (find(v.begin(), v.end(), path) != v.end()) {
info("execsnoop noproxy: %d %s", pid, path);
free(path);
return attach(pid, config.cgroup_noproxy_preserved);
}
v = config.program_proxy;
if (find(v.begin(), v.end(), path) != v.end()) {
info("execsnoop proxied: %d %s", pid, path);
free(path);
return attach(pid, config.cgroup_proxy_preserved);
}
free(path);
return 0;
}
static void signalHandler(int signum) {
debug("Signal %d received.", signum);
if (!instance) {
error("no cgproxyd instance assigned");
} else {
instance->stop();
}
exit(0);
}
// single process instance
int lock_fd;
void lock() {
lock_fd = open(PID_LOCK_FILE, O_CREAT | O_RDWR, 0666);
int rc = flock(lock_fd, LOCK_EX | LOCK_NB);
if (rc == -1) {
perror(PID_LOCK_FILE);
error("maybe another cgproxyd is running");
exit(EXIT_FAILURE);
} else {
ofstream ofs(PID_LOCK_FILE);
ofs << getpid() << endl;
ofs.close();
}
}
void unlock() {
close(lock_fd);
unlink(PID_LOCK_FILE);
}
int handle_msg(char *msg) {
debug("received msg: %s", msg);
json j;
try {
j = json::parse(msg);
} catch (exception &e) {
debug("msg paser error");
return MSG_ERROR;
}
int type, status;
int pid, cgroup_target;
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();
return status;
break;
case MSG_TYPE_CONFIG_PATH:
status = config.loadFromFile(j.at("data").get<string>());
if (status == SUCCESS) status = applyConfig();
return status;
break;
case MSG_TYPE_PROXY_PID:
pid = j.at("data").get<int>();
status = attach(pid, config.cgroup_proxy_preserved);
return status;
break;
case MSG_TYPE_NOPROXY_PID:
pid = j.at("data").get<int>();
status = attach(pid, config.cgroup_noproxy_preserved);
return status;
break;
default: return MSG_ERROR; break;
};
} catch (out_of_range &e) { return MSG_ERROR; } catch (exception &e) {
return ERROR;
}
}
pthread_t startSocketListeningThread() {
socketserver_thread_arg.handle_msg = &handle_msg_static;
pthread_t thread_id;
int status =
pthread_create(&thread_id, NULL, &SOCKET::startThread, &socketserver_thread_arg);
if (status != 0) {
error("socket thread create failed");
return THREAD_UNDEF;
}
return thread_id;
}
pthread_t startExecsnoopThread() {
if (!EXECSNOOP::loadExecsnoopLib() || EXECSNOOP::_startThread == NULL) {
error("execsnoop start failed, maybe bcc not installed");
return THREAD_UNDEF;
}
execsnoop_thread_arg.handle_pid = &handle_pid_static;
pthread_t thread_id;
int status =
pthread_create(&thread_id, NULL, EXECSNOOP::_startThread, &execsnoop_thread_arg);
if (status != 0) {
error("execsnoop thread create failed");
return THREAD_UNDEF;
}
return thread_id;
}
void processRunningProgram() {
debug("process running program") for (auto &path :
config.program_noproxy) for (auto &pid :
bash_pidof(path)) {
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)) {
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:
int start() {
lock();
signal(SIGINT, &signalHandler);
signal(SIGTERM, &signalHandler);
signal(SIGHUP, &signalHandler);
assignStaticInstance();
config.loadFromFile(DEFAULT_CONFIG_FILE);
applyConfig();
processRunningProgram();
if (enable_socketserver) {
socket_thread_id = startSocketListeningThread();
if (socket_thread_id > 0) info("socket server listening thread started");
}
if (enable_execsnoop) {
execsnoop_thread_id = startExecsnoopThread();
if (execsnoop_thread_id > 0) info("execsnoop thread started");
}
cout << flush;
pthread_join(socket_thread_id, NULL);
pthread_join(execsnoop_thread_id, NULL);
return 0;
}
int applyConfig() {
system(TPROXY_IPTABLS_CLEAN);
config.print_summary();
config.toEnv();
system(TPROXY_IPTABLS_START);
// no need to track running status
return 0;
}
void stop() {
debug("stopping");
system(TPROXY_IPTABLS_CLEAN);
unlock();
}
~cgproxyd() { stop(); }
};
cgproxyd *cgproxyd::instance = NULL;
void print_usage() {
cout << "Start a daemon with unix socket to accept control" << endl;
cout << "Usage: cgproxyd [--help] [--debug]" << endl;
cout << "Alias: cgproxyd = cgproxy --daemon" << endl;
}
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; }
}
}
int main(int argc, char *argv[]) {
setbuf(stdout, NULL);
processArgs(argc, argv);
if (print_help) {
print_usage();
exit(0);
}
if (getuid() != 0) {
error("permission denied, need root");
exit(EXIT_FAILURE);
}
cgproxyd d;
return d.start();
}
} // namespace CGPROXY::CGPROXYD
#endif

94
src/cgroup_attach.cpp Normal file
View File

@@ -0,0 +1,94 @@
#include "cgroup_attach.h"
#include "common.h"
#include <errno.h>
#include <fstream>
#include <iostream>
#include <regex>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
namespace CGPROXY::CGROUP {
string cgroup2_mount_point = get_cgroup2_mount_point();
bool exist(string path) {
struct stat st;
if (stat(path.c_str(), &st) != -1) { return S_ISDIR(st.st_mode); }
return false;
}
string get_cgroup2_mount_point() {
stringstream buffer;
FILE *fp = popen("findmnt -t cgroup2 -n -o TARGET", "r");
if (!fp) return "";
char buf[64];
while (fgets(buf, 64, fp) != NULL) { buffer << buf; }
pclose(fp);
string s = buffer.str();
s.pop_back(); // remove newline character
return s;
}
bool validate(string pid, string cgroup) {
bool pid_v = validPid(pid);
bool cg_v = validCgroup(cgroup);
if (pid_v && cg_v) return true;
error("attach paramater validate error");
return_error;
}
int attach(const string pid, const string cgroup_target) {
if (getuid() != 0) {
error("need root to attach cgroup");
return_error;
}
debug("attaching %s to %s", pid.c_str(), cgroup_target.c_str());
int status;
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 (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;
}
// error("cgroup %s not exist",cgroup_target.c_str());
// return_error
}
// 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;
}
procs << pid.c_str() << endl;
procs.close();
// maybe there some write error, for example process pid may not exist
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_success;
}
int attach(const int pid, const string cgroup_target) {
return attach(to_str(pid), cgroup_target);
}
} // namespace CGPROXY::CGROUP

18
src/cgroup_attach.h Normal file
View File

@@ -0,0 +1,18 @@
#ifndef CGPROUP_ATTACH_H
#define CGPROUP_ATTACH_H
#include <stdlib.h>
#include <string>
using namespace std;
namespace CGPROXY::CGROUP {
extern string cgroup2_mount_point;
bool exist(string path);
bool validate(string pid, string cgroup);
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

95
src/common.cpp Normal file
View File

@@ -0,0 +1,95 @@
#include "common.h"
#include <fstream>
#include <limits.h>
#include <linux/limits.h>
#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;
for (const auto &e : t) e != *(t.end() - 1) ? s += e + delm : s += e;
return s;
}
string join2str(const int argc, char **argv, const char delm) {
string s;
for (int i = 0; i < argc; i++) {
s += argv[i];
if (i != argc - 1) s += delm;
}
return s;
}
bool validCgroup(const string cgroup) {
return regex_match(cgroup, regex("^/[a-zA-Z0-9\\-_./@]*$"));
}
bool validCgroup(const vector<string> cgroup) {
for (auto &e : cgroup) {
if (!regex_match(e, regex("^/[a-zA-Z0-9\\-_./@]*$"))) { return false; }
}
return true;
}
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;
FILE *fp = popen(to_str("pidof ", path).c_str(), "r");
if (!fp) return pids;
int pid;
char buf[64];
while (fscanf(fp, "%d", &pid) != EOF) { pids.push_back(pid); }
pclose(fp);
return pids;
}
string bash_which(const string &name) {
stringstream buffer;
FILE *fp = popen(to_str("which ", name).c_str(), "r");
if (!fp) return "";
char buf[64];
while (fgets(buf, 64, fp) != NULL) { buffer << buf; }
pclose(fp);
string s = buffer.str();
s.pop_back(); // remove newline character
return s;
}
string bash_readlink(const string &path) {
stringstream buffer;
FILE *fp = popen(to_str("readlink -e ", path).c_str(), "r");
if (!fp) return "";
char buf[64];
while (fgets(buf, 64, fp) != NULL) { buffer << buf; }
pclose(fp);
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;
}

90
src/common.h Normal file
View File

@@ -0,0 +1,90 @@
#ifndef COMMON_H
#define COMMON_H 1
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
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 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 CGROUP_PROXY_PRESVERED "/proxy.slice"
#define CGROUP_NOPROXY_PRESVERED "/noproxy.slice"
#define THREAD_UNDEF 0
#define MSG_TYPE_CONFIG_JSON 1
#define MSG_TYPE_CONFIG_PATH 2
#define MSG_TYPE_PROXY_PID 3
#define MSG_TYPE_NOPROXY_PID 4
#define UNKNOWN_ERROR 99
#define ERROR -1
#define SUCCESS 0
#define CONN_ERROR 1
#define MSG_ERROR 2
#define PARSE_ERROR 3
#define PARAM_ERROR 4
#define APPLY_ERROR 5
#define CGROUP_ERROR 6
#define FILE_ERROR 7
extern bool enable_debug;
extern bool enable_info;
#define error(...) \
{ \
fprintf(stderr, "error: "); \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, "\n"); \
}
#define debug(...) \
if (enable_debug) { \
fprintf(stdout, "debug: "); \
fprintf(stdout, __VA_ARGS__); \
fprintf(stdout, "\n"); \
}
#define info(...) \
if (enable_info) { \
fprintf(stdout, "info: "); \
fprintf(stdout, __VA_ARGS__); \
fprintf(stdout, "\n"); \
}
#define return_error return -1
#define return_success return 0
template <typename... T> string to_str(T... args) {
stringstream ss;
ss.clear();
ss << std::boolalpha;
(ss << ... << args);
return ss.str();
}
string join2str(const vector<string> t, const char delm = ' ');
string join2str(const int argc, char **argv, const char delm = ' ');
bool validCgroup(const string cgroup);
bool validCgroup(const vector<string> cgroup);
bool validPid(const string pid);
bool validPort(const int port);
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);
#endif

154
src/config.cpp Normal file
View File

@@ -0,0 +1,154 @@
#include "config.h"
#include "common.h"
#include <fstream>
#include <iomanip>
#include <nlohmann/json.hpp>
#include <set>
#include <vector>
using json = nlohmann::json;
#define add2json(v) j[#v] = v;
#define tryassign(v) \
try { \
j.at(#v).get_to(v); \
} catch (exception & e) {}
#define merge(v) \
{ \
v.erase(std::remove(v.begin(), v.end(), v##_preserved), v.end()); \
v.insert(v.begin(), v##_preserved); \
}
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);
setenv("port", to_str(port).c_str(), 1);
setenv("enable_dns", to_str(enable_dns).c_str(), 1);
setenv("enable_tcp", to_str(enable_tcp).c_str(), 1);
setenv("enable_udp", to_str(enable_udp).c_str(), 1);
setenv("enable_ipv4", to_str(enable_ipv4).c_str(), 1);
setenv("enable_ipv6", to_str(enable_ipv6).c_str(), 1);
}
int Config::saveToFile(const string f) {
ofstream o(f);
if (!o.is_open()) return FILE_ERROR;
string js = toJsonStr();
o << setw(4) << js << endl;
o.close();
return 0;
}
string Config::toJsonStr() {
json j;
add2json(program_proxy);
add2json(program_noproxy);
add2json(cgroup_proxy);
add2json(cgroup_noproxy);
add2json(enable_gateway);
add2json(port);
add2json(enable_dns);
add2json(enable_tcp);
add2json(enable_udp);
add2json(enable_ipv4);
add2json(enable_ipv6);
return j.dump();
}
int Config::loadFromFile(const string f) {
debug("loading config: %s", f.c_str());
ifstream ifs(f);
if (ifs.is_open()) {
string js = to_str(ifs.rdbuf());
ifs.close();
return loadFromJsonStr(js);
} else {
error("open failed: %s", f.c_str());
return FILE_ERROR;
}
}
int Config::loadFromJsonStr(const string js) {
if (!validateJsonStr(js)) {
error("json validate fail");
return PARAM_ERROR;
}
json j = json::parse(js);
tryassign(program_proxy);
tryassign(program_noproxy);
tryassign(cgroup_proxy);
tryassign(cgroup_noproxy);
tryassign(enable_gateway);
tryassign(port);
tryassign(enable_dns);
tryassign(enable_tcp);
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);
return 0;
}
void Config::mergeReserved() {
merge(cgroup_proxy);
merge(cgroup_noproxy);
}
bool Config::validateJsonStr(const string js) {
json j = json::parse(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;
// TODO what if vector<int> etc.
if (value.is_array() && !validCgroup((vector<string>)value)) status = false;
if (!value.is_string() && !value.is_array()) status = false;
} else if (key == "port") {
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;
}
if (!status) {
error("invalid value for key: %s", key.c_str());
return false;
}
}
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;
}
} // namespace CGPROXY::CONFIG

42
src/config.h Normal file
View File

@@ -0,0 +1,42 @@
#ifndef CONFIG_H
#define CONFIG_H
#include "common.h"
#include <stdlib.h>
#include <string>
#include <vector>
using namespace std;
namespace CGPROXY::CONFIG {
class Config {
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;
int port = 12345;
bool enable_dns = true;
bool enable_tcp = true;
bool enable_udp = true;
bool enable_ipv4 = true;
bool enable_ipv6 = true;
void toEnv();
int saveToFile(const string f);
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
#endif

102
src/execsnoop.cpp Normal file
View File

@@ -0,0 +1,102 @@
#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;
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) {
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;
}
while (true) bpf.poll_perf_buffer("events");
return 0;
}
void *startThread(void *arg) {
thread_arg *p = (thread_arg *)arg;
callback = p->handle_pid;
execsnoop();
return (void *)0;
}
} // namespace CGPROXY::EXECSNOOP
extern "C" void *_startThread(void *arg) { return CGPROXY::EXECSNOOP::startThread(arg); }

22
src/execsnoop.h Normal file
View File

@@ -0,0 +1,22 @@
#ifndef EXECSNOOP_HPP
#define EXECSNOOP_HPP 1
#include <functional>
#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();
struct thread_arg {
function<int(int)> handle_pid;
};
void *startThread(void *arg);
} // namespace CGPROXY::EXECSNOOP
#endif

17
src/main.cpp Normal file
View File

@@ -0,0 +1,17 @@
#include "cgproxy.hpp"
#include "cgproxyd.hpp"
bool as_cgproxyd = false;
void processArgs(const int argc, char *argv[]) {
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--daemon") == 0) { as_cgproxyd = true; }
if (argv[i][0] != '-') { break; }
}
}
int main(int argc, char *argv[]) {
processArgs(argc, argv);
if (as_cgproxyd) ::CGPROXY::CGPROXYD::main(argc, argv);
else
::CGPROXY::CGPROXY::main(argc, argv);
}

53
src/socket_client.cpp Normal file
View File

@@ -0,0 +1,53 @@
#include "socket_client.h"
#include "common.h"
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#define return_if_error(flag, msg) \
if (flag == -1) { \
perror(msg); \
status = CONN_ERROR; \
close(sfd); \
return; \
}
namespace CGPROXY::SOCKET {
void send(const char *msg, int &status) {
debug("send msg: %s", msg);
status = UNKNOWN_ERROR;
int flag;
int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un unix_socket;
memset(&unix_socket, '\0', sizeof(struct sockaddr_un));
unix_socket.sun_family = AF_UNIX;
strncpy(unix_socket.sun_path, SOCKET_PATH, sizeof(unix_socket.sun_path) - 1);
flag = connect(sfd, (struct sockaddr *)&unix_socket, sizeof(struct sockaddr_un));
return_if_error(flag, "connect");
int msg_len = strlen(msg);
flag = write(sfd, &msg_len, sizeof(int));
return_if_error(flag, "write length");
flag = write(sfd, msg, msg_len * sizeof(char));
return_if_error(flag, "write msg");
flag = read(sfd, &status, sizeof(int));
return_if_error(flag, "read return value");
close(sfd);
}
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);
debug("return status: %d", status);
}
} // namespace CGPROXY::SOCKET

14
src/socket_client.h Normal file
View File

@@ -0,0 +1,14 @@
#ifndef SOCKET_CLIENT_H
#define SOCKET_CLIENT_H
#include <stdlib.h>
#include <string>
using namespace std;
namespace CGPROXY::SOCKET {
void send(const char *msg, int &status);
void send(const string msg, int &status);
} // namespace CGPROXY::SOCKET
#endif

65
src/socket_server.cpp Normal file
View File

@@ -0,0 +1,65 @@
#include "socket_server.h"
#include "common.h"
#include <filesystem>
#include <sys/socket.h>
#include <sys/stat.h>
#include <unistd.h>
namespace fs = std::filesystem;
namespace CGPROXY::SOCKET {
void SocketServer::socketListening(function<int(char *)> callback) {
debug("starting socket listening");
sfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fs::exists(SOCKET_PATH) && unlink(SOCKET_PATH) == -1) {
error("%s exist, and can't unlink", SOCKET_PATH);
return;
}
memset(&unix_socket, '\0', sizeof(struct sockaddr_un));
unix_socket.sun_family = AF_UNIX;
strncpy(unix_socket.sun_path, SOCKET_PATH, sizeof(unix_socket.sun_path) - 1);
bind(sfd, (struct sockaddr *)&unix_socket, sizeof(struct sockaddr_un));
listen(sfd, LISTEN_BACKLOG);
chmod(SOCKET_PATH, S_IRWXU | S_IRWXG | S_IRWXO);
while (true) {
close(cfd);
cfd = accept(sfd, NULL, NULL);
continue_if_error(cfd, "accept");
debug("accept connection: %d", cfd);
// read length
int msg_len;
flag = read(cfd, &msg_len, sizeof(int));
continue_if_error(flag, "read length");
// read msg
char msg[msg_len];
flag = read(cfd, msg, msg_len * sizeof(char));
continue_if_error(flag, "read msg");
msg[msg_len] = '\0';
// handle msg
int status = callback(msg);
// send back flag
flag = write(cfd, &status, sizeof(int));
continue_if_error(flag, "write back");
}
}
void *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);
}
} // namespace CGPROXY::SOCKET

33
src/socket_server.h Normal file
View File

@@ -0,0 +1,33 @@
#ifndef SOCKET_SERVER_H
#define SOCKET_SERVER_H
#include <functional>
#include <stdlib.h>
#include <sys/un.h>
using namespace std;
namespace CGPROXY::SOCKET {
#define continue_if_error(flag, msg) \
if (flag == -1) { \
perror(msg); \
continue; \
}
struct thread_arg {
function<int(char *)> handle_msg;
};
void *startThread(void *arg);
class SocketServer {
public:
int sfd = -1, cfd = -1, flag = -1;
struct sockaddr_un unix_socket;
void socketListening(function<int(char *)> callback);
~SocketServer();
};
} // namespace CGPROXY::SOCKET
#endif

7
test/CMakeLists.txt Normal file
View File

@@ -0,0 +1,7 @@
include_directories(${PROJECT_SOURCE_DIR})
include_directories(${PROJECT_SOURCE_DIR}/src)
find_package(nlohmann_json REQUIRED)
add_executable(client_test socket_client_test.cpp
../src/socket_client.cpp ../src/common.cpp ../src/config.cpp)
target_link_libraries(client_test nlohmann_json::nlohmann_json)

View File

@@ -0,0 +1,48 @@
#include "common.h"
#include "config.h"
#include "socket_client.h"
#include <nlohmann/json.hpp>
using namespace std;
using json = nlohmann::json;
using namespace CGPROXY;
using namespace CGPROXY::CONFIG;
void send_config(Config &config, int &status) {
json j;
j["type"] = MSG_TYPE_CONFIG_JSON;
j["data"] = config.toJsonStr();
SOCKET::send(j.dump(), status);
}
void send_config_path(const string s, int &status) {
json j;
j["type"] = MSG_TYPE_CONFIG_PATH;
j["data"] = s;
SOCKET::send(j.dump(), status);
}
void send_pid(const pid_t pid, bool proxy, int &status) {
json j;
j["type"] = proxy ? MSG_TYPE_PROXY_PID : MSG_TYPE_NOPROXY_PID;
j["data"] = pid;
SOCKET::send(j.dump(), status);
}
void test_config() {
Config config;
config.cgroup_proxy = {"/"};
int status;
send_config(config, status);
}
void test_config_path() {
string path = "/etc/cgproxy/config.json";
int status;
send_config_path(path, status);
}
int main() {
test_config();
return 0;
}

12
tools/CMakeLists.txt Normal file
View File

@@ -0,0 +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})
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()

31
tools/cgattach.cpp Normal file
View File

@@ -0,0 +1,31 @@
#include "cgroup_attach.h"
#include "common.h"
#include <cstdlib>
#include <unistd.h>
using namespace std;
void print_usage() { fprintf(stdout, "usage: cgattach <pid> <cgroup>\n"); }
int main(int argc, char *argv[]) {
int flag = setuid(0);
if (flag != 0) {
perror("cgattach need root");
exit(EXIT_FAILURE);
}
if (argc != 3) {
error("need exact 2 paramaters");
print_usage();
exit(EXIT_FAILURE);
}
string pid = string(argv[1]);
string cgroup_target = string(argv[2]);
if (validPid(pid) && validCgroup(cgroup_target)) {
CGPROXY::CGROUP::attach(pid, cgroup_target);
} else {
error("param not valid");
exit(EXIT_FAILURE);
}
}

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;
}

View File

@@ -1,5 +1,5 @@
{
"log": {
"loglevel": "debug"
"loglevel": "none"
}
}

View File

@@ -1,6 +1,11 @@
{
"dns": {
"hosts": {
"geosite:category-ads": "127.0.0.1"
},
"servers": [
"https+local://223.5.5.5/dns-query",
"https://1.1.1.1/dns-query",
{
"address": "localhost",
"port": 53,
@@ -10,9 +15,7 @@
"expectIPs": [
"geoip:cn"
]
},
"https+local://1.1.1.1/dns-query",
"223.6.6.6"
}
],
"tag": "dns_inbound"
}

View File

@@ -2,6 +2,20 @@
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"domain": [
"geosite:category-ads-all"
],
"outboundTag": "outBound_BLACKHOLE",
"type": "field"
},
{
"inboundTag": [
"inbound_API"
],
"outboundTag": "API",
"type": "field"
},
{
"outboundTag": "dns-out",
"port": "53",
@@ -20,27 +34,6 @@
"outboundTag": "outBound_PROXY",
"type": "field"
},
{
"ip": [
"geoip:private"
],
"outboundTag": "outBound_DIRECT",
"type": "field"
},
{
"domain": [
"geosite:category-ads-all"
],
"outboundTag": "outBound_BLACKHOLE",
"type": "field"
},
{
"ip": [
"geoip:cn"
],
"outboundTag": "outBound_DIRECT",
"type": "field"
},
{
"domain": [
"geosite:cn"
@@ -49,10 +42,11 @@
"type": "field"
},
{
"inboundTag": [
"inbound_API"
"ip": [
"geoip:cn",
"geoip:private"
],
"outboundTag": "API",
"outboundTag": "outBound_DIRECT",
"type": "field"
}
]

View File

@@ -0,0 +1,30 @@
{
"inbounds": [
{
"listen": "127.0.0.1",
"port": 12345,
"protocol": "dokodemo-door",
"settings": {
"address": "",
"followRedirect": true,
"network": "tcp,udp",
"port": 0,
"timeout": 300,
"userLevel": 0
},
"sniffing": {
"destOverride": [
"http",
"tls"
],
"enabled": true
},
"streamSettings": {
"sockopt": {
"tproxy": "tproxy"
}
},
"tag": "tproxy_IN_ipv4lo"
}
]
}

View File

@@ -1,7 +1,7 @@
{
"inbounds": [
{
"listen": "0.0.0.0",
"listen": "::1",
"port": 12345,
"protocol": "dokodemo-door",
"settings": {
@@ -9,7 +9,7 @@
"followRedirect": true,
"network": "tcp,udp",
"port": 0,
"timeout": 0,
"timeout": 300,
"userLevel": 0
},
"sniffing": {
@@ -24,7 +24,7 @@
"tproxy": "tproxy"
}
},
"tag": "tproxy_IN"
"tag": "tproxy_IN_ipv6lo"
}
]
}

View File

@@ -0,0 +1 @@
{}

View File

@@ -1 +0,0 @@
{}

View File

@@ -1,2 +1,2 @@
#!/bin/bash
jq -rs 'reduce .[] as $item ({}; . + $item + {inbounds: (.inbounds + $item.inbounds)} + {outbounds: ($item.outbounds + .outbounds)})' *.json |sudo tee /etc/v2ray/config.json
jq -rs 'reduce .[] as $item ({}; . + $item + {inbounds: (.inbounds + $item.inbounds)} + {outbounds: ($item.outbounds + .outbounds)})' *.json |sudo tee /etc/v2ray/config.json > /dev/null

View File

@@ -1 +1,8 @@
Fill `06_outbounds_myproxy.json` with your vmess proxy config with tag `outBound_PROXY`.
## Usage
- Fill `06_outbounds_myproxy.json` with your vmess proxy config with tag `outBound_PROXY`.
- Start with `sudo v2ray -confdir .`
## Reference
- [v2ray multi-file config](https://www.v2fly.org/chapter_02/multiple_config.html)

View File

@@ -5,12 +5,11 @@ After=network.target nss-lookup.target
Wants=network-online.target
[Service]
Type=simple
User=nobody
#AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
Type=exec
ExecStart=/usr/lib/v2ray/v2ray -config /etc/v2ray/config.json
DynamicUser=yes
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
NoNewPrivileges=yes
ExecStart=+/usr/lib/v2ray/v2ray -config /etc/v2ray/config.json
Restart=on-failure
# Don't restart in the case of configuration error
RestartPreventExitStatus=23