mirror of
https://github.com/springzfx/cgproxy.git
synced 2026-01-07 13:07:56 +08:00
Compare commits
121 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06da477373 | ||
|
|
314ff64b8b | ||
|
|
d1bf9620bf | ||
|
|
b7ea8e441b | ||
|
|
b031aa8064 | ||
|
|
8226db37e0 | ||
|
|
5bbec4d1bf | ||
|
|
9b4ec44897 | ||
|
|
a1a1ed5bd9 | ||
|
|
34e5d81329 | ||
|
|
dfc688b5e5 | ||
|
|
ec9609cec8 | ||
|
|
cc83c1ae55 | ||
|
|
75751f4887 | ||
|
|
af78ad2012 | ||
|
|
c32457a1aa | ||
|
|
1d29828d1b | ||
|
|
4fea0d39a2 | ||
|
|
badf282842 | ||
|
|
41f856acd2 | ||
|
|
45adf0a233 | ||
|
|
1f4dd2fde2 | ||
|
|
40bd709995 | ||
|
|
221a75ae7b | ||
|
|
16a341205f | ||
|
|
076651b984 | ||
|
|
1c72a204a1 | ||
|
|
f501c7e476 | ||
|
|
0ec9caefe1 | ||
|
|
94b73b5103 | ||
|
|
c30df999b8 | ||
|
|
932f2bbc94 | ||
|
|
1bbd1ab6ec | ||
|
|
fa7d877de5 | ||
|
|
3475001ca3 | ||
|
|
0b25b5263a | ||
|
|
388ba6a4c8 | ||
|
|
5dbce18f95 | ||
|
|
792a156647 | ||
|
|
92abcb1851 | ||
|
|
a73b697cab | ||
|
|
aace8c3d31 | ||
|
|
665e02ceaa | ||
|
|
bfe3289201 | ||
|
|
2c8625c110 | ||
|
|
ba0b780adf | ||
|
|
1fa0d51e1d | ||
|
|
aedebf4e31 | ||
|
|
051742eef1 | ||
|
|
619fcaae8e | ||
|
|
78c10e3e3e | ||
|
|
99b29195f2 | ||
|
|
53fccbe72d | ||
|
|
db4757316a | ||
|
|
3a4e62b3c2 | ||
|
|
4525d83a53 | ||
|
|
c5ec1027ad | ||
|
|
ee3a5d0fa2 | ||
|
|
f2210f9bda | ||
|
|
e86ea01f6e | ||
|
|
bc9f6d4d4e | ||
|
|
6de88897b2 | ||
|
|
39275452da | ||
|
|
a16cefbfb2 | ||
|
|
2adba75b3e | ||
|
|
fbcc499ba8 | ||
|
|
1c16f57193 | ||
|
|
87cd5a6d99 | ||
|
|
138fa698be | ||
|
|
ffea0fb2b9 | ||
|
|
696fcb6b4e | ||
|
|
9b7d6804f7 | ||
|
|
ad362998d8 | ||
|
|
2b5ff745ac | ||
|
|
b2b3168463 | ||
|
|
2838ffbb70 | ||
|
|
749fe38ca8 | ||
|
|
c0668fd8d2 | ||
|
|
06ae0b9fc5 | ||
|
|
4e04dcf84a | ||
|
|
c0e9ea24c1 | ||
|
|
b5701d8b49 | ||
|
|
4e37bccc1a | ||
|
|
f8e0abbb55 | ||
|
|
6c24c68831 | ||
|
|
d3b2dc0465 | ||
|
|
4be7be2083 | ||
|
|
25f94968ae | ||
|
|
3b4b67df33 | ||
|
|
31ae519193 | ||
|
|
7f0ebe9d35 | ||
|
|
236c08172b | ||
|
|
c07ae13030 | ||
|
|
d5ea832b4f | ||
|
|
aa5ca6f204 | ||
|
|
a80187f947 | ||
|
|
dca895c7cc | ||
|
|
08097a54d7 | ||
|
|
bce568d802 | ||
|
|
98c07a31af | ||
|
|
916c11d280 | ||
|
|
72579bc84a | ||
|
|
5c16fdfb9f | ||
|
|
f55b09ec12 | ||
|
|
6ae12bf5c4 | ||
|
|
31627dd956 | ||
|
|
9f8d540c78 | ||
|
|
b8204126c5 | ||
|
|
daa68f20ea | ||
|
|
836a34cdc8 | ||
|
|
3c21882b88 | ||
|
|
71d924a303 | ||
|
|
5d0b137778 | ||
|
|
8a29e88bab | ||
|
|
dd234b08a0 | ||
|
|
168779ea90 | ||
|
|
5b65ac0ffd | ||
|
|
6c77233e1e | ||
|
|
6be14b9935 | ||
|
|
c1cd7e6e07 | ||
|
|
0848d1ddeb |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,4 +1,7 @@
|
||||
build
|
||||
.directory
|
||||
.vscode
|
||||
cgproxy2.sh
|
||||
.clangd
|
||||
v2ray_config/proxy
|
||||
v2ray_config/06_outbounds_proxy.json
|
||||
aur-*
|
||||
|
||||
@@ -1,23 +1,45 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
project(cgproxy VERSION 1.0)
|
||||
add_executable(cgattach cgattach.cpp)
|
||||
project(cgproxy VERSION 0.15)
|
||||
add_compile_options(-Wall -Wextra -Wpedantic -Wno-unused-result -Wno-unused-parameter)
|
||||
|
||||
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)
|
||||
# for clangd
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
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/)
|
||||
option(with_execsnoop "enable program level proxy control feature, need bcc installed" ON)
|
||||
option(build_tools OFF)
|
||||
option(build_test OFF)
|
||||
|
||||
set(basic_permission OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
||||
|
||||
add_subdirectory(src)
|
||||
add_subdirectory(pack)
|
||||
if (build_tools)
|
||||
add_subdirectory(tools)
|
||||
endif()
|
||||
if (build_test)
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
|
||||
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/)
|
||||
|
||||
# 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
137
_clang-format
Normal 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
|
||||
...
|
||||
|
||||
94
cgattach.cpp
94
cgattach.cpp
@@ -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;
|
||||
}
|
||||
16
cgnoproxy.sh
16
cgnoproxy.sh
@@ -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 "config error" && exit 1
|
||||
|
||||
exec "$@"
|
||||
30
cgproxy.conf
30
cgproxy.conf
@@ -1,30 +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="/noproxy.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
|
||||
enable_dns=true
|
||||
|
||||
|
||||
########################################################################
|
||||
## do not modify this if you don't known what you are doing
|
||||
table=100
|
||||
mark_proxy=0x01
|
||||
mark_noproxy=0xff
|
||||
mark_newin=0x02
|
||||
@@ -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
|
||||
|
||||
16
cgproxy.sh
16
cgproxy.sh
@@ -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 "config error" && exit 1
|
||||
|
||||
exec "$@"
|
||||
208
cgroup-tproxy.sh
208
cgroup-tproxy.sh
@@ -30,54 +30,86 @@ cat << 'DOC'
|
||||
DOC
|
||||
}
|
||||
|
||||
## check root
|
||||
[ ! $(id -u) -eq 0 ] && { >&2 echo "iptables: 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
|
||||
[ -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
|
||||
mark_proxy=0x01
|
||||
mark_noproxy=0xff
|
||||
fwmark=0x01
|
||||
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 "iptables: cleaning tproxy iptables"
|
||||
iptables -t mangle -D PREROUTING -j TPROXY_PRE
|
||||
iptables -t mangle -D OUTPUT -j TPROXY_OUT
|
||||
iptables -t mangle -F TPROXY_PRE
|
||||
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 -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_proxy lookup $table
|
||||
ip route flush table $table
|
||||
ip -6 rule delete fwmark $mark_proxy lookup $table
|
||||
ip -6 route flush table $table
|
||||
iptables -t nat -A OUTPUT -F
|
||||
ip6tables -t nat -A OUTPUT -F
|
||||
stop
|
||||
exit 0
|
||||
;;
|
||||
--config=*)
|
||||
config=${i#*=}
|
||||
source $config
|
||||
shift
|
||||
;;
|
||||
--help)
|
||||
print_help
|
||||
@@ -87,76 +119,116 @@ 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 "iptables: applying tproxy iptables"
|
||||
## use TPROXY
|
||||
#ipv4#
|
||||
ip rule add fwmark $mark_proxy table $table
|
||||
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 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 -p udp -m mark --mark $mark_proxy -j TPROXY --on-ip 127.0.0.1 --on-port $port --tproxy-mark $mark_proxy
|
||||
iptables -t mangle -A TPROXY_PRE -p tcp -m mark --mark $mark_proxy -j TPROXY --on-ip 127.0.0.1 --on-port $port --tproxy-mark $mark_proxy
|
||||
iptables -t mangle -A TPROXY_PRE -m conntrack --ctstate NEW -j CONNMARK --set-mark $make_newin
|
||||
iptables -t mangle -A TPROXY_PRE -m conntrack --ctstate NEW -j CONNMARK --restore-mark
|
||||
iptables -t mangle -A TPROXY_PRE -m socket --transparent -j MARK --set-mark $fwmark
|
||||
iptables -t mangle -A TPROXY_PRE -m socket --transparent -j RETURN
|
||||
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 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 # return incoming connection directly, v2ray tproxy not work for this situation, see this: https://github.com/Kr328/ClashForAndroid/issues/146
|
||||
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 $mark_proxy
|
||||
iptables -t mangle -A TPROXY_OUT -m connmark --mark $make_newin -j RETURN
|
||||
iptables -t mangle -A TPROXY_OUT -m addrtype --dst-type LOCAL -j RETURN
|
||||
iptables -t mangle -A TPROXY_OUT -m addrtype ! --dst-type UNICAST -j RETURN
|
||||
for cg in ${cgroup_noproxy[@]}; do
|
||||
iptables -t mangle -A TPROXY_OUT -m cgroup --path $cg -j RETURN || { >&2 echo "iptables: $cg not exist, won't apply"; }
|
||||
done
|
||||
for cg in ${cgroup_proxy[@]}; do
|
||||
iptables -t mangle -A TPROXY_OUT -m cgroup --path $cg -j MARK --set-mark $fwmark || { >&2 echo "iptables: $cg not exist, won't apply"; }
|
||||
done
|
||||
iptables -t mangle -A OUTPUT -j TPROXY_OUT
|
||||
|
||||
#ipv6#
|
||||
ip -6 rule add fwmark $mark_proxy table $table
|
||||
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 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 -p udp -m mark --mark $mark_proxy -j TPROXY --on-ip ::1 --on-port $port --tproxy-mark $mark_proxy
|
||||
ip6tables -t mangle -A TPROXY_PRE -p tcp -m mark --mark $mark_proxy -j TPROXY --on-ip ::1 --on-port $port --tproxy-mark $mark_proxy
|
||||
ip6tables -t mangle -A TPROXY_PRE -m conntrack --ctstate NEW -j CONNMARK --set-mark $make_newin
|
||||
ip6tables -t mangle -A TPROXY_PRE -m conntrack --ctstate NEW -j CONNMARK --restore-mark
|
||||
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 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 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 -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 $mark_proxy
|
||||
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 || { >&2 echo "iptables: $cg not exist, won't apply"; }
|
||||
done
|
||||
for cg in ${cgroup_proxy[@]}; do
|
||||
ip6tables -t mangle -A TPROXY_OUT -m cgroup --path $cg -j MARK --set-mark $fwmark || { >&2 echo "iptables: $cg not exist, won't apply"; }
|
||||
done
|
||||
ip6tables -t mangle -A OUTPUT -j TPROXY_OUT
|
||||
|
||||
|
||||
## use REDIRECT
|
||||
# iptables -t nat -A OUTPUT -p tcp -m cgroup --path $cgroup_proxy -j DNAT --to-destination 127.0.0.1:12345
|
||||
# ip6tables -t nat -A OUTPUT -p tcp -m cgroup --path $cgroup_proxy -j DNAT --to-destination [::1]:12345
|
||||
|
||||
## 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
|
||||
fi
|
||||
|
||||
## 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
|
||||
## 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 -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
|
||||
iptables -t mangle -I TPROXY_PRE -m addrtype ! --src-type LOCAL -m conntrack --ctstate NEW -j CONNMARK --set-mark $make_newin
|
||||
ip6tables -t mangle -I TPROXY_PRE -m addrtype ! --src-type LOCAL -m conntrack --ctstate NEW -j CONNMARK --set-mark $make_newin
|
||||
|
||||
## message for user
|
||||
cat << DOC
|
||||
proxied cgroup: $cgroup_proxy
|
||||
iptables: noproxy cgroup: ${cgroup_noproxy[@]}
|
||||
iptables: proxied cgroup: ${cgroup_proxy[@]}
|
||||
DOC
|
||||
|
||||
## tproxy need Root or cap_net_admin capability
|
||||
# setcap cap_net_admin+ep /usr/lib/v2ray/v2ray
|
||||
|
||||
if $enable_gateway; then
|
||||
iptables -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 "ipatbles: gateway enabled"
|
||||
fi
|
||||
|
||||
13
config.json
Normal file
13
config.json
Normal 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
16
man/cgnoproxy.1
Normal 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
14
man/cgproxy.1
Normal 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
54
man/cgproxyd.1
Normal 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 bcc installed to actually work
|
||||
.SH CONFIGURATION
|
||||
.I /etc/cgproxy/config.json
|
||||
.br
|
||||
.B port
|
||||
tproxy listenning port
|
||||
.br
|
||||
program level proxy controll, need `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
26
pack/CMakeLists.txt
Normal 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 "libbpfcc")
|
||||
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 "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)
|
||||
2
pack/postinst
Normal file
2
pack/postinst
Normal file
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
systemctl enable --now cgproxy.service
|
||||
2
pack/prerm
Normal file
2
pack/prerm
Normal file
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
systemctl disable --now cgproxy.service
|
||||
186
readme.md
186
readme.md
@@ -1,27 +1,36 @@
|
||||
# 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. See [Global transparent proxy](#global-transparent-proxy)
|
||||
Main feature:
|
||||
|
||||
- supports cgroup/program level proxy control.
|
||||
- supports global transparent proxy and gateway proxy.
|
||||
|
||||
## Contents
|
||||
|
||||
<!--ts-->
|
||||
|
||||
* [Transparent Proxy with cgroup v2](#transparent-proxy-with-cgroup-v2)
|
||||
* [Introduction](#introduction)
|
||||
* [Prerequest](#prerequest)
|
||||
* [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)
|
||||
* [NOTES](#notes)
|
||||
* [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-->
|
||||
|
||||
@@ -29,14 +38,19 @@ It aslo supports global transparent proxy. See [Global transparent proxy](#globa
|
||||
|
||||
- cgroup2
|
||||
|
||||
Both cgroup and cgroup2 are enable in linux by default. So you don't have to do anything about this.
|
||||
Both cgroup and cgroup2 are enabled in linux by default. So you don't have to do anything about this.
|
||||
- `systemd-cgls` to see the cgroup hierarchical tree.
|
||||
- Why cgroup v2? Because simple, elegant and intuitive.
|
||||
|
||||
- TPROXY
|
||||
|
||||
A process listening on port (e.g. 12345) to accept iptables TPROXY, for example v2ray's dokodemo-door in tproxy mode.
|
||||
- Why not REDIRECT? Because REDIRECT only supports tcp and ipv4.
|
||||
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,61 +58,88 @@ It aslo supports global transparent proxy. See [Global transparent proxy](#globa
|
||||
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).
|
||||
|
||||
## How to use
|
||||
- DEB and RPM are packaged in [release page](https://github.com/springzfx/cgproxy/releases).
|
||||
|
||||
- First enable service
|
||||
## Default usage
|
||||
|
||||
- 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
|
||||
```
|
||||
|
||||
More config in `/etc/cgproxy.conf`:
|
||||
- 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
|
||||
|
||||
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
|
||||
mark_proxy=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
|
||||
}
|
||||
```
|
||||
|
||||
- **port** tproxy listenning port
|
||||
|
||||
- program level proxy control, need `bcc` and `linux-headers` installed to work
|
||||
|
||||
- **program_proxy** program need to be proxied
|
||||
- **program_noproxy** program that won't be proxied
|
||||
|
||||
- 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
|
||||
@@ -107,57 +148,70 @@ 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
|
||||
- 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
|
||||
|
||||
```bash
|
||||
cgnoproxy <PROXY PROGRAM>
|
||||
# qv2ray as example
|
||||
cgnoproxy qv2ray
|
||||
# v2ray as example
|
||||
cgnoproxy sudo v2ray --config config_file
|
||||
```
|
||||
## Gateway proxy
|
||||
|
||||
- Finally, restart service `sudo systemctl restart cgproxy.service`, that's all
|
||||
- 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 permiassion
|
||||
- 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_bind_service=+ep cap_net_admin=+ep" /usr/lib/v2ray/v2ray
|
||||
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 pity.
|
||||
|
||||
## TIPS
|
||||
|
||||
- `systemd-cgls` to see the cgroup hierarchical tree.
|
||||
- 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
|
||||
|
||||
|
||||

|
||||
|
||||
## Licences
|
||||
|
||||
|
||||
@@ -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
19
src/CMakeLists.txt
Normal 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 ${CMAKE_DL_LIBS})
|
||||
set_target_properties(main PROPERTIES LINKER_LANGUAGE CXX)
|
||||
set_target_properties(main PROPERTIES OUTPUT_NAME cgproxy)
|
||||
|
||||
install(TARGETS main DESTINATION /usr/bin PERMISSIONS ${basic_permission})
|
||||
84
src/cgproxy.hpp
Normal file
84
src/cgproxy.hpp
Normal 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
|
||||
355
src/cgproxyd.hpp
Normal file
355
src/cgproxyd.hpp
Normal file
@@ -0,0 +1,355 @@
|
||||
#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 <future>
|
||||
#include <nlohmann/json.hpp>
|
||||
#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 {
|
||||
bool loadExecsnoopLib() {
|
||||
try {
|
||||
info("loading %s", LIBEXECSNOOP_SO);
|
||||
void *handle_dl = dlopen(LIBEXECSNOOP_SO, RTLD_NOW);
|
||||
if (handle_dl == NULL) {
|
||||
error("dlopen %s failed: %s", LIBEXECSNOOP_SO, dlerror());
|
||||
return false;
|
||||
}
|
||||
_startThread = reinterpret_cast<startThread_t *>(dlsym(handle_dl, "startThread"));
|
||||
if (_startThread == NULL) {
|
||||
error("dlsym startThread func failed: %s", dlerror());
|
||||
return false;
|
||||
}
|
||||
info("dlsym startThread func success");
|
||||
return true;
|
||||
} catch (exception &e) {
|
||||
debug("exception: %s", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} // namespace CGPROXY::EXECSNOOP
|
||||
|
||||
namespace CGPROXY::CGPROXYD {
|
||||
|
||||
bool print_help = false;
|
||||
bool enable_socketserver = true;
|
||||
bool enable_execsnoop = false;
|
||||
|
||||
class cgproxyd {
|
||||
thread socketserver_thread;
|
||||
thread execsnoop_thread;
|
||||
|
||||
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) {
|
||||
unique_ptr<char[], decltype(&free)> path(
|
||||
realpath(to_str("/proc/", pid, "/exe").c_str(), NULL), &free);
|
||||
if (path == NULL) {
|
||||
debug("execsnoop: pid %d live life too short", pid);
|
||||
return 0;
|
||||
}
|
||||
debug("execsnoop: %d %s", pid, path.get());
|
||||
|
||||
vector<string> v;
|
||||
|
||||
v = config.program_noproxy;
|
||||
if (find(v.begin(), v.end(), path.get()) != v.end()) {
|
||||
string cg = getCgroup(pid);
|
||||
if (cg.empty()) {
|
||||
debug("execsnoop: cgroup get failed, ignore: %d %s", pid, path.get());
|
||||
return 0;
|
||||
}
|
||||
if (belongToCgroup(cg, config.cgroup_proxy_preserved) ||
|
||||
belongToCgroup(cg, config.cgroup_noproxy_preserved)) {
|
||||
info("execsnoop: already in preserverd cgroup, leave alone: %d %s", pid,
|
||||
path.get());
|
||||
return 0;
|
||||
}
|
||||
if (!belongToCgroup(cg, config.cgroup_noproxy)) {
|
||||
info("execsnoop; noproxy: %d %s", pid, path.get());
|
||||
return attach(pid, config.cgroup_noproxy_preserved);
|
||||
}
|
||||
}
|
||||
|
||||
v = config.program_proxy;
|
||||
if (find(v.begin(), v.end(), path.get()) != v.end()) {
|
||||
string cg = getCgroup(pid);
|
||||
if (cg.empty()) {
|
||||
debug("execsnoop: cgroup get failed, ignore: %d %s", pid, path.get());
|
||||
return 0;
|
||||
}
|
||||
if (belongToCgroup(cg, config.cgroup_proxy_preserved) ||
|
||||
belongToCgroup(cg, config.cgroup_noproxy_preserved)) {
|
||||
info("execsnoop: already in preserverd cgroup, leave alone: %d %s", pid,
|
||||
path.get());
|
||||
return 0;
|
||||
}
|
||||
if (!belongToCgroup(cg, config.cgroup_proxy)) {
|
||||
info("execsnoop: proxied: %d %s", pid, path.get());
|
||||
return attach(pid, config.cgroup_proxy_preserved);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void signalHandler(int signum) {
|
||||
debug("Signal %d received.", signum);
|
||||
if (!instance) {
|
||||
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, pid;
|
||||
try {
|
||||
type = j.at("type").get<int>();
|
||||
switch (type) {
|
||||
case MSG_TYPE_CONFIG_JSON:
|
||||
status = config.loadFromJsonStr(j.at("data").dump());
|
||||
info("process received config json msg");
|
||||
if (status == SUCCESS) status = applyConfig();
|
||||
return status;
|
||||
break;
|
||||
case MSG_TYPE_CONFIG_PATH:
|
||||
status = config.loadFromFile(j.at("data").get<string>());
|
||||
info("process received config path msg");
|
||||
if (status == SUCCESS) status = applyConfig();
|
||||
return status;
|
||||
break;
|
||||
case MSG_TYPE_PROXY_PID:
|
||||
pid = j.at("data").get<int>();
|
||||
info("process proxy pid msg: %d", pid);
|
||||
status = attach(pid, config.cgroup_proxy_preserved);
|
||||
return status;
|
||||
break;
|
||||
case MSG_TYPE_NOPROXY_PID:
|
||||
pid = j.at("data").get<int>();
|
||||
info("process noproxy pid msg: %d", pid);
|
||||
status = attach(pid, config.cgroup_noproxy_preserved);
|
||||
return status;
|
||||
break;
|
||||
default:
|
||||
error("unknown msg");
|
||||
return MSG_ERROR;
|
||||
break;
|
||||
};
|
||||
} catch (out_of_range &e) { return MSG_ERROR; } catch (exception &e) {
|
||||
return ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
void startSocketListeningThread() {
|
||||
promise<void> status;
|
||||
future<void> status_f = status.get_future();
|
||||
thread th(SOCKET::startThread, handle_msg_static, move(status));
|
||||
socketserver_thread = move(th);
|
||||
|
||||
future_status fstatus = status_f.wait_for(chrono::seconds(THREAD_TIMEOUT));
|
||||
if (fstatus == std::future_status::ready) {
|
||||
info("socketserver thread started");
|
||||
} else {
|
||||
error("socketserver thread timeout, maybe failed");
|
||||
}
|
||||
}
|
||||
|
||||
void startExecsnoopThread() {
|
||||
if (!EXECSNOOP::loadExecsnoopLib() || EXECSNOOP::_startThread == NULL) {
|
||||
error("execsnoop not ready to start, maybe bcc not installed");
|
||||
return;
|
||||
}
|
||||
|
||||
promise<void> status;
|
||||
future<void> status_f = status.get_future();
|
||||
thread th(EXECSNOOP::_startThread, handle_pid_static, move(status));
|
||||
execsnoop_thread = move(th);
|
||||
|
||||
future_status fstatus = status_f.wait_for(chrono::seconds(THREAD_TIMEOUT));
|
||||
if (fstatus == std::future_status::ready) {
|
||||
info("execsnoop thread started");
|
||||
processRunningProgram();
|
||||
} else {
|
||||
error("execsnoop thread timeout, maybe failed");
|
||||
}
|
||||
}
|
||||
|
||||
void processRunningProgram() {
|
||||
debug("process running program");
|
||||
for (auto &path : config.program_noproxy)
|
||||
for (auto &pid : bash_pidof(path)) {
|
||||
string cg = getCgroup(pid);
|
||||
if (cg.empty()) {
|
||||
debug("cgroup get failed, ignore: %d %s", pid, path.c_str());
|
||||
continue;
|
||||
}
|
||||
if (belongToCgroup(cg, config.cgroup_proxy_preserved) ||
|
||||
belongToCgroup(cg, config.cgroup_noproxy_preserved)) {
|
||||
debug("already in preserverd cgroup, leave alone: %d %s", pid, path.c_str());
|
||||
continue;
|
||||
}
|
||||
if (!belongToCgroup(cg, config.cgroup_noproxy)) {
|
||||
int status = attach(pid, config.cgroup_noproxy_preserved);
|
||||
if (status == 0) info("noproxy running process %d %s", pid, path.c_str());
|
||||
}
|
||||
}
|
||||
for (auto &path : config.program_proxy)
|
||||
for (auto &pid : bash_pidof(path)) {
|
||||
string cg = getCgroup(pid);
|
||||
if (cg.empty()) {
|
||||
debug("cgroup get failed, ignore: %d %s", pid, path.c_str());
|
||||
continue;
|
||||
}
|
||||
if (belongToCgroup(cg, config.cgroup_proxy_preserved) ||
|
||||
belongToCgroup(cg, config.cgroup_noproxy_preserved)) {
|
||||
debug("already in preserverd cgroup, leave alone: %d %s", pid, path.c_str());
|
||||
continue;
|
||||
}
|
||||
if (!belongToCgroup(cg, config.cgroup_proxy)) {
|
||||
int status = attach(pid, config.cgroup_proxy_preserved);
|
||||
if (status == 0) info("proxied running process %d %s", pid, path.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void assignStaticInstance() { instance = this; }
|
||||
|
||||
public:
|
||||
int start() {
|
||||
lock();
|
||||
signal(SIGINT, &signalHandler);
|
||||
signal(SIGTERM, &signalHandler);
|
||||
signal(SIGHUP, &signalHandler);
|
||||
|
||||
assignStaticInstance();
|
||||
|
||||
config.loadFromFile(DEFAULT_CONFIG_FILE);
|
||||
applyConfig();
|
||||
|
||||
if (enable_socketserver) startSocketListeningThread();
|
||||
if (enable_execsnoop) startExecsnoopThread();
|
||||
|
||||
if (socketserver_thread.joinable()) socketserver_thread.join();
|
||||
if (execsnoop_thread.joinable()) execsnoop_thread.join();
|
||||
|
||||
return 0;
|
||||
}
|
||||
int applyConfig() {
|
||||
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[]) {
|
||||
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
|
||||
92
src/cgroup_attach.cpp
Normal file
92
src/cgroup_attach.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
#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();
|
||||
|
||||
string get_cgroup2_mount_point() {
|
||||
stringstream buffer;
|
||||
unique_ptr<FILE, decltype(&pclose)> fp(popen("findmnt -t cgroup2 -n -o TARGET", "r"),
|
||||
&pclose);
|
||||
if (!fp) return "";
|
||||
char buf[READ_SIZE_MAX];
|
||||
while (fgets(buf, READ_SIZE_MAX, fp.get()) != NULL) { buffer << buf; }
|
||||
string s = buffer.str();
|
||||
s.pop_back(); // remove newline character
|
||||
return s;
|
||||
}
|
||||
|
||||
bool validate(string pid, string cgroup) {
|
||||
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());
|
||||
|
||||
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 (!dirExist(cgroup_target_path)) {
|
||||
if (mkdir(cgroup_target_path.c_str(),
|
||||
S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == 0) {
|
||||
debug("created cgroup %s success", cgroup_target.c_str());
|
||||
} else {
|
||||
error("created cgroup %s failed, errno %d", cgroup_target.c_str(), errno);
|
||||
return_error;
|
||||
}
|
||||
// error("cgroup %s not exist",cgroup_target.c_str());
|
||||
// return_error
|
||||
}
|
||||
|
||||
if (getCgroup(pid) == cgroup_target) {
|
||||
debug("%s already in %s", pid.c_str(), cgroup_target.c_str());
|
||||
return_success;
|
||||
}
|
||||
|
||||
// put pid to target cgroup
|
||||
ofstream procs(cgroup_target_procs, ofstream::app);
|
||||
if (!procs.is_open()) {
|
||||
error("open file %s failed", cgroup_target_procs.c_str());
|
||||
return_error;
|
||||
}
|
||||
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
|
||||
17
src/cgroup_attach.h
Normal file
17
src/cgroup_attach.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#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 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
|
||||
124
src/common.cpp
Normal file
124
src/common.cpp
Normal file
@@ -0,0 +1,124 @@
|
||||
#include "common.h"
|
||||
#include <fstream>
|
||||
#include <regex>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
bool enable_debug = false;
|
||||
bool enable_info = true;
|
||||
|
||||
string join2str(const vector<string> t, const char delm) {
|
||||
string s;
|
||||
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 startWith(string s, string prefix) { return s.rfind(prefix, 0) == 0; }
|
||||
|
||||
bool validCgroup(const string cgroup) {
|
||||
return regex_match(cgroup, regex("^/[a-zA-Z0-9\\-_./@]*$"));
|
||||
}
|
||||
|
||||
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;
|
||||
unique_ptr<FILE, decltype(&pclose)> fp(popen(to_str("pidof ", path).c_str(), "r"),
|
||||
&pclose);
|
||||
if (!fp) return pids;
|
||||
int pid;
|
||||
while (fscanf(fp.get(), "%d", &pid) != EOF) { pids.push_back(pid); }
|
||||
return pids;
|
||||
}
|
||||
|
||||
string bash_which(const string &name) {
|
||||
stringstream buffer;
|
||||
unique_ptr<FILE, decltype(&pclose)> fp(popen(to_str("which ", name).c_str(), "r"),
|
||||
&pclose);
|
||||
if (!fp) return "";
|
||||
char buf[READ_SIZE_MAX];
|
||||
while (fgets(buf, READ_SIZE_MAX, fp.get()) != NULL) { buffer << buf; }
|
||||
string s = buffer.str();
|
||||
s.pop_back(); // remove newline character
|
||||
return s;
|
||||
}
|
||||
|
||||
string bash_readlink(const string &path) {
|
||||
stringstream buffer;
|
||||
unique_ptr<FILE, decltype(&pclose)> fp(popen(to_str("readlink -e ", path).c_str(), "r"),
|
||||
&pclose);
|
||||
if (!fp) return "";
|
||||
char buf[READ_SIZE_MAX];
|
||||
while (fgets(buf, READ_SIZE_MAX, fp.get()) != NULL) { buffer << buf; }
|
||||
string s = buffer.str();
|
||||
s.pop_back(); // remove newline character
|
||||
return s;
|
||||
}
|
||||
|
||||
string getRealExistPath(const string &name) {
|
||||
if (name[0] == '/' && fileExist(name)) return name;
|
||||
string path;
|
||||
path = bash_which(name);
|
||||
if (path.empty()) return "";
|
||||
path = bash_readlink(path);
|
||||
if (!fileExist(path)) return "";
|
||||
return path;
|
||||
}
|
||||
|
||||
bool belongToCgroup(string cg1, string cg2) { return startWith(cg1 + '/', cg2 + '/'); }
|
||||
|
||||
bool belongToCgroup(string cg1, vector<string> cg2) {
|
||||
for (const auto &s : cg2) {
|
||||
if (startWith(cg1 + '/', s + '/')) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
string getCgroup(const pid_t &pid) { return getCgroup(to_str(pid)); }
|
||||
|
||||
string getCgroup(const string &pid) {
|
||||
string cgroup_f = to_str("/proc/", pid, "/cgroup");
|
||||
if (!fileExist(cgroup_f)) return "";
|
||||
|
||||
string cgroup, line;
|
||||
ifstream ifs(cgroup_f);
|
||||
debug("prcessing file %s", cgroup_f.c_str());
|
||||
while (ifs.good() && getline(ifs, line)) {
|
||||
debug("process line: %s", line.c_str());
|
||||
if (line[0] == '0') {
|
||||
cgroup = line.substr(3);
|
||||
debug("get cgroup of %s: %s", pid.c_str(), cgroup.c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
ifs.close();
|
||||
return cgroup;
|
||||
}
|
||||
103
src/common.h
Normal file
103
src/common.h
Normal file
@@ -0,0 +1,103 @@
|
||||
#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 READ_SIZE_MAX 128
|
||||
|
||||
#define CGROUP_PROXY_PRESVERED "/proxy.slice"
|
||||
#define CGROUP_NOPROXY_PRESVERED "/noproxy.slice"
|
||||
|
||||
#define THREAD_TIMEOUT 5
|
||||
|
||||
#define MSG_TYPE_CONFIG_JSON 1
|
||||
#define MSG_TYPE_CONFIG_PATH 2
|
||||
#define MSG_TYPE_PROXY_PID 3
|
||||
#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"); \
|
||||
fflush(stderr); \
|
||||
}
|
||||
|
||||
#define debug(...) \
|
||||
if (enable_debug) { \
|
||||
fprintf(stdout, "debug: "); \
|
||||
fprintf(stdout, __VA_ARGS__); \
|
||||
fprintf(stdout, "\n"); \
|
||||
fflush(stdout); \
|
||||
}
|
||||
|
||||
#define info(...) \
|
||||
if (enable_info) { \
|
||||
fprintf(stdout, "info: "); \
|
||||
fprintf(stdout, __VA_ARGS__); \
|
||||
fprintf(stdout, "\n"); \
|
||||
fflush(stdout); \
|
||||
}
|
||||
|
||||
#define return_error return -1
|
||||
#define return_success return 0
|
||||
|
||||
template <typename... T> string to_str(T... args) {
|
||||
stringstream ss;
|
||||
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 startWith(string prefix);
|
||||
|
||||
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);
|
||||
|
||||
/**
|
||||
* whether cg1 belongs to cg2
|
||||
*/
|
||||
bool belongToCgroup(string cg1, string cg2);
|
||||
bool belongToCgroup(string cg1, vector<string> cg2);
|
||||
string getCgroup(const pid_t &pid);
|
||||
string getCgroup(const string &pid);
|
||||
|
||||
#endif
|
||||
159
src/config.cpp
Normal file
159
src/config.cpp
Normal file
@@ -0,0 +1,159 @@
|
||||
#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() {
|
||||
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);
|
||||
|
||||
mergeReserved();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
#undef tryassign
|
||||
#undef add2json
|
||||
#undef merge
|
||||
|
||||
} // namespace CGPROXY::CONFIG
|
||||
42
src/config.h
Normal file
42
src/config.h
Normal 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 = {cgroup_proxy_preserved};
|
||||
vector<string> program_noproxy = {cgroup_noproxy_preserved};
|
||||
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
|
||||
104
src/execsnoop.cpp
Normal file
104
src/execsnoop.cpp
Normal file
@@ -0,0 +1,104 @@
|
||||
#include "execsnoop.h"
|
||||
#include "bcc/BPF.h"
|
||||
#include "common.h"
|
||||
#include <bcc/libbpf.h>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
using namespace std;
|
||||
|
||||
namespace CGPROXY::EXECSNOOP {
|
||||
|
||||
const string BPF_PROGRAM = R"(
|
||||
#include <linux/fs.h>
|
||||
#include <linux/sched.h>
|
||||
#include <uapi/linux/ptrace.h>
|
||||
|
||||
struct data_t {
|
||||
int pid;
|
||||
};
|
||||
|
||||
BPF_PERF_OUTPUT(events);
|
||||
|
||||
int syscall_execve(struct pt_regs *ctx,
|
||||
const char __user *filename,
|
||||
const char __user *const __user *__argv,
|
||||
const char __user *const __user *__envp)
|
||||
{
|
||||
struct data_t data = {};
|
||||
data.pid = bpf_get_current_pid_tgid();
|
||||
events.perf_submit(ctx, &data, sizeof(struct data_t));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ret_syscall_execve(struct pt_regs *ctx){
|
||||
struct data_t data = {};
|
||||
data.pid = bpf_get_current_pid_tgid();
|
||||
int retval = PT_REGS_RC(ctx);
|
||||
if (retval==0)
|
||||
events.perf_submit(ctx, &data, sizeof(struct data_t));
|
||||
return 0;
|
||||
}
|
||||
)";
|
||||
|
||||
struct data_t {
|
||||
int pid;
|
||||
};
|
||||
|
||||
function<int(int)> callback = NULL;
|
||||
promise<void> status;
|
||||
|
||||
void handle_events(void *cb_cookie, void *data, int data_size) {
|
||||
auto event = static_cast<data_t *>(data);
|
||||
int pid = event->pid;
|
||||
|
||||
if (callback) callback(pid);
|
||||
}
|
||||
|
||||
int execsnoop() {
|
||||
debug("starting execsnoop");
|
||||
ebpf::BPF bpf;
|
||||
|
||||
auto init_res = bpf.init(BPF_PROGRAM);
|
||||
if (init_res.code() != 0) {
|
||||
error("bpf init failed, maybe linux-headers not installed");
|
||||
std::cerr << init_res.msg() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
string execve_fnname = bpf.get_syscall_fnname("execve");
|
||||
// auto attach_res = bpf.attach_kprobe(execve_fnname, "syscall_execve");
|
||||
auto attach_res =
|
||||
bpf.attach_kprobe(execve_fnname, "ret_syscall_execve", 0, BPF_PROBE_RETURN);
|
||||
if (attach_res.code() != 0) {
|
||||
std::cerr << attach_res.msg() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto open_res = bpf.open_perf_buffer("events", &handle_events);
|
||||
if (open_res.code() != 0) {
|
||||
std::cerr << open_res.msg() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (bpf.free_bcc_memory()) {
|
||||
std::cerr << "Failed to free llvm/clang memory" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
status.set_value();
|
||||
|
||||
while (true) bpf.poll_perf_buffer("events");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void startThread(function<int(int)> c, promise<void> _status) {
|
||||
status = move(_status);
|
||||
callback = c;
|
||||
execsnoop();
|
||||
}
|
||||
|
||||
} // namespace CGPROXY::EXECSNOOP
|
||||
22
src/execsnoop.h
Normal file
22
src/execsnoop.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef EXECSNOOP_HPP
|
||||
#define EXECSNOOP_HPP 1
|
||||
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <string>
|
||||
using namespace std;
|
||||
|
||||
namespace CGPROXY::EXECSNOOP {
|
||||
|
||||
extern const string BPF_PROGRAM;
|
||||
struct data_t;
|
||||
extern function<int(int)> callback;
|
||||
void handle_events(void *cb_cookie, void *data, int data_size);
|
||||
int execsnoop();
|
||||
|
||||
extern "C" void startThread(function<int(int)> c, promise<void> _status);
|
||||
typedef void startThread_t(function<int(int)>, promise<void>);
|
||||
startThread_t *_startThread; // only for dlsym()
|
||||
|
||||
} // namespace CGPROXY::EXECSNOOP
|
||||
#endif
|
||||
17
src/main.cpp
Normal file
17
src/main.cpp
Normal 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);
|
||||
}
|
||||
49
src/socket_client.cpp
Normal file
49
src/socket_client.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#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) {
|
||||
send(msg.c_str(), status);
|
||||
debug("return status: %d", status);
|
||||
}
|
||||
|
||||
} // namespace CGPROXY::SOCKET
|
||||
14
src/socket_client.h
Normal file
14
src/socket_client.h
Normal 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
|
||||
67
src/socket_server.cpp
Normal file
67
src/socket_server.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#include "socket_server.h"
|
||||
#include "common.h"
|
||||
#include <filesystem>
|
||||
#include <stdlib.h>
|
||||
#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, promise<void> status) {
|
||||
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);
|
||||
|
||||
status.set_value();
|
||||
|
||||
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
|
||||
auto msg = (char *)malloc(msg_len + 1);
|
||||
flag = read(cfd, msg, msg_len * sizeof(char));
|
||||
continue_if_error(flag, "read msg");
|
||||
msg[msg_len] = '\0';
|
||||
// handle msg
|
||||
int status = callback(msg);
|
||||
free(msg);
|
||||
// send back flag
|
||||
flag = write(cfd, &status, sizeof(int));
|
||||
continue_if_error(flag, "write back");
|
||||
}
|
||||
}
|
||||
|
||||
SocketServer::~SocketServer() {
|
||||
close(sfd);
|
||||
close(cfd);
|
||||
unlink(SOCKET_PATH);
|
||||
}
|
||||
|
||||
void startThread(function<int(char *)> callback, promise<void> status) {
|
||||
SocketServer server;
|
||||
server.socketListening(callback, move(status));
|
||||
}
|
||||
|
||||
} // namespace CGPROXY::SOCKET
|
||||
31
src/socket_server.h
Normal file
31
src/socket_server.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#ifndef SOCKET_SERVER_H
|
||||
#define SOCKET_SERVER_H
|
||||
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#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; \
|
||||
}
|
||||
|
||||
class SocketServer {
|
||||
public:
|
||||
int sfd = -1, cfd = -1, flag = -1;
|
||||
struct sockaddr_un unix_socket;
|
||||
|
||||
void socketListening(function<int(char *)> callback, promise<void> status);
|
||||
~SocketServer();
|
||||
};
|
||||
|
||||
void startThread(function<int(char *)> callback, promise<void> status);
|
||||
|
||||
} // namespace CGPROXY::SOCKET
|
||||
|
||||
#endif
|
||||
7
test/CMakeLists.txt
Normal file
7
test/CMakeLists.txt
Normal 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)
|
||||
48
test/socket_client_test.cpp
Normal file
48
test/socket_client_test.cpp
Normal 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
12
tools/CMakeLists.txt
Normal 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
31
tools/cgattach.cpp
Normal 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
24
tools/execsnoop.cpp
Normal 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;
|
||||
}
|
||||
5
v2ray_config/00_log.json
Normal file
5
v2ray_config/00_log.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"log": {
|
||||
"loglevel": "none"
|
||||
}
|
||||
}
|
||||
10
v2ray_config/01_api.json
Normal file
10
v2ray_config/01_api.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"api": {
|
||||
"services": [
|
||||
"HandlerService",
|
||||
"LoggerService",
|
||||
"StatsService"
|
||||
],
|
||||
"tag": "API"
|
||||
}
|
||||
}
|
||||
22
v2ray_config/02_dns.json
Normal file
22
v2ray_config/02_dns.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"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,
|
||||
"domains": [
|
||||
"geosite:cn"
|
||||
],
|
||||
"expectIPs": [
|
||||
"geoip:cn"
|
||||
]
|
||||
}
|
||||
],
|
||||
"tag": "dns_inbound"
|
||||
}
|
||||
}
|
||||
8
v2ray_config/03_policy.json
Normal file
8
v2ray_config/03_policy.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"policy": {
|
||||
"system": {
|
||||
"statsInboundDownlink": true,
|
||||
"statsInboundUplink": true
|
||||
}
|
||||
}
|
||||
}
|
||||
54
v2ray_config/04_routing_00.json
Normal file
54
v2ray_config/04_routing_00.json
Normal file
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"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",
|
||||
"type": "field"
|
||||
},
|
||||
{
|
||||
"domain": [
|
||||
"geosite:google",
|
||||
"geosite:github",
|
||||
"geosite:netflix",
|
||||
"geosite:steam",
|
||||
"geosite:telegram",
|
||||
"geosite:tumblr",
|
||||
"geosite:bbc"
|
||||
],
|
||||
"outboundTag": "outBound_PROXY",
|
||||
"type": "field"
|
||||
},
|
||||
{
|
||||
"domain": [
|
||||
"geosite:cn"
|
||||
],
|
||||
"outboundTag": "outBound_DIRECT",
|
||||
"type": "field"
|
||||
},
|
||||
{
|
||||
"ip": [
|
||||
"geoip:cn",
|
||||
"geoip:private"
|
||||
],
|
||||
"outboundTag": "outBound_DIRECT",
|
||||
"type": "field"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
14
v2ray_config/05_inbounds_00_api.json
Normal file
14
v2ray_config/05_inbounds_00_api.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"listen": "127.0.0.1",
|
||||
"port": 15490,
|
||||
"protocol": "dokodemo-door",
|
||||
"settings": {
|
||||
"address": "127.0.0.1"
|
||||
},
|
||||
"sniffing": {},
|
||||
"tag": "inbound_API"
|
||||
}
|
||||
]
|
||||
}
|
||||
30
v2ray_config/05_inbounds_01_tproxy_ipv4lo.json
Normal file
30
v2ray_config/05_inbounds_01_tproxy_ipv4lo.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
30
v2ray_config/05_inbounds_02_tproxy_ipv6lo.json
Normal file
30
v2ray_config/05_inbounds_02_tproxy_ipv6lo.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"listen": "::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_ipv6lo"
|
||||
}
|
||||
]
|
||||
}
|
||||
13
v2ray_config/05_inbounds_03_http.json
Normal file
13
v2ray_config/05_inbounds_03_http.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"listen": "127.0.0.1",
|
||||
"port": 8888,
|
||||
"protocol": "http",
|
||||
"sniffing": {
|
||||
"enabled": false
|
||||
},
|
||||
"tag": "http_IN"
|
||||
}
|
||||
]
|
||||
}
|
||||
17
v2ray_config/05_inbounds_04_socks5.json
Normal file
17
v2ray_config/05_inbounds_04_socks5.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"listen": "127.0.0.1",
|
||||
"port": 1080,
|
||||
"protocol": "socks",
|
||||
"settings": {
|
||||
"auth": "noauth",
|
||||
"userLevel": 0
|
||||
},
|
||||
"sniffing": {
|
||||
"enabled": false
|
||||
},
|
||||
"tag": "socks_IN"
|
||||
}
|
||||
]
|
||||
}
|
||||
19
v2ray_config/06_outbounds_00_blackhole.json
Normal file
19
v2ray_config/06_outbounds_00_blackhole.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"outbounds": [
|
||||
{
|
||||
"protocol": "blackhole",
|
||||
"sendThrough": "0.0.0.0",
|
||||
"settings": {
|
||||
"response": {
|
||||
"type": "none"
|
||||
}
|
||||
},
|
||||
"streamSettings": {
|
||||
"sockopt": {
|
||||
"mark": 255
|
||||
}
|
||||
},
|
||||
"tag": "outBound_BLACKHOLE"
|
||||
}
|
||||
]
|
||||
}
|
||||
19
v2ray_config/06_outbounds_01_freedom.json
Normal file
19
v2ray_config/06_outbounds_01_freedom.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"outbounds": [
|
||||
{
|
||||
"protocol": "freedom",
|
||||
"sendThrough": "0.0.0.0",
|
||||
"settings": {
|
||||
"domainStrategy": "UseIP",
|
||||
"redirect": ":0",
|
||||
"userLevel": 0
|
||||
},
|
||||
"streamSettings": {
|
||||
"sockopt": {
|
||||
"mark": 255
|
||||
}
|
||||
},
|
||||
"tag": "outBound_DIRECT"
|
||||
}
|
||||
]
|
||||
}
|
||||
13
v2ray_config/06_outbounds_02_dns.json
Normal file
13
v2ray_config/06_outbounds_02_dns.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"outbounds": [
|
||||
{
|
||||
"protocol": "dns",
|
||||
"streamSettings": {
|
||||
"sockopt": {
|
||||
"mark": 255
|
||||
}
|
||||
},
|
||||
"tag": "dns-out"
|
||||
}
|
||||
]
|
||||
}
|
||||
1
v2ray_config/06_outbounds_10_myproxy.json
Normal file
1
v2ray_config/06_outbounds_10_myproxy.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
1
v2ray_config/07_transport.json
Normal file
1
v2ray_config/07_transport.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
3
v2ray_config/08_stats.json
Normal file
3
v2ray_config/08_stats.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"stats": {}
|
||||
}
|
||||
1
v2ray_config/09_reverse.json
Normal file
1
v2ray_config/09_reverse.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
2
v2ray_config/merge.sh
Normal file
2
v2ray_config/merge.sh
Normal file
@@ -0,0 +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 > /dev/null
|
||||
8
v2ray_config/readme.md
Normal file
8
v2ray_config/readme.md
Normal file
@@ -0,0 +1,8 @@
|
||||
## 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)
|
||||
|
||||
18
v2ray_config/v2ray.service
Normal file
18
v2ray_config/v2ray.service
Normal file
@@ -0,0 +1,18 @@
|
||||
[Unit]
|
||||
Description=V2Ray - A unified platform for anti-censorship
|
||||
Documentation=https://v2ray.com https://guide.v2fly.org
|
||||
After=network.target nss-lookup.target
|
||||
Wants=network-online.target
|
||||
|
||||
[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
|
||||
Restart=on-failure
|
||||
# Don't restart in the case of configuration error
|
||||
RestartPreventExitStatus=23
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Reference in New Issue
Block a user