151 Commits
v0.01 ... v0.18

Author SHA1 Message Date
Fancy Zhang
892c6587dc bump version 2020-07-05 23:07:54 +08:00
Fancy Zhang
8ea4062384 test on deepin 15.11 2020-07-05 22:36:53 +08:00
Fancy Zhang
85e7bb3317 allow static build, mainly for deb and rpm pack 2020-07-05 22:30:36 +08:00
Fancy Zhang
9d2c26e765 customize cgroup mount point 2020-07-05 22:29:43 +08:00
Fancy Zhang
00cd293842 tested on ubuntu 18.04 2020-07-05 17:58:10 +08:00
Fancy Zhang
b8b70dcad5 build deb and rpm static prefered 2020-07-05 17:41:41 +08:00
Fancy Zhang
5398740bf3 add cmake option: build_execsnoop_dl, build_static 2020-07-05 16:57:00 +08:00
Fancy Zhang
f5dc84e34a bump version 2020-07-04 15:59:39 +08:00
Fancy Zhang
720806317b update readme 2020-07-04 15:54:31 +08:00
Fancy Zhang
5509be3926 add libbpf depency for deb and rpm 2020-07-04 15:34:20 +08:00
Fancy Zhang
ab98bae840 add warning output 2020-07-04 15:33:02 +08:00
Fancy Zhang
4753d2be68 filter cgroup that not exist 2020-07-04 15:32:36 +08:00
Fancy Zhang
9f4c980c3e minor adjustments 2020-07-04 14:37:28 +08:00
Fancy Zhang
e4437071c9 use execsnoop-kernel which btf is not requested 2020-07-04 13:23:43 +08:00
Fancy Zhang
3b5378a79d use bpftool-gen to generate skel 2020-07-04 11:07:56 +08:00
Fancy Zhang
7f132c5d63 add execsnoop-kernel which need to be built in kernel tree 2020-07-03 22:36:35 +08:00
Fancy Zhang
6cb169522c update install 2020-06-29 15:16:56 +08:00
Fancy Zhang
0d2725d00c expose table, fwmark, mark_newin to config 2020-06-28 00:12:38 +08:00
Fancy Zhang
a4628cfed8 use more uncommon route table and mark 2020-06-27 12:52:48 +08:00
Fancy Zhang
77b57247ea commit add .vscode things 2020-06-27 10:13:04 +08:00
Fancy Zhang
4618b8f475 fix #5: segfault when program not exist 2020-06-27 10:06:04 +08:00
Fancy Zhang
46fb9bae2b optimize cmake 2020-06-21 22:07:30 +08:00
Fancy Zhang
c223af9d71 updated readme 2020-06-18 15:54:17 +08:00
Fancy Zhang
25d64f7cdb bump version 2020-06-18 14:58:29 +08:00
Fancy Zhang
c5ef430652 update pack 2020-06-18 14:57:33 +08:00
Fancy Zhang
58159dbc15 replace bcc with libbpf to optimize resource usage 2020-06-18 14:42:56 +08:00
Fancy Zhang
3f1bee745b replace typedef with using decltype 2020-06-15 21:46:47 +08:00
Fancy Zhang
049741249d update readme 2020-06-12 10:09:37 +08:00
springzfx
bc94e58cb1 check again after small period(100ms) to avoid kde cgroup override 2020-06-12 00:54:39 +08:00
springzfx
840a8338a8 update readme: add known docker issue 2020-06-02 14:50:50 +08:00
springzfx
06da477373 bump version 2020-06-01 00:38:56 +08:00
springzfx
314ff64b8b remove uninitialized pid ref 2020-06-01 00:36:48 +08:00
springzfx
d1bf9620bf only process running program when execsnoop thread started 2020-05-30 15:54:58 +08:00
springzfx
b7ea8e441b execsnoop: leave alone if already in preserved cgroup
so make cgproxy and cgnoproxy command have highest priority
2020-05-30 15:30:47 +08:00
springzfx
b031aa8064 update readme 2020-05-30 03:52:22 +08:00
springzfx
8226db37e0 avoid unused variable and VLA 2020-05-30 03:46:36 +08:00
springzfx
5bbec4d1bf remove pthread.h, use CMAKE_DL_LIBS 2020-05-30 02:37:45 +08:00
springzfx
9b4ec44897 set timeout for thread start 2020-05-30 02:18:07 +08:00
springzfx
a1a1ed5bd9 linux-headers needed fot bpf init 2020-05-30 02:02:03 +08:00
springzfx
34e5d81329 change status signal place 2020-05-30 01:59:24 +08:00
springzfx
dfc688b5e5 linux-headers needed fot bpf init 2020-05-30 01:43:37 +08:00
springzfx
ec9609cec8 replace pthread with c++ thread 2020-05-30 01:43:30 +08:00
springzfx
cc83c1ae55 use deltype 2020-05-29 21:30:18 +08:00
springzfx
75751f4887 fix processing /proc/<pid>/cgroup 2020-05-29 17:43:12 +08:00
springzfx
af78ad2012 flush fprintf 2020-05-29 16:16:32 +08:00
springzfx
c32457a1aa robust detect already in correspond cgroup 2020-05-27 00:02:56 +08:00
springzfx
1d29828d1b remove path max len limit 2020-05-26 23:04:12 +08:00
springzfx
4fea0d39a2 add iptables output prefix 2020-05-26 00:10:14 +08:00
springzfx
badf282842 detect already in correspond cgroup 2020-05-26 00:05:38 +08:00
springzfx
41f856acd2 update deb depency 2020-05-25 19:37:23 +08:00
springzfx
45adf0a233 update man and readme 2020-05-25 19:20:56 +08:00
springzfx
1f4dd2fde2 update deb/rpm depency 2020-05-25 17:26:53 +08:00
springzfx
40bd709995 bump version 2020-05-25 17:22:40 +08:00
springzfx
221a75ae7b clang format 2020-05-25 17:13:47 +08:00
springzfx
16a341205f add with_execsnoop option 2020-05-25 16:53:34 +08:00
springzfx
076651b984 make execsnoop optional as module 2020-05-25 16:52:49 +08:00
springzfx
1c72a204a1 execsnoop as library 2020-05-25 16:37:57 +08:00
springzfx
f501c7e476 add execsnoop in c++ 2020-05-25 05:35:07 +08:00
springzfx
0ec9caefe1 fix [cgproxy --pid] not return early 2020-05-24 18:30:05 +08:00
springzfx
94b73b5103 execsnoop: add --debug arg 2020-05-24 18:27:33 +08:00
springzfx
c30df999b8 execsnoop: fix process path resolve 2020-05-24 01:11:09 +08:00
springzfx
932f2bbc94 updated readme 2020-05-23 16:28:35 +08:00
springzfx
1bbd1ab6ec updated readme 2020-05-23 16:25:20 +08:00
springzfx
fa7d877de5 bump version 2020-05-23 15:15:05 +08:00
springzfx
3475001ca3 update deb//rpm depency 2020-05-23 15:14:11 +08:00
springzfx
0b25b5263a update man and readme 2020-05-23 14:41:12 +08:00
springzfx
388ba6a4c8 execsnoop: fix get_pid 2020-05-23 13:49:09 +08:00
springzfx
5dbce18f95 python-bcc optional 2020-05-23 13:44:59 +08:00
springzfx
792a156647 add execsnoop 2020-05-23 13:29:11 +08:00
springzfx
92abcb1851 add --pid option 2020-05-23 03:30:46 +08:00
springzfx
a73b697cab use exec 2020-05-22 01:21:53 +08:00
springzfx
aace8c3d31 add tips that service may not running 2020-05-21 20:50:44 +08:00
springzfx
665e02ceaa readme: add iptables version requirement 2020-05-21 20:24:49 +08:00
fancy
bfe3289201 use DynamicUser in v2ray.service 2020-05-21 14:41:58 +08:00
fancy
2c8625c110 add man page 2020-05-20 00:42:00 +08:00
fancy
ba0b780adf update readme 2020-05-19 15:01:25 +08:00
fancy
1fa0d51e1d bump version 2020-05-18 21:34:30 +08:00
fancy
aedebf4e31 merge to one executable 2020-05-18 21:34:30 +08:00
fancy
051742eef1 rename .hpp to .h 2020-05-18 21:34:30 +08:00
fancy
619fcaae8e build library 2020-05-18 21:34:30 +08:00
fancy
78c10e3e3e bump version 2020-05-18 21:34:30 +08:00
fancy
99b29195f2 spearete package pack 2020-05-18 21:34:30 +08:00
fancy
53fccbe72d single cgproxyd process only 2020-05-18 21:34:30 +08:00
fancy
db4757316a fix print usage and clang format 2020-05-18 21:34:30 +08:00
fancy
3a4e62b3c2 remove no-need depency 2020-05-18 21:34:30 +08:00
fancy
4525d83a53 reorganize project 2020-05-18 21:34:30 +08:00
fancy
c5ec1027ad clang format 2020-05-17 17:32:12 +08:00
fancy
ee3a5d0fa2 merge from dev 2020-05-16 16:05:56 +08:00
fancy
f2210f9bda updated readme 2020-05-16 15:17:40 +08:00
fancy
e86ea01f6e updated readme 2020-05-16 15:12:39 +08:00
fancy
bc9f6d4d4e clean from v3.x, add json dependency 2020-05-16 14:35:59 +08:00
fancy
6de88897b2 add todo comment 2020-05-16 14:22:04 +08:00
fancy
39275452da fix precess args 2020-05-16 14:12:44 +08:00
fancy
a16cefbfb2 handle signal, robust iptables clean 2020-05-16 14:07:07 +08:00
fancy
2adba75b3e cgproxyd as class 2020-05-16 11:53:15 +08:00
fancy
fbcc499ba8 rename .h to .hpp 2020-05-16 10:49:47 +08:00
fancy
1c16f57193 now based on unix socket and json config 2020-05-16 10:29:34 +08:00
fancy
87cd5a6d99 missing one file 2020-05-16 00:17:37 +08:00
fancy
138fa698be update readme 2020-05-16 00:12:03 +08:00
fancy
ffea0fb2b9 add local aur build 2020-05-16 00:12:03 +08:00
fancy
696fcb6b4e update readme 2020-05-16 00:12:03 +08:00
fancy
9b7d6804f7 update readme 2020-05-16 00:12:03 +08:00
fancy
ad362998d8 allow array input for cgroup_proxy and cgroup_noproxy 2020-05-16 00:11:25 +08:00
fancy
2b5ff745ac core socket feature 2020-05-16 00:07:04 +08:00
fancy
b2b3168463 update readme 2020-05-14 16:16:45 +08:00
fancy
2838ffbb70 bump version 2020-05-14 14:28:08 +08:00
fancy
749fe38ca8 add local aur build 2020-05-14 14:21:40 +08:00
fancy
c0668fd8d2 use macro 2020-05-14 13:57:39 +08:00
fancy
06ae0b9fc5 update readme 2020-05-14 12:07:12 +08:00
fancy
4e04dcf84a update readme 2020-05-14 04:53:23 +08:00
fancy
c0e9ea24c1 allow array input for cgroup_proxy and cgroup_noproxy 2020-05-14 04:47:29 +08:00
fancy
b5701d8b49 allow array input for cgroup_proxy and cgroup_noproxy 2020-05-14 04:39:02 +08:00
fancy
4e37bccc1a add basic unix socket control 2020-05-13 23:43:28 +08:00
fancy
f8e0abbb55 check root, and check iptables before clean 2020-05-13 23:42:05 +08:00
fancy
6c24c68831 only masquerade ipv6 private address 2020-05-08 11:47:51 +08:00
fancy
d3b2dc0465 small fix 2020-05-07 00:22:08 +08:00
fancy
4be7be2083 example: rename to keep order 2020-05-06 02:42:44 +08:00
fancy
25f94968ae install readme.md to doc 2020-05-05 20:14:26 +08:00
fancy
3b4b67df33 small change 2020-05-05 19:30:35 +08:00
fancy
31ae519193 v2ray.service without root 2020-05-05 00:32:09 +08:00
fancy
7f0ebe9d35 remove mark_noproxy, and other small change 2020-05-04 20:41:50 +08:00
fancy
236c08172b example: update readme 2020-05-03 18:29:08 +08:00
fancy
c07ae13030 example: add aliyun doh 2020-05-03 17:00:34 +08:00
fancy
d5ea832b4f change timeout, and dns no need to proxy 2020-05-03 15:05:56 +08:00
fancy
aa5ca6f204 update example, limit to localhost 2020-05-02 08:01:19 +08:00
fancy
a80187f947 optimize function get_cgroup2_mount_point 2020-05-02 04:15:54 +08:00
fancy
dca895c7cc readme update 2020-05-02 03:45:36 +08:00
fancy
08097a54d7 readme update 2020-05-02 03:42:22 +08:00
fancy
bce568d802 readme update 2020-05-02 03:40:32 +08:00
fancy
98c07a31af readme update 2020-05-02 03:37:58 +08:00
fancy
916c11d280 update config example 2020-05-01 17:51:53 +08:00
fancy
72579bc84a typo fix 2020-05-01 13:55:06 +08:00
fancy
5c16fdfb9f add v2ray config example 2020-05-01 11:55:57 +08:00
fancy
f55b09ec12 imporved gateway nat, and bypass broadcast things 2020-05-01 11:26:46 +08:00
fancy
6ae12bf5c4 optimize 2020-04-30 16:32:07 +08:00
fancy
31627dd956 update readme 2020-04-30 02:05:03 +08:00
fancy
9f8d540c78 important optimize 2020-04-30 02:02:11 +08:00
fancy
b8204126c5 maintain update 2020-04-28 20:25:44 +08:00
fancy
daa68f20ea package deb rpm 2020-04-28 19:58:40 +08:00
fancy
836a34cdc8 also handle gateway dns 2020-04-28 13:49:26 +08:00
fancy
3c21882b88 more compact 2020-04-28 12:17:25 +08:00
fancy
71d924a303 more robust clean whem stop 2020-04-28 03:28:48 +08:00
fancy
5d0b137778 more robust
do not handle local device connection through tproxy if gateway is not enabled
2020-04-28 02:37:36 +08:00
fancy
8a29e88bab update clean iptables 2020-04-27 23:00:25 +08:00
fancy
dd234b08a0 typo fix 2020-04-27 21:50:09 +08:00
fancy
168779ea90 fix #1 2020-04-27 21:29:44 +08:00
fancy
5b65ac0ffd typo fix 2020-04-27 14:49:56 +08:00
fancy
6c77233e1e little fix, and update readme 2020-04-27 14:43:40 +08:00
fancy
6be14b9935 new feature: add gateway support 2020-04-27 00:40:39 +08:00
fancy
c1cd7e6e07 clean stuff 2020-04-26 03:43:18 +08:00
fancy
0848d1ddeb small update 2020-04-26 00:28:10 +08:00
91 changed files with 111650 additions and 383 deletions

9
.gitignore vendored
View File

@@ -1,4 +1,9 @@
build
.directory
.vscode
cgproxy2.sh
.clangd
v2ray_config/proxy
v2ray_config/06_outbounds_proxy.json
aur-*
gh-md-toc
add_toc.sh
compile_commands.json

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "execsnoop-libbpf/libbpf"]
path = execsnoop-libbpf/libbpf
url = https://github.com/libbpf/libbpf.git

2
.vscode/gdb-root vendored Executable file
View File

@@ -0,0 +1,2 @@
#!/bin/sh
exec sudo -E /usr/bin/gdb "$@"

23
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,23 @@
{
"configurations": [
{
"name": "(gdb) cgproxyd",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/build/src/cgproxy",
"args": ["--daemon", "--debug", "--execsnoop"],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"miDebuggerPath": "${workspaceFolder}/.vscode/gdb-root",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}]
}

View File

@@ -1,23 +1,45 @@
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.14)
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.18)
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)
include(GNUInstallDirs)
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/)
add_compile_options(-Wall -Wextra -Wpedantic -Wno-unused-result -Wno-unused-parameter)
# for clangd
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# option(with_execsnoop "enable program level proxy control feature, need bcc installed" ON)
option(build_execsnoop_dl "build libexecsnoop.so which will be dynamic loaded, otherwise built directly into cgproxy" ON)
option(build_static "build with static link prefered" OFF)
option(build_tools OFF)
option(build_test "for develop" OFF)
add_subdirectory(src)
add_subdirectory(execsnoop-kernel)
add_subdirectory(pack)
if (build_tools)
add_subdirectory(tools)
endif()
if (build_test)
add_subdirectory(test)
endif()
# instal scripts and other things
install(PROGRAMS cgproxyd TYPE BIN)
install(PROGRAMS cgnoproxy TYPE BIN)
install(PROGRAMS cgroup-tproxy.sh DESTINATION ${CMAKE_INSTALL_DATADIR}/cgproxy/scripts)
install(FILES cgproxy.service DESTINATION ${CMAKE_INSTALL_LIBDIR}/systemd/system)
install(FILES config.json DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/cgproxy)
install(FILES readme.md DESTINATION ${CMAKE_INSTALL_DOCDIR})
# man pages
set(man_gz ${PROJECT_SOURCE_DIR}/man/cgproxyd.1.gz ${PROJECT_SOURCE_DIR}/man/cgproxy.1.gz ${PROJECT_SOURCE_DIR}/man/cgnoproxy.1.gz)
add_custom_command(OUTPUT ${man_gz}
COMMAND gzip -fk cgproxyd.1 cgproxy.1 cgnoproxy.1
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/man
)
add_custom_target(man ALL DEPENDS ${man_gz})
install(FILES ${man_gz} DESTINATION ${CMAKE_INSTALL_MANDIR}/man1/)

137
_clang-format Normal file
View File

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

View File

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

2
cgnoproxy Normal file
View File

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

View File

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

View File

@@ -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

View File

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

View File

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

2
cgproxyd Normal file
View File

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

View File

@@ -30,54 +30,100 @@ 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
## do not modify this if you don't known what you are doing
table=100
mark_proxy=0x01
mark_noproxy=0xff
make_newin=0x02
## 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
##
get_available_route_table(){
table=10007
while true; do
ip route show table $table &> /dev/null && ((table++)) || { echo $table && break; }
done
}
## mark/route things
[ -z ${table+x} ] && table=10007 # just a prime number
[ -z ${fwmark+x} ] && fwmark=0x9973
[ -z ${mark_newin+x} ] && mark_newin=0x9967
# echo "table: $table fwmark: $fwmark, mark_newin: $mark_newin"
## 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_type="cgroup2"
cgroup_procs_file="cgroup.procs"
[ -z ${cgroup_mount_point+x} ] && cgroup_mount_point=$(findmnt -t cgroup2 -n -o TARGET | head -n 1)
[ -z $cgroup_mount_point ] && { >&2 echo "iptables: no cgroup2 mount point available"; exit -1; }
[ ! -d $cgroup_mount_point ] && mkdir -p $cgroup_mount_point
[ "$(findmnt -M $cgroup_mount_point -n -o FSTYPE)" != "cgroup2" ] && mount -t cgroup2 none $cgroup_mount_point
[ "$(findmnt -M $cgroup_mount_point -n -o FSTYPE)" != "cgroup2" ] && { >&2 echo "iptables: mount $cgroup_mount_point failed"; exit -1; }
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
## unmount cgroup2
[ "$(findmnt -M $cgroup_mount_point -n -o FSTYPE)" = "cgroup2" ] && umount $cgroup_mount_point
}
## 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 +133,130 @@ 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;
## filter cgroup that not exist
_cgroup_noproxy=()
for cg in ${cgroup_noproxy[@]}; do
test -d $cgroup_mount_point$cg && _cgroup_noproxy+=($cg) || { >&2 echo "iptables: $cg not exist, ignore";}
done
unset cgroup_noproxy && cgroup_noproxy=${_cgroup_noproxy[@]}
## filter cgroup that not exist
_cgroup_proxy=()
for cg in ${cgroup_proxy[@]}; do
test -d $cgroup_mount_point$cg && _cgroup_proxy+=($cg) || { >&2 echo "iptables: $cg not exist, ignore";}
done
unset cgroup_proxy && cgroup_proxy=${_cgroup_proxy[@]}
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 127.0.0.1 --on-port $port --tproxy-mark $fwmark
iptables -t mangle -A TPROXY_ENT -p udp -j TPROXY --on-ip 127.0.0.1 --on-port $port --tproxy-mark $fwmark
iptables -t mangle -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 $mark_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
done
for cg in ${cgroup_proxy[@]}; do
iptables -t mangle -A TPROXY_OUT -m cgroup --path $cg -j MARK --set-mark $fwmark
done
iptables -t mangle -A OUTPUT -j TPROXY_OUT
#ipv6#
ip -6 rule add fwmark $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 ::1 --on-port $port --tproxy-mark $fwmark
ip6tables -t mangle -A TPROXY_ENT -p udp -j TPROXY --on-ip ::1 --on-port $port --tproxy-mark $fwmark
ip6tables -t mangle -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 -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 -p icmpv6 -j RETURN
ip6tables -t mangle -A TPROXY_OUT -m connmark --mark $mark_newin -j RETURN
ip6tables -t mangle -A TPROXY_OUT -m addrtype --dst-type LOCAL -j RETURN
ip6tables -t mangle -A TPROXY_OUT -m addrtype ! --dst-type UNICAST -j RETURN
for cg in ${cgroup_noproxy[@]}; do
ip6tables -t mangle -A TPROXY_OUT -m cgroup --path $cg -j RETURN
done
for cg in ${cgroup_proxy[@]}; do
ip6tables -t mangle -A TPROXY_OUT -m cgroup --path $cg -j MARK --set-mark $fwmark
done
ip6tables -t mangle -A OUTPUT -j TPROXY_OUT
## 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
## message for user
## 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 $mark_newin
ip6tables -t mangle -I TPROXY_PRE -m addrtype ! --src-type LOCAL -m conntrack --ctstate NEW -j CONNMARK --set-mark $mark_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

18
config.json Normal file
View File

@@ -0,0 +1,18 @@
{
"comment":"For usgae, see https://github.com/springzfx/cgproxy",
"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,
"table": 10007,
"fwmark": 39283,
"mark_newin": 39271
}

View File

@@ -0,0 +1,6 @@
include_directories(${PROJECT_SOURCE_DIR})
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
add_library(execsnoop MODULE execsnoop.cpp ../src/common.cpp)
target_link_libraries(execsnoop bcc)
install(TARGETS execsnoop DESTINATION /usr/lib/cgproxy/ PERMISSIONS ${basic_permission})

104
execsnoop-bcc/execsnoop.cpp Normal file
View File

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

23
execsnoop-bcc/execsnoop.h Normal file
View File

@@ -0,0 +1,23 @@
#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>);
using startThread_t=decltype(startThread);
startThread_t *_startThread; // only for dlsym()
} // namespace CGPROXY::EXECSNOOP
#endif

3
execsnoop-bcc/readme.md Normal file
View File

@@ -0,0 +1,3 @@
- depend [bcc](https://github.com/iovisor/bcc)
- huge memory usage, at least 50M

View File

@@ -0,0 +1,19 @@
# find libbpf
if (build_static)
find_library(LIBBPF libbpf.a)
else()
find_library(LIBBPF bpf)
endif()
if (LIBBPF-NOTFOUND)
message(FATAL_ERROR "libbpf not found")
endif()
if (build_execsnoop_dl)
add_library(execsnoop MODULE execsnoop_share.cpp)
install(TARGETS execsnoop DESTINATION ${CMAKE_INSTALL_LIBDIR}/cgproxy/)
target_link_libraries(execsnoop PRIVATE ${LIBBPF} -lelf -lz)
else()
add_library(execsnoop STATIC execsnoop_share.cpp)
target_link_libraries(execsnoop PRIVATE ${LIBBPF} -l:libelf.a -l:libz.a)
endif()

BIN
execsnoop-kernel/execsnoop Executable file

Binary file not shown.

View File

@@ -0,0 +1,93 @@
#include "linux/sched.h"
#include <linux/version.h>
#include <linux/ptrace.h>
#include <uapi/linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#define TASK_COMM_LEN 16
struct event {
char comm[TASK_COMM_LEN];
pid_t pid;
pid_t tgid;
pid_t ppid;
uid_t uid;
};
/* /sys/kernel/debug/tracing/events/syscalls/sys_enter_execve/format */
struct syscalls_enter_execve_args {
__u64 unused;
int syscall_nr;
const char filename_ptr;
const char *const * argv;
const char *const * envp;
};
/* /sys/kernel/debug/tracing/events/syscalls/sys_exit_execve/format */
struct syscalls_exit_execve_args {
__u64 unused;
int syscall_nr;
long ret;
};
struct bpf_map_def SEC("maps") records = {
.type = BPF_MAP_TYPE_HASH,
.key_size = sizeof(pid_t),
.value_size = sizeof(struct event),
.max_entries = 1024,
};
struct bpf_map_def SEC("maps") perf_events = {
.type = BPF_MAP_TYPE_PERF_EVENT_ARRAY,
.key_size = sizeof(u32),
.value_size = sizeof(u32),
.max_entries = 128,
};
SEC("tracepoint/syscalls/sys_enter_execve")
int syscall_enter_execve(struct syscalls_enter_execve_args *ctx){
pid_t pid, tgid; uid_t uid;
struct event *event;
struct task_struct *task, *task_p;
u64 id = bpf_get_current_pid_tgid();
pid = (pid_t)id;
tgid = id >> 32;
uid = (u32)bpf_get_current_uid_gid();
struct event empty_event={};
if (bpf_map_update_elem(&records, &pid, &empty_event, BPF_NOEXIST)!=0) return 0;
event = bpf_map_lookup_elem(&records, &pid);
if (!event) return 0;
event->pid = pid;
event->tgid = tgid;
event->uid = uid;
/* ppid is not reliable here, normal in arch, but become 0 in ubuntu 20.04 */
task = (struct task_struct*)bpf_get_current_task();
bpf_probe_read(&task_p, sizeof(struct task_struct*),&task->real_parent);
bpf_probe_read(&event->ppid,sizeof(pid_t),&task_p->tgid);
return 0;
}
SEC("tracepoint/syscalls/sys_exit_execve")
int syscall_exit_execve(struct syscalls_exit_execve_args *ctx){
pid_t pid;
struct event *event;
pid = (pid_t)bpf_get_current_pid_tgid();
event = bpf_map_lookup_elem(&records,&pid);
if (!event) return 0;
if (ctx->ret < 0) goto cleanup;
/* get comm */
bpf_get_current_comm(&event->comm,sizeof(event->comm));
bpf_perf_event_output(ctx, &perf_events, BPF_F_CURRENT_CPU, event, sizeof(struct event));
cleanup:
bpf_map_delete_elem(&records, &pid);
return 0;
}
char _license[] SEC("license") = "GPL";
u32 _version SEC("version") = LINUX_VERSION_CODE;

Binary file not shown.

View File

@@ -0,0 +1,225 @@
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
/* THIS FILE IS AUTOGENERATED! */
#ifndef __EXECSNOOP_KERN_SKEL_H__
#define __EXECSNOOP_KERN_SKEL_H__
#include <stdlib.h>
#include <bpf/libbpf.h>
struct execsnoop_kern {
struct bpf_object_skeleton *skeleton;
struct bpf_object *obj;
struct {
struct bpf_map *perf_events;
struct bpf_map *records;
} maps;
struct {
struct bpf_program *syscall_enter_execve;
struct bpf_program *syscall_exit_execve;
} progs;
struct {
struct bpf_link *syscall_enter_execve;
struct bpf_link *syscall_exit_execve;
} links;
};
static void
execsnoop_kern__destroy(struct execsnoop_kern *obj)
{
if (!obj)
return;
if (obj->skeleton)
bpf_object__destroy_skeleton(obj->skeleton);
free(obj);
}
static inline int
execsnoop_kern__create_skeleton(struct execsnoop_kern *obj);
static inline struct execsnoop_kern *
execsnoop_kern__open_opts(const struct bpf_object_open_opts *opts)
{
struct execsnoop_kern *obj;
obj = (typeof(obj))calloc(1, sizeof(*obj));
if (!obj)
return NULL;
if (execsnoop_kern__create_skeleton(obj))
goto err;
if (bpf_object__open_skeleton(obj->skeleton, opts))
goto err;
return obj;
err:
execsnoop_kern__destroy(obj);
return NULL;
}
static inline struct execsnoop_kern *
execsnoop_kern__open(void)
{
return execsnoop_kern__open_opts(NULL);
}
static inline int
execsnoop_kern__load(struct execsnoop_kern *obj)
{
return bpf_object__load_skeleton(obj->skeleton);
}
static inline struct execsnoop_kern *
execsnoop_kern__open_and_load(void)
{
struct execsnoop_kern *obj;
obj = execsnoop_kern__open();
if (!obj)
return NULL;
if (execsnoop_kern__load(obj)) {
execsnoop_kern__destroy(obj);
return NULL;
}
return obj;
}
static inline int
execsnoop_kern__attach(struct execsnoop_kern *obj)
{
return bpf_object__attach_skeleton(obj->skeleton);
}
static inline void
execsnoop_kern__detach(struct execsnoop_kern *obj)
{
return bpf_object__detach_skeleton(obj->skeleton);
}
static inline int
execsnoop_kern__create_skeleton(struct execsnoop_kern *obj)
{
struct bpf_object_skeleton *s;
s = (typeof(s))calloc(1, sizeof(*s));
if (!s)
return -1;
obj->skeleton = s;
s->sz = sizeof(*s);
s->name = "execsnoop_kern";
s->obj = &obj->obj;
/* maps */
s->map_cnt = 2;
s->map_skel_sz = sizeof(*s->maps);
s->maps = (typeof(s->maps))calloc(s->map_cnt, s->map_skel_sz);
if (!s->maps)
goto err;
s->maps[0].name = "perf_events";
s->maps[0].map = &obj->maps.perf_events;
s->maps[1].name = "records";
s->maps[1].map = &obj->maps.records;
/* programs */
s->prog_cnt = 2;
s->prog_skel_sz = sizeof(*s->progs);
s->progs = (typeof(s->progs))calloc(s->prog_cnt, s->prog_skel_sz);
if (!s->progs)
goto err;
s->progs[0].name = "syscall_enter_execve";
s->progs[0].prog = &obj->progs.syscall_enter_execve;
s->progs[0].link = &obj->links.syscall_enter_execve;
s->progs[1].name = "syscall_exit_execve";
s->progs[1].prog = &obj->progs.syscall_exit_execve;
s->progs[1].link = &obj->links.syscall_exit_execve;
s->data_sz = 2320;
s->data = (void *)"\
\x7f\x45\x4c\x46\x02\x01\x01\0\0\0\0\0\0\0\0\0\x01\0\xf7\0\x01\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\xd0\x05\0\0\0\0\0\0\0\0\0\0\x40\0\0\0\0\0\x40\0\x0d\0\
\x01\0\x85\0\0\0\x0e\0\0\0\xbf\x06\0\0\0\0\0\0\x63\x6a\xfc\xff\0\0\0\0\x85\0\0\
\0\x0f\0\0\0\xbf\x07\0\0\0\0\0\0\xb7\x01\0\0\0\0\0\0\x7b\x1a\xe8\xff\0\0\0\0\
\x7b\x1a\xe0\xff\0\0\0\0\x7b\x1a\xd8\xff\0\0\0\0\x7b\x1a\xd0\xff\0\0\0\0\xbf\
\xa2\0\0\0\0\0\0\x07\x02\0\0\xfc\xff\xff\xff\xbf\xa3\0\0\0\0\0\0\x07\x03\0\0\
\xd0\xff\xff\xff\x18\x01\0\0\0\0\0\0\0\0\0\0\0\0\0\0\xb7\x04\0\0\x01\0\0\0\x85\
\0\0\0\x02\0\0\0\x67\0\0\0\x20\0\0\0\x77\0\0\0\x20\0\0\0\x55\0\x19\0\0\0\0\0\
\xbf\xa2\0\0\0\0\0\0\x07\x02\0\0\xfc\xff\xff\xff\x18\x01\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\x85\0\0\0\x01\0\0\0\xbf\x08\0\0\0\0\0\0\x15\x08\x12\0\0\0\0\0\x77\x06\0\
\0\x20\0\0\0\x61\xa1\xfc\xff\0\0\0\0\x63\x78\x1c\0\0\0\0\0\x63\x68\x14\0\0\0\0\
\0\x63\x18\x10\0\0\0\0\0\x85\0\0\0\x23\0\0\0\x07\0\0\0\x18\x05\0\0\xbf\xa1\0\0\
\0\0\0\0\x07\x01\0\0\xf0\xff\xff\xff\xb7\x02\0\0\x08\0\0\0\xbf\x03\0\0\0\0\0\0\
\x85\0\0\0\x04\0\0\0\x07\x08\0\0\x18\0\0\0\x79\xa3\xf0\xff\0\0\0\0\x07\x03\0\0\
\x0c\x05\0\0\xbf\x81\0\0\0\0\0\0\xb7\x02\0\0\x04\0\0\0\x85\0\0\0\x04\0\0\0\xb7\
\0\0\0\0\0\0\0\x95\0\0\0\0\0\0\0\xbf\x16\0\0\0\0\0\0\x85\0\0\0\x0e\0\0\0\x63\
\x0a\xfc\xff\0\0\0\0\xbf\xa2\0\0\0\0\0\0\x07\x02\0\0\xfc\xff\xff\xff\x18\x01\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\x85\0\0\0\x01\0\0\0\xbf\x07\0\0\0\0\0\0\x15\x07\x13\
\0\0\0\0\0\x79\x61\x10\0\0\0\0\0\xb7\x02\0\0\0\0\0\0\x6d\x12\x0b\0\0\0\0\0\xbf\
\x71\0\0\0\0\0\0\xb7\x02\0\0\x10\0\0\0\x85\0\0\0\x10\0\0\0\xbf\x61\0\0\0\0\0\0\
\x18\x02\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x18\x03\0\0\xff\xff\xff\xff\0\0\0\0\0\0\0\
\0\xbf\x74\0\0\0\0\0\0\xb7\x05\0\0\x20\0\0\0\x85\0\0\0\x19\0\0\0\xbf\xa2\0\0\0\
\0\0\0\x07\x02\0\0\xfc\xff\xff\xff\x18\x01\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x85\0\0\
\0\x03\0\0\0\xb7\0\0\0\0\0\0\0\x95\0\0\0\0\0\0\0\x01\0\0\0\x04\0\0\0\x20\0\0\0\
\0\x04\0\0\0\0\0\0\x04\0\0\0\x04\0\0\0\x04\0\0\0\x80\0\0\0\0\0\0\0\x47\x50\x4c\
\0\x06\x07\x05\0\x10\0\0\0\0\0\0\0\x01\x7a\x52\0\x08\x7c\x0b\x01\x0c\0\0\0\x18\
\0\0\0\x18\0\0\0\0\0\0\0\0\0\0\0\x80\x01\0\0\0\0\0\0\0\0\0\0\x1c\0\0\0\x34\0\0\
\0\0\0\0\0\0\0\0\0\xf8\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\xba\0\0\0\x04\0\xf1\xff\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\xe9\0\0\0\0\0\x03\0\x70\x01\0\0\0\0\0\0\0\0\0\0\0\0\0\0\xe2\0\0\0\0\0\x05\0\
\xc0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\xdb\0\0\0\0\0\x05\0\xe8\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\x03\0\x03\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x03\0\
\x05\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\xa3\0\0\0\x11\0\x08\0\0\0\0\0\0\0\0\0\
\x04\0\0\0\0\0\0\0\x20\0\0\0\x11\0\x09\0\0\0\0\0\0\0\0\0\x04\0\0\0\0\0\0\0\x07\
\0\0\0\x11\0\x07\0\x14\0\0\0\0\0\0\0\x14\0\0\0\0\0\0\0\x18\0\0\0\x11\0\x07\0\0\
\0\0\0\0\0\0\0\x14\0\0\0\0\0\0\0\x8e\0\0\0\x12\0\x03\0\0\0\0\0\0\0\0\0\x80\x01\
\0\0\0\0\0\0\x51\0\0\0\x12\0\x05\0\0\0\0\0\0\0\0\0\xf8\0\0\0\0\0\0\0\x70\0\0\0\
\0\0\0\0\x01\0\0\0\x0a\0\0\0\xb8\0\0\0\0\0\0\0\x01\0\0\0\x0a\0\0\0\x28\0\0\0\0\
\0\0\0\x01\0\0\0\x0a\0\0\0\x88\0\0\0\0\0\0\0\x01\0\0\0\x09\0\0\0\xd0\0\0\0\0\0\
\0\0\x01\0\0\0\x0a\0\0\0\x1c\0\0\0\0\0\0\0\x01\0\0\0\x05\0\0\0\x38\0\0\0\0\0\0\
\0\x01\0\0\0\x06\0\0\0\0\x2e\x74\x65\x78\x74\0\x70\x65\x72\x66\x5f\x65\x76\x65\
\x6e\x74\x73\0\x6d\x61\x70\x73\0\x72\x65\x63\x6f\x72\x64\x73\0\x5f\x76\x65\x72\
\x73\x69\x6f\x6e\0\x2e\x72\x65\x6c\x74\x72\x61\x63\x65\x70\x6f\x69\x6e\x74\x2f\
\x73\x79\x73\x63\x61\x6c\x6c\x73\x2f\x73\x79\x73\x5f\x65\x78\x69\x74\x5f\x65\
\x78\x65\x63\x76\x65\0\x73\x79\x73\x63\x61\x6c\x6c\x5f\x65\x78\x69\x74\x5f\x65\
\x78\x65\x63\x76\x65\0\x2e\x72\x65\x6c\x74\x72\x61\x63\x65\x70\x6f\x69\x6e\x74\
\x2f\x73\x79\x73\x63\x61\x6c\x6c\x73\x2f\x73\x79\x73\x5f\x65\x6e\x74\x65\x72\
\x5f\x65\x78\x65\x63\x76\x65\0\x73\x79\x73\x63\x61\x6c\x6c\x5f\x65\x6e\x74\x65\
\x72\x5f\x65\x78\x65\x63\x76\x65\0\x5f\x6c\x69\x63\x65\x6e\x73\x65\0\x2e\x72\
\x65\x6c\x2e\x65\x68\x5f\x66\x72\x61\x6d\x65\0\x65\x78\x65\x63\x73\x6e\x6f\x6f\
\x70\x5f\x6b\x65\x72\x6e\x2e\x63\0\x2e\x73\x74\x72\x74\x61\x62\0\x2e\x73\x79\
\x6d\x74\x61\x62\0\x4c\x42\x42\x31\x5f\x34\0\x4c\x42\x42\x31\x5f\x33\0\x4c\x42\
\x42\x30\x5f\x33\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\xcb\0\0\0\
\x03\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\xe0\x04\0\0\0\0\0\0\xf0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\x01\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\x01\0\0\0\x06\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x40\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x69\0\0\0\x01\0\0\0\x06\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\x40\0\0\0\0\0\0\0\x80\x01\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x08\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\x65\0\0\0\x09\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x70\
\x04\0\0\0\0\0\0\x20\0\0\0\0\0\0\0\x0c\0\0\0\x03\0\0\0\x08\0\0\0\0\0\0\0\x10\0\
\0\0\0\0\0\0\x2d\0\0\0\x01\0\0\0\x06\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\xc0\x01\0\0\
\0\0\0\0\xf8\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x08\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\x29\0\0\0\x09\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x90\x04\0\0\0\0\0\0\x30\0\
\0\0\0\0\0\0\x0c\0\0\0\x05\0\0\0\x08\0\0\0\0\0\0\0\x10\0\0\0\0\0\0\0\x13\0\0\0\
\x01\0\0\0\x03\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\xb8\x02\0\0\0\0\0\0\x28\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\xa4\0\0\0\x01\0\0\0\x03\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\xe0\x02\0\0\0\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\x01\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x21\0\0\0\x01\0\0\0\x03\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\xe4\x02\0\0\0\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x04\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\xb0\0\0\0\x01\0\0\0\x02\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\xe8\x02\0\0\0\0\0\0\x50\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x08\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\xac\0\0\0\x09\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\xc0\x04\0\0\0\
\0\0\0\x20\0\0\0\0\0\0\0\x0c\0\0\0\x0a\0\0\0\x08\0\0\0\0\0\0\0\x10\0\0\0\0\0\0\
\0\xd3\0\0\0\x02\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x38\x03\0\0\0\0\0\0\x38\
\x01\0\0\0\0\0\0\x01\0\0\0\x07\0\0\0\x08\0\0\0\0\0\0\0\x18\0\0\0\0\0\0\0";
return 0;
err:
bpf_object__destroy_skeleton(s);
return -1;
}
#endif /* __EXECSNOOP_KERN_SKEL_H__ */

View File

@@ -0,0 +1,83 @@
#include <signal.h>
#include <bpf/libbpf.h>
#include <sys/resource.h>
#include "execsnoop_kern_skel.h"
#include "execsnoop_share.h"
namespace CGPROXY::EXECSNOOP {
#define PERF_BUFFER_PAGES 64
#define TASK_COMM_LEN 16
struct event {
char comm[TASK_COMM_LEN];
pid_t pid;
pid_t tgid;
pid_t ppid;
uid_t uid;
};
function<int(int)> callback = NULL;
promise<void> status;
static void handle_event(void *ctx, int cpu, void *data, __u32 size) {
auto e = static_cast<event*>(data);
if (callback) callback(e->pid);
}
void handle_lost_events(void *ctx, int cpu, __u64 lost_cnt) {
fprintf(stderr, "Lost %llu events on CPU #%d!\n", lost_cnt, cpu);
}
int bump_memlock_rlimit(void) {
struct rlimit rlim_new = { RLIM_INFINITY, RLIM_INFINITY };
return setrlimit(RLIMIT_MEMLOCK, &rlim_new);
}
int execsnoop() {
struct perf_buffer_opts pb_opts = {};
struct perf_buffer *pb;
int err;
err = bump_memlock_rlimit();
if (err) {
fprintf(stderr, "failed to increase rlimit: %d\n", err);
return 1;
}
struct execsnoop_kern *obj=execsnoop_kern__open_and_load();
if (!obj) {
fprintf(stderr, "failed to open and/or load BPF object\n");
return 1;
}
err = execsnoop_kern__attach(obj);
if (err) {
fprintf(stderr, "failed to attach BPF programs\n");
return err;
}
pb_opts.sample_cb = handle_event;
pb_opts.lost_cb = handle_lost_events;
pb = perf_buffer__new(bpf_map__fd(obj->maps.perf_events), PERF_BUFFER_PAGES, &pb_opts);
err = libbpf_get_error(pb);
if (err) {
printf("failed to setup perf_buffer: %d\n", err);
return 1;
}
// notify
status.set_value();
while ((err = perf_buffer__poll(pb, -1)) >= 0) {}
kill(0, SIGINT);
return err;
}
void startThread(function<int(int)> c, promise<void> _status) {
status = move(_status);
callback = c;
execsnoop();
}
}

View File

@@ -0,0 +1,20 @@
#ifndef EXECSNOOP_SHARE_HPP
#define EXECSNOOP_SHARE_HPP 1
#include <functional>
#include <future>
#include <string>
using namespace std;
namespace CGPROXY::EXECSNOOP {
extern "C" void startThread(function<int(int)> c, promise<void> _status);
#ifdef BUIlD_EXECSNOOP_DL
// only for dlsym()
using startThread_t=decltype(startThread);
startThread_t *_startThread;
#endif
} // namespace CGPROXY::EXECSNOOP
#endif

View File

@@ -0,0 +1,40 @@
#include <signal.h>
#include <bpf/libbpf.h>
#include "bpf_load.h"
#define TASK_COMM_LEN 16
struct event {
char comm[TASK_COMM_LEN];
pid_t pid;
pid_t tgid;
pid_t ppid;
uid_t uid;
};
static void print_bpf_output(void *ctx, int cpu, void *data, __u32 size) {
struct event *e=data;
printf("comm: %s, pid: %d, tgid: %d, ppid: %d, uid: %d\n",e->comm,e->pid,e->tgid,e->ppid,e->uid);
}
int main(int argc, char **argv) {
struct perf_buffer_opts pb_opts = {};
struct perf_buffer *pb;
int ret;
if (load_bpf_file("execsnoop_kern.o")!=0) {
printf("%s", bpf_log_buf);
return 1;
}
pb_opts.sample_cb = print_bpf_output;
pb = perf_buffer__new(map_fd[1], 8, &pb_opts);
ret = libbpf_get_error(pb);
if (ret) {
printf("failed to setup perf_buffer: %d\n", ret);
return 1;
}
while ((ret = perf_buffer__poll(pb, -1)) >= 0) {}
kill(0, SIGINT);
return ret;
}

View File

@@ -0,0 +1,69 @@
#include <signal.h>
#include <bpf/libbpf.h>
#include <sys/resource.h>
#include "execsnoop_kern_skel.h"
// #include "bpf_load.h"
#define TASK_COMM_LEN 16
struct event {
char comm[TASK_COMM_LEN];
pid_t pid;
pid_t tgid;
pid_t ppid;
uid_t uid;
};
#define PERF_BUFFER_PAGES 64
static void print_bpf_output(void *ctx, int cpu, void *data, __u32 size) {
struct event *e=data;
printf("comm: %s, pid: %d, tgid: %d, ppid: %d, uid: %d\n",e->comm,e->pid,e->tgid,e->ppid,e->uid);
}
int bump_memlock_rlimit(void)
{
struct rlimit rlim_new = {
.rlim_cur = RLIM_INFINITY,
.rlim_max = RLIM_INFINITY,
};
return setrlimit(RLIMIT_MEMLOCK, &rlim_new);
}
int main(int argc, char **argv) {
struct perf_buffer_opts pb_opts = {};
struct perf_buffer *pb;
int err;
err = bump_memlock_rlimit();
if (err) {
fprintf(stderr, "failed to increase rlimit: %d\n", err);
return 1;
}
struct execsnoop_kern *obj=execsnoop_kern__open_and_load();
if (!obj) {
fprintf(stderr, "failed to open and/or load BPF object\n");
return 1;
}
err = execsnoop_kern__attach(obj);
if (err) {
fprintf(stderr, "failed to attach BPF programs\n");
return err;
}
pb_opts.sample_cb = print_bpf_output;
pb = perf_buffer__new(bpf_map__fd(obj->maps.perf_events), PERF_BUFFER_PAGES, &pb_opts);
err = libbpf_get_error(pb);
if (err) {
printf("failed to setup perf_buffer: %d\n", err);
return 1;
}
while ((err = perf_buffer__poll(pb, -1)) >= 0) {}
kill(0, SIGINT);
return err;
}

119
execsnoop-kernel/readme.md Normal file
View File

@@ -0,0 +1,119 @@
## Prons and cons
- use stable execve tracepoint, so build once and should work everywhere
- need to build in kernel tree
## Build in kernel tree
- ready and config kernel tree
```bash
# kernel config
gunzip -c /proc/config.gz > .config
make oldconfig && make prepare
# install headers to ./usr/include
make headers_install -j8
# build bpf
make M=samples/bpf -j8
```
- put or link `execsnoop_kern.c` and `execsnoop_user.c` to *samples/bpf/*
- edit *samples/bpf/makefile*
```makefile
# in samples/bpf/makefile
tprogs-y += execsnoop
execsnoop-objs := bpf_load.o execsnoop_user.o $(TRACE_HELPERS)
always-y += execsnoop_kern.o
```
## Run
```bash
cd samples/bpf
sudo bash -c "ulimit -l unlimited && ./execsnoop"
```
## Detail build command
using `make V=1 M=samples/bpf | tee -a log.txt` to get and filter following command
- build `execsnoop_kern.o`
note `-g` is needed if with BPF CO-RE
```bash
clang -nostdinc \
-isystem /usr/lib/gcc/x86_64-pc-linux-gnu/10.1.0/include \
-I./arch/x86/include \
-I./arch/x86/include/generated \
-I./include \
-I./arch/x86/include/uapi \
-I./arch/x86/include/generated/uapi \
-I./include/uapi \
-I./include/generated/uapi \
-include ./include/linux/kconfig.h \
-I./samples/bpf \
-I./tools/testing/selftests/bpf/ \
-I./tools/lib/ \
-include asm_goto_workaround.h \
-D__KERNEL__ -D__BPF_TRACING__ -Wno-unused-value -Wno-pointer-sign \
-D__TARGET_ARCH_x86 -Wno-compare-distinct-pointer-types \
-Wno-gnu-variable-sized-type-not-at-end \
-Wno-address-of-packed-member -Wno-tautological-compare \
-Wno-unknown-warning-option \
-fno-stack-protector \
-O2 -emit-llvm -c samples/bpf/execsnoop_kern.c \
-o - | llc -march=bpf -filetype=obj -o samples/bpf/execsnoop_kern.o
```
- build `execsnoop_user.o`
```bash
gcc -Wall -O2 -Wmissing-prototypes -Wstrict-prototypes \
-I./usr/include \
-I./tools/testing/selftests/bpf/ \
-I./tools/lib/ \
-I./tools/include \
-I./tools/perf \
-DHAVE_ATTR_TEST=0 \
-c -o samples/bpf/execsnoop_user.o samples/bpf/execsnoop_user.c
```
- build `execsnoop`
```bash
gcc -Wall -O2 -Wmissing-prototypes -Wstrict-prototypes \
-I./usr/include \
-I./tools/testing/selftests/bpf/ \
-I./tools/lib/ \
-I./tools/include \
-I./tools/perf \
-DHAVE_ATTR_TEST=0 \
-o samples/bpf/execsnoop \
samples/bpf/bpf_load.o samples/bpf/execsnoop_user.o \
tools/testing/selftests/bpf/trace_helpers.o tools/lib/bpf/libbpf.a \
-lelf -lz
```
## With bpftool
- gen
```
bpftool gen skeleton execsnoop_kern.o > execsnoop_kern_skel.h
```
- build
```
gcc -Wall -O2 execsnoop_user_1.c -o execsnoop -lbpf
```
## Some resources
- [A thorough introduction to eBPF](https://lwn.net/Articles/740157/)
- [Write eBPF program in pure C](http://terenceli.github.io/%E6%8A%80%E6%9C%AF/2020/01/18/ebpf-in-c)

87
execsnoop-libbpf/Makefile Normal file
View File

@@ -0,0 +1,87 @@
# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
OUTPUT := $(abspath build)
CLANG ?= clang
CFLAGS ?= -g -O2 -Wall
LLVM_STRIP ?= llvm-strip
BPFTOOL ?= bpftool
LIBBPF_DIR := $(abspath libbpf/src)
LIBBPF_OBJ := $(OUTPUT)/usr/lib64/libbpf.a
LIBBPF_H := $(OUTPUT)/usr/include
INCLUDES := -I$(OUTPUT) -I$(LIBBPF_H)
ARCH := $(shell uname -m | sed 's/x86_64/x86/')
APPS = execsnoop
LIBSO = libexecsnoop.so
.PHONY: all
all: $(APPS) $(LIBSO)
ifeq ($(V),1)
Q =
msg =
else
Q = @
msg = @printf ' %-8s %s%s\n' "$(1)" "$(notdir $(2))" "$(if $(3), $(3))";
MAKEFLAGS += --no-print-directory
endif
COMMON_OBJ = \
$(OUTPUT)/trace_helpers.o \
$(OUTPUT)/syscall_helpers.o \
$(OUTPUT)/errno_helpers.o \
.PHONY: install
install: $(LIBSO)
install -D $(LIBSO) -t $(DESTDIR)/usr/lib/cgproxy/
.PHONY: clean
clean:
$(call msg,CLEAN)
$(Q)rm -rf $(OUTPUT) $(APPS) $(LIBSO)
$(OUTPUT) $(OUTPUT)/libbpf:
$(call msg,MKDIR,$@)
$(Q)mkdir -p $@
$(APPS): %: $(OUTPUT)/%.o $(LIBBPF_OBJ) $(COMMON_OBJ) | $(OUTPUT)
$(call msg,BINARY,$@)
$(Q)$(CC) $(CFLAGS) $^ -lelf -lz -o $@
$(patsubst %,$(OUTPUT)/%.o,$(APPS)): %.o: %.skel.h
$(OUTPUT)/%.o: %.c $(wildcard %.h) | $(OUTPUT)
$(call msg,CC,$@)
$(Q)$(CC) $(CFLAGS) $(INCLUDES) -fPIC -c $(filter %.c,$^) -o $@
$(OUTPUT)/%.skel.h: $(OUTPUT)/%.bpf.o | $(OUTPUT)
$(call msg,GEN-SKEL,$@)
$(Q)$(BPFTOOL) gen skeleton $< > $@
$(OUTPUT)/%.bpf.o: %.bpf.c $(LIBBPF_OBJ) $(wildcard %.h) vmlinux.h | $(OUTPUT)
$(call msg,BPF,$@)
$(Q)$(CLANG) -g -O2 -target bpf -D__TARGET_ARCH_$(ARCH) \
$(INCLUDES) -c $(filter %.c,$^) -o $@ && \
$(LLVM_STRIP) -g $@
vmlinux.h:
$(Q)$(BPFTOOL) btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
$(LIBBPF_OBJ):
$(Q)cd $(LIBBPF_DIR) && make DESTDIR=${OUTPUT} prefix=$(OUTPUT) CPPFLAGS="-fPIC" install install_headers
# patch: | $(LIBBPF_DIR)/Makefile
# @echo "patching libbpf-fPIC.patch"
# $(Q)cd libbpf/src && patch -p1 --forward -i ../../libbpf-fPIC.patch || true
# build libexecsnoop.so
$(LIBSO): execsnoop_share.cpp $(OUTPUT)/execsnoop.skel.h $(LIBBPF_OBJ) $(COMMON_OBJ) | $(OUTPUT)
$(call msg,LIB,$@)
$(Q)$(CXX) -std=c++17 $(CFLAGS) $(INCLUDES) -fPIC $< $(COMMON_OBJ) -shared -lelf -lz -Wl,--no-whole-archive,--no-undefined $(LIBBPF_OBJ) -o $@
# delete failed targets
.DELETE_ON_ERROR:
# keep intermediate (.skel.h, .bpf.o, etc) targets
.SECONDARY:

View File

@@ -0,0 +1,232 @@
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
// Copyright (c) 2020 Anton Protopopov
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#define warn(...) fprintf(stderr, __VA_ARGS__)
#ifdef __x86_64__
static int errno_by_name_x86_64(const char *errno_name)
{
#define strcase(X, N) if (!strcmp(errno_name, (X))) return N
strcase("EPERM", 1);
strcase("ENOENT", 2);
strcase("ESRCH", 3);
strcase("EINTR", 4);
strcase("EIO", 5);
strcase("ENXIO", 6);
strcase("E2BIG", 7);
strcase("ENOEXEC", 8);
strcase("EBADF", 9);
strcase("ECHILD", 10);
strcase("EAGAIN", 11);
strcase("EWOULDBLOCK", 11);
strcase("ENOMEM", 12);
strcase("EACCES", 13);
strcase("EFAULT", 14);
strcase("ENOTBLK", 15);
strcase("EBUSY", 16);
strcase("EEXIST", 17);
strcase("EXDEV", 18);
strcase("ENODEV", 19);
strcase("ENOTDIR", 20);
strcase("EISDIR", 21);
strcase("EINVAL", 22);
strcase("ENFILE", 23);
strcase("EMFILE", 24);
strcase("ENOTTY", 25);
strcase("ETXTBSY", 26);
strcase("EFBIG", 27);
strcase("ENOSPC", 28);
strcase("ESPIPE", 29);
strcase("EROFS", 30);
strcase("EMLINK", 31);
strcase("EPIPE", 32);
strcase("EDOM", 33);
strcase("ERANGE", 34);
strcase("EDEADLK", 35);
strcase("EDEADLOCK", 35);
strcase("ENAMETOOLONG", 36);
strcase("ENOLCK", 37);
strcase("ENOSYS", 38);
strcase("ENOTEMPTY", 39);
strcase("ELOOP", 40);
strcase("ENOMSG", 42);
strcase("EIDRM", 43);
strcase("ECHRNG", 44);
strcase("EL2NSYNC", 45);
strcase("EL3HLT", 46);
strcase("EL3RST", 47);
strcase("ELNRNG", 48);
strcase("EUNATCH", 49);
strcase("ENOCSI", 50);
strcase("EL2HLT", 51);
strcase("EBADE", 52);
strcase("EBADR", 53);
strcase("EXFULL", 54);
strcase("ENOANO", 55);
strcase("EBADRQC", 56);
strcase("EBADSLT", 57);
strcase("EBFONT", 59);
strcase("ENOSTR", 60);
strcase("ENODATA", 61);
strcase("ETIME", 62);
strcase("ENOSR", 63);
strcase("ENONET", 64);
strcase("ENOPKG", 65);
strcase("EREMOTE", 66);
strcase("ENOLINK", 67);
strcase("EADV", 68);
strcase("ESRMNT", 69);
strcase("ECOMM", 70);
strcase("EPROTO", 71);
strcase("EMULTIHOP", 72);
strcase("EDOTDOT", 73);
strcase("EBADMSG", 74);
strcase("EOVERFLOW", 75);
strcase("ENOTUNIQ", 76);
strcase("EBADFD", 77);
strcase("EREMCHG", 78);
strcase("ELIBACC", 79);
strcase("ELIBBAD", 80);
strcase("ELIBSCN", 81);
strcase("ELIBMAX", 82);
strcase("ELIBEXEC", 83);
strcase("EILSEQ", 84);
strcase("ERESTART", 85);
strcase("ESTRPIPE", 86);
strcase("EUSERS", 87);
strcase("ENOTSOCK", 88);
strcase("EDESTADDRREQ", 89);
strcase("EMSGSIZE", 90);
strcase("EPROTOTYPE", 91);
strcase("ENOPROTOOPT", 92);
strcase("EPROTONOSUPPORT", 93);
strcase("ESOCKTNOSUPPORT", 94);
strcase("ENOTSUP", 95);
strcase("EOPNOTSUPP", 95);
strcase("EPFNOSUPPORT", 96);
strcase("EAFNOSUPPORT", 97);
strcase("EADDRINUSE", 98);
strcase("EADDRNOTAVAIL", 99);
strcase("ENETDOWN", 100);
strcase("ENETUNREACH", 101);
strcase("ENETRESET", 102);
strcase("ECONNABORTED", 103);
strcase("ECONNRESET", 104);
strcase("ENOBUFS", 105);
strcase("EISCONN", 106);
strcase("ENOTCONN", 107);
strcase("ESHUTDOWN", 108);
strcase("ETOOMANYREFS", 109);
strcase("ETIMEDOUT", 110);
strcase("ECONNREFUSED", 111);
strcase("EHOSTDOWN", 112);
strcase("EHOSTUNREACH", 113);
strcase("EALREADY", 114);
strcase("EINPROGRESS", 115);
strcase("ESTALE", 116);
strcase("EUCLEAN", 117);
strcase("ENOTNAM", 118);
strcase("ENAVAIL", 119);
strcase("EISNAM", 120);
strcase("EREMOTEIO", 121);
strcase("EDQUOT", 122);
strcase("ENOMEDIUM", 123);
strcase("EMEDIUMTYPE", 124);
strcase("ECANCELED", 125);
strcase("ENOKEY", 126);
strcase("EKEYEXPIRED", 127);
strcase("EKEYREVOKED", 128);
strcase("EKEYREJECTED", 129);
strcase("EOWNERDEAD", 130);
strcase("ENOTRECOVERABLE", 131);
strcase("ERFKILL", 132);
strcase("EHWPOISON", 133);
#undef strcase
return -1;
}
#endif
/* Try to find the errno number using the errno(1) program */
static int errno_by_name_dynamic(const char *errno_name)
{
int len = strlen(errno_name);
int err, number = -1;
char buf[128];
char cmd[64];
char *end;
long val;
FILE *f;
/* sanity check to not call popen with random input */
for (int i = 0; i < len; i++) {
if (errno_name[i] < 'A' || errno_name[i] > 'Z') {
warn("errno_name contains invalid char 0x%02x: %s\n",
errno_name[i], errno_name);
return -1;
}
}
snprintf(cmd, sizeof(cmd), "errno %s", errno_name);
f = popen(cmd, "r");
if (!f) {
warn("popen: %s: %s\n", cmd, strerror(errno));
return -1;
}
if (!fgets(buf, sizeof(buf), f)) {
goto close;
} else if (ferror(f)) {
warn("fgets: %s\n", strerror(errno));
goto close;
}
// expecting "<name> <number> <description>"
if (strncmp(errno_name, buf, len) || strlen(buf) < len+2) {
warn("expected '%s': %s\n", errno_name, buf);
goto close;
}
errno = 0;
val = strtol(buf+len+2, &end, 10);
if (errno || end == (buf+len+2) || number < 0 || number > INT_MAX) {
warn("can't parse the second column, expected int: %s\n", buf);
goto close;
}
number = val;
close:
err = pclose(f);
if (err < 0)
warn("pclose: %s\n", strerror(errno));
#ifndef __x86_64__
/* Ignore the error for x86_64 where we have a table compiled in */
else if (err && WEXITSTATUS(err) == 127) {
warn("errno(1) required for errno name/number mapping\n");
} else if (err) {
warn("errno(1) exit status (see wait(2)): 0x%x\n", err);
}
#endif
return number;
}
int errno_by_name(const char *errno_name)
{
#ifdef __x86_64__
int err;
err = errno_by_name_x86_64(errno_name);
if (err >= 0)
return err;
#endif
return errno_by_name_dynamic(errno_name);
}

View File

@@ -0,0 +1,7 @@
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
#ifndef __ERRNO_HELPERS_H
#define __ERRNO_HELPERS_H
int errno_by_name(const char *errno_name);
#endif /* __ERRNO_HELPERS_H */

View File

@@ -0,0 +1,128 @@
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>
#include "execsnoop.h"
const volatile bool ignore_failed = true;
const volatile uid_t targ_uid = INVALID_UID;
const volatile int max_args = DEFAULT_MAXARGS;
static const struct event empty_event = {};
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 10240);
__type(key, pid_t);
__type(value, struct event);
} execs SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
__uint(key_size, sizeof(u32));
__uint(value_size, sizeof(u32));
} events SEC(".maps");
static __always_inline bool valid_uid(uid_t uid) {
return uid != INVALID_UID;
}
SEC("tracepoint/syscalls/sys_enter_execve")
int tracepoint__syscalls__sys_enter_execve(struct trace_event_raw_sys_enter* ctx)
{
u64 id;
pid_t pid, tgid;
unsigned int ret;
struct event *event;
struct task_struct *task;
const char **args = (const char **)(ctx->args[1]);
const char *argp;
uid_t uid = (u32)bpf_get_current_uid_gid();
if (valid_uid(targ_uid) && targ_uid != uid)
return 0;
id = bpf_get_current_pid_tgid();
pid = (pid_t)id;
tgid = id >> 32;
if (bpf_map_update_elem(&execs, &pid, &empty_event, BPF_NOEXIST))
return 0;
event = bpf_map_lookup_elem(&execs, &pid);
if (!event)
return 0;
event->pid = pid;
event->tgid = tgid;
event->uid = uid;
task = (struct task_struct*)bpf_get_current_task();
event->ppid = (pid_t)BPF_CORE_READ(task, real_parent, tgid);
event->args_count = 0;
event->args_size = 0;
ret = bpf_probe_read_str(event->args, ARGSIZE, (const char*)ctx->args[0]);
if (ret <= ARGSIZE) {
event->args_size += ret;
} else {
/* write an empty string */
event->args[0] = '\0';
event->args_size++;
}
event->args_count++;
#pragma unroll
for (int i = 1; i < TOTAL_MAX_ARGS && i < max_args; i++) {
bpf_probe_read(&argp, sizeof(argp), &args[i]);
if (!argp)
return 0;
if (event->args_size > LAST_ARG)
return 0;
ret = bpf_probe_read_str(&event->args[event->args_size], ARGSIZE, argp);
if (ret > ARGSIZE)
return 0;
event->args_count++;
event->args_size += ret;
}
/* try to read one more argument to check if there is one */
bpf_probe_read(&argp, sizeof(argp), &args[max_args]);
if (!argp)
return 0;
/* pointer to max_args+1 isn't null, asume we have more arguments */
event->args_count++;
return 0;
}
SEC("tracepoint/syscalls/sys_exit_execve")
int tracepoint__syscalls__sys_exit_execve(struct trace_event_raw_sys_exit* ctx)
{
u64 id;
pid_t pid;
int ret;
struct event *event;
u32 uid = (u32)bpf_get_current_uid_gid();
if (valid_uid(targ_uid) && targ_uid != uid)
return 0;
id = bpf_get_current_pid_tgid();
pid = (pid_t)id;
event = bpf_map_lookup_elem(&execs, &pid);
if (!event)
return 0;
ret = ctx->ret;
if (ignore_failed && ret < 0)
goto cleanup;
event->retval = ret;
bpf_get_current_comm(&event->comm, sizeof(event->comm));
size_t len = EVENT_SIZE(event);
if (len <= sizeof(*event))
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, event, len);
cleanup:
bpf_map_delete_elem(&execs, &pid);
return 0;
}
char LICENSE[] SEC("license") = "GPL";

View File

@@ -0,0 +1,329 @@
// Based on execsnoop(8) from BCC by Brendan Gregg and others.
//
#include <argp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#include <bpf/libbpf.h>
#include <bpf/bpf.h>
#include "execsnoop.h"
#include "execsnoop.skel.h"
#include "trace_helpers.h"
#define PERF_BUFFER_PAGES 64
#define NSEC_PRECISION (NSEC_PER_SEC / 1000)
#define MAX_ARGS_KEY 259
static struct env {
bool time;
bool timestamp;
bool fails;
uid_t uid;
bool quote;
const char *name;
const char *line;
bool print_uid;
bool verbose;
int max_args;
} env = {
.max_args = DEFAULT_MAXARGS,
.uid = INVALID_UID
};
static struct timespec start_time;
const char *argp_program_version = "execsnoop 0.1";
const char *argp_program_bug_address = "<bpf@vger.kernel.org>";
const char argp_program_doc[] =
"Trace open family syscalls\n"
"\n"
"USAGE: execsnoop [-h] [-T] [-t] [-x] [-u UID] [-q] [-n NAME] [-l LINE] [-U]\n"
" [--max-args MAX_ARGS]\n"
"\n"
"EXAMPLES:\n"
" ./execsnoop # trace all exec() syscalls\n"
" ./execsnoop -x # include failed exec()s\n"
" ./execsnoop -T # include time (HH:MM:SS)\n"
" ./execsnoop -U # include UID\n"
" ./execsnoop -u 1000 # only trace UID 1000\n"
" ./execsnoop -t # include timestamps\n"
" ./execsnoop -q # add \"quotemarks\" around arguments\n"
" ./execsnoop -n main # only print command lines containing \"main\"\n"
" ./execsnoop -l tpkg # only print command where arguments contains \"tpkg\"";
static const struct argp_option opts[] = {
{ "time", 'T', NULL, 0, "include time column on output (HH:MM:SS)"},
{ "timestamp", 't', NULL, 0, "include timestamp on output"},
{ "fails", 'x', NULL, 0, "include failed exec()s"},
{ "uid", 'u', "UID", 0, "trace this UID only"},
{ "quote", 'q', NULL, 0, "Add quotemarks (\") around arguments"},
{ "name", 'n', "NAME", 0, "only print commands matching this name, any arg"},
{ "line", 'l', "LINE", 0, "only print commands where arg contains this line"},
{ "print-uid", 'U', NULL, 0, "print UID column"},
{ "max-args", MAX_ARGS_KEY, "MAX_ARGS", 0,
"maximum number of arguments parsed and displayed, defaults to 20"},
{ "verbose", 'v', NULL, 0, "Verbose debug output" },
{},
};
static error_t parse_arg(int key, char *arg, struct argp_state *state)
{
long int uid, max_args;
switch (key) {
case 'h':
argp_usage(state);
break;
case 'T':
env.time = true;
break;
case 't':
env.timestamp = true;
break;
case 'x':
env.fails = true;
break;
case 'u':
errno = 0;
uid = strtol(arg, NULL, 10);
if (errno || uid < 0 || uid >= INVALID_UID) {
fprintf(stderr, "Invalid UID %s\n", arg);
argp_usage(state);
}
env.uid = uid;
break;
case 'q':
env.quote = true;
break;
case 'n':
env.name = arg;
break;
case 'l':
env.line = arg;
break;
case 'U':
env.print_uid = true;
break;
case 'v':
env.verbose = true;
break;
case MAX_ARGS_KEY:
errno = 0;
max_args = strtol(arg, NULL, 10);
if (errno || max_args < 1 || max_args > TOTAL_MAX_ARGS) {
fprintf(stderr, "Invalid MAX_ARGS %s, should be in [1, %d] range\n",
arg, TOTAL_MAX_ARGS);
argp_usage(state);
}
env.max_args = max_args;
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
int libbpf_print_fn(enum libbpf_print_level level,
const char *format, va_list args)
{
if (level == LIBBPF_DEBUG && !env.verbose)
return 0;
return vfprintf(stderr, format, args);
}
static void time_since_start()
{
long nsec, sec;
static struct timespec cur_time;
double time_diff;
clock_gettime(CLOCK_MONOTONIC, &cur_time);
nsec = cur_time.tv_nsec - start_time.tv_nsec;
sec = cur_time.tv_sec - start_time.tv_sec;
if (nsec < 0) {
nsec += NSEC_PER_SEC;
sec--;
}
time_diff = sec + (double)nsec / NSEC_PER_SEC;
printf("%-8.3f", time_diff);
}
static void inline quoted_symbol(char c) {
switch(c) {
case '"':
putchar('\\');
putchar('"');
break;
case '\t':
putchar('\\');
putchar('t');
break;
case '\n':
putchar('\\');
putchar('n');
break;
default:
putchar(c);
break;
}
}
static void print_args(const struct event *e, bool quote)
{
int args_counter = 0;
if (env.quote)
putchar('"');
for (int i = 0; i < e->args_size && args_counter < e->args_count; i++) {
char c = e->args[i];
if (env.quote) {
if (c == '\0') {
args_counter++;
putchar('"');
putchar(' ');
if (args_counter < e->args_count) {
putchar('"');
}
} else {
quoted_symbol(c);
}
} else {
if (c == '\0') {
args_counter++;
putchar(' ');
} else {
putchar(c);
}
}
}
if (e->args_count == env.max_args + 1) {
fputs(" ...", stdout);
}
}
void handle_event(void *ctx, int cpu, void *data, __u32 data_sz)
{
const struct event *e = data;
time_t t;
struct tm *tm;
char ts[32];
/* TODO: use pcre lib */
if (env.name && strstr(e->comm, env.name) == NULL)
return;
/* TODO: use pcre lib */
if (env.line && strstr(e->comm, env.line) == NULL)
return;
time(&t);
tm = localtime(&t);
strftime(ts, sizeof(ts), "%H:%M:%S", tm);
if (env.time) {
printf("%-8s ", ts);
}
if (env.timestamp) {
time_since_start();
}
if (env.print_uid)
printf("%-6d", e->uid);
printf("%-16s %-6d %-6d %3d ", e->comm, e->pid, e->ppid, e->retval);
print_args(e, env.quote);
putchar('\n');
}
void handle_lost_events(void *ctx, int cpu, __u64 lost_cnt)
{
fprintf(stderr, "Lost %llu events on CPU #%d!\n", lost_cnt, cpu);
}
int main(int argc, char **argv)
{
static const struct argp argp = {
.options = opts,
.parser = parse_arg,
.doc = argp_program_doc,
};
struct perf_buffer_opts pb_opts;
struct perf_buffer *pb = NULL;
struct execsnoop_bpf *obj;
int err;
err = argp_parse(&argp, argc, argv, 0, NULL, NULL);
if (err)
return err;
libbpf_set_print(libbpf_print_fn);
err = bump_memlock_rlimit();
if (err) {
fprintf(stderr, "failed to increase rlimit: %d\n", err);
return 1;
}
obj = execsnoop_bpf__open();
if (!obj) {
fprintf(stderr, "failed to open and/or load BPF object\n");
return 1;
}
/* initialize global data (filtering options) */
obj->rodata->ignore_failed = !env.fails;
obj->rodata->targ_uid = env.uid;
obj->rodata->max_args = env.max_args;
err = execsnoop_bpf__load(obj);
if (err) {
fprintf(stderr, "failed to load BPF object: %d\n", err);
goto cleanup;
}
clock_gettime(CLOCK_MONOTONIC, &start_time);
err = execsnoop_bpf__attach(obj);
if (err) {
fprintf(stderr, "failed to attach BPF programs\n");
goto cleanup;
}
/* print headers */
if (env.time) {
printf("%-9s", "TIME");
}
if (env.timestamp) {
printf("%-8s ", "TIME(s)");
}
if (env.print_uid) {
printf("%-6s ", "UID");
}
printf("%-16s %-6s %-6s %3s %s\n", "PCOMM", "PID", "PPID", "RET", "ARGS");
/* setup event callbacks */
pb_opts.sample_cb = handle_event;
pb_opts.lost_cb = handle_lost_events;
pb = perf_buffer__new(bpf_map__fd(obj->maps.events), PERF_BUFFER_PAGES, &pb_opts);
err = libbpf_get_error(pb);
if (err) {
pb = NULL;
fprintf(stderr, "failed to open perf buffer: %d\n", err);
goto cleanup;
}
/* main: poll */
while ((err = perf_buffer__poll(pb, 100)) >= 0)
;
printf("Error polling perf buffer: %d\n", err);
cleanup:
perf_buffer__free(pb);
execsnoop_bpf__destroy(obj);
return err != 0;
}

View File

@@ -0,0 +1,27 @@
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
#ifndef __EXECSNOOP_H
#define __EXECSNOOP_H
#define ARGSIZE 128
#define TASK_COMM_LEN 16
#define TOTAL_MAX_ARGS 60
#define DEFAULT_MAXARGS 20
#define FULL_MAX_ARGS_ARR (TOTAL_MAX_ARGS * ARGSIZE)
#define INVALID_UID ((uid_t)-1)
#define BASE_EVENT_SIZE (size_t)(&((struct event*)0)->args)
#define EVENT_SIZE(e) (BASE_EVENT_SIZE + e->args_size)
#define LAST_ARG (FULL_MAX_ARGS_ARR - ARGSIZE)
struct event {
char comm[TASK_COMM_LEN];
pid_t pid;
pid_t tgid;
pid_t ppid;
uid_t uid;
int retval;
int args_count;
unsigned int args_size;
char args[FULL_MAX_ARGS_ARR];
};
#endif /* __EXECSNOOP_H */

View File

@@ -0,0 +1,101 @@
// Based on execsnoop(8) from BCC by Brendan Gregg and others.
//
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
extern "C" {
#define typeof(x) decltype(x)
#include "execsnoop.h"
#include "execsnoop.skel.h"
#include "trace_helpers.h"
}
#include "execsnoop_share.h"
#define PERF_BUFFER_PAGES 64
#define NSEC_PRECISION (NSEC_PER_SEC / 1000)
#define MAX_ARGS_KEY 259
namespace CGPROXY::EXECSNOOP {
function<int(int)> callback = NULL;
promise<void> status;
void handle_event(void *ctx, int cpu, void *data, __u32 data_sz) {
auto e = static_cast<event*>(data);
int pid = e->pid;
if (callback) callback(pid);
}
void handle_lost_events(void *ctx, int cpu, __u64 lost_cnt) {
fprintf(stderr, "Lost %llu events on CPU #%d!\n", lost_cnt, cpu);
}
int execsnoop() {
struct perf_buffer_opts pb_opts;
struct perf_buffer *pb = NULL;
struct execsnoop_bpf *obj;
int err;
// libbpf_set_print(libbpf_print_fn);
err = bump_memlock_rlimit();
if (err) {
fprintf(stderr, "failed to increase rlimit: %d\n", err);
return 1;
}
obj = execsnoop_bpf__open();
if (!obj) {
fprintf(stderr, "failed to open and/or load BPF object\n");
return 1;
}
/* initialize global data (filtering options) */
obj->rodata->ignore_failed = true;
err = execsnoop_bpf__load(obj);
if (err) {
fprintf(stderr, "failed to load BPF object: %d\n", err);
goto cleanup;
}
err = execsnoop_bpf__attach(obj);
if (err) {
fprintf(stderr, "failed to attach BPF programs\n");
goto cleanup;
}
/* setup event callbacks */
pb_opts.sample_cb = handle_event;
pb_opts.lost_cb = handle_lost_events;
pb = perf_buffer__new(bpf_map__fd(obj->maps.events), PERF_BUFFER_PAGES, &pb_opts);
err = libbpf_get_error(pb);
if (err) {
pb = NULL;
fprintf(stderr, "failed to open perf buffer: %d\n", err);
goto cleanup;
}
status.set_value();
/* main: poll */
while ((err = perf_buffer__poll(pb, 100)) >= 0);
printf("Error polling perf buffer: %d\n", err);
cleanup:
perf_buffer__free(pb);
execsnoop_bpf__destroy(obj);
return err != 0;
}
void startThread(function<int(int)> c, promise<void> _status) {
status = move(_status);
callback = c;
execsnoop();
}
}

View File

@@ -0,0 +1,16 @@
#ifndef EXECSNOOP_SHARE_HPP
#define EXECSNOOP_SHARE_HPP 1
#include <functional>
#include <future>
#include <string>
using namespace std;
namespace CGPROXY::EXECSNOOP {
extern "C" void startThread(function<int(int)> c, promise<void> _status);
// typedef void startThread_t(function<int(int)>, promise<void>);
using startThread_t=decltype(startThread);
startThread_t *_startThread; // only for dlsym()
} // namespace CGPROXY::EXECSNOOP
#endif

View File

@@ -0,0 +1,21 @@
diff --git src/Makefile src/Makefile
index d0308c3..fcc3b6f 100644
--- src/Makefile
+++ src/Makefile
@@ -15,6 +15,7 @@ ifneq ($(FEATURE_REALLOCARRAY),)
ALL_CFLAGS += -DCOMPAT_NEED_REALLOCARRAY
endif
+STATIC_CFLAGS += -fPIC
SHARED_CFLAGS += -fPIC -fvisibility=hidden -DSHARED
CFLAGS ?= -g -O2 -Werror -Wall
@@ -99,7 +100,7 @@ $(SHARED_OBJDIR):
mkdir -p $(SHARED_OBJDIR)
$(STATIC_OBJDIR)/%.o: %.c | $(STATIC_OBJDIR)
- $(CC) $(ALL_CFLAGS) $(CPPFLAGS) -c $< -o $@
+ $(CC) $(ALL_CFLAGS) $(STATIC_CFLAGS) $(CPPFLAGS) -c $< -o $@
$(SHARED_OBJDIR)/%.o: %.c | $(SHARED_OBJDIR)
$(CC) $(ALL_CFLAGS) $(SHARED_CFLAGS) $(CPPFLAGS) -c $< -o $@

View File

@@ -0,0 +1,21 @@
## Resource
- [libbpf](https://github.com/libbpf/libbpf)
- [libbpf-tools](https://github.com/iovisor/bcc/tree/master/libbpf-tools) examples use libbpf
- [bpftool](https://www.archlinux.org/packages/community/x86_64/bpf/) to generate skeleton and dump btf
## Prons
- BPF CO-RE (Compile Once Run Everywhere)
- small memory usage
## Cons
- `vmlinux.h`does not contain `#define` etc. And often causes confilct with other headers to cause redifinition error
- comment in code for different types is gone
- need kernel built with `CONFIG_DEBUG_INFO_BTF=y`, while ubuntu 20.04 still not
## Build
- see *makefile*

View File

@@ -0,0 +1,526 @@
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
// Copyright (c) 2020 Anton Protopopov
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <stdio.h>
#include <errno.h>
static const char **syscall_names;
static size_t syscall_names_size;
#define warn(...) fprintf(stderr, __VA_ARGS__)
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
static const char *parse_syscall(const char *buf, int *number)
{
char *end;
long x;
errno = 0;
x = strtol(buf, &end, 10);
if (errno) {
warn("strtol(%s): %s\n", buf, strerror(errno));
return NULL;
} else if (end == buf) {
warn("strtol(%s): no digits found\n", buf);
return NULL;
} else if (x < 0 || x > INT_MAX) {
warn("strtol(%s): bad syscall number: %ld\n", buf, x);
return NULL;
}
if (*end != '\t') {
warn("bad input: %s (expected <num>\t<name>)\n", buf);
return NULL;
}
*number = x;
return ++end;
}
void init_syscall_names(void)
{
size_t old_size, size = 1024;
const char *name;
char buf[64];
int number;
int err;
FILE *f;
f = popen("ausyscall --dump 2>/dev/null", "r");
if (!f) {
warn("popen: ausyscall --dump: %s\n", strerror(errno));
return;
}
syscall_names = calloc(size, sizeof(char *));
if (!syscall_names) {
warn("calloc: %s\n", strerror(errno));
goto close;
}
/* skip the header */
fgets(buf, sizeof(buf), f);
while (fgets(buf, sizeof(buf), f)) {
if (buf[strlen(buf) - 1] == '\n')
buf[strlen(buf) - 1] = '\0';
name = parse_syscall(buf, &number);
if (!name || !name[0])
goto close;
/* In a rare case when syscall number is > than initial 1024 */
if (number >= size) {
old_size = size;
size = 1024 * (1 + number / 1024);
syscall_names = realloc(syscall_names,
size * sizeof(char *));
if (!syscall_names) {
warn("realloc: %s\n", strerror(errno));
goto close;
}
memset(syscall_names+old_size, 0,
(size - old_size) * sizeof(char *));
}
if (syscall_names[number]) {
warn("duplicate number: %d (stored: %s)",
number, syscall_names[number]);
goto close;
}
syscall_names[number] = strdup(name);
if (!syscall_names[number]) {
warn("strdup: %s\n", strerror(errno));
goto close;
}
syscall_names_size = MAX(number+1, syscall_names_size);
}
if (ferror(f))
warn("fgets: %s\n", strerror(errno));
close:
err = pclose(f);
if (err < 0)
warn("pclose: %s\n", strerror(errno));
#ifndef __x86_64__
/* Ignore the error for x86_64 where we have a table compiled in */
else if (err && WEXITSTATUS(err) == 127) {
warn("ausyscall required for syscalls number/name mapping\n");
} else if (err) {
warn("ausyscall exit status (see wait(2)): 0x%x\n", err);
}
#endif
}
void free_syscall_names(void)
{
for (size_t i = 0; i < syscall_names_size; i++)
free((void *) syscall_names[i]);
free(syscall_names);
}
/*
* Syscall table for Linux x86_64.
*
* Semi-automatically generated from strace/linux/x86_64/syscallent.h and
* linux/syscallent-common.h using the following commands:
*
* awk -F\" '/SEN/{printf("%d %s\n", substr($0,2,3), $(NF-1));}' syscallent.h
* awk '/SEN/ { printf("%d %s\n", $3, $9); }' syscallent-common.h
*
* (The idea is taken from src/python/bcc/syscall.py.)
*/
#ifdef __x86_64__
static const char *syscall_names_x86_64[] = {
[0] = "read",
[1] = "write",
[2] = "open",
[3] = "close",
[4] = "stat",
[5] = "fstat",
[6] = "lstat",
[7] = "poll",
[8] = "lseek",
[9] = "mmap",
[10] = "mprotect",
[11] = "munmap",
[12] = "brk",
[13] = "rt_sigaction",
[14] = "rt_sigprocmask",
[15] = "rt_sigreturn",
[16] = "ioctl",
[17] = "pread64",
[18] = "pwrite64",
[19] = "readv",
[20] = "writev",
[21] = "access",
[22] = "pipe",
[23] = "select",
[24] = "sched_yield",
[25] = "mremap",
[26] = "msync",
[27] = "mincore",
[28] = "madvise",
[29] = "shmget",
[30] = "shmat",
[31] = "shmctl",
[32] = "dup",
[33] = "dup2",
[34] = "pause",
[35] = "nanosleep",
[36] = "getitimer",
[37] = "alarm",
[38] = "setitimer",
[39] = "getpid",
[40] = "sendfile",
[41] = "socket",
[42] = "connect",
[43] = "accept",
[44] = "sendto",
[45] = "recvfrom",
[46] = "sendmsg",
[47] = "recvmsg",
[48] = "shutdown",
[49] = "bind",
[50] = "listen",
[51] = "getsockname",
[52] = "getpeername",
[53] = "socketpair",
[54] = "setsockopt",
[55] = "getsockopt",
[56] = "clone",
[57] = "fork",
[58] = "vfork",
[59] = "execve",
[60] = "exit",
[61] = "wait4",
[62] = "kill",
[63] = "uname",
[64] = "semget",
[65] = "semop",
[66] = "semctl",
[67] = "shmdt",
[68] = "msgget",
[69] = "msgsnd",
[70] = "msgrcv",
[71] = "msgctl",
[72] = "fcntl",
[73] = "flock",
[74] = "fsync",
[75] = "fdatasync",
[76] = "truncate",
[77] = "ftruncate",
[78] = "getdents",
[79] = "getcwd",
[80] = "chdir",
[81] = "fchdir",
[82] = "rename",
[83] = "mkdir",
[84] = "rmdir",
[85] = "creat",
[86] = "link",
[87] = "unlink",
[88] = "symlink",
[89] = "readlink",
[90] = "chmod",
[91] = "fchmod",
[92] = "chown",
[93] = "fchown",
[94] = "lchown",
[95] = "umask",
[96] = "gettimeofday",
[97] = "getrlimit",
[98] = "getrusage",
[99] = "sysinfo",
[100] = "times",
[101] = "ptrace",
[102] = "getuid",
[103] = "syslog",
[104] = "getgid",
[105] = "setuid",
[106] = "setgid",
[107] = "geteuid",
[108] = "getegid",
[109] = "setpgid",
[110] = "getppid",
[111] = "getpgrp",
[112] = "setsid",
[113] = "setreuid",
[114] = "setregid",
[115] = "getgroups",
[116] = "setgroups",
[117] = "setresuid",
[118] = "getresuid",
[119] = "setresgid",
[120] = "getresgid",
[121] = "getpgid",
[122] = "setfsuid",
[123] = "setfsgid",
[124] = "getsid",
[125] = "capget",
[126] = "capset",
[127] = "rt_sigpending",
[128] = "rt_sigtimedwait",
[129] = "rt_sigqueueinfo",
[130] = "rt_sigsuspend",
[131] = "sigaltstack",
[132] = "utime",
[133] = "mknod",
[134] = "uselib",
[135] = "personality",
[136] = "ustat",
[137] = "statfs",
[138] = "fstatfs",
[139] = "sysfs",
[140] = "getpriority",
[141] = "setpriority",
[142] = "sched_setparam",
[143] = "sched_getparam",
[144] = "sched_setscheduler",
[145] = "sched_getscheduler",
[146] = "sched_get_priority_max",
[147] = "sched_get_priority_min",
[148] = "sched_rr_get_interval",
[149] = "mlock",
[150] = "munlock",
[151] = "mlockall",
[152] = "munlockall",
[153] = "vhangup",
[154] = "modify_ldt",
[155] = "pivot_root",
[156] = "_sysctl",
[157] = "prctl",
[158] = "arch_prctl",
[159] = "adjtimex",
[160] = "setrlimit",
[161] = "chroot",
[162] = "sync",
[163] = "acct",
[164] = "settimeofday",
[165] = "mount",
[166] = "umount2",
[167] = "swapon",
[168] = "swapoff",
[169] = "reboot",
[170] = "sethostname",
[171] = "setdomainname",
[172] = "iopl",
[173] = "ioperm",
[174] = "create_module",
[175] = "init_module",
[176] = "delete_module",
[177] = "get_kernel_syms",
[178] = "query_module",
[179] = "quotactl",
[180] = "nfsservctl",
[181] = "getpmsg",
[182] = "putpmsg",
[183] = "afs_syscall",
[184] = "tuxcall",
[185] = "security",
[186] = "gettid",
[187] = "readahead",
[188] = "setxattr",
[189] = "lsetxattr",
[190] = "fsetxattr",
[191] = "getxattr",
[192] = "lgetxattr",
[193] = "fgetxattr",
[194] = "listxattr",
[195] = "llistxattr",
[196] = "flistxattr",
[197] = "removexattr",
[198] = "lremovexattr",
[199] = "fremovexattr",
[200] = "tkill",
[201] = "time",
[202] = "futex",
[203] = "sched_setaffinity",
[204] = "sched_getaffinity",
[205] = "set_thread_area",
[206] = "io_setup",
[207] = "io_destroy",
[208] = "io_getevents",
[209] = "io_submit",
[210] = "io_cancel",
[211] = "get_thread_area",
[212] = "lookup_dcookie",
[213] = "epoll_create",
[214] = "epoll_ctl_old",
[215] = "epoll_wait_old",
[216] = "remap_file_pages",
[217] = "getdents64",
[218] = "set_tid_address",
[219] = "restart_syscall",
[220] = "semtimedop",
[221] = "fadvise64",
[222] = "timer_create",
[223] = "timer_settime",
[224] = "timer_gettime",
[225] = "timer_getoverrun",
[226] = "timer_delete",
[227] = "clock_settime",
[228] = "clock_gettime",
[229] = "clock_getres",
[230] = "clock_nanosleep",
[231] = "exit_group",
[232] = "epoll_wait",
[233] = "epoll_ctl",
[234] = "tgkill",
[235] = "utimes",
[236] = "vserver",
[237] = "mbind",
[238] = "set_mempolicy",
[239] = "get_mempolicy",
[240] = "mq_open",
[241] = "mq_unlink",
[242] = "mq_timedsend",
[243] = "mq_timedreceive",
[244] = "mq_notify",
[245] = "mq_getsetattr",
[246] = "kexec_load",
[247] = "waitid",
[248] = "add_key",
[249] = "request_key",
[250] = "keyctl",
[251] = "ioprio_set",
[252] = "ioprio_get",
[253] = "inotify_init",
[254] = "inotify_add_watch",
[255] = "inotify_rm_watch",
[256] = "migrate_pages",
[257] = "openat",
[258] = "mkdirat",
[259] = "mknodat",
[260] = "fchownat",
[261] = "futimesat",
[262] = "newfstatat",
[263] = "unlinkat",
[264] = "renameat",
[265] = "linkat",
[266] = "symlinkat",
[267] = "readlinkat",
[268] = "fchmodat",
[269] = "faccessat",
[270] = "pselect6",
[271] = "ppoll",
[272] = "unshare",
[273] = "set_robust_list",
[274] = "get_robust_list",
[275] = "splice",
[276] = "tee",
[277] = "sync_file_range",
[278] = "vmsplice",
[279] = "move_pages",
[280] = "utimensat",
[281] = "epoll_pwait",
[282] = "signalfd",
[283] = "timerfd_create",
[284] = "eventfd",
[285] = "fallocate",
[286] = "timerfd_settime",
[287] = "timerfd_gettime",
[288] = "accept4",
[289] = "signalfd4",
[290] = "eventfd2",
[291] = "epoll_create1",
[292] = "dup3",
[293] = "pipe2",
[294] = "inotify_init1",
[295] = "preadv",
[296] = "pwritev",
[297] = "rt_tgsigqueueinfo",
[298] = "perf_event_open",
[299] = "recvmmsg",
[300] = "fanotify_init",
[301] = "fanotify_mark",
[302] = "prlimit64",
[303] = "name_to_handle_at",
[304] = "open_by_handle_at",
[305] = "clock_adjtime",
[306] = "syncfs",
[307] = "sendmmsg",
[308] = "setns",
[309] = "getcpu",
[310] = "process_vm_readv",
[311] = "process_vm_writev",
[312] = "kcmp",
[313] = "finit_module",
[314] = "sched_setattr",
[315] = "sched_getattr",
[316] = "renameat2",
[317] = "seccomp",
[318] = "getrandom",
[319] = "memfd_create",
[320] = "kexec_file_load",
[321] = "bpf",
[322] = "execveat",
[323] = "userfaultfd",
[324] = "membarrier",
[325] = "mlock2",
[326] = "copy_file_range",
[327] = "preadv2",
[328] = "pwritev2",
[329] = "pkey_mprotect",
[330] = "pkey_alloc",
[331] = "pkey_free",
[332] = "statx",
[333] = "io_pgetevents",
[334] = "rseq",
[424] = "pidfd_send_signal",
[425] = "io_uring_setup",
[426] = "io_uring_enter",
[427] = "io_uring_register",
[428] = "open_tree",
[429] = "move_mount",
[430] = "fsopen",
[431] = "fsconfig",
[432] = "fsmount",
[433] = "fspick",
[434] = "pidfd_open",
[435] = "clone3",
[437] = "openat2",
[438] = "pidfd_getfd",
};
size_t syscall_names_x86_64_size = sizeof(syscall_names_x86_64)/sizeof(char*);
#endif
void syscall_name(unsigned n, char *buf, size_t size)
{
const char *name = NULL;
if (n < syscall_names_size)
name = syscall_names[n];
#ifdef __x86_64__
else if (n < syscall_names_x86_64_size)
name = syscall_names_x86_64[n];
#endif
if (name)
strncpy(buf, name, size-1);
else
snprintf(buf, size, "[unknown: %u]", n);
}
int list_syscalls(void)
{
const char **list = syscall_names;
size_t size = syscall_names_size;
#ifdef __x86_64__
if (!size) {
size = syscall_names_x86_64_size;
list = syscall_names_x86_64;
}
#endif
for (size_t i = 0; i < size; i++) {
if (list[i])
printf("%3zd: %s\n", i, list[i]);
}
return (!list || !size);
}

View File

@@ -0,0 +1,12 @@
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
#ifndef __SYSCALL_HELPERS_H
#define __SYSCALL_HELPERS_H
#include <stddef.h>
void init_syscall_names(void);
void free_syscall_names(void);
void list_syscalls(void);
void syscall_name(unsigned n, char *buf, size_t size);
#endif /* __SYSCALL_HELPERS_H */

View File

@@ -0,0 +1,234 @@
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <sys/resource.h>
#include <time.h>
#include "trace_helpers.h"
#define min(x, y) ({ \
typeof(x) _min1 = (x); \
typeof(y) _min2 = (y); \
(void) (&_min1 == &_min2); \
_min1 < _min2 ? _min1 : _min2; })
struct ksyms {
struct ksym *syms;
int syms_sz;
int syms_cap;
char *strs;
int strs_sz;
int strs_cap;
};
static int ksyms__add_symbol(struct ksyms *ksyms, const char *name, unsigned long addr)
{
size_t new_cap, name_len = strlen(name) + 1;
struct ksym *ksym;
void *tmp;
if (ksyms->strs_sz + name_len > ksyms->strs_cap) {
new_cap = ksyms->strs_cap * 4 / 3;
if (new_cap < ksyms->strs_sz + name_len)
new_cap = ksyms->strs_sz + name_len;
if (new_cap < 1024)
new_cap = 1024;
tmp = realloc(ksyms->strs, new_cap);
if (!tmp)
return -1;
ksyms->strs = tmp;
ksyms->strs_cap = new_cap;
}
if (ksyms->syms_sz + 1 > ksyms->syms_cap) {
new_cap = ksyms->syms_cap * 4 / 3;
if (new_cap < 1024)
new_cap = 1024;
tmp = realloc(ksyms->syms, sizeof(*ksyms->syms) * new_cap);
if (!tmp)
return -1;
ksyms->syms = tmp;
ksyms->syms_cap = new_cap;
}
ksym = &ksyms->syms[ksyms->syms_sz];
/* while constructing, re-use pointer as just a plain offset */
ksym->name = (void *)(unsigned long)ksyms->strs_sz;
ksym->addr = addr;
memcpy(ksyms->strs + ksyms->strs_sz, name, name_len);
ksyms->strs_sz += name_len;
ksyms->syms_sz++;
return 0;
}
static int ksym_cmp(const void *p1, const void *p2)
{
const struct ksym *s1 = p1, *s2 = p2;
if (s1->addr == s2->addr)
return strcmp(s1->name, s2->name);
return s1->addr < s2->addr ? -1 : 1;
}
struct ksyms *ksyms__load(void)
{
char sym_type, sym_name[256];
struct ksyms *ksyms;
unsigned long sym_addr;
int i, ret;
FILE *f;
f = fopen("/proc/kallsyms", "r");
if (!f)
return NULL;
ksyms = calloc(1, sizeof(*ksyms));
if (!ksyms)
goto err_out;
while (true) {
ret = fscanf(f, "%lx %c %s%*[^\n]\n",
&sym_addr, &sym_type, sym_name);
if (ret == EOF && feof(f))
break;
if (ret != 3)
goto err_out;
if (ksyms__add_symbol(ksyms, sym_name, sym_addr))
goto err_out;
}
/* now when strings are finalized, adjust pointers properly */
for (i = 0; i < ksyms->syms_sz; i++)
ksyms->syms[i].name += (unsigned long)ksyms->strs;
qsort(ksyms->syms, ksyms->syms_sz, sizeof(*ksyms->syms), ksym_cmp);
fclose(f);
return ksyms;
err_out:
ksyms__free(ksyms);
fclose(f);
return NULL;
}
void ksyms__free(struct ksyms *ksyms)
{
if (!ksyms)
return;
free(ksyms->syms);
free(ksyms->strs);
free(ksyms);
}
const struct ksym *ksyms__map_addr(const struct ksyms *ksyms,
unsigned long addr)
{
int start = 0, end = ksyms->syms_sz - 1, mid;
unsigned long sym_addr;
/* find largest sym_addr <= addr using binary search */
while (start < end) {
mid = start + (end - start + 1) / 2;
sym_addr = ksyms->syms[mid].addr;
if (sym_addr <= addr)
start = mid;
else
end = mid - 1;
}
if (start == end && ksyms->syms[start].addr <= addr)
return &ksyms->syms[start];
return NULL;
}
const struct ksym *ksyms__get_symbol(const struct ksyms *ksyms,
const char *name)
{
int i;
for (i = 0; i < ksyms->syms_sz; i++) {
if (strcmp(ksyms->syms[i].name, name) == 0)
return &ksyms->syms[i];
}
return NULL;
}
static void print_stars(unsigned int val, unsigned int val_max, int width)
{
int num_stars, num_spaces, i;
bool need_plus;
num_stars = min(val, val_max) * width / val_max;
num_spaces = width - num_stars;
need_plus = val > val_max;
for (i = 0; i < num_stars; i++)
printf("*");
for (i = 0; i < num_spaces; i++)
printf(" ");
if (need_plus)
printf("+");
}
void print_log2_hist(unsigned int *vals, int vals_size, char *val_type)
{
int stars_max = 40, idx_max = -1;
unsigned int val, val_max = 0;
unsigned long long low, high;
int stars, width, i;
for (i = 0; i < vals_size; i++) {
val = vals[i];
if (val > 0)
idx_max = i;
if (val > val_max)
val_max = val;
}
if (idx_max < 0)
return;
printf("%*s%-*s : count distribution\n", idx_max <= 32 ? 5 : 15, "",
idx_max <= 32 ? 19 : 29, val_type);
if (idx_max <= 32)
stars = stars_max;
else
stars = stars_max / 2;
for (i = 0; i <= idx_max; i++) {
low = (1ULL << (i + 1)) >> 1;
high = (1ULL << (i + 1)) - 1;
if (low == high)
low -= 1;
val = vals[i];
width = idx_max <= 32 ? 10 : 20;
printf("%*lld -> %-*lld : %-8d |", width, low, width, high, val);
print_stars(val, val_max, stars);
printf("|\n");
}
}
unsigned long long get_ktime_ns(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec * NSEC_PER_SEC + ts.tv_nsec;
}
int bump_memlock_rlimit(void)
{
struct rlimit rlim_new = {
.rlim_cur = RLIM_INFINITY,
.rlim_max = RLIM_INFINITY,
};
return setrlimit(RLIMIT_MEMLOCK, &rlim_new);
}

View File

@@ -0,0 +1,26 @@
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
#ifndef __TRACE_HELPERS_H
#define __TRACE_HELPERS_H
#define NSEC_PER_SEC 1000000000ULL
struct ksym {
const char *name;
unsigned long addr;
};
struct ksyms;
struct ksyms *ksyms__load(void);
void ksyms__free(struct ksyms *ksyms);
const struct ksym *ksyms__map_addr(const struct ksyms *ksyms,
unsigned long addr);
const struct ksym *ksyms__get_symbol(const struct ksyms *ksyms,
const char *name);
void print_log2_hist(unsigned int *vals, int vals_size, char *val_type);
unsigned long long get_ktime_ns(void);
int bump_memlock_rlimit(void);
#endif /* __TRACE_HELPERS_H */

106795
execsnoop-libbpf/vmlinux.h Normal file

File diff suppressed because it is too large Load Diff

16
man/cgnoproxy.1 Normal file
View File

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

14
man/cgproxy.1 Normal file
View File

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

54
man/cgproxyd.1 Normal file
View File

@@ -0,0 +1,54 @@
.\" Manpage for cgproxyd
.TH man 1 "19 May 2020" "1.0" "cgproxyd man page"
.SH NAME
cgproxyd \- Start a daemon with unix socket to accept control from cgproxy/cgnoproxy
.SH SYNOPSIS
cgproxyd [--help] [--debug] [--execsnoop]
.SH ALIAS
cgproxyd = cgproxy --daemon
.SH OPTIONS
.B --execsnoop
enable execsnoop to support program level proxy, need 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)

24
pack/CMakeLists.txt Normal file
View File

@@ -0,0 +1,24 @@
## 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 "amd64")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "systemd")
set(CPACK_DEBIAN_PACKAGE_SECTION "network")
set(CPACK_DEBIAN_PACKAGE_PRIORITY "Optional")
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/springzfx/cgproxy")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "springzfx@gmail.com")
set(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_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
View File

@@ -0,0 +1,2 @@
#!/bin/sh
systemctl enable --now cgproxy.service

2
pack/prerm Normal file
View File

@@ -0,0 +1,2 @@
#!/bin/sh
systemctl disable --now cgproxy.service

227
readme.md
View File

@@ -1,27 +1,37 @@
# Transparent Proxy with cgroup v2
# Transparent Proxy powered by 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)
* [Transparent Proxy powered by cgroup v2](#transparent-proxy-powered-by-cgroup-v2)
* [Introduction](#introduction)
* [Contents](#contents)
* [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)
* [Known Issus](#known-issus)
<!-- Added by: fancy, at: Thu 23 Apr 2020 01:23:57 PM HKT -->
<!-- Added by: fancy, at: Sat 04 Jul 2020 03:52:07 PM CST -->
<!--te-->
@@ -29,76 +39,134 @@ 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.
## How to install
- 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 build and install
### distro install
- For debian and redhat series, download from [Release page](https://github.com/springzfx/cgproxy/releases)
- For archlinux series, see[archlinux AUR](https://aur.archlinux.org/packages/?K=cgproxy)
- **Tested on archlinux, fedora 32, ubuntu 18.04, ubuntu 20.04, deepin 15.11, deepin v20 beta**
### build
- before build, install depencies: clang, nlohmann-json, libbpf
- then cmake standard build
```bash
mkdir build && cd build && cmake .. && make && make install
# ready build dir
mkdir build
cd build
# generate
cmake -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr \
-Dbuild_execsnoop_dl=ON \
-Dbuild_static=OFF \
..
# compile
make
```
It is alreay in [archlinux AUR](https://aur.archlinux.org/packages/cgproxy/).
## Default usage
## How to use
- First enable service
- First enable and start service
```bash
sudo systemctl enable --now cgproxy.service
sudo systemctl status cgproxy.service
```
- Then prefix with cgproxy with your command, just like proxychains
```
cgproxy <CMD>
```bash
cgproxy [--debug] <CMD>
```
- For example, test proxy
```bash
cgproxy curl -vIs https://www.google.com
cgproxy curl -vI https://www.google.com
```
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
```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,
"table": 10007,
"fwmark": 39283,
"mark_newin": 39271
}
## 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
```
- **port** tproxy listenning port
- program level proxy control:
- **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**
- **table**, **fwmark**, **mark_newin** you can specify iptables and route table related parameter in case conflict.
- options priority
```
program_noproxy > program_proxy > cgroup_noproxy > cgroup_proxy
enable_ipv6 > enable_ipv4 > enable_tcp > enable_udp > enable_dns
ommand cgproxy and cgnoproxy always have highest priority
```
**Note**: cgroup in configuration need to be exist, otherwise ignored
If you changed config, remember to restart service
```bash
@@ -107,58 +175,65 @@ 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.
```bash
run_in_cgroup --cgroup=CGROUP <COMMAND>
# example
run_in_cgroup --cgroup=/mycgroup.slice ping 127.0.0.1
```
- `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
![Qv2ray config example](https://i.loli.net/2020/06/12/6ltax93bv7NCTHU.png)
## Licences
cgproxy is licenced under [![License: GPL v3](https://img.shields.io/badge/License-GPL%20v2-blue.svg)](https://www.gnu.org/licenses/gpl-2.0)
## Known Issus
- docker breaks cgroup path match, add kernel parameter `cgroup_no_v1=net_cls,net_prio` to resolve, see [issue #3](https://github.com/springzfx/cgproxy/issues/3) for detail

View File

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

24
src/CMakeLists.txt Normal file
View File

@@ -0,0 +1,24 @@
find_package(nlohmann_json REQUIRED)
include_directories(${PROJECT_SOURCE_DIR})
include_directories(${PROJECT_SOURCE_DIR}/execsnoop-kernel/)
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
if (build_execsnoop_dl)
add_definitions(-DBUIlD_EXECSNOOP_DL)
set(DL_LIB "-ldl")
set(EXECSNOOP_LIB "")
else()
set(EXECSNOOP_LIB "execsnoop")
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 ${DL_LIB} ${EXECSNOOP_LIB})
set_target_properties(main PROPERTIES OUTPUT_NAME cgproxy)
install(TARGETS main RUNTIME)
if (build_static)
target_link_libraries(main PRIVATE -static -Wl,--whole-archive -lpthread -Wl,--no-whole-archive)
else()
target_link_libraries(main PRIVATE -lpthread)
endif()

84
src/cgproxy.hpp Normal file
View File

@@ -0,0 +1,84 @@
#include "common.h"
#include "config.h"
#include "socket_client.h"
#include <cstdlib>
#include <nlohmann/json.hpp>
#include <unistd.h>
using json = nlohmann::json;
using namespace ::CGPROXY;
using namespace ::CGPROXY::CONFIG;
namespace CGPROXY::CGPROXY {
bool print_help = false, proxy = true;
bool attach_pid = false;
string arg_pid;
inline void print_usage() {
if (proxy) {
cout << "Run program with proxy" << endl;
cout << "Usage: cgproxy [--help] [--debug] <CMD>" << endl;
} else {
cout << "Run program without proxy" << endl;
cout << "Usage: cgpnoroxy [--help] [--debug] <CMD>" << endl;
cout << "Alias: cgnoproxy = cgproxy --noproxy" << endl;
}
}
bool processArgs(const int argc, char *argv[], int &shift) {
int i;
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "--pid") == 0) {
attach_pid = true;
i++;
if (i == argc) return false;
arg_pid = argv[i];
if (!validPid(arg_pid)) return false;
continue;
}
if (strcmp(argv[i], "--noproxy") == 0) { proxy = false; }
if (strcmp(argv[i], "--debug") == 0) { enable_debug = true; }
if (strcmp(argv[i], "--help") == 0) { print_help = true; }
if (argv[i][0] != '-') { break; }
}
shift = i;
return true;
}
void send_pid(const pid_t pid, bool proxy, int &status) {
json j;
j["type"] = proxy ? MSG_TYPE_PROXY_PID : MSG_TYPE_NOPROXY_PID;
j["data"] = pid;
SOCKET::send(j.dump(), status);
}
int main(int argc, char *argv[]) {
int shift = -1;
if (!processArgs(argc, argv, shift)) {
error("parameter error");
exit(EXIT_FAILURE);
}
if (print_help) {
print_usage();
exit(0);
}
if (!attach_pid && argc == shift) {
error("no program specified");
exit(EXIT_FAILURE);
}
int status = -1;
send_pid(attach_pid ? stoi(arg_pid) : getpid(), proxy, status);
if (status != 0) {
error("attach process failed");
if (status == 1) error("maybe cgproxy.service not running");
exit(EXIT_FAILURE);
}
// if just attach pid, return here
if (attach_pid) return 0;
string s = join2str(argc - shift, argv + shift, ' ');
return system(s.c_str());
}
} // namespace CGPROXY::CGPROXY

378
src/cgproxyd.hpp Normal file
View File

@@ -0,0 +1,378 @@
#ifndef CGPROXYD_HPP
#define CGPROXYD_HPP
#include "cgroup_attach.h"
#include "common.h"
#include "config.h"
#include "execsnoop_share.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;
#ifdef BUIlD_EXECSNOOP_DL
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
#endif
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)) {
int res = attach(pid, config.cgroup_noproxy_preserved);
if (res == 0) {
info("execsnoop; noproxy: %d %s", pid, path.get());
} else {
info("execsnoop; noproxy failed: %d %s", pid, path.get());
}
return res;
}
}
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)) {
int res = attach(pid, config.cgroup_proxy_preserved);
if (res == 0) {
info("execsnoop: proxied: %d %s", pid, path.get());
} else {
info("execsnoop: proxied failed: %d %s", pid, path.get());
}
return res;
}
}
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() {
#ifdef BUIlD_EXECSNOOP_DL
if (!EXECSNOOP::loadExecsnoopLib() || EXECSNOOP::_startThread == NULL) {
error("execsnoop not ready to start, maybe missing libbpf");
return;
}
#endif
promise<void> status;
future<void> status_f = status.get_future();
#ifdef BUIlD_EXECSNOOP_DL
thread th(EXECSNOOP::_startThread, handle_pid_static, move(status));
#else
thread th(EXECSNOOP::startThread, handle_pid_static, move(status));
#endif
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();
if (config.loadFromFile(DEFAULT_CONFIG_FILE)!=SUCCESS) {
error("load config file failed");
return -1;
}
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
View 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 <thread>
#include <unistd.h>
namespace CGPROXY::CGROUP {
string cgroup2_mount_point = CGROUP2_MOUNT_POINT;
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
if (write2procs(pid, cgroup_target_procs) != 0) return_error;
// wait for small period and check again
this_thread::sleep_for(std::chrono::milliseconds(100));
if (getCgroup(pid) != cgroup_target && write2procs(pid, cgroup_target_procs) != 0)
return_error;
return_success;
}
int write2procs(string pid, string procspath) {
ofstream procs(procspath, ofstream::app);
if (!procs.is_open()) {
error("open file %s failed", procspath.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(),
procspath.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
View 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);
int attach(const string pid, const string cgroup_target);
int attach(const int pid, const string cgroup_target);
int write2procs(string pid, string procspath);
} // namespace CGPROXY::CGROUP
#endif

124
src/common.cpp Normal file
View 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();
if (!s.empty()) 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();
if (!s.empty()) 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;
}

112
src/common.h Normal file
View File

@@ -0,0 +1,112 @@
#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 CGROUP2_MOUNT_POINT "/var/run/cgproxy/cgroup2"
#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 warning(...) \
{ \
fprintf(stderr, "warning: "); \
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

170
src/config.cpp Normal file
View File

@@ -0,0 +1,170 @@
#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("cgroup_mount_point", CGROUP2_MOUNT_POINT, 1);
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);
setenv("table", to_str(table).c_str(), 1);
setenv("fwmark", to_str(fwmark).c_str(), 1);
setenv("mark_newin", to_str(mark_newin).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);
add2json(table);
add2json(fwmark);
add2json(mark_newin);
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);
tryassign(table);
tryassign(fwmark);
tryassign(mark_newin);
// 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", "comment", "table", "fwmark", "mark_newin"};
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());
info("table: %d, fwmark: %d, mark_newin: %d", table, fwmark, mark_newin);
}
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
warning("%s not exist or broken link", p.c_str());
}
v = tmp;
}
#undef tryassign
#undef add2json
#undef merge
} // namespace CGPROXY::CONFIG

47
src/config.h Normal file
View File

@@ -0,0 +1,47 @@
#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;
// for iptables
int table=10007;
int fwmark=0x9973;
int mark_newin=0x9967;
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

17
src/main.cpp Normal file
View File

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

49
src/socket_client.cpp Normal file
View 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
View File

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

67
src/socket_server.cpp Normal file
View 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
View 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
View File

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

View File

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

5
tools/CMakeLists.txt Normal file
View File

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

31
tools/cgattach.cpp Normal file
View File

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

5
v2ray_config/00_log.json Normal file
View File

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

10
v2ray_config/01_api.json Normal file
View File

@@ -0,0 +1,10 @@
{
"api": {
"services": [
"HandlerService",
"LoggerService",
"StatsService"
],
"tag": "API"
}
}

22
v2ray_config/02_dns.json Normal file
View 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"
}
}

View File

@@ -0,0 +1,8 @@
{
"policy": {
"system": {
"statsInboundDownlink": true,
"statsInboundUplink": true
}
}
}

View 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"
}
]
}
}

View 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"
}
]
}

View File

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

View File

@@ -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"
}
]
}

View File

@@ -0,0 +1,13 @@
{
"inbounds": [
{
"listen": "127.0.0.1",
"port": 8888,
"protocol": "http",
"sniffing": {
"enabled": false
},
"tag": "http_IN"
}
]
}

View File

@@ -0,0 +1,18 @@
{
"inbounds": [
{
"listen": "127.0.0.1",
"port": 1080,
"protocol": "socks",
"settings": {
"udp": true,
"auth": "noauth",
"userLevel": 0
},
"sniffing": {
"enabled": false
},
"tag": "socks_IN"
}
]
}

View File

@@ -0,0 +1,19 @@
{
"outbounds": [
{
"protocol": "blackhole",
"sendThrough": "0.0.0.0",
"settings": {
"response": {
"type": "none"
}
},
"streamSettings": {
"sockopt": {
"mark": 255
}
},
"tag": "outBound_BLACKHOLE"
}
]
}

View 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"
}
]
}

View File

@@ -0,0 +1,13 @@
{
"outbounds": [
{
"protocol": "dns",
"streamSettings": {
"sockopt": {
"mark": 255
}
},
"tag": "dns-out"
}
]
}

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
{
"stats": {}
}

View File

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

2
v2ray_config/merge.sh Normal file
View 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
View 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)

View 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