├── .gitignore ├── .gitmodules ├── .vscode ├── c_cpp_properties.json ├── gdb-root ├── launch.json └── settings.json ├── CMakeLists.txt ├── LICENSE ├── _clang-format ├── cgnoproxy.cmake ├── cgproxy.service.cmake ├── cgproxyd.cmake ├── cgroup-tproxy.sh ├── config.json ├── execsnoop-bcc ├── CMakeLists.txt ├── execsnoop.cpp ├── execsnoop.h └── readme.md ├── execsnoop-kernel ├── CMakeLists.txt ├── aarch64 │ └── execsnoop_kern_skel.h ├── arm64.md ├── execsnoop_kern.c ├── execsnoop_share.cpp ├── execsnoop_share.h ├── execsnoop_user.c ├── execsnoop_user_1.c ├── readme.md └── x86_64 │ └── execsnoop_kern_skel.h ├── execsnoop-libbpf ├── CMakeLists.txt ├── execsnoop.bpf.c ├── execsnoop.c ├── execsnoop.skel.h ├── execsnoop_share.cpp ├── execsnoop_share.h └── readme.md ├── man ├── cgnoproxy.1 ├── cgproxy.1 └── cgproxyd.1 ├── pack ├── CMakeLists.txt ├── postinst └── prerm ├── readme.md ├── src ├── CMakeLists.txt ├── cgproxy.hpp ├── cgproxyd.hpp ├── cgroup_attach.cpp ├── cgroup_attach.h ├── common.cmake.h ├── common.cpp ├── config.cpp ├── config.h ├── main.cpp ├── socket_client.cpp ├── socket_client.h ├── socket_server.cpp └── socket_server.h ├── test ├── CMakeLists.txt └── socket_client_test.cpp ├── tools ├── CMakeLists.txt └── cgattach.cpp └── v2ray_config ├── 00_log.json ├── 01_api.json ├── 02_dns.json ├── 03_policy.json ├── 04_routing_00.json ├── 05_inbounds_00_api.json ├── 05_inbounds_01_tproxy_ipv4lo.json ├── 05_inbounds_02_tproxy_ipv6lo.json ├── 05_inbounds_03_http.json ├── 05_inbounds_04_socks5.json ├── 06_outbounds_00_blackhole.json ├── 06_outbounds_01_freedom.json ├── 06_outbounds_02_dns.json ├── 06_outbounds_10_myproxy.json ├── 07_transport.json ├── 08_stats.json ├── 09_reverse.json ├── merge.sh ├── readme.md └── v2ray.service /.gitignore: -------------------------------------------------------------------------------- 1 | build* 2 | .cache 3 | .directory 4 | .clangd 5 | v2ray_config/proxy 6 | v2ray_config/06_outbounds_proxy.json 7 | aur-* 8 | gh-md-toc 9 | add_toc.sh 10 | compile_commands.json 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "execsnoop-libbpf/libbpf"] 2 | path = execsnoop-libbpf/libbpf 3 | url = https://github.com/libbpf/libbpf.git 4 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [], 6 | "defines": ["USE_VMLINUX"], 7 | "compilerPath": "/usr/bin/gcc", 8 | "cStandard": "c18", 9 | "cppStandard": "gnu++17", 10 | "intelliSenseMode": "gcc-x64", 11 | "configurationProvider": "ms-vscode.cmake-tools" 12 | } 13 | ], 14 | "version": 4 15 | } -------------------------------------------------------------------------------- /.vscode/gdb-root: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exec sudo -E /usr/bin/gdb "$@" -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "(gdb) cgproxyd", 5 | "type": "cppdbg", 6 | "request": "launch", 7 | "program": "${workspaceFolder}/build/src/cgproxy", 8 | "args": ["--daemon", "--debug", "--execsnoop"], 9 | "stopAtEntry": false, 10 | "cwd": "${workspaceFolder}", 11 | "environment": [], 12 | "externalConsole": false, 13 | "MIMode": "gdb", 14 | "miDebuggerPath": "${workspaceFolder}/.vscode/gdb-root", 15 | "setupCommands": [ 16 | { 17 | "description": "Enable pretty-printing for gdb", 18 | "text": "-enable-pretty-printing", 19 | "ignoreFailures": true 20 | } 21 | ] 22 | }] 23 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cmake.configureOnOpen": true, 3 | "cmake.configureArgs": [ 4 | "-Dbuild_static=OFF", 5 | "-Dbuild_execsnoop_dl=ON", 6 | "-Dbuild_tools=ON", 7 | "-Dbuild_test=ON" 8 | ] 9 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | set(CMAKE_CXX_STANDARD 17) 3 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 4 | 5 | project(cgproxy VERSION 0.20) 6 | 7 | include(GNUInstallDirs) 8 | 9 | add_compile_options(-Wall -Wextra -Wpedantic -Wno-unused-result -Wno-unused-parameter -Wno-overlength-strings) 10 | 11 | # for clangd 12 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 13 | 14 | # option(with_execsnoop "enable program level proxy control feature, need bcc installed" ON) 15 | option(build_execsnoop_dl "build libexecsnoop.so which will be dynamic loaded, otherwise built directly into cgproxy" ON) 16 | option(build_static "build with static link prefered" OFF) 17 | option(build_tools OFF) 18 | option(build_test "for develop" OFF) 19 | 20 | add_subdirectory(src) 21 | add_subdirectory(execsnoop-libbpf) 22 | add_subdirectory(pack) 23 | if (build_tools) 24 | add_subdirectory(tools) 25 | endif() 26 | if (build_test) 27 | add_subdirectory(test) 28 | endif() 29 | 30 | configure_file(cgnoproxy.cmake cgnoproxy) 31 | configure_file(cgproxyd.cmake cgproxyd) 32 | configure_file(cgproxy.service.cmake cgproxy.service) 33 | 34 | # instal scripts and other things 35 | install(PROGRAMS ${CMAKE_BINARY_DIR}/cgproxyd DESTINATION ${CMAKE_INSTALL_FULL_BINDIR}) 36 | install(PROGRAMS ${CMAKE_BINARY_DIR}/cgnoproxy DESTINATION ${CMAKE_INSTALL_FULL_BINDIR}) 37 | install(PROGRAMS cgroup-tproxy.sh DESTINATION ${CMAKE_INSTALL_FULL_DATADIR}/cgproxy/scripts) 38 | install(FILES ${CMAKE_BINARY_DIR}/cgproxy.service DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/systemd/system) 39 | install(FILES config.json DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/cgproxy) 40 | install(FILES readme.md DESTINATION ${CMAKE_INSTALL_FULL_DOCDIR}) 41 | 42 | # man pages 43 | set(man_gz ${PROJECT_SOURCE_DIR}/man/cgproxyd.1.gz ${PROJECT_SOURCE_DIR}/man/cgproxy.1.gz ${PROJECT_SOURCE_DIR}/man/cgnoproxy.1.gz) 44 | add_custom_command(OUTPUT ${man_gz} 45 | COMMAND gzip -fk cgproxyd.1 cgproxy.1 cgnoproxy.1 46 | WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/man 47 | ) 48 | add_custom_target(man ALL DEPENDS ${man_gz}) 49 | install(FILES ${man_gz} DESTINATION ${CMAKE_INSTALL_FULL_MANDIR}/man1/) 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /_clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveMacros: false 7 | AlignConsecutiveAssignments: false 8 | AlignConsecutiveDeclarations: false 9 | AlignEscapedNewlines: Right 10 | AlignOperands: true 11 | AlignTrailingComments: true 12 | AllowAllArgumentsOnNextLine: true 13 | AllowAllConstructorInitializersOnNextLine: true 14 | AllowAllParametersOfDeclarationOnNextLine: true 15 | AllowShortBlocksOnASingleLine: Always 16 | AllowShortCaseLabelsOnASingleLine: true 17 | AllowShortFunctionsOnASingleLine: All 18 | AllowShortLambdasOnASingleLine: All 19 | AllowShortIfStatementsOnASingleLine: Always 20 | AllowShortLoopsOnASingleLine: true 21 | AlwaysBreakAfterDefinitionReturnType: None 22 | AlwaysBreakAfterReturnType: None 23 | AlwaysBreakBeforeMultilineStrings: false 24 | AlwaysBreakTemplateDeclarations: MultiLine 25 | BinPackArguments: true 26 | BinPackParameters: true 27 | BraceWrapping: 28 | AfterCaseLabel: false 29 | AfterClass: false 30 | AfterControlStatement: false 31 | AfterEnum: false 32 | AfterFunction: false 33 | AfterNamespace: false 34 | AfterObjCDeclaration: false 35 | AfterStruct: false 36 | AfterUnion: false 37 | AfterExternBlock: false 38 | BeforeCatch: false 39 | BeforeElse: false 40 | IndentBraces: false 41 | SplitEmptyFunction: true 42 | SplitEmptyRecord: true 43 | SplitEmptyNamespace: true 44 | BreakBeforeBinaryOperators: None 45 | BreakBeforeBraces: Attach 46 | BreakBeforeInheritanceComma: false 47 | BreakInheritanceList: BeforeColon 48 | BreakBeforeTernaryOperators: true 49 | BreakConstructorInitializersBeforeComma: false 50 | BreakConstructorInitializers: BeforeColon 51 | BreakAfterJavaFieldAnnotations: false 52 | BreakStringLiterals: true 53 | ColumnLimit: 90 54 | CommentPragmas: '^ IWYU pragma:' 55 | CompactNamespaces: false 56 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 57 | ConstructorInitializerIndentWidth: 4 58 | ContinuationIndentWidth: 4 59 | Cpp11BracedListStyle: true 60 | DeriveLineEnding: true 61 | DerivePointerAlignment: false 62 | DisableFormat: false 63 | ExperimentalAutoDetectBinPacking: false 64 | FixNamespaceComments: true 65 | ForEachMacros: 66 | - foreach 67 | - Q_FOREACH 68 | - BOOST_FOREACH 69 | IncludeBlocks: Preserve 70 | IncludeCategories: 71 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 72 | Priority: 2 73 | SortPriority: 0 74 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 75 | Priority: 3 76 | SortPriority: 0 77 | - Regex: '.*' 78 | Priority: 1 79 | SortPriority: 0 80 | IncludeIsMainRegex: '(Test)?$' 81 | IncludeIsMainSourceRegex: '' 82 | IndentCaseLabels: false 83 | IndentGotoLabels: true 84 | IndentPPDirectives: None 85 | IndentWidth: 2 86 | IndentWrappedFunctionNames: false 87 | JavaScriptQuotes: Leave 88 | JavaScriptWrapImports: true 89 | KeepEmptyLinesAtTheStartOfBlocks: true 90 | MacroBlockBegin: '' 91 | MacroBlockEnd: '' 92 | MaxEmptyLinesToKeep: 1 93 | NamespaceIndentation: None 94 | ObjCBinPackProtocolList: Auto 95 | ObjCBlockIndentWidth: 2 96 | ObjCSpaceAfterProperty: false 97 | ObjCSpaceBeforeProtocolList: true 98 | PenaltyBreakAssignment: 2 99 | PenaltyBreakBeforeFirstCallParameter: 19 100 | PenaltyBreakComment: 300 101 | PenaltyBreakFirstLessLess: 120 102 | PenaltyBreakString: 1000 103 | PenaltyBreakTemplateDeclaration: 10 104 | PenaltyExcessCharacter: 1000000 105 | PenaltyReturnTypeOnItsOwnLine: 60 106 | PointerAlignment: Right 107 | ReflowComments: true 108 | SortIncludes: true 109 | SortUsingDeclarations: true 110 | SpaceAfterCStyleCast: false 111 | SpaceAfterLogicalNot: false 112 | SpaceAfterTemplateKeyword: true 113 | SpaceBeforeAssignmentOperators: true 114 | SpaceBeforeCpp11BracedList: false 115 | SpaceBeforeCtorInitializerColon: true 116 | SpaceBeforeInheritanceColon: true 117 | SpaceBeforeParens: ControlStatements 118 | SpaceBeforeRangeBasedForLoopColon: true 119 | SpaceInEmptyBlock: false 120 | SpaceInEmptyParentheses: false 121 | SpacesBeforeTrailingComments: 1 122 | SpacesInAngles: false 123 | SpacesInConditionalStatement: false 124 | SpacesInContainerLiterals: true 125 | SpacesInCStyleCastParentheses: false 126 | SpacesInParentheses: false 127 | SpacesInSquareBrackets: false 128 | SpaceBeforeSquareBrackets: false 129 | Standard: Latest 130 | StatementMacros: 131 | - Q_UNUSED 132 | - QT_REQUIRE_VERSION 133 | TabWidth: 8 134 | UseCRLF: false 135 | UseTab: Never 136 | ... 137 | 138 | -------------------------------------------------------------------------------- /cgnoproxy.cmake: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exec @CMAKE_INSTALL_FULL_BINDIR@/cgproxy --noproxy $@ -------------------------------------------------------------------------------- /cgproxy.service.cmake: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=cgproxy service 3 | After=network.target network-online.target 4 | 5 | [Service] 6 | Type=simple 7 | ExecStart=@CMAKE_INSTALL_FULL_BINDIR@/cgproxyd --execsnoop 8 | 9 | [Install] 10 | WantedBy=multi-user.target 11 | -------------------------------------------------------------------------------- /cgproxyd.cmake: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exec @CMAKE_INSTALL_FULL_BINDIR@/cgproxy --daemon $@ -------------------------------------------------------------------------------- /cgroup-tproxy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ### This script will proxy/noproxy anything running in specific cgroup 3 | ### need cgroup2 support, and iptables cgroup2 path match support 4 | ### 5 | ### script usage: 6 | ### cgroup-tproxy.sh [--help|--config|stop] 7 | ### options: 8 | ### --config=FILE load config from file 9 | ### --help show help info 10 | ### stop clean then stop. Variables may change when stopping, which should be avoid 11 | ### so always stop first in the last context before start new context 12 | ### 13 | ### available variables with default value: 14 | ### cgroup_noproxy="/noproxy.slice" 15 | ### cgroup_proxy="/proxy.slice" 16 | ### port=12345 17 | ### enable_dns=true 18 | ### enable_udp=true 19 | ### enable_tcp=true 20 | ### enable_ipv4=true 21 | ### enable_ipv6=true 22 | ### enable_gateway=false 23 | ### table=10007 24 | ### fwmark=0x9973 25 | ### cgroup_mount_point=$(findmnt -t cgroup2 -n -o TARGET | head -n 1) 26 | ### 27 | ### semicolon to seperate multi cgroup: 28 | ### cgroup_noproxy="/noproxy1.slice:/noproxy2.slice" 29 | ### cgroup_proxy="/proxy1.slice:/proxy2.slice" 30 | 31 | print_help(){ 32 | sed -rn 's/^### ?//;T;p' "$0" 33 | } 34 | 35 | ## check root 36 | [ ! $(id -u) -eq 0 ] && { >&2 echo "iptables: need root to modify iptables";exit -1; } 37 | 38 | ## any process in this cgroup will be proxied 39 | if [ -z ${cgroup_proxy+x} ]; then 40 | cgroup_proxy="/proxy.slice" 41 | else 42 | IFS=':' read -r -a cgroup_proxy <<< "$cgroup_proxy" 43 | fi 44 | 45 | ## any process in this cgroup will not be proxied 46 | if [ -z ${cgroup_noproxy+x} ]; then 47 | cgroup_noproxy="/noproxy.slice" 48 | else 49 | IFS=':' read -r -a cgroup_noproxy <<< "$cgroup_noproxy" 50 | fi 51 | 52 | ## tproxy listening port 53 | [ -z ${port+x} ] && port=12345 54 | 55 | ## controll options 56 | [ -z ${enable_dns+x} ] && enable_dns=true 57 | [ -z ${enable_udp+x} ] && enable_udp=true 58 | [ -z ${enable_tcp+x} ] && enable_tcp=true 59 | [ -z ${enable_ipv4+x} ] && enable_ipv4=true 60 | [ -z ${enable_ipv6+x} ] && enable_ipv6=true 61 | [ -z ${enable_gateway+x} ] && enable_gateway=false 62 | 63 | ## mark/route things 64 | [ -z ${table+x} ] && table=10007 65 | [ -z ${fwmark+x} ] && fwmark=0x9973 66 | [ -z ${table_reroute+x} ] && table_reroute=$table 67 | [ -z ${table_tproxy+x} ] && table_tproxy=$table 68 | [ -z ${fwmark_reroute+x} ] && fwmark_reroute=$fwmark 69 | [ -z ${fwmark_tproxy+x} ] && fwmark_tproxy=$fwmark 70 | 71 | ## cgroup mount point things 72 | [ -z ${cgroup_mount_point+x} ] && cgroup_mount_point=$(findmnt -t cgroup2 -n -o TARGET | head -n 1) 73 | 74 | 75 | stop(){ 76 | iptables -w 60 -t mangle -L TPROXY_ENT &> /dev/null || return 77 | echo "iptables: cleaning tproxy iptables" 78 | 79 | iptables -w 60 -t mangle -D PREROUTING -j TPROXY_PRE 80 | iptables -w 60 -t mangle -D OUTPUT -j TPROXY_OUT 81 | 82 | iptables -w 60 -t mangle -F TPROXY_PRE 83 | iptables -w 60 -t mangle -F TPROXY_ENT 84 | iptables -w 60 -t mangle -F TPROXY_OUT 85 | iptables -w 60 -t mangle -F TPROXY_MARK 86 | 87 | iptables -w 60 -t mangle -X TPROXY_PRE 88 | iptables -w 60 -t mangle -X TPROXY_ENT 89 | iptables -w 60 -t mangle -X TPROXY_OUT 90 | iptables -w 60 -t mangle -X TPROXY_MARK 91 | 92 | ip rule delete fwmark $fwmark_tproxy lookup $table_tproxy 93 | ip rule delete fwmark $fwmark_reroute lookup $table_reroute &> /dev/null 94 | ip route flush table $table_tproxy 95 | ip route flush table $table_reroute &> /dev/null 96 | 97 | ip6tables -w 60 -t mangle -D PREROUTING -j TPROXY_PRE 98 | ip6tables -w 60 -t mangle -D OUTPUT -j TPROXY_OUT 99 | 100 | ip6tables -w 60 -t mangle -F TPROXY_PRE 101 | ip6tables -w 60 -t mangle -F TPROXY_OUT 102 | ip6tables -w 60 -t mangle -F TPROXY_ENT 103 | ip6tables -w 60 -t mangle -F TPROXY_MARK 104 | 105 | ip6tables -w 60 -t mangle -X TPROXY_PRE 106 | ip6tables -w 60 -t mangle -X TPROXY_OUT 107 | ip6tables -w 60 -t mangle -X TPROXY_ENT 108 | ip6tables -w 60 -t mangle -X TPROXY_MARK 109 | 110 | ip -6 rule delete fwmark $fwmark_tproxy lookup $table_tproxy 111 | ip -6 rule delete fwmark $fwmark_reroute lookup $table_reroute &> /dev/null 112 | ip -6 route flush table $table_tproxy 113 | ip -6 route flush table $table_reroute &> /dev/null 114 | 115 | ## may not exist, just ignore, and tracking their existence is not reliable 116 | iptables -w 60 -t nat -D POSTROUTING -m owner ! --socket-exists -j MASQUERADE &> /dev/null 117 | ip6tables -w 60 -t nat -D POSTROUTING -m owner ! --socket-exists -s fc00::/7 -j MASQUERADE &> /dev/null 118 | 119 | ## unmount cgroup2 120 | [ "$(findmnt -M $cgroup_mount_point -n -o FSTYPE)" = "cgroup2" ] && umount $cgroup_mount_point 121 | } 122 | 123 | ## parse parameter 124 | for i in "$@" 125 | do 126 | case $i in 127 | stop) 128 | stop 129 | exit 0 130 | ;; 131 | --config=*) 132 | config=${i#*=} 133 | source $config 134 | ;; 135 | --help) 136 | print_help 137 | exit 0 138 | ;; 139 | *) 140 | print_help 141 | exit 0 142 | ;; 143 | esac 144 | done 145 | 146 | 147 | ## check cgroup_mount_point, create and mount if necessary 148 | [ -z $cgroup_mount_point ] && { >&2 echo "iptables: no cgroup2 mount point available"; exit -1; } 149 | [ ! -d $cgroup_mount_point ] && mkdir -p $cgroup_mount_point 150 | [ "$(findmnt -M $cgroup_mount_point -n -o FSTYPE)" != "cgroup2" ] && mount -t cgroup2 none $cgroup_mount_point 151 | [ "$(findmnt -M $cgroup_mount_point -n -o FSTYPE)" != "cgroup2" ] && { >&2 echo "iptables: mount $cgroup_mount_point failed"; exit -1; } 152 | 153 | ## only create the first one in arrary 154 | test -d $cgroup_mount_point$cgroup_proxy || mkdir $cgroup_mount_point$cgroup_proxy || exit -1; 155 | test -d $cgroup_mount_point$cgroup_noproxy || mkdir $cgroup_mount_point$cgroup_noproxy || exit -1; 156 | 157 | ## filter cgroup that not exist 158 | _cgroup_noproxy=() 159 | for cg in ${cgroup_noproxy[@]}; do 160 | test -d $cgroup_mount_point$cg && _cgroup_noproxy+=($cg) || { >&2 echo "iptables: $cg not exist, ignore";} 161 | done 162 | unset cgroup_noproxy && cgroup_noproxy=${_cgroup_noproxy[@]} 163 | 164 | ## filter cgroup that not exist 165 | _cgroup_proxy=() 166 | for cg in ${cgroup_proxy[@]}; do 167 | test -d $cgroup_mount_point$cg && _cgroup_proxy+=($cg) || { >&2 echo "iptables: $cg not exist, ignore";} 168 | done 169 | unset cgroup_proxy && cgroup_proxy=${_cgroup_proxy[@]} 170 | 171 | 172 | ## ipv4 ######################################################################### 173 | echo "iptables: applying tproxy iptables" 174 | ## mangle prerouting 175 | ip rule add fwmark $fwmark_tproxy table $table_tproxy 176 | ip route add local default dev lo table $table_tproxy 177 | # core 178 | iptables -w 60 -t mangle -N TPROXY_ENT 179 | iptables -w 60 -t mangle -A TPROXY_ENT -m socket -j MARK --set-mark $fwmark_tproxy 180 | iptables -w 60 -t mangle -A TPROXY_ENT -m socket -j ACCEPT 181 | iptables -w 60 -t mangle -A TPROXY_ENT -p tcp -j TPROXY --on-ip 127.0.0.1 --on-port $port --tproxy-mark $fwmark_tproxy 182 | iptables -w 60 -t mangle -A TPROXY_ENT -p udp -j TPROXY --on-ip 127.0.0.1 --on-port $port --tproxy-mark $fwmark_tproxy 183 | # filter 184 | iptables -w 60 -t mangle -N TPROXY_PRE 185 | iptables -w 60 -t mangle -A TPROXY_PRE -m addrtype --dst-type LOCAL -j RETURN 186 | iptables -w 60 -t mangle -A TPROXY_PRE -m addrtype ! --dst-type UNICAST -j RETURN 187 | $enable_gateway || iptables -w 60 -t mangle -A TPROXY_PRE -m addrtype ! --src-type LOCAL -j RETURN 188 | $enable_dns && iptables -w 60 -t mangle -A TPROXY_PRE -p udp --dport 53 -j TPROXY_ENT 189 | $enable_udp && iptables -w 60 -t mangle -A TPROXY_PRE -p udp -j TPROXY_ENT 190 | $enable_tcp && iptables -w 60 -t mangle -A TPROXY_PRE -p tcp -j TPROXY_ENT 191 | # hook 192 | iptables -w 60 -t mangle -A PREROUTING -j TPROXY_PRE 193 | 194 | ## mangle output 195 | if [ $fwmark_reroute != $fwmark_tproxy ]; then 196 | ip rule add fwmark $fwmark_reroute table $table_reroute 197 | ip route add local default dev lo table $table_reroute 198 | fi 199 | # filter 200 | iptables -w 60 -t mangle -N TPROXY_MARK 201 | iptables -w 60 -t mangle -A TPROXY_MARK -m addrtype ! --dst-type UNICAST -j RETURN 202 | $enable_dns && iptables -w 60 -t mangle -A TPROXY_MARK -p udp --dport 53 -j MARK --set-mark $fwmark_reroute 203 | $enable_udp && iptables -w 60 -t mangle -A TPROXY_MARK -p udp -j MARK --set-mark $fwmark_reroute 204 | $enable_tcp && iptables -w 60 -t mangle -A TPROXY_MARK -p tcp -j MARK --set-mark $fwmark_reroute 205 | # cgroup 206 | iptables -w 60 -t mangle -N TPROXY_OUT 207 | iptables -w 60 -t mangle -A TPROXY_OUT -m conntrack --ctdir REPLY -j RETURN 208 | for cg in ${cgroup_noproxy[@]}; do 209 | iptables -w 60 -t mangle -A TPROXY_OUT -m cgroup --path $cg -j RETURN 210 | done 211 | for cg in ${cgroup_proxy[@]}; do 212 | iptables -w 60 -t mangle -A TPROXY_OUT -m cgroup --path $cg -j TPROXY_MARK 213 | done 214 | # hook 215 | $enable_ipv4 && iptables -w 60 -t mangle -A OUTPUT -j TPROXY_OUT 216 | 217 | ## ipv6 ######################################################################### 218 | ## mangle prerouting 219 | ip -6 rule add fwmark $fwmark_tproxy table $table_tproxy 220 | ip -6 route add local default dev lo table $table_tproxy 221 | # core 222 | ip6tables -w 60 -t mangle -N TPROXY_ENT 223 | ip6tables -w 60 -t mangle -A TPROXY_ENT -m socket -j MARK --set-mark $fwmark_tproxy 224 | ip6tables -w 60 -t mangle -A TPROXY_ENT -m socket -j ACCEPT 225 | ip6tables -w 60 -t mangle -A TPROXY_ENT -p tcp -j TPROXY --on-ip ::1 --on-port $port --tproxy-mark $fwmark_tproxy 226 | ip6tables -w 60 -t mangle -A TPROXY_ENT -p udp -j TPROXY --on-ip ::1 --on-port $port --tproxy-mark $fwmark_tproxy 227 | # filter 228 | ip6tables -w 60 -t mangle -N TPROXY_PRE 229 | ip6tables -w 60 -t mangle -A TPROXY_PRE -m addrtype --dst-type LOCAL -j RETURN 230 | ip6tables -w 60 -t mangle -A TPROXY_PRE -m addrtype ! --dst-type UNICAST -j RETURN 231 | $enable_gateway || ip6tables -w 60 -t mangle -A TPROXY_PRE -m addrtype ! --src-type LOCAL -j RETURN 232 | $enable_dns && ip6tables -w 60 -t mangle -A TPROXY_PRE -p udp --dport 53 -j TPROXY_ENT 233 | $enable_udp && ip6tables -w 60 -t mangle -A TPROXY_PRE -p udp -j TPROXY_ENT 234 | $enable_tcp && ip6tables -w 60 -t mangle -A TPROXY_PRE -p tcp -j TPROXY_ENT 235 | # hook 236 | ip6tables -w 60 -t mangle -A PREROUTING -j TPROXY_PRE 237 | 238 | ## mangle output 239 | if [ $fwmark_reroute != $fwmark_tproxy ]; then 240 | ip -6 rule add fwmark $fwmark_reroute table $table_reroute 241 | ip -6 route add local default dev lo table $table_reroute 242 | fi 243 | # filter 244 | ip6tables -w 60 -t mangle -N TPROXY_MARK 245 | ip6tables -w 60 -t mangle -A TPROXY_MARK -m addrtype ! --dst-type UNICAST -j RETURN 246 | $enable_dns && ip6tables -w 60 -t mangle -A TPROXY_MARK -p udp --dport 53 -j MARK --set-mark $fwmark_reroute 247 | $enable_udp && ip6tables -w 60 -t mangle -A TPROXY_MARK -p udp -j MARK --set-mark $fwmark_reroute 248 | $enable_tcp && ip6tables -w 60 -t mangle -A TPROXY_MARK -p tcp -j MARK --set-mark $fwmark_reroute 249 | # cgroup 250 | ip6tables -w 60 -t mangle -N TPROXY_OUT 251 | ip6tables -w 60 -t mangle -A TPROXY_OUT -m conntrack --ctdir REPLY -j RETURN 252 | for cg in ${cgroup_noproxy[@]}; do 253 | ip6tables -w 60 -t mangle -A TPROXY_OUT -m cgroup --path $cg -j RETURN 254 | done 255 | for cg in ${cgroup_proxy[@]}; do 256 | ip6tables -w 60 -t mangle -A TPROXY_OUT -m cgroup --path $cg -j TPROXY_MARK 257 | done 258 | # hook 259 | $enable_ipv6 && ip6tables -w 60 -t mangle -A OUTPUT -j TPROXY_OUT 260 | 261 | ## forward ####################################################################### 262 | if $enable_gateway; then 263 | iptables -t nat -A POSTROUTING -m owner ! --socket-exists -j MASQUERADE 264 | ip6tables -w 60 -t nat -A POSTROUTING -m owner ! --socket-exists -s fc00::/7 -j MASQUERADE # only masquerade ipv6 private address 265 | sysctl -w net.ipv4.ip_forward=1 266 | sysctl -w net.ipv6.conf.all.forwarding=1 267 | echo "iptables: gateway enabled" 268 | fi 269 | 270 | ## message for user 271 | cat << DOC 272 | iptables: noproxy cgroup: ${cgroup_noproxy[@]} 273 | iptables: proxied cgroup: ${cgroup_proxy[@]} 274 | DOC 275 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment":"For usage, see https://github.com/springzfx/cgproxy", 3 | 4 | "port": 12345, 5 | "program_noproxy": ["v2ray", "qv2ray"], 6 | "program_proxy": [], 7 | "cgroup_noproxy": ["/system.slice/v2ray.service"], 8 | "cgroup_proxy": [], 9 | "enable_gateway": false, 10 | "enable_dns": true, 11 | "enable_udp": true, 12 | "enable_tcp": true, 13 | "enable_ipv4": true, 14 | "enable_ipv6": true, 15 | "table": 10007, 16 | "fwmark": 39283 17 | } 18 | -------------------------------------------------------------------------------- /execsnoop-bcc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories(${PROJECT_SOURCE_DIR}) 2 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}) 3 | 4 | add_library(execsnoop MODULE execsnoop.cpp ../src/common.cpp) 5 | target_link_libraries(execsnoop bcc) 6 | install(TARGETS execsnoop DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}/cgproxy/ PERMISSIONS ${basic_permission}) 7 | -------------------------------------------------------------------------------- /execsnoop-bcc/execsnoop.cpp: -------------------------------------------------------------------------------- 1 | #include "execsnoop.h" 2 | #include "bcc/BPF.h" 3 | #include "common.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | using namespace std; 11 | 12 | namespace CGPROXY::EXECSNOOP { 13 | 14 | const string BPF_PROGRAM = R"( 15 | #include 16 | #include 17 | #include 18 | 19 | struct data_t { 20 | int pid; 21 | }; 22 | 23 | BPF_PERF_OUTPUT(events); 24 | 25 | int syscall_execve(struct pt_regs *ctx, 26 | const char __user *filename, 27 | const char __user *const __user *__argv, 28 | const char __user *const __user *__envp) 29 | { 30 | struct data_t data = {}; 31 | data.pid = bpf_get_current_pid_tgid(); 32 | events.perf_submit(ctx, &data, sizeof(struct data_t)); 33 | return 0; 34 | } 35 | 36 | int ret_syscall_execve(struct pt_regs *ctx){ 37 | struct data_t data = {}; 38 | data.pid = bpf_get_current_pid_tgid(); 39 | int retval = PT_REGS_RC(ctx); 40 | if (retval==0) 41 | events.perf_submit(ctx, &data, sizeof(struct data_t)); 42 | return 0; 43 | } 44 | )"; 45 | 46 | struct data_t { 47 | int pid; 48 | }; 49 | 50 | function callback = NULL; 51 | promise status; 52 | 53 | void handle_events(void *cb_cookie, void *data, int data_size) { 54 | auto event = static_cast(data); 55 | int pid = event->pid; 56 | 57 | if (callback) callback(pid); 58 | } 59 | 60 | int execsnoop() { 61 | debug("starting execsnoop"); 62 | ebpf::BPF bpf; 63 | 64 | auto init_res = bpf.init(BPF_PROGRAM); 65 | if (init_res.code() != 0) { 66 | error("bpf init failed, maybe linux-headers not installed"); 67 | std::cerr << init_res.msg() << std::endl; 68 | return 1; 69 | } 70 | 71 | string execve_fnname = bpf.get_syscall_fnname("execve"); 72 | // auto attach_res = bpf.attach_kprobe(execve_fnname, "syscall_execve"); 73 | auto attach_res = 74 | bpf.attach_kprobe(execve_fnname, "ret_syscall_execve", 0, BPF_PROBE_RETURN); 75 | if (attach_res.code() != 0) { 76 | std::cerr << attach_res.msg() << std::endl; 77 | return 1; 78 | } 79 | 80 | auto open_res = bpf.open_perf_buffer("events", &handle_events); 81 | if (open_res.code() != 0) { 82 | std::cerr << open_res.msg() << std::endl; 83 | return 1; 84 | } 85 | 86 | if (bpf.free_bcc_memory()) { 87 | std::cerr << "Failed to free llvm/clang memory" << std::endl; 88 | return 1; 89 | } 90 | 91 | status.set_value(); 92 | 93 | while (true) bpf.poll_perf_buffer("events"); 94 | 95 | return 0; 96 | } 97 | 98 | void startThread(function c, promise _status) { 99 | status = move(_status); 100 | callback = c; 101 | execsnoop(); 102 | } 103 | 104 | } // namespace CGPROXY::EXECSNOOP -------------------------------------------------------------------------------- /execsnoop-bcc/execsnoop.h: -------------------------------------------------------------------------------- 1 | #ifndef EXECSNOOP_HPP 2 | #define EXECSNOOP_HPP 1 3 | 4 | #include 5 | #include 6 | #include 7 | using namespace std; 8 | 9 | namespace CGPROXY::EXECSNOOP { 10 | 11 | extern const string BPF_PROGRAM; 12 | struct data_t; 13 | extern function callback; 14 | void handle_events(void *cb_cookie, void *data, int data_size); 15 | int execsnoop(); 16 | 17 | extern "C" void startThread(function c, promise _status); 18 | // typedef void startThread_t(function, promise); 19 | using startThread_t=decltype(startThread); 20 | startThread_t *_startThread; // only for dlsym() 21 | 22 | } // namespace CGPROXY::EXECSNOOP 23 | #endif -------------------------------------------------------------------------------- /execsnoop-bcc/readme.md: -------------------------------------------------------------------------------- 1 | - depend [bcc](https://github.com/iovisor/bcc) 2 | 3 | - huge memory usage, at least 50M -------------------------------------------------------------------------------- /execsnoop-kernel/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # find libbpf 2 | if (build_static) 3 | find_library(LIBBPF libbpf.a REQUIRED) 4 | else() 5 | find_library(LIBBPF bpf REQUIRED) 6 | endif() 7 | 8 | if (build_execsnoop_dl) 9 | add_library(execsnoop MODULE execsnoop_share.cpp) 10 | install(TARGETS execsnoop DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}/cgproxy/) 11 | target_link_libraries(execsnoop PRIVATE ${LIBBPF} -lelf -lz) 12 | else() 13 | add_library(execsnoop STATIC execsnoop_share.cpp) 14 | target_link_libraries(execsnoop PRIVATE ${LIBBPF} -l:libelf.a -l:libz.a) 15 | endif() 16 | -------------------------------------------------------------------------------- /execsnoop-kernel/aarch64/execsnoop_kern_skel.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ 2 | 3 | /* THIS FILE IS AUTOGENERATED! */ 4 | #ifndef __EXECSNOOP_KERN_SKEL_H__ 5 | #define __EXECSNOOP_KERN_SKEL_H__ 6 | 7 | #include 8 | #include 9 | 10 | struct execsnoop_kern { 11 | struct bpf_object_skeleton *skeleton; 12 | struct bpf_object *obj; 13 | struct { 14 | struct bpf_map *perf_events; 15 | struct bpf_map *records; 16 | } maps; 17 | struct { 18 | struct bpf_program *syscall_enter_execve; 19 | struct bpf_program *syscall_exit_execve; 20 | } progs; 21 | struct { 22 | struct bpf_link *syscall_enter_execve; 23 | struct bpf_link *syscall_exit_execve; 24 | } links; 25 | }; 26 | 27 | static void 28 | execsnoop_kern__destroy(struct execsnoop_kern *obj) 29 | { 30 | if (!obj) 31 | return; 32 | if (obj->skeleton) 33 | bpf_object__destroy_skeleton(obj->skeleton); 34 | free(obj); 35 | } 36 | 37 | static inline int 38 | execsnoop_kern__create_skeleton(struct execsnoop_kern *obj); 39 | 40 | static inline struct execsnoop_kern * 41 | execsnoop_kern__open_opts(const struct bpf_object_open_opts *opts) 42 | { 43 | struct execsnoop_kern *obj; 44 | 45 | obj = (typeof(obj))calloc(1, sizeof(*obj)); 46 | if (!obj) 47 | return NULL; 48 | if (execsnoop_kern__create_skeleton(obj)) 49 | goto err; 50 | if (bpf_object__open_skeleton(obj->skeleton, opts)) 51 | goto err; 52 | 53 | return obj; 54 | err: 55 | execsnoop_kern__destroy(obj); 56 | return NULL; 57 | } 58 | 59 | static inline struct execsnoop_kern * 60 | execsnoop_kern__open(void) 61 | { 62 | return execsnoop_kern__open_opts(NULL); 63 | } 64 | 65 | static inline int 66 | execsnoop_kern__load(struct execsnoop_kern *obj) 67 | { 68 | return bpf_object__load_skeleton(obj->skeleton); 69 | } 70 | 71 | static inline struct execsnoop_kern * 72 | execsnoop_kern__open_and_load(void) 73 | { 74 | struct execsnoop_kern *obj; 75 | 76 | obj = execsnoop_kern__open(); 77 | if (!obj) 78 | return NULL; 79 | if (execsnoop_kern__load(obj)) { 80 | execsnoop_kern__destroy(obj); 81 | return NULL; 82 | } 83 | return obj; 84 | } 85 | 86 | static inline int 87 | execsnoop_kern__attach(struct execsnoop_kern *obj) 88 | { 89 | return bpf_object__attach_skeleton(obj->skeleton); 90 | } 91 | 92 | static inline void 93 | execsnoop_kern__detach(struct execsnoop_kern *obj) 94 | { 95 | return bpf_object__detach_skeleton(obj->skeleton); 96 | } 97 | 98 | static inline int 99 | execsnoop_kern__create_skeleton(struct execsnoop_kern *obj) 100 | { 101 | struct bpf_object_skeleton *s; 102 | 103 | s = (typeof(s))calloc(1, sizeof(*s)); 104 | if (!s) 105 | return -1; 106 | obj->skeleton = s; 107 | 108 | s->sz = sizeof(*s); 109 | s->name = "execsnoop_kern"; 110 | s->obj = &obj->obj; 111 | 112 | /* maps */ 113 | s->map_cnt = 2; 114 | s->map_skel_sz = sizeof(*s->maps); 115 | s->maps = (typeof(s->maps))calloc(s->map_cnt, s->map_skel_sz); 116 | if (!s->maps) 117 | goto err; 118 | 119 | s->maps[0].name = "perf_events"; 120 | s->maps[0].map = &obj->maps.perf_events; 121 | 122 | s->maps[1].name = "records"; 123 | s->maps[1].map = &obj->maps.records; 124 | 125 | /* programs */ 126 | s->prog_cnt = 2; 127 | s->prog_skel_sz = sizeof(*s->progs); 128 | s->progs = (typeof(s->progs))calloc(s->prog_cnt, s->prog_skel_sz); 129 | if (!s->progs) 130 | goto err; 131 | 132 | s->progs[0].name = "syscall_enter_execve"; 133 | s->progs[0].prog = &obj->progs.syscall_enter_execve; 134 | s->progs[0].link = &obj->links.syscall_enter_execve; 135 | 136 | s->progs[1].name = "syscall_exit_execve"; 137 | s->progs[1].prog = &obj->progs.syscall_exit_execve; 138 | s->progs[1].link = &obj->links.syscall_exit_execve; 139 | 140 | s->data_sz = 2024; 141 | s->data = (void *)"\ 142 | \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\ 143 | \0\0\0\0\0\0\0\0\0\0\0\x28\x05\0\0\0\0\0\0\0\0\0\0\x40\0\0\0\0\0\x40\0\x0b\0\ 144 | \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\ 145 | \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\ 146 | \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\ 147 | \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\ 148 | \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\ 149 | \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\ 150 | \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\ 151 | \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\ 152 | \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\ 153 | \0\x63\x18\x10\0\0\0\0\0\x85\0\0\0\x23\0\0\0\x07\0\0\0\x78\x04\0\0\xbf\xa1\0\0\ 154 | \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\ 155 | \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\ 156 | \x6c\x04\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\ 157 | \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\ 158 | \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\ 159 | \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\ 160 | \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\ 161 | \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\ 162 | \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\ 163 | \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\ 164 | \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\ 165 | \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\ 166 | \0\x28\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\ 167 | \0\x06\x07\x05\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\xac\0\0\0\x04\ 168 | \0\xf1\xff\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\xdb\0\0\0\0\0\x03\0\x70\x01\0\0\0\0\ 169 | \0\0\0\0\0\0\0\0\0\0\xd4\0\0\0\0\0\x05\0\xc0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\xcd\ 170 | \0\0\0\0\0\x05\0\xe8\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\ 171 | \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\ 172 | \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\ 173 | \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\ 174 | \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\ 175 | \x70\0\0\0\0\0\0\0\x01\0\0\0\x08\0\0\0\xb8\0\0\0\0\0\0\0\x01\0\0\0\x08\0\0\0\ 176 | \x28\0\0\0\0\0\0\0\x01\0\0\0\x08\0\0\0\x88\0\0\0\0\0\0\0\x01\0\0\0\x07\0\0\0\ 177 | \xd0\0\0\0\0\0\0\0\x01\0\0\0\x08\0\0\0\0\x2e\x74\x65\x78\x74\0\x70\x65\x72\x66\ 178 | \x5f\x65\x76\x65\x6e\x74\x73\0\x6d\x61\x70\x73\0\x72\x65\x63\x6f\x72\x64\x73\0\ 179 | \x5f\x76\x65\x72\x73\x69\x6f\x6e\0\x2e\x72\x65\x6c\x74\x72\x61\x63\x65\x70\x6f\ 180 | \x69\x6e\x74\x2f\x73\x79\x73\x63\x61\x6c\x6c\x73\x2f\x73\x79\x73\x5f\x65\x78\ 181 | \x69\x74\x5f\x65\x78\x65\x63\x76\x65\0\x73\x79\x73\x63\x61\x6c\x6c\x5f\x65\x78\ 182 | \x69\x74\x5f\x65\x78\x65\x63\x76\x65\0\x2e\x72\x65\x6c\x74\x72\x61\x63\x65\x70\ 183 | \x6f\x69\x6e\x74\x2f\x73\x79\x73\x63\x61\x6c\x6c\x73\x2f\x73\x79\x73\x5f\x65\ 184 | \x6e\x74\x65\x72\x5f\x65\x78\x65\x63\x76\x65\0\x73\x79\x73\x63\x61\x6c\x6c\x5f\ 185 | \x65\x6e\x74\x65\x72\x5f\x65\x78\x65\x63\x76\x65\0\x5f\x6c\x69\x63\x65\x6e\x73\ 186 | \x65\0\x65\x78\x65\x63\x73\x6e\x6f\x6f\x70\x5f\x6b\x65\x72\x6e\x2e\x63\0\x2e\ 187 | \x73\x74\x72\x74\x61\x62\0\x2e\x73\x79\x6d\x74\x61\x62\0\x4c\x42\x42\x31\x5f\ 188 | \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\ 189 | \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\ 190 | \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\xbd\0\0\0\x03\0\0\0\0\0\0\0\0\0\0\0\ 191 | \0\0\0\0\0\0\0\0\x40\x04\0\0\0\0\0\0\xe2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\ 192 | \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\ 193 | \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\ 194 | \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\ 195 | \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\ 196 | \0\0\x09\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\xf0\x03\0\0\0\0\0\0\x20\0\0\0\0\ 197 | \0\0\0\x0a\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\ 198 | \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\ 199 | \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\ 200 | \0\0\0\0\0\0\0\0\0\0\0\x10\x04\0\0\0\0\0\0\x30\0\0\0\0\0\0\0\x0a\0\0\0\x05\0\0\ 201 | \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\ 202 | \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\ 203 | \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\ 204 | \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\ 205 | \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\ 206 | \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\xc5\ 207 | \0\0\0\x02\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\xe8\x02\0\0\0\0\0\0\x08\x01\0\ 208 | \0\0\0\0\0\x01\0\0\0\x05\0\0\0\x08\0\0\0\0\0\0\0\x18\0\0\0\0\0\0\0"; 209 | 210 | return 0; 211 | err: 212 | bpf_object__destroy_skeleton(s); 213 | return -1; 214 | } 215 | 216 | #endif /* __EXECSNOOP_KERN_SKEL_H__ */ 217 | -------------------------------------------------------------------------------- /execsnoop-kernel/arm64.md: -------------------------------------------------------------------------------- 1 | ## Cross Compile 2 | 3 | ```bash 4 | docker pull debian:buster 5 | 6 | # in container 7 | dpkg --add-architecture arm64 8 | apt update 9 | apt install gcc-8-aarch64-linux-gnu # cross compile toolchain 10 | apt install libelf-dev:arm64 # target depency library 11 | ... 12 | ``` 13 | 14 | ## Emulation 15 | 16 | ### Register qemu-user-static 17 | 18 | - before register binfmt 19 | 20 | ```bash 21 | docker run --rm -t arm64/ubuntu uname -m 22 | ``` 23 | 24 | - register binfmt 25 | 26 | ```bash 27 | # through docker 28 | docker run --rm --privileged multiarch/qemu-user-static --reset -p yes 29 | # or through systemd 30 | pacman -S qemu-user-static binfmt-qemu-static 31 | systemctl restart systemd-binfmt.service 32 | # test 33 | cat /proc/sys/fs/binfmt_misc/qemu-aarch64 34 | ``` 35 | 36 | - after register binfmt 37 | 38 | ```bash 39 | docker run --rm -t arm64/ubuntu uname -m 40 | ``` 41 | 42 | ### M1: Docker 43 | 44 | ```bash 45 | # start container background 46 | docker run -dit --name arm64 -v /home/fancy/workspace-xps:/data arm64v8/ubuntu 47 | 48 | # enter container 49 | docker exec -it arm64 bash 50 | ``` 51 | 52 | ### M2: Chroot 53 | 54 | download image [ubuntu-base-20.04-base-arm64.tar.gz](http://cdimage.ubuntu.com/ubuntu-base/releases/20.04/release/ubuntu-base-20.04-base-arm64.tar.gz), extract and chroot to it. 55 | 56 | ```bash 57 | sudo arch-chroot ubuntu-base-20.04-base-arm64 58 | ``` 59 | 60 | ### Refer 61 | 62 | - https://www.stereolabs.com/docs/docker/building-arm-container-on-x86/ 63 | 64 | - https://github.com/junaruga/fedora-workshop-multiarch/blob/master/slides/Lets-add-Fedora-multiarch-to-CI.pdf 65 | 66 | - https://wiki.debian.org/QemuUserEmulation 67 | 68 | ### Compile 69 | 70 | ready some depencies. 71 | 72 | ```bash 73 | # maybe repository: https://mirrors.tuna.tsinghua.edu.cn/help/ubuntu/ 74 | # install in container for kernel bpf build 75 | apt install dialog apt-utils 76 | apt install build-essential gcc clang llvm 77 | apt install bison flex bc rsync libssl-dev binutils-dev libreadline-dev libelf-dev 78 | apt install make cmake 79 | # for cgproxy 80 | apt install nlohmann-json3-dev rpm 81 | ``` 82 | 83 | -------------------------------------------------------------------------------- /execsnoop-kernel/execsnoop_kern.c: -------------------------------------------------------------------------------- 1 | #ifdef USE_VMLINUX 2 | #include "vmlinux.h" 3 | #else 4 | #include "linux/sched.h" 5 | #include 6 | #include 7 | #endif 8 | #include 9 | #include 10 | #include 11 | 12 | #define TASK_COMM_LEN 16 13 | struct event { 14 | char comm[TASK_COMM_LEN]; 15 | pid_t pid; 16 | pid_t tgid; 17 | pid_t ppid; 18 | uid_t uid; 19 | }; 20 | 21 | /* /sys/kernel/debug/tracing/events/syscalls/sys_enter_execve/format */ 22 | struct syscalls_enter_execve_args { 23 | __u64 unused; 24 | int syscall_nr; 25 | const char filename_ptr; 26 | const char *const * argv; 27 | const char *const * envp; 28 | }; 29 | 30 | /* /sys/kernel/debug/tracing/events/syscalls/sys_exit_execve/format */ 31 | struct syscalls_exit_execve_args { 32 | __u64 unused; 33 | int syscall_nr; 34 | long ret; 35 | }; 36 | 37 | struct bpf_map_def SEC("maps") records = { 38 | .type = BPF_MAP_TYPE_HASH, 39 | .key_size = sizeof(pid_t), 40 | .value_size = sizeof(struct event), 41 | .max_entries = 10240, 42 | }; 43 | struct bpf_map_def SEC("maps") perf_events = { 44 | .type = BPF_MAP_TYPE_PERF_EVENT_ARRAY, 45 | .key_size = sizeof(u32), 46 | .value_size = sizeof(u32), 47 | .max_entries = 128, 48 | }; 49 | 50 | SEC("tracepoint/syscalls/sys_enter_execve") 51 | int syscall_enter_execve(struct syscalls_enter_execve_args *ctx){ 52 | pid_t pid, tgid; uid_t uid; 53 | struct event *event; 54 | struct task_struct *task, *task_p; 55 | 56 | u64 id = bpf_get_current_pid_tgid(); 57 | pid = (pid_t)id; 58 | tgid = id >> 32; 59 | uid = (u32)bpf_get_current_uid_gid(); 60 | 61 | struct event empty_event={}; 62 | if (bpf_map_update_elem(&records, &pid, &empty_event, BPF_NOEXIST)!=0) return 0; 63 | event = bpf_map_lookup_elem(&records, &pid); 64 | if (!event) return 0; 65 | 66 | event->pid = pid; 67 | event->tgid = tgid; 68 | event->uid = uid; 69 | 70 | /* ppid is not reliable here, normal in arch, but become 0 in ubuntu 20.04 */ 71 | task = (struct task_struct*)bpf_get_current_task(); 72 | bpf_probe_read(&task_p, sizeof(struct task_struct*),&task->real_parent); 73 | bpf_probe_read(&event->ppid,sizeof(pid_t),&task_p->tgid); 74 | 75 | return 0; 76 | } 77 | 78 | SEC("tracepoint/syscalls/sys_exit_execve") 79 | int syscall_exit_execve(struct syscalls_exit_execve_args *ctx){ 80 | pid_t pid; 81 | struct event *event; 82 | 83 | pid = (pid_t)bpf_get_current_pid_tgid(); 84 | event = bpf_map_lookup_elem(&records,&pid); 85 | if (!event) return 0; 86 | if (ctx->ret < 0) goto cleanup; 87 | 88 | /* get comm */ 89 | bpf_get_current_comm(&event->comm,sizeof(event->comm)); 90 | bpf_perf_event_output(ctx, &perf_events, BPF_F_CURRENT_CPU, event, sizeof(struct event)); 91 | cleanup: 92 | bpf_map_delete_elem(&records, &pid); 93 | return 0; 94 | } 95 | 96 | char _license[] SEC("license") = "GPL"; 97 | u32 _version SEC("version") = LINUX_VERSION_CODE; 98 | -------------------------------------------------------------------------------- /execsnoop-kernel/execsnoop_share.cpp: -------------------------------------------------------------------------------- 1 | #include "execsnoop_share.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #if defined(__x86_64__) 9 | #include "x86_64/execsnoop_kern_skel.h" 10 | #elif defined(__aarch64__) 11 | #include "aarch64/execsnoop_kern_skel.h" 12 | #endif 13 | 14 | namespace CGPROXY::EXECSNOOP { 15 | 16 | #define PERF_BUFFER_PAGES 64 17 | #define TASK_COMM_LEN 16 18 | struct event { 19 | char comm[TASK_COMM_LEN]; 20 | pid_t pid; 21 | pid_t tgid; 22 | pid_t ppid; 23 | uid_t uid; 24 | }; 25 | 26 | function callback = NULL; 27 | promise status; 28 | 29 | static void handle_event(void *ctx, int cpu, void *data, __u32 size) { 30 | auto e = static_cast(data); 31 | if (callback) callback(e->pid); 32 | } 33 | 34 | void handle_lost_events(void *ctx, int cpu, __u64 lost_cnt) { 35 | fprintf(stderr, "Lost %llu events on CPU #%d!\n", lost_cnt, cpu); 36 | } 37 | 38 | int bump_memlock_rlimit(void) { 39 | struct rlimit rlim_new = { RLIM_INFINITY, RLIM_INFINITY }; 40 | return setrlimit(RLIMIT_MEMLOCK, &rlim_new); 41 | } 42 | 43 | int execsnoop() { 44 | struct perf_buffer_opts pb_opts = {}; 45 | struct perf_buffer *pb; 46 | int err; 47 | bool notified=false; 48 | 49 | err = bump_memlock_rlimit(); 50 | if (err) { 51 | fprintf(stderr, "failed to increase rlimit: %d\n", err); 52 | return 1; 53 | } 54 | 55 | struct execsnoop_kern *obj=execsnoop_kern__open_and_load(); 56 | if (!obj) { 57 | fprintf(stderr, "failed to open and/or load BPF object\n"); 58 | return 1; 59 | } 60 | 61 | err = execsnoop_kern__attach(obj); 62 | if (err) { 63 | fprintf(stderr, "failed to attach BPF programs\n"); 64 | return err; 65 | } 66 | 67 | main_loop: 68 | pb_opts.sample_cb = handle_event; 69 | pb_opts.lost_cb = handle_lost_events; 70 | pb = perf_buffer__new(bpf_map__fd(obj->maps.perf_events), PERF_BUFFER_PAGES, &pb_opts); 71 | err = libbpf_get_error(pb); 72 | if (err) { 73 | printf("failed to setup perf_buffer: %d\n", err); 74 | return 1; 75 | } 76 | 77 | // notify 78 | if (!notified) {status.set_value(); notified=true;} 79 | 80 | while ((err = perf_buffer__poll(pb, -1)) >= 0) {} 81 | perf_buffer__free(pb); 82 | /* handle Interrupted system call when sleep */ 83 | if (err == -EINTR) goto main_loop; 84 | 85 | perror("perf_buffer__poll"); 86 | kill(0, SIGINT); 87 | return err; 88 | } 89 | 90 | void startThread(function c, promise _status) { 91 | status = move(_status); 92 | callback = c; 93 | execsnoop(); 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /execsnoop-kernel/execsnoop_share.h: -------------------------------------------------------------------------------- 1 | #ifndef EXECSNOOP_SHARE_HPP 2 | #define EXECSNOOP_SHARE_HPP 1 3 | 4 | #include 5 | #include 6 | #include 7 | using namespace std; 8 | 9 | namespace CGPROXY::EXECSNOOP { 10 | 11 | extern "C" void startThread(function c, promise _status); 12 | 13 | #ifdef BUIlD_EXECSNOOP_DL 14 | // only for dlsym() 15 | using startThread_t=decltype(startThread); 16 | startThread_t *_startThread; 17 | #endif 18 | 19 | } // namespace CGPROXY::EXECSNOOP 20 | #endif 21 | -------------------------------------------------------------------------------- /execsnoop-kernel/execsnoop_user.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include "bpf_load.h" 5 | 6 | #define TASK_COMM_LEN 16 7 | struct event { 8 | char comm[TASK_COMM_LEN]; 9 | pid_t pid; 10 | pid_t tgid; 11 | pid_t ppid; 12 | uid_t uid; 13 | }; 14 | static void print_bpf_output(void *ctx, int cpu, void *data, __u32 size) { 15 | struct event *e=data; 16 | printf("comm: %s, pid: %d, tgid: %d, ppid: %d, uid: %d\n",e->comm,e->pid,e->tgid,e->ppid,e->uid); 17 | } 18 | 19 | int main(int argc, char **argv) { 20 | struct perf_buffer_opts pb_opts = {}; 21 | struct perf_buffer *pb; 22 | int ret; 23 | 24 | if (load_bpf_file("execsnoop_kern.o")!=0) { 25 | printf("%s", bpf_log_buf); 26 | return 1; 27 | } 28 | 29 | pb_opts.sample_cb = print_bpf_output; 30 | pb = perf_buffer__new(map_fd[1], 8, &pb_opts); 31 | ret = libbpf_get_error(pb); 32 | if (ret) { 33 | printf("failed to setup perf_buffer: %d\n", ret); 34 | return 1; 35 | } 36 | 37 | while ((ret = perf_buffer__poll(pb, -1)) >= 0) {} 38 | kill(0, SIGINT); 39 | return ret; 40 | } 41 | -------------------------------------------------------------------------------- /execsnoop-kernel/execsnoop_user_1.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #if defined(__x86_64__) 6 | #include "x86_64/execsnoop_kern_skel.h" 7 | #elif defined(__aarch64__) 8 | #include "aarch64/execsnoop_kern_skel.h" 9 | #endif 10 | 11 | #define TASK_COMM_LEN 16 12 | struct event { 13 | char comm[TASK_COMM_LEN]; 14 | pid_t pid; 15 | pid_t tgid; 16 | pid_t ppid; 17 | uid_t uid; 18 | }; 19 | 20 | #define PERF_BUFFER_PAGES 64 21 | 22 | static void print_bpf_output(void *ctx, int cpu, void *data, __u32 size) { 23 | struct event *e=data; 24 | printf("comm: %s, pid: %d, tgid: %d, ppid: %d, uid: %d\n",e->comm,e->pid,e->tgid,e->ppid,e->uid); 25 | } 26 | 27 | int bump_memlock_rlimit(void) 28 | { 29 | struct rlimit rlim_new = { 30 | .rlim_cur = RLIM_INFINITY, 31 | .rlim_max = RLIM_INFINITY, 32 | }; 33 | 34 | return setrlimit(RLIMIT_MEMLOCK, &rlim_new); 35 | } 36 | 37 | 38 | int main(int argc, char **argv) { 39 | struct perf_buffer_opts pb_opts = {}; 40 | struct perf_buffer *pb; 41 | int err; 42 | 43 | err = bump_memlock_rlimit(); 44 | if (err) { 45 | fprintf(stderr, "failed to increase rlimit: %d\n", err); 46 | return 1; 47 | } 48 | 49 | struct execsnoop_kern *obj=execsnoop_kern__open_and_load(); 50 | if (!obj) { 51 | fprintf(stderr, "failed to open and/or load BPF object\n"); 52 | return 1; 53 | } 54 | 55 | err = execsnoop_kern__attach(obj); 56 | if (err) { 57 | fprintf(stderr, "failed to attach BPF programs\n"); 58 | return err; 59 | } 60 | 61 | pb_opts.sample_cb = print_bpf_output; 62 | pb = perf_buffer__new(bpf_map__fd(obj->maps.perf_events), PERF_BUFFER_PAGES, &pb_opts); 63 | err = libbpf_get_error(pb); 64 | if (err) { 65 | printf("failed to setup perf_buffer: %d\n", err); 66 | return 1; 67 | } 68 | 69 | while ((err = perf_buffer__poll(pb, -1)) >= 0) {} 70 | kill(0, SIGINT); 71 | return err; 72 | } 73 | -------------------------------------------------------------------------------- /execsnoop-kernel/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ## Prons and cons 3 | 4 | - use stable execve tracepoint, so build once and should work everywhere 5 | - build in kernel tree or build with VMLINUX 6 | 7 | 8 | 9 | ## Build `execsnoop_kern.o` 10 | 11 | ### M1: Build in kernel tree 12 | 13 | - download kernel source code 14 | - ready and config kernel tree 15 | 16 | ```bash 17 | # kernel config 18 | #gunzip -c /proc/config.gz > .config 19 | #make oldconfig && make prepare 20 | make defconfig && make prepare 21 | # install headers to ./usr/include 22 | make headers_install -j8 23 | # build samples/bpf 24 | make M=samples/bpf -j8 25 | # build bpftool 26 | make tools/bpf -j8 27 | ``` 28 | 29 | - put or link `execsnoop_kern.c` and `execsnoop_user.c` to *samples/bpf/* 30 | - edit *samples/bpf/makefile* 31 | 32 | ```makefile 33 | # in samples/bpf/makefile 34 | tprogs-y += execsnoop 35 | execsnoop-objs := bpf_load.o execsnoop_user.o $(TRACE_HELPERS) 36 | always-y += execsnoop_kern.o 37 | ``` 38 | 39 | - compile again 40 | 41 | ``` 42 | make M=samples/bpf -j8 43 | ``` 44 | 45 | - run test 46 | 47 | ```bash 48 | cd samples/bpf 49 | sudo bash -c "ulimit -l unlimited && ./execsnoop" 50 | ``` 51 | 52 | 53 | **Detail build command** 54 | 55 | using `make V=1 M=samples/bpf | tee -a log.txt` to get and filter following command 56 | 57 | - build `execsnoop_kern.o` 58 | 59 | 60 | ```bash 61 | clang -nostdinc \ 62 | -isystem /usr/lib/gcc/x86_64-pc-linux-gnu/10.1.0/include \ 63 | -I./arch/x86/include \ 64 | -I./arch/x86/include/generated \ 65 | -I./include \ 66 | -I./arch/x86/include/uapi \ 67 | -I./arch/x86/include/generated/uapi \ 68 | -I./include/uapi \ 69 | -I./include/generated/uapi \ 70 | -include ./include/linux/kconfig.h \ 71 | -I./samples/bpf \ 72 | -I./tools/testing/selftests/bpf/ \ 73 | -I./tools/lib/ \ 74 | -include asm_goto_workaround.h \ 75 | -D__KERNEL__ -D__BPF_TRACING__ -Wno-unused-value -Wno-pointer-sign \ 76 | -D__TARGET_ARCH_x86 -Wno-compare-distinct-pointer-types \ 77 | -Wno-gnu-variable-sized-type-not-at-end \ 78 | -Wno-address-of-packed-member -Wno-tautological-compare \ 79 | -Wno-unknown-warning-option \ 80 | -fno-stack-protector \ 81 | -O2 -emit-llvm -c samples/bpf/execsnoop_kern.c \ 82 | -o - | llc -march=bpf -filetype=obj -o samples/bpf/execsnoop_kern.o 83 | ``` 84 | 85 | 86 | 87 | ### M2: Build with VMLINUX 88 | 89 | - get `vmlinux.h` 90 | 91 | ``` 92 | bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h 93 | ``` 94 | 95 | - compile 96 | 97 | note `-g` is needed if with BPF CO-RE 98 | 99 | ``` 100 | clang -O2 -target bpf -DUSE_VMLINUX -c execsnoop_kern.c -o execsnoop_kern.o 101 | ``` 102 | 103 | ## Generate `execsnoop_kern_skel.h` 104 | 105 | - generate `execsnoop_kern_skel.h` 106 | 107 | ``` 108 | bpftool gen skeleton execsnoop_kern.o > execsnoop_kern_skel.h 109 | ``` 110 | 111 | - build execsnoop 112 | 113 | ``` 114 | gcc -Wall -O2 execsnoop_user_1.c -o execsnoop -lbpf 115 | ``` 116 | 117 | 118 | 119 | ## Multiarch build 120 | 121 | - Cross compile, fast, but library link can be mess 122 | - aarch64-linux-gnu-gcc 123 | - Emulation, the easist, but with perfomance cost 124 | - qemu-user-static + binfmt-qemu-static + docker/chroot 125 | - see `arm64.md` to see how to setup 126 | 127 | 128 | 129 | ```bash 130 | # if cross compile 131 | export ARCH=arm64 132 | export CROSS_COMPILE=aarch64-linux-gnu- 133 | export SYSROOT=/home/fancy/workspace-xps/linux/ArchLinuxARM-aarch64-latest 134 | export C_INCLUDE_PATH=$SYSROOT/usr/include 135 | ``` 136 | 137 | - edit `tools/lib/bpf/makefile` #192 to: 138 | 139 | ```makefile 140 | $(OUTPUT)libbpf.so.$(LIBBPF_VERSION): $(BPF_IN_SHARED) 141 | $(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) 142 | ``` 143 | 144 | - make 145 | 146 | ```bash 147 | # clean 148 | make mrproper 149 | make clean 150 | make -C tools clean 151 | make -C samples/bpf clean 152 | # make 153 | make defconfig && make prepare 154 | make headers_install -j8 155 | # build samples/bpf 156 | make M=samples/bpf -j8 157 | # build bpftool 158 | make tools/bpf -j8 159 | ``` 160 | 161 | - detail build `execsnoop_kern.o` 162 | 163 | 164 | ```bash 165 | clang -nostdinc \ 166 | -isystem /usr/lib/gcc/aarch64-linux-gnu/9/include \ 167 | -I./arch/arm64/include -I./arch/arm64/include/generated \ 168 | -I./include -I./arch/arm64/include/uapi \ 169 | -I./arch/arm64/include/generated/uapi \ 170 | -I./include/uapi \ 171 | -I./include/generated/uapi \ 172 | -include ./include/linux/kconfig.h \ 173 | -I./samples/bpf \ 174 | -I./tools/testing/selftests/bpf/ \ 175 | -I./tools/lib/ \ 176 | -include asm_goto_workaround.h \ 177 | -D__KERNEL__ -D__BPF_TRACING__ -Wno-unused-value -Wno-pointer-sign \ 178 | -D__TARGET_ARCH_arm64 -Wno-compare-distinct-pointer-types \ 179 | -Wno-gnu-variable-sized-type-not-at-end \ 180 | -Wno-address-of-packed-member -Wno-tautological-compare \ 181 | -Wno-unknown-warning-option \ 182 | -fno-stack-protector \ 183 | -O2 -emit-llvm -c samples/bpf/execsnoop_kern.c \ 184 | -o -| llc -march=bpf -filetype=obj -o samples/bpf/execsnoop_kern.o 185 | ``` 186 | 187 | - generate 188 | 189 | ``` 190 | bpftool gen skeleton execsnoop_kern.o > aarch64/execsnoop_kern_skel.h 191 | ``` 192 | 193 | 194 | 195 | ## Refer 196 | 197 | - [A thorough introduction to eBPF](https://lwn.net/Articles/740157/) 198 | 199 | -------------------------------------------------------------------------------- /execsnoop-kernel/x86_64/execsnoop_kern_skel.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ 2 | 3 | /* THIS FILE IS AUTOGENERATED! */ 4 | #ifndef __EXECSNOOP_KERN_SKEL_H__ 5 | #define __EXECSNOOP_KERN_SKEL_H__ 6 | 7 | #include 8 | #include 9 | 10 | struct execsnoop_kern { 11 | struct bpf_object_skeleton *skeleton; 12 | struct bpf_object *obj; 13 | struct { 14 | struct bpf_map *perf_events; 15 | struct bpf_map *records; 16 | } maps; 17 | struct { 18 | struct bpf_program *syscall_enter_execve; 19 | struct bpf_program *syscall_exit_execve; 20 | } progs; 21 | struct { 22 | struct bpf_link *syscall_enter_execve; 23 | struct bpf_link *syscall_exit_execve; 24 | } links; 25 | }; 26 | 27 | static void 28 | execsnoop_kern__destroy(struct execsnoop_kern *obj) 29 | { 30 | if (!obj) 31 | return; 32 | if (obj->skeleton) 33 | bpf_object__destroy_skeleton(obj->skeleton); 34 | free(obj); 35 | } 36 | 37 | static inline int 38 | execsnoop_kern__create_skeleton(struct execsnoop_kern *obj); 39 | 40 | static inline struct execsnoop_kern * 41 | execsnoop_kern__open_opts(const struct bpf_object_open_opts *opts) 42 | { 43 | struct execsnoop_kern *obj; 44 | 45 | obj = (typeof(obj))calloc(1, sizeof(*obj)); 46 | if (!obj) 47 | return NULL; 48 | if (execsnoop_kern__create_skeleton(obj)) 49 | goto err; 50 | if (bpf_object__open_skeleton(obj->skeleton, opts)) 51 | goto err; 52 | 53 | return obj; 54 | err: 55 | execsnoop_kern__destroy(obj); 56 | return NULL; 57 | } 58 | 59 | static inline struct execsnoop_kern * 60 | execsnoop_kern__open(void) 61 | { 62 | return execsnoop_kern__open_opts(NULL); 63 | } 64 | 65 | static inline int 66 | execsnoop_kern__load(struct execsnoop_kern *obj) 67 | { 68 | return bpf_object__load_skeleton(obj->skeleton); 69 | } 70 | 71 | static inline struct execsnoop_kern * 72 | execsnoop_kern__open_and_load(void) 73 | { 74 | struct execsnoop_kern *obj; 75 | 76 | obj = execsnoop_kern__open(); 77 | if (!obj) 78 | return NULL; 79 | if (execsnoop_kern__load(obj)) { 80 | execsnoop_kern__destroy(obj); 81 | return NULL; 82 | } 83 | return obj; 84 | } 85 | 86 | static inline int 87 | execsnoop_kern__attach(struct execsnoop_kern *obj) 88 | { 89 | return bpf_object__attach_skeleton(obj->skeleton); 90 | } 91 | 92 | static inline void 93 | execsnoop_kern__detach(struct execsnoop_kern *obj) 94 | { 95 | return bpf_object__detach_skeleton(obj->skeleton); 96 | } 97 | 98 | static inline int 99 | execsnoop_kern__create_skeleton(struct execsnoop_kern *obj) 100 | { 101 | struct bpf_object_skeleton *s; 102 | 103 | s = (typeof(s))calloc(1, sizeof(*s)); 104 | if (!s) 105 | return -1; 106 | obj->skeleton = s; 107 | 108 | s->sz = sizeof(*s); 109 | s->name = "execsnoop_kern"; 110 | s->obj = &obj->obj; 111 | 112 | /* maps */ 113 | s->map_cnt = 2; 114 | s->map_skel_sz = sizeof(*s->maps); 115 | s->maps = (typeof(s->maps))calloc(s->map_cnt, s->map_skel_sz); 116 | if (!s->maps) 117 | goto err; 118 | 119 | s->maps[0].name = "perf_events"; 120 | s->maps[0].map = &obj->maps.perf_events; 121 | 122 | s->maps[1].name = "records"; 123 | s->maps[1].map = &obj->maps.records; 124 | 125 | /* programs */ 126 | s->prog_cnt = 2; 127 | s->prog_skel_sz = sizeof(*s->progs); 128 | s->progs = (typeof(s->progs))calloc(s->prog_cnt, s->prog_skel_sz); 129 | if (!s->progs) 130 | goto err; 131 | 132 | s->progs[0].name = "syscall_enter_execve"; 133 | s->progs[0].prog = &obj->progs.syscall_enter_execve; 134 | s->progs[0].link = &obj->links.syscall_enter_execve; 135 | 136 | s->progs[1].name = "syscall_exit_execve"; 137 | s->progs[1].prog = &obj->progs.syscall_exit_execve; 138 | s->progs[1].link = &obj->links.syscall_exit_execve; 139 | 140 | s->data_sz = 2320; 141 | s->data = (void *)"\ 142 | \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\ 143 | \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\ 144 | \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\ 145 | \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\ 146 | \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\ 147 | \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\ 148 | \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\ 149 | \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\ 150 | \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\ 151 | \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\ 152 | \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\ 153 | \0\x63\x18\x10\0\0\0\0\0\x85\0\0\0\x23\0\0\0\x07\0\0\0\xa0\x04\0\0\xbf\xa1\0\0\ 154 | \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\ 155 | \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\ 156 | \x94\x04\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\ 157 | \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\ 158 | \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\ 159 | \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\ 160 | \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\ 161 | \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\ 162 | \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\ 163 | \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\ 164 | \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\ 165 | \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\ 166 | \0\x28\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\ 167 | \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\ 168 | \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\ 169 | \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\ 170 | \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\ 171 | \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\ 172 | \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\ 173 | \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\ 174 | \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\ 175 | \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\ 176 | \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\ 177 | \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\ 178 | \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\ 179 | \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\ 180 | \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\ 181 | \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\ 182 | \0\x01\0\0\0\x06\0\0\0\0\x2e\x74\x65\x78\x74\0\x70\x65\x72\x66\x5f\x65\x76\x65\ 183 | \x6e\x74\x73\0\x6d\x61\x70\x73\0\x72\x65\x63\x6f\x72\x64\x73\0\x5f\x76\x65\x72\ 184 | \x73\x69\x6f\x6e\0\x2e\x72\x65\x6c\x74\x72\x61\x63\x65\x70\x6f\x69\x6e\x74\x2f\ 185 | \x73\x79\x73\x63\x61\x6c\x6c\x73\x2f\x73\x79\x73\x5f\x65\x78\x69\x74\x5f\x65\ 186 | \x78\x65\x63\x76\x65\0\x73\x79\x73\x63\x61\x6c\x6c\x5f\x65\x78\x69\x74\x5f\x65\ 187 | \x78\x65\x63\x76\x65\0\x2e\x72\x65\x6c\x74\x72\x61\x63\x65\x70\x6f\x69\x6e\x74\ 188 | \x2f\x73\x79\x73\x63\x61\x6c\x6c\x73\x2f\x73\x79\x73\x5f\x65\x6e\x74\x65\x72\ 189 | \x5f\x65\x78\x65\x63\x76\x65\0\x73\x79\x73\x63\x61\x6c\x6c\x5f\x65\x6e\x74\x65\ 190 | \x72\x5f\x65\x78\x65\x63\x76\x65\0\x5f\x6c\x69\x63\x65\x6e\x73\x65\0\x2e\x72\ 191 | \x65\x6c\x2e\x65\x68\x5f\x66\x72\x61\x6d\x65\0\x65\x78\x65\x63\x73\x6e\x6f\x6f\ 192 | \x70\x5f\x6b\x65\x72\x6e\x2e\x63\0\x2e\x73\x74\x72\x74\x61\x62\0\x2e\x73\x79\ 193 | \x6d\x74\x61\x62\0\x4c\x42\x42\x31\x5f\x34\0\x4c\x42\x42\x31\x5f\x33\0\x4c\x42\ 194 | \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\ 195 | \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\ 196 | \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\ 197 | \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\ 198 | \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\ 199 | \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\ 200 | \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\ 201 | \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\ 202 | \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\ 203 | \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\ 204 | \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\ 205 | \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\ 206 | \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\ 207 | \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\ 208 | \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\ 209 | \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\ 210 | \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\ 211 | \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\ 212 | \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\ 213 | \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\ 214 | \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\ 215 | \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\ 216 | \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\ 217 | \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"; 218 | 219 | return 0; 220 | err: 221 | bpf_object__destroy_skeleton(s); 222 | return -1; 223 | } 224 | 225 | #endif /* __EXECSNOOP_KERN_SKEL_H__ */ 226 | -------------------------------------------------------------------------------- /execsnoop-libbpf/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_library(LIBBPF bpf REQUIRED) 2 | 3 | # generate execsnoop.skel.h 4 | if (EXISTS /sys/kernel/btf/vmlinux) 5 | add_custom_command(OUTPUT execsnoop.skel.h 6 | COMMAND bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h 7 | COMMAND clang -O2 -g -target bpf -c execsnoop.bpf.c -o execsnoop.bpf.o 8 | COMMAND bpftool gen skeleton execsnoop.bpf.o > execsnoop.skel.h 9 | DEPENDS execsnoop.bpf.c 10 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 11 | ) 12 | endif() 13 | 14 | if (build_execsnoop_dl) 15 | add_library(execsnoop MODULE execsnoop_share.cpp execsnoop.skel.h) 16 | target_link_libraries(execsnoop PRIVATE ${LIBBPF}) 17 | install(TARGETS execsnoop DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}/cgproxy/) 18 | else() 19 | add_library(execsnoop execsnoop_share.cpp execsnoop.skel.h) 20 | target_link_libraries(execsnoop PRIVATE ${LIBBPF}) 21 | endif() 22 | -------------------------------------------------------------------------------- /execsnoop-libbpf/execsnoop.bpf.c: -------------------------------------------------------------------------------- 1 | #include "vmlinux.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #define TASK_COMM_LEN 16 7 | struct event { 8 | char comm[TASK_COMM_LEN]; 9 | pid_t pid; 10 | pid_t tgid; 11 | pid_t ppid; 12 | uid_t uid; 13 | }; 14 | 15 | /* /sys/kernel/debug/tracing/events/syscalls/sys_enter_execve/format */ 16 | struct syscalls_enter_execve_args { 17 | __u64 unused; 18 | int syscall_nr; 19 | const char filename_ptr; 20 | const char *const * argv; 21 | const char *const * envp; 22 | }; 23 | 24 | /* /sys/kernel/debug/tracing/events/syscalls/sys_exit_execve/format */ 25 | struct syscalls_exit_execve_args { 26 | __u64 unused; 27 | int syscall_nr; 28 | long ret; 29 | }; 30 | 31 | struct { 32 | __uint(type, BPF_MAP_TYPE_HASH); 33 | __uint(max_entries, 10240); 34 | __type(key, pid_t); 35 | __type(value, struct event); 36 | } records SEC(".maps"); 37 | 38 | struct { 39 | __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); 40 | __uint(max_entries, 128); 41 | __type(key, u32); 42 | __type(value, u32); 43 | } perf_events SEC(".maps"); 44 | 45 | SEC("tracepoint/syscalls/sys_enter_execve") 46 | int syscall_enter_execve(struct syscalls_enter_execve_args *ctx){ 47 | pid_t pid, tgid; uid_t uid; 48 | struct event *event; 49 | struct task_struct *task, *task_p; 50 | 51 | u64 id = bpf_get_current_pid_tgid(); 52 | pid = (pid_t)id; 53 | tgid = id >> 32; 54 | uid = (u32)bpf_get_current_uid_gid(); 55 | 56 | struct event empty_event={}; 57 | if (bpf_map_update_elem(&records, &pid, &empty_event, BPF_NOEXIST)!=0) return 0; 58 | event = bpf_map_lookup_elem(&records, &pid); 59 | if (!event) return 0; 60 | 61 | event->pid = pid; 62 | event->tgid = tgid; 63 | event->uid = uid; 64 | 65 | /* ppid is not reliable here, normal in arch, but become 0 in ubuntu 20.04 */ 66 | task = (struct task_struct*)bpf_get_current_task(); 67 | bpf_probe_read(&task_p, sizeof(struct task_struct*),&task->real_parent); 68 | bpf_probe_read(&event->ppid,sizeof(pid_t),&task_p->tgid); 69 | 70 | return 0; 71 | } 72 | 73 | SEC("tracepoint/syscalls/sys_exit_execve") 74 | int syscall_exit_execve(struct syscalls_exit_execve_args *ctx){ 75 | pid_t pid; 76 | struct event *event; 77 | 78 | pid = (pid_t)bpf_get_current_pid_tgid(); 79 | event = bpf_map_lookup_elem(&records,&pid); 80 | if (!event) return 0; 81 | if (ctx->ret < 0) goto cleanup; 82 | 83 | /* get comm */ 84 | bpf_get_current_comm(&event->comm,sizeof(event->comm)); 85 | bpf_perf_event_output(ctx, &perf_events, BPF_F_CURRENT_CPU, event, sizeof(struct event)); 86 | cleanup: 87 | bpf_map_delete_elem(&records, &pid); 88 | return 0; 89 | } 90 | 91 | char _license[] SEC("license") = "GPL"; 92 | u32 _version SEC("version") = LINUX_VERSION_CODE; 93 | -------------------------------------------------------------------------------- /execsnoop-libbpf/execsnoop.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "execsnoop.skel.h" 5 | 6 | #define TASK_COMM_LEN 16 7 | struct event { 8 | char comm[TASK_COMM_LEN]; 9 | pid_t pid; 10 | pid_t tgid; 11 | pid_t ppid; 12 | uid_t uid; 13 | }; 14 | 15 | #define PERF_BUFFER_PAGES 64 16 | 17 | static void handle_event(void *ctx, int cpu, void *data, __u32 size) { 18 | auto e = static_cast(data); 19 | printf("comm: %s, pid: %d, tgid: %d, ppid: %d, uid: %d\n",e->comm,e->pid,e->tgid,e->ppid,e->uid); 20 | } 21 | 22 | void handle_lost_events(void *ctx, int cpu, __u64 lost_cnt) { 23 | fprintf(stderr, "Lost %llu events on CPU #%d!\n", lost_cnt, cpu); 24 | } 25 | 26 | int bump_memlock_rlimit(void) { 27 | struct rlimit rlim_new = { RLIM_INFINITY, RLIM_INFINITY }; 28 | return setrlimit(RLIMIT_MEMLOCK, &rlim_new); 29 | } 30 | 31 | int main(int argc, char **argv) { 32 | struct perf_buffer_opts pb_opts = {}; 33 | struct perf_buffer *pb; 34 | int err; 35 | 36 | err = bump_memlock_rlimit(); 37 | if (err) { 38 | fprintf(stderr, "failed to increase rlimit: %d\n", err); 39 | return 1; 40 | } 41 | 42 | struct execsnoop_bpf *obj=execsnoop_bpf__open_and_load(); 43 | if (!obj) { 44 | fprintf(stderr, "failed to open and/or load BPF object\n"); 45 | return 1; 46 | } 47 | 48 | err = execsnoop_bpf__attach(obj); 49 | if (err) { 50 | fprintf(stderr, "failed to attach BPF programs\n"); 51 | return err; 52 | } 53 | 54 | #if LIBBPF_MAJOR_VERSION == 0 55 | pb_opts.sample_cb = handle_event; 56 | pb_opts.lost_cb = handle_lost_events; 57 | pb = perf_buffer__new(bpf_map__fd(obj->maps.perf_events), PERF_BUFFER_PAGES, &pb_opts); 58 | #else 59 | pb_opts.sz = sizeof(pb_opts); 60 | pb = perf_buffer__new(bpf_map__fd(obj->maps.perf_events), PERF_BUFFER_PAGES, handle_event, handle_lost_events, nullptr, &pb_opts); 61 | #endif 62 | err = libbpf_get_error(pb); 63 | if (err) { 64 | printf("failed to setup perf_buffer: %d\n", err); 65 | return 1; 66 | } 67 | 68 | while ((err = perf_buffer__poll(pb, -1)) >= 0) {} 69 | kill(0, SIGINT); 70 | return err; 71 | } 72 | -------------------------------------------------------------------------------- /execsnoop-libbpf/execsnoop_share.cpp: -------------------------------------------------------------------------------- 1 | #include "execsnoop_share.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "execsnoop.skel.h" 8 | 9 | namespace CGPROXY::EXECSNOOP { 10 | 11 | #define PERF_BUFFER_PAGES 64 12 | #define TASK_COMM_LEN 16 13 | struct event { 14 | char comm[TASK_COMM_LEN]; 15 | pid_t pid; 16 | pid_t tgid; 17 | pid_t ppid; 18 | uid_t uid; 19 | }; 20 | 21 | function callback = NULL; 22 | promise status; 23 | 24 | static void handle_event(void *ctx, int cpu, void *data, __u32 size) { 25 | auto e = static_cast(data); 26 | if (callback) callback(e->pid); 27 | } 28 | 29 | void handle_lost_events(void *ctx, int cpu, __u64 lost_cnt) { 30 | fprintf(stderr, "Lost %llu events on CPU #%d!\n", lost_cnt, cpu); 31 | } 32 | 33 | int bump_memlock_rlimit(void) { 34 | struct rlimit rlim_new = { RLIM_INFINITY, RLIM_INFINITY }; 35 | return setrlimit(RLIMIT_MEMLOCK, &rlim_new); 36 | } 37 | 38 | int execsnoop() { 39 | struct perf_buffer_opts pb_opts = {}; 40 | struct perf_buffer *pb; 41 | int err; 42 | bool notified=false; 43 | 44 | err = bump_memlock_rlimit(); 45 | if (err) { 46 | fprintf(stderr, "failed to increase rlimit: %d\n", err); 47 | return 1; 48 | } 49 | 50 | struct execsnoop_bpf *obj=execsnoop_bpf__open_and_load(); 51 | if (!obj) { 52 | fprintf(stderr, "failed to open and/or load BPF object\n"); 53 | return 1; 54 | } 55 | 56 | err = execsnoop_bpf__attach(obj); 57 | if (err) { 58 | fprintf(stderr, "failed to attach BPF programs\n"); 59 | return err; 60 | } 61 | 62 | main_loop: 63 | #if LIBBPF_MAJOR_VERSION == 0 64 | pb_opts.sample_cb = handle_event; 65 | pb_opts.lost_cb = handle_lost_events; 66 | pb = perf_buffer__new(bpf_map__fd(obj->maps.perf_events), PERF_BUFFER_PAGES, &pb_opts); 67 | #else 68 | pb_opts.sz = sizeof(pb_opts); 69 | pb = perf_buffer__new(bpf_map__fd(obj->maps.perf_events), PERF_BUFFER_PAGES, handle_event, handle_lost_events, nullptr, &pb_opts); 70 | #endif 71 | err = libbpf_get_error(pb); 72 | if (err) { 73 | printf("failed to setup perf_buffer: %d\n", err); 74 | return 1; 75 | } 76 | 77 | // notify 78 | if (!notified) {status.set_value(); notified=true;} 79 | 80 | while ((err = perf_buffer__poll(pb, -1)) >= 0) {} 81 | perf_buffer__free(pb); 82 | /* handle Interrupted system call when sleep */ 83 | if (err == -EINTR) goto main_loop; 84 | 85 | perror("perf_buffer__poll"); 86 | kill(0, SIGINT); 87 | return err; 88 | } 89 | 90 | void startThread(function c, promise _status) { 91 | status = move(_status); 92 | callback = c; 93 | execsnoop(); 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /execsnoop-libbpf/execsnoop_share.h: -------------------------------------------------------------------------------- 1 | #ifndef EXECSNOOP_SHARE_HPP 2 | #define EXECSNOOP_SHARE_HPP 1 3 | 4 | #include 5 | #include 6 | #include 7 | using namespace std; 8 | 9 | namespace CGPROXY::EXECSNOOP { 10 | 11 | extern "C" void startThread(function c, promise _status); 12 | 13 | #ifdef BUIlD_EXECSNOOP_DL 14 | // only for dlsym() 15 | using startThread_t=decltype(startThread); 16 | startThread_t *_startThread; 17 | #endif 18 | 19 | } // namespace CGPROXY::EXECSNOOP 20 | #endif 21 | -------------------------------------------------------------------------------- /execsnoop-libbpf/readme.md: -------------------------------------------------------------------------------- 1 | generate `vmlinux.h` 2 | 3 | ```shell 4 | bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h 5 | ``` 6 | 7 | compiled into BPF ELF file 8 | 9 | ```shell 10 | clang -O2 -g -target bpf -c execsnoop.bpf.c -o execsnoop.bpf.o 11 | ``` 12 | 13 | generate BPF skeleton .skel.h 14 | 15 | ```shell 16 | bpftool gen skeleton execsnoop.bpf.o > execsnoop.skel.h 17 | ``` 18 | 19 | -------------------------------------------------------------------------------- /man/cgnoproxy.1: -------------------------------------------------------------------------------- 1 | .\" Manpage for cgproxyd 2 | .TH man 1 "19 May 2020" "1.0" "cgnoproxy man page" 3 | .SH NAME 4 | cgnoproxy \- Run program without proxy 5 | .SH SYNOPSIS 6 | cgnoproxy --help 7 | .br 8 | cgnoproxy [--debug] 9 | .br 10 | cgnoproxy [--debug] --pid 11 | .SH ALIAS 12 | cgnoproxy = cgproxy --noproxy 13 | .SH DESCRIPTION 14 | cgnoproxy send current running process pid or specified pid to cgproxyd through unix socket, then pid is attached to non-proxied cgroup 15 | .SH EXAMPLES 16 | cgnoproxy sudo v2ray -config config_file 17 | .SH SEE ALSO 18 | cgproxyd(1), cgproxy(1), cgnoproxy(1) 19 | -------------------------------------------------------------------------------- /man/cgproxy.1: -------------------------------------------------------------------------------- 1 | .\" Manpage for cgproxyd 2 | .TH man 1 "19 May 2020" "1.0" "cgproxy man page" 3 | .SH NAME 4 | cgproxy \- Run program with proxy 5 | .SH SYNOPSIS 6 | cgproxy --help 7 | .br 8 | cgproxy [--debug] 9 | .br 10 | cgproxy [--debug] --pid 11 | .SH DESCRIPTION 12 | cgproxy send current running process pid or specified pid to cgproxyd through unix socket, then pid is attached to proxied cgroup 13 | .SH EXAMPLES 14 | cgproxy curl -vI https://www.google.com 15 | .SH SEE ALSO 16 | cgproxyd(1), cgproxy(1), cgnoproxy(1) 17 | -------------------------------------------------------------------------------- /man/cgproxyd.1: -------------------------------------------------------------------------------- 1 | .\" Manpage for cgproxyd 2 | .TH man 1 "19 May 2020" "1.0" "cgproxyd man page" 3 | .SH NAME 4 | cgproxyd \- Start a daemon with unix socket to accept control from cgproxy/cgnoproxy 5 | .SH SYNOPSIS 6 | cgproxyd [--help] [--debug] [--execsnoop] 7 | .SH ALIAS 8 | cgproxyd = cgproxy --daemon 9 | .SH OPTIONS 10 | .B --execsnoop 11 | enable execsnoop to support program level proxy, need bcc installed to actually work 12 | .SH CONFIGURATION 13 | .I /etc/cgproxy/config.json 14 | .br 15 | .B port 16 | tproxy listenning port 17 | .br 18 | program level proxy controll, only work when execsnoop enabled: 19 | .br 20 | .RS 21 | .B program_proxy 22 | program need to be proxied 23 | .br 24 | .B program_noproxy 25 | program that won't be proxied 26 | .RE 27 | .br 28 | cgroup level proxy control: 29 | .br 30 | .RS 31 | .B cgroup_noproxy 32 | cgroup array that no need to proxy, /noproxy.slice is preserved. 33 | .br 34 | .B cgroup_proxy 35 | cgroup array that need to proxy, /proxy.slice is preserved. 36 | .RE 37 | .br 38 | .B enable_gateway 39 | enable gateway proxy for local devices. 40 | .br 41 | .B enable_dns 42 | enable dns to go to proxy. 43 | .br 44 | .B enable_tcp 45 | .br 46 | .B enable_udp 47 | .br 48 | .B enable_ipv4 49 | .br 50 | .B enable_ipv6 51 | .br 52 | .SH SEE ALSO 53 | cgproxyd(1), cgproxy(1), cgnoproxy(1) 54 | 55 | -------------------------------------------------------------------------------- /pack/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ## package for deb and rpm 2 | set(CPACK_GENERATOR "DEB;RPM") 3 | set(CPACK_PACKAGE_NAME "cgproxy") 4 | set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "cgproxy will transparent proxy anything running in specific cgroup.It aslo supports global transparent proxy and gateway proxy") 5 | 6 | ## deb pack 7 | execute_process(COMMAND dpkg --print-architecture 8 | OUTPUT_VARIABLE DEBIAN_ARCH 9 | OUTPUT_STRIP_TRAILING_WHITESPACE) 10 | set(CPACK_DEBIAN_FILE_NAME ${CPACK_PACKAGE_NAME}_${CMAKE_PROJECT_VERSION}_${DEBIAN_ARCH}.deb) 11 | set(CPACK_DEBIAN_PACKAGE_NAME "cgproxy") 12 | # set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "amd64") 13 | set(CPACK_DEBIAN_PACKAGE_DEPENDS "systemd") 14 | set(CPACK_DEBIAN_PACKAGE_SECTION "network") 15 | set(CPACK_DEBIAN_PACKAGE_PRIORITY "Optional") 16 | set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/springzfx/cgproxy") 17 | set(CPACK_DEBIAN_PACKAGE_MAINTAINER "springzfx@gmail.com") 18 | set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_CURRENT_SOURCE_DIR}/postinst;${CMAKE_CURRENT_SOURCE_DIR}/prerm") 19 | 20 | ## rpm pack 21 | execute_process(COMMAND uname -m 22 | OUTPUT_VARIABLE RPM_ARCH 23 | OUTPUT_STRIP_TRAILING_WHITESPACE) 24 | set(CPACK_RPM_FILE_NAME ${CPACK_PACKAGE_NAME}_${CMAKE_PROJECT_VERSION}_${RPM_ARCH}.rpm) 25 | # set(CPACK_RPM_PACKAGE_ARCHITECTURE, "x86_64") 26 | set(CPACK_RPM_PACKAGE_REQUIRES "systemd") 27 | set(CPACK_RPM_PACKAGE_GROUP "network") 28 | set(CPACK_RPM_PACKAGE_URL "https://github.com/springzfx/cgproxy") 29 | set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/postinst") 30 | set(CPACK_RPM_PRE_UNINSTALL_SCRIPT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/prerm") 31 | 32 | include(CPack) 33 | -------------------------------------------------------------------------------- /pack/postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | systemctl enable --now cgproxy.service 3 | -------------------------------------------------------------------------------- /pack/prerm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | systemctl disable --now cgproxy.service 3 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Transparent Proxy powered by cgroup v2 4 | 5 | 6 | 7 | ## Introduction 8 | 9 | cgproxy will transparent proxy anything running in specific cgroup. It resembles with *proxychains* and *tsock*s in default setting. 10 | 11 | Main feature: 12 | 13 | - supports cgroup/program level proxy control. 14 | - supports global transparent proxy and gateway proxy. 15 | 16 | ## Contents 17 | 18 | 19 | * [Transparent Proxy powered by cgroup v2](#transparent-proxy-powered-by-cgroup-v2) 20 | * [Introduction](#introduction) 21 | * [Contents](#contents) 22 | * [Prerequest](#prerequest) 23 | * [How to build and install](#how-to-build-and-install) 24 | * [Default usage](#default-usage) 25 | * [Configuration](#configuration) 26 | * [Global transparent proxy](#global-transparent-proxy) 27 | * [Gateway proxy](#gateway-proxy) 28 | * [Other useful tools provided in this project](#other-useful-tools-provided-in-this-project) 29 | * [NOTES](#notes) 30 | * [TIPS](#tips) 31 | * [Licences](#licences) 32 | * [Known Issues](#known-issues) 33 | 34 | 35 | 36 | 37 | 38 | ## Prerequest 39 | 40 | - cgroup2 41 | 42 | Both cgroup and cgroup2 are enabled in linux by default. So you don't have to do anything about this. 43 | - `systemd-cgls` to see the cgroup hierarchical tree. 44 | - Why cgroup v2? Because simple, elegant and intuitive. 45 | 46 | - TPROXY 47 | 48 | A process listening on port (e.g. 12345) to accept iptables TPROXY, for example v2ray's dokodemo-door in tproxy mode. 49 | 50 | - Iptables 51 | 52 | Iptables version should be at least 1.6.0, run `iptables --version` to check. 53 | 54 | ubuntu 16.04, debian 9, fedora 27 and later are desired 55 | 56 | ## How to build and install 57 | 58 | ### distro install 59 | 60 | - For debian and redhat series, download from [Release page](https://github.com/springzfx/cgproxy/releases) 61 | 62 | - For archlinux series, already in archlinuxcn repo, or see [archlinux AUR](https://aur.archlinux.org/packages/?K=cgproxy) 63 | 64 | - **Tested on archlinux, fedora 32, ubuntu 18.04, ubuntu 20.04, deepin 15.11, deepin v20 beta** 65 | 66 | ### build 67 | 68 | - before build, install depencies: clang, nlohmann-json, libbpf, bpf(bpftool) 69 | - then cmake standard build 70 | 71 | ```bash 72 | # ready build dir 73 | mkdir build 74 | cd build 75 | # generate 76 | cmake -DCMAKE_BUILD_TYPE=Release \ 77 | -DCMAKE_INSTALL_PREFIX=/usr \ 78 | -Dbuild_execsnoop_dl=ON \ 79 | -Dbuild_static=OFF \ 80 | .. 81 | # compile 82 | make 83 | ``` 84 | 85 | ## Default usage 86 | 87 | - First enable and start service 88 | 89 | ```bash 90 | sudo systemctl enable --now cgproxy.service 91 | ``` 92 | 93 | - Then prefix with cgproxy with your command, just like proxychains 94 | 95 | ```bash 96 | cgproxy [--debug] 97 | ``` 98 | 99 | - For example, test proxy 100 | 101 | ```bash 102 | cgproxy curl -vI https://www.google.com 103 | ``` 104 | 105 | - To completely stop 106 | ``` 107 | sudo systemctl disable --now cgproxy.service 108 | ``` 109 | 110 | ## Configuration 111 | 112 | Config file: **/etc/cgproxy/config.json** 113 | 114 | ```json 115 | { 116 | "port": 12345, 117 | "program_noproxy": ["v2ray", "qv2ray"], 118 | "program_proxy": [], 119 | "cgroup_noproxy": ["/system.slice/v2ray.service"], 120 | "cgroup_proxy": [], 121 | "enable_gateway": false, 122 | "enable_dns": true, 123 | "enable_udp": true, 124 | "enable_tcp": true, 125 | "enable_ipv4": true, 126 | "enable_ipv6": true, 127 | "table": 10007, 128 | "fwmark": 39283 129 | } 130 | 131 | ``` 132 | 133 | - **port** tproxy listenning port 134 | 135 | - program level proxy control, need execsnoop enabled: 136 | 137 | - **program_proxy** program need to be proxied 138 | - **program_noproxy** program that won't be proxied 139 | 140 | - cgroup level proxy control: 141 | 142 | - **cgroup_noproxy** cgroup array that no need to proxy, `/noproxy.slice` is preserved 143 | - **cgroup_proxy** cgroup array that need to proxy, `/proxy.slice` is preserved 144 | 145 | - **enable_gateway** enable gateway proxy for local devices 146 | 147 | - **enable_dns** enable dns to go to proxy 148 | 149 | - **enable_tcp** 150 | 151 | - **enable_udp** 152 | 153 | - **enable_ipv4** 154 | 155 | - **enable_ipv6** 156 | 157 | - **table**, **fwmark** you can specify iptables and route table related parameter in case conflict. 158 | 159 | - options priority 160 | 161 | ``` 162 | program_noproxy > program_proxy > cgroup_noproxy > cgroup_proxy 163 | enable_ipv6 = enable_ipv4 > enable_dns > enable_tcp = enable_udp 164 | command cgproxy and cgnoproxy always have highest priority 165 | ``` 166 | 167 | **Note**: cgroup in configuration need to be exist, otherwise ignored 168 | 169 | If you changed config, remember to restart service 170 | 171 | ```bash 172 | sudo systemctl restart cgproxy.service 173 | ``` 174 | 175 | ## Global transparent proxy 176 | 177 | - Set `"cgroup_proxy":["/"]` in configuration, this will proxy all connection 178 | 179 | - Allow your proxy program (v2ray) direct to internet to avoid loop. Two ways: 180 | 181 | - active way, run command 182 | 183 | example: `cgnoproxy sudo v2ray -config config_file` 184 | 185 | example: `cgnoproxy qv2ray` 186 | 187 | - passive way, persistent config 188 | 189 | example: `"program_noproxy":["v2ray" ,"qv2ray"]` 190 | 191 | example: `"cgroup_noproxy":["/system.slice/v2ray.service"]` 192 | 193 | - Finally, restart cgproxy service, that's all 194 | 195 | ## Gateway proxy 196 | 197 | - Set `"enable_gateway":true` in configuration 198 | - And allow your proxy software (v2ray) direct to internet if necessary, described above 199 | - Other device set this host as gateway, and set public dns if need 200 | 201 | ## Other useful tools provided in this project 202 | 203 | - `cgnoproxy` run program wihout proxy, very useful in global transparent proxy 204 | 205 | ```bash 206 | cgnoproxy [--debug] 207 | cgnoproxy [--debug] --pid 208 | ``` 209 | 210 | - For more detail command usage, see `man cgproxyd` `man cgproxy` `man cgnoproxy` 211 | 212 | ## NOTES 213 | 214 | - v2ray TPROXY need root or special permission, use [service](/v2ray_config/v2ray.service) or 215 | 216 | ```bash 217 | sudo setcap "cap_net_admin,cap_net_bind_service=ep" /usr/lib/v2ray/v2ray 218 | ``` 219 | 220 | - 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/config/dns.html#dnsobject), so no mark at all, that's pity. 221 | 222 | ## TIPS 223 | 224 | - `systemd-cgls` to see the cgroup hierarchical tree. 225 | - Check cgroup2 support `findmnt -t cgroup2` 226 | - Offer you v2ray service and full config exmaple in [v2ray_config](https://github.com/springzfx/cgproxy/tree/master/v2ray_config) 227 | - Offer you qv2ray config example 228 | 229 | 230 | ![Qv2ray config example](https://i.loli.net/2020/08/17/P6y5SfLoUwGjaxM.png) 231 | 232 | ## Licences 233 | 234 | 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) 235 | 236 | ## Known Issues 237 | 238 | - docker breaks cgroup v2 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 239 | 240 | - docker load `br_netfilter` module due to [hairpin nat](https://wiki.mikrotik.com/wiki/Hairpin_NAT), which is not a big deal, see [commit](https://github.com/moby/moby/pull/13162). 241 | 242 | It enables data link layer packet to go through iptables and only once. However TPROXY do not accept this kind of packets. So to get it working, set following parameter to disable this behavior or unload br_netfilter module manualy. see [issue #10](https://github.com/springzfx/cgproxy/issues/10) for detail. 243 | 244 | ``` 245 | sudo sysctl -w net.bridge.bridge-nf-call-iptables=0 246 | sudo sysctl -w net.bridge.bridge-nf-call-ip6tables=0 247 | sudo sysctl -w net.bridge.bridge-nf-call-arptables = 0 248 | ``` 249 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(nlohmann_json REQUIRED) 2 | include_directories(${PROJECT_SOURCE_DIR}) 3 | include_directories(${PROJECT_SOURCE_DIR}/execsnoop-libbpf/) 4 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}) 5 | include_directories(${CMAKE_CURRENT_BINARY_DIR}) 6 | 7 | configure_file(common.cmake.h ${CMAKE_CURRENT_SOURCE_DIR}/common.h) 8 | 9 | if (build_execsnoop_dl) 10 | add_definitions(-DBUIlD_EXECSNOOP_DL) 11 | set(DL_LIB "-ldl") 12 | set(EXECSNOOP_LIB "") 13 | else() 14 | set(EXECSNOOP_LIB "execsnoop") 15 | endif() 16 | 17 | add_executable(main main.cpp common.cpp config.cpp cgroup_attach.cpp socket_client.cpp socket_server.cpp) 18 | target_link_libraries(main PRIVATE nlohmann_json::nlohmann_json ${DL_LIB} ${EXECSNOOP_LIB}) 19 | set_target_properties(main PROPERTIES OUTPUT_NAME cgproxy) 20 | install(TARGETS main DESTINATION ${CMAKE_INSTALL_FULL_BINDIR}) 21 | 22 | if (build_static) 23 | target_link_libraries(main PRIVATE -static -Wl,--whole-archive -lpthread -Wl,--no-whole-archive) 24 | else() 25 | target_link_libraries(main PRIVATE -lpthread) 26 | endif() 27 | -------------------------------------------------------------------------------- /src/cgproxy.hpp: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include "config.h" 3 | #include "socket_client.h" 4 | #include 5 | #include 6 | #include 7 | using json = nlohmann::json; 8 | using namespace ::CGPROXY; 9 | using namespace ::CGPROXY::CONFIG; 10 | 11 | namespace CGPROXY::CGPROXY { 12 | 13 | bool print_help = false, proxy = true; 14 | bool attach_pid = false; 15 | string arg_pid; 16 | inline void print_usage() { 17 | if (proxy) { 18 | cout << "Run program with proxy" << endl; 19 | cout << "Usage: cgproxy [--help] [--debug] " << endl; 20 | } else { 21 | cout << "Run program without proxy" << endl; 22 | cout << "Usage: cgpnoroxy [--help] [--debug] " << endl; 23 | cout << "Alias: cgnoproxy = cgproxy --noproxy" << endl; 24 | } 25 | } 26 | 27 | bool processArgs(const int argc, char *argv[], int &shift) { 28 | int i; 29 | for (i = 1; i < argc; i++) { 30 | if (strcmp(argv[i], "--pid") == 0) { 31 | attach_pid = true; 32 | i++; 33 | if (i == argc) return false; 34 | arg_pid = argv[i]; 35 | if (!validPid(arg_pid)) return false; 36 | continue; 37 | } 38 | if (strcmp(argv[i], "--noproxy") == 0) { proxy = false; } 39 | if (strcmp(argv[i], "--debug") == 0) { enable_debug = true; } 40 | if (strcmp(argv[i], "--help") == 0) { print_help = true; } 41 | if (argv[i][0] != '-') { break; } 42 | } 43 | shift = i; 44 | return true; 45 | } 46 | 47 | void send_pid(const pid_t pid, bool proxy, int &status) { 48 | json j; 49 | j["type"] = proxy ? MSG_TYPE_PROXY_PID : MSG_TYPE_NOPROXY_PID; 50 | j["data"] = pid; 51 | SOCKET::send(j.dump(), status); 52 | } 53 | 54 | int main(int argc, char *argv[]) { 55 | int shift = -1; 56 | if (!processArgs(argc, argv, shift)) { 57 | error("parameter error"); 58 | exit(EXIT_FAILURE); 59 | } 60 | 61 | if (print_help) { 62 | print_usage(); 63 | exit(0); 64 | } 65 | 66 | if (!attach_pid && argc == shift) { 67 | error("no program specified"); 68 | exit(EXIT_FAILURE); 69 | } 70 | 71 | int status = -1; 72 | send_pid(attach_pid ? stoi(arg_pid) : getpid(), proxy, status); 73 | if (status != 0) { 74 | error("attach process failed"); 75 | if (status == 1) error("maybe cgproxy.service not running"); 76 | exit(EXIT_FAILURE); 77 | } 78 | // if just attach pid, return here 79 | if (attach_pid) return 0; 80 | 81 | string s = join2str(argc - shift, argv + shift, ' '); 82 | debug("executing: %s", s.c_str()); 83 | return system(s.c_str()); 84 | } 85 | } // namespace CGPROXY::CGPROXY 86 | -------------------------------------------------------------------------------- /src/cgproxyd.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CGPROXYD_HPP 2 | #define CGPROXYD_HPP 3 | 4 | #include "cgroup_attach.h" 5 | #include "common.h" 6 | #include "config.h" 7 | #include "execsnoop_share.h" 8 | #include "socket_server.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | using namespace std; 23 | using json = nlohmann::json; 24 | using namespace ::CGPROXY::SOCKET; 25 | using namespace ::CGPROXY::CONFIG; 26 | using namespace ::CGPROXY::CGROUP; 27 | // using namespace ::CGPROXY::EXECSNOOP; 28 | 29 | #ifdef BUIlD_EXECSNOOP_DL 30 | namespace CGPROXY::EXECSNOOP { 31 | bool loadExecsnoopLib() { 32 | try { 33 | info("loading %s", LIBEXECSNOOP_SO); 34 | void *handle_dl = dlopen(LIBEXECSNOOP_SO, RTLD_NOW); 35 | if (handle_dl == NULL) { 36 | error("dlopen %s failed: %s", LIBEXECSNOOP_SO, dlerror()); 37 | return false; 38 | } 39 | _startThread = reinterpret_cast(dlsym(handle_dl, "startThread")); 40 | if (_startThread == NULL) { 41 | error("dlsym startThread func failed: %s", dlerror()); 42 | return false; 43 | } 44 | info("dlsym startThread func success"); 45 | return true; 46 | } catch (exception &e) { 47 | debug("exception: %s", e.what()); 48 | return false; 49 | } 50 | } 51 | } // namespace CGPROXY::EXECSNOOP 52 | #endif 53 | 54 | namespace CGPROXY::CGPROXYD { 55 | 56 | bool print_help = false; 57 | bool enable_socketserver = true; 58 | bool enable_execsnoop = false; 59 | 60 | class cgproxyd { 61 | thread socketserver_thread; 62 | thread execsnoop_thread; 63 | 64 | Config config; 65 | 66 | static cgproxyd *instance; 67 | static int handle_msg_static(char *msg) { 68 | if (!instance) { 69 | error("no cgproxyd instance assigned"); 70 | return ERROR; 71 | } 72 | return instance->handle_msg(msg); 73 | } 74 | 75 | static int handle_pid_static(int pid) { 76 | if (!instance) { 77 | error("no cgproxyd instance assigned"); 78 | return ERROR; 79 | } 80 | return instance->handle_pid(pid); 81 | } 82 | 83 | int handle_pid(int pid) { 84 | unique_ptr path( 85 | realpath(to_str("/proc/", pid, "/exe").c_str(), NULL), &free); 86 | if (path == NULL) { 87 | debug("execsnoop: pid %d live life too short", pid); 88 | return 0; 89 | } 90 | debug("execsnoop: %d %s", pid, path.get()); 91 | 92 | vector v; 93 | 94 | v = config.program_noproxy; 95 | if (find(v.begin(), v.end(), path.get()) != v.end()) { 96 | string cg = getCgroup(pid); 97 | if (cg.empty()) { 98 | debug("execsnoop: cgroup get failed, ignore: %d %s", pid, path.get()); 99 | return 0; 100 | } 101 | if (belongToCgroup(cg, config.cgroup_proxy_preserved) || 102 | belongToCgroup(cg, config.cgroup_noproxy_preserved)) { 103 | info("execsnoop: already in preserverd cgroup, leave alone: %d %s", pid, 104 | path.get()); 105 | return 0; 106 | } 107 | if (!belongToCgroup(cg, config.cgroup_noproxy)) { 108 | int res = attach(pid, config.cgroup_noproxy_preserved); 109 | if (res == 0) { 110 | info("execsnoop; noproxy: %d %s", pid, path.get()); 111 | } else { 112 | info("execsnoop; noproxy failed: %d %s", pid, path.get()); 113 | } 114 | return res; 115 | } 116 | } 117 | 118 | v = config.program_proxy; 119 | if (find(v.begin(), v.end(), path.get()) != v.end()) { 120 | string cg = getCgroup(pid); 121 | if (cg.empty()) { 122 | debug("execsnoop: cgroup get failed, ignore: %d %s", pid, path.get()); 123 | return 0; 124 | } 125 | if (belongToCgroup(cg, config.cgroup_proxy_preserved) || 126 | belongToCgroup(cg, config.cgroup_noproxy_preserved)) { 127 | info("execsnoop: already in preserverd cgroup, leave alone: %d %s", pid, 128 | path.get()); 129 | return 0; 130 | } 131 | if (!belongToCgroup(cg, config.cgroup_proxy)) { 132 | int res = attach(pid, config.cgroup_proxy_preserved); 133 | if (res == 0) { 134 | info("execsnoop: proxied: %d %s", pid, path.get()); 135 | } else { 136 | info("execsnoop: proxied failed: %d %s", pid, path.get()); 137 | } 138 | return res; 139 | } 140 | } 141 | return 0; 142 | } 143 | 144 | static void signalHandler(int signum) { 145 | debug("Signal %d received.", signum); 146 | if (!instance) { 147 | error("no cgproxyd instance assigned"); 148 | } else { 149 | instance->stop(); 150 | } 151 | exit(0); 152 | } 153 | 154 | // single process instance 155 | int lock_fd; 156 | void lock() { 157 | lock_fd = open(PID_LOCK_FILE, O_CREAT | O_RDWR, 0666); 158 | int rc = flock(lock_fd, LOCK_EX | LOCK_NB); 159 | if (rc == -1) { 160 | perror(PID_LOCK_FILE); 161 | error("maybe another cgproxyd is running"); 162 | exit(EXIT_FAILURE); 163 | } else { 164 | ofstream ofs(PID_LOCK_FILE); 165 | ofs << getpid() << endl; 166 | ofs.close(); 167 | } 168 | } 169 | void unlock() { 170 | close(lock_fd); 171 | unlink(PID_LOCK_FILE); 172 | } 173 | 174 | int handle_msg(char *msg) { 175 | debug("received msg: %s", msg); 176 | json j; 177 | try { 178 | j = json::parse(msg); 179 | } catch (exception &e) { 180 | debug("msg paser error"); 181 | return MSG_ERROR; 182 | } 183 | 184 | int type, status, pid; 185 | try { 186 | type = j.at("type").get(); 187 | switch (type) { 188 | case MSG_TYPE_CONFIG_JSON: 189 | status = config.loadFromJsonStr(j.at("data").dump()); 190 | info("process received config json msg"); 191 | if (status == SUCCESS) status = applyConfig(); 192 | return status; 193 | break; 194 | case MSG_TYPE_CONFIG_PATH: 195 | status = config.loadFromFile(j.at("data").get()); 196 | info("process received config path msg"); 197 | if (status == SUCCESS) status = applyConfig(); 198 | return status; 199 | break; 200 | case MSG_TYPE_PROXY_PID: 201 | pid = j.at("data").get(); 202 | info("process proxy pid msg: %d", pid); 203 | status = attach(pid, config.cgroup_proxy_preserved); 204 | return status; 205 | break; 206 | case MSG_TYPE_NOPROXY_PID: 207 | pid = j.at("data").get(); 208 | info("process noproxy pid msg: %d", pid); 209 | status = attach(pid, config.cgroup_noproxy_preserved); 210 | return status; 211 | break; 212 | default: 213 | error("unknown msg"); 214 | return MSG_ERROR; 215 | break; 216 | }; 217 | } catch (out_of_range &e) { return MSG_ERROR; } catch (exception &e) { 218 | return ERROR; 219 | } 220 | } 221 | 222 | void startSocketListeningThread() { 223 | promise status; 224 | future status_f = status.get_future(); 225 | thread th(SOCKET::startThread, handle_msg_static, move(status)); 226 | socketserver_thread = move(th); 227 | 228 | future_status fstatus = status_f.wait_for(chrono::seconds(THREAD_TIMEOUT)); 229 | if (fstatus == std::future_status::ready) { 230 | info("socketserver thread started"); 231 | } else { 232 | error("socketserver thread timeout, maybe failed"); 233 | } 234 | } 235 | 236 | void startExecsnoopThread() { 237 | #ifdef BUIlD_EXECSNOOP_DL 238 | if (!EXECSNOOP::loadExecsnoopLib() || EXECSNOOP::_startThread == NULL) { 239 | error("execsnoop not ready to start, maybe missing libbpf"); 240 | return; 241 | } 242 | #endif 243 | 244 | promise status; 245 | future status_f = status.get_future(); 246 | #ifdef BUIlD_EXECSNOOP_DL 247 | thread th(EXECSNOOP::_startThread, handle_pid_static, move(status)); 248 | #else 249 | thread th(EXECSNOOP::startThread, handle_pid_static, move(status)); 250 | #endif 251 | 252 | execsnoop_thread = move(th); 253 | 254 | future_status fstatus = status_f.wait_for(chrono::seconds(THREAD_TIMEOUT)); 255 | if (fstatus == std::future_status::ready) { 256 | info("execsnoop thread started"); 257 | processRunningProgram(); 258 | } else { 259 | error("execsnoop thread timeout, maybe failed"); 260 | } 261 | } 262 | 263 | void processRunningProgram() { 264 | debug("process running program"); 265 | for (auto &path : config.program_noproxy) 266 | for (auto &pid : bash_pidof(path)) { 267 | string cg = getCgroup(pid); 268 | if (cg.empty()) { 269 | debug("cgroup get failed, ignore: %d %s", pid, path.c_str()); 270 | continue; 271 | } 272 | if (belongToCgroup(cg, config.cgroup_proxy_preserved) || 273 | belongToCgroup(cg, config.cgroup_noproxy_preserved)) { 274 | info("already in preserverd cgroup, leave alone: %d %s", pid, path.c_str()); 275 | continue; 276 | } 277 | if (!belongToCgroup(cg, config.cgroup_noproxy)) { 278 | int status = attach(pid, config.cgroup_noproxy_preserved); 279 | if (status == 0) info("noproxy running process %d %s", pid, path.c_str()); 280 | } 281 | } 282 | for (auto &path : config.program_proxy) 283 | for (auto &pid : bash_pidof(path)) { 284 | string cg = getCgroup(pid); 285 | if (cg.empty()) { 286 | debug("cgroup get failed, ignore: %d %s", pid, path.c_str()); 287 | continue; 288 | } 289 | if (belongToCgroup(cg, config.cgroup_proxy_preserved) || 290 | belongToCgroup(cg, config.cgroup_noproxy_preserved)) { 291 | info("already in preserverd cgroup, leave alone: %d %s", pid, path.c_str()); 292 | continue; 293 | } 294 | if (!belongToCgroup(cg, config.cgroup_proxy)) { 295 | int status = attach(pid, config.cgroup_proxy_preserved); 296 | if (status == 0) info("proxied running process %d %s", pid, path.c_str()); 297 | } 298 | } 299 | } 300 | 301 | void assignStaticInstance() { instance = this; } 302 | 303 | public: 304 | int start() { 305 | lock(); 306 | signal(SIGINT, &signalHandler); 307 | signal(SIGTERM, &signalHandler); 308 | signal(SIGHUP, &signalHandler); 309 | 310 | assignStaticInstance(); 311 | 312 | if (config.loadFromFile(DEFAULT_CONFIG_FILE)!=SUCCESS) { 313 | error("load config file failed"); 314 | return -1; 315 | } 316 | applyConfig(); 317 | 318 | if (enable_socketserver) startSocketListeningThread(); 319 | if (enable_execsnoop) startExecsnoopThread(); 320 | 321 | if (socketserver_thread.joinable()) socketserver_thread.join(); 322 | if (execsnoop_thread.joinable()) execsnoop_thread.join(); 323 | 324 | return 0; 325 | } 326 | 327 | int applyConfig() { 328 | system(TPROXY_IPTABLS_CLEAN); 329 | config.print_summary(); 330 | config.toEnv(); 331 | system(TPROXY_IPTABLS_START); 332 | // no need to track running status 333 | return 0; 334 | } 335 | 336 | void stop() { 337 | debug("stopping"); 338 | system(TPROXY_IPTABLS_CLEAN); 339 | unlock(); 340 | } 341 | 342 | ~cgproxyd() { stop(); } 343 | }; 344 | 345 | cgproxyd *cgproxyd::instance = NULL; 346 | 347 | void print_usage() { 348 | cout << "Start a daemon with unix socket to accept control" << endl; 349 | cout << "Usage: cgproxyd [--help] [--debug]" << endl; 350 | cout << "Alias: cgproxyd = cgproxy --daemon" << endl; 351 | } 352 | 353 | void processArgs(const int argc, char *argv[]) { 354 | for (int i = 1; i < argc; i++) { 355 | if (strcmp(argv[i], "--debug") == 0) { enable_debug = true; } 356 | if (strcmp(argv[i], "--help") == 0) { print_help = true; } 357 | if (strcmp(argv[i], "--execsnoop") == 0) { enable_execsnoop = true; } 358 | if (argv[i][0] != '-') { break; } 359 | } 360 | } 361 | 362 | int main(int argc, char *argv[]) { 363 | processArgs(argc, argv); 364 | if (print_help) { 365 | print_usage(); 366 | exit(0); 367 | } 368 | 369 | if (getuid() != 0) { 370 | error("permission denied, need root"); 371 | exit(EXIT_FAILURE); 372 | } 373 | 374 | cgproxyd d; 375 | return d.start(); 376 | } 377 | } // namespace CGPROXY::CGPROXYD 378 | #endif 379 | -------------------------------------------------------------------------------- /src/cgroup_attach.cpp: -------------------------------------------------------------------------------- 1 | #include "cgroup_attach.h" 2 | #include "common.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace CGPROXY::CGROUP { 16 | 17 | string cgroup2_mount_point = CGROUP2_MOUNT_POINT; 18 | 19 | 20 | bool validate(string pid, string cgroup) { 21 | bool pid_v = validPid(pid); 22 | bool cg_v = validCgroup(cgroup); 23 | if (pid_v && cg_v) return true; 24 | 25 | error("attach paramater validate error"); 26 | return_error; 27 | } 28 | 29 | int attach(const string pid, const string cgroup_target) { 30 | if (getuid() != 0) { 31 | error("need root to attach cgroup"); 32 | return_error; 33 | } 34 | 35 | debug("attaching %s to %s", pid.c_str(), cgroup_target.c_str()); 36 | 37 | if (!validate(pid, cgroup_target)) return_error; 38 | if (cgroup2_mount_point.empty()) return_error; 39 | string cgroup_target_path = cgroup2_mount_point + cgroup_target; 40 | string cgroup_target_procs = cgroup_target_path + "/cgroup.procs"; 41 | 42 | // check if exist, we will create it if not exist 43 | if (!dirExist(cgroup_target_path)) { 44 | if (mkdir(cgroup_target_path.c_str(), 45 | S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == 0) { 46 | debug("created cgroup %s success", cgroup_target.c_str()); 47 | } else { 48 | error("created cgroup %s failed, errno %d", cgroup_target.c_str(), errno); 49 | return_error; 50 | } 51 | // error("cgroup %s not exist",cgroup_target.c_str()); 52 | // return_error 53 | } 54 | 55 | string cg; 56 | 57 | cg = getCgroup(pid); 58 | if (cg.empty()) return_success; 59 | if (cg == cgroup_target) { 60 | debug("%s already in %s", pid.c_str(), cgroup_target.c_str()); 61 | return_success; 62 | } 63 | 64 | // put pid to target cgroup 65 | if (write2procs(pid, cgroup_target_procs) != 0) return_error; 66 | 67 | // wait for small period and check again 68 | this_thread::sleep_for(std::chrono::milliseconds(100)); 69 | cg = getCgroup(pid); 70 | if (cg.empty()) return_success; 71 | if (cg != cgroup_target && write2procs(pid, cgroup_target_procs) != 0) 72 | return_error; 73 | return_success; 74 | } 75 | 76 | int write2procs(string pid, string procspath) { 77 | ofstream procs(procspath, ofstream::app); 78 | if (!procs.is_open()) { 79 | error("open file %s failed", procspath.c_str()); 80 | return_error; 81 | } 82 | procs << pid.c_str() << endl; 83 | procs.close(); 84 | 85 | // maybe there some write error, for example process pid may not exist 86 | if (!procs) { 87 | error("write %s to %s failed, maybe process %s live too short", pid.c_str(), 88 | procspath.c_str(), pid.c_str()); 89 | return_error; 90 | } 91 | return_success; 92 | } 93 | 94 | int attach(const int pid, const string cgroup_target) { 95 | return attach(to_str(pid), cgroup_target); 96 | } 97 | 98 | } // namespace CGPROXY::CGROUP 99 | -------------------------------------------------------------------------------- /src/cgroup_attach.h: -------------------------------------------------------------------------------- 1 | #ifndef CGPROUP_ATTACH_H 2 | #define CGPROUP_ATTACH_H 3 | 4 | #include 5 | #include 6 | using namespace std; 7 | 8 | namespace CGPROXY::CGROUP { 9 | extern string cgroup2_mount_point; 10 | bool validate(string pid, string cgroup); 11 | int attach(const string pid, const string cgroup_target); 12 | int attach(const int pid, const string cgroup_target); 13 | int write2procs(string pid, string procspath); 14 | 15 | } // namespace CGPROXY::CGROUP 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /src/common.cmake.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMON_H 2 | #define COMMON_H 1 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | using namespace std; 9 | 10 | #define TPROXY_IPTABLS_START "@CMAKE_INSTALL_FULL_DATADIR@/cgproxy/scripts/cgroup-tproxy.sh" 11 | #define TPROXY_IPTABLS_CLEAN "@CMAKE_INSTALL_FULL_DATADIR@/cgproxy/scripts/cgroup-tproxy.sh stop" 12 | 13 | #define LIBEXECSNOOP_SO "@CMAKE_INSTALL_FULL_LIBDIR@/cgproxy/libexecsnoop.so" 14 | #define CGROUP2_MOUNT_POINT "/var/run/cgproxy/cgroup2" 15 | #define PID_LOCK_FILE "/var/run/cgproxyd.pid" 16 | #define SOCKET_PATH "/tmp/cgproxy_unix_socket" 17 | #define LISTEN_BACKLOG 64 18 | #define DEFAULT_CONFIG_FILE "@CMAKE_INSTALL_FULL_SYSCONFDIR@/cgproxy/config.json" 19 | #define READ_SIZE_MAX 128 20 | 21 | #define CGROUP_PROXY_PRESVERED "/proxy.slice" 22 | #define CGROUP_NOPROXY_PRESVERED "/noproxy.slice" 23 | 24 | #define THREAD_TIMEOUT 5 25 | 26 | #define MSG_TYPE_CONFIG_JSON 1 27 | #define MSG_TYPE_CONFIG_PATH 2 28 | #define MSG_TYPE_PROXY_PID 3 29 | #define MSG_TYPE_NOPROXY_PID 4 30 | 31 | #define UNKNOWN_ERROR 99 32 | #define ERROR -1 33 | #define SUCCESS 0 34 | #define CONN_ERROR 1 35 | #define MSG_ERROR 2 36 | #define PARSE_ERROR 3 37 | #define PARAM_ERROR 4 38 | #define APPLY_ERROR 5 39 | #define CGROUP_ERROR 6 40 | #define FILE_ERROR 7 41 | 42 | extern bool enable_debug; 43 | extern bool enable_info; 44 | 45 | #define error(...) \ 46 | { \ 47 | fprintf(stderr, "error: "); \ 48 | fprintf(stderr, __VA_ARGS__); \ 49 | fprintf(stderr, "\n"); \ 50 | fflush(stderr); \ 51 | } 52 | 53 | #define warning(...) \ 54 | { \ 55 | fprintf(stderr, "warning: "); \ 56 | fprintf(stderr, __VA_ARGS__); \ 57 | fprintf(stderr, "\n"); \ 58 | fflush(stderr); \ 59 | } 60 | 61 | #define debug(...) \ 62 | if (enable_debug) { \ 63 | fprintf(stdout, "debug: "); \ 64 | fprintf(stdout, __VA_ARGS__); \ 65 | fprintf(stdout, "\n"); \ 66 | fflush(stdout); \ 67 | } 68 | 69 | #define info(...) \ 70 | if (enable_info) { \ 71 | fprintf(stdout, "info: "); \ 72 | fprintf(stdout, __VA_ARGS__); \ 73 | fprintf(stdout, "\n"); \ 74 | fflush(stdout); \ 75 | } 76 | 77 | #define return_error return -1 78 | #define return_success return 0 79 | 80 | template string to_str(T... args) { 81 | stringstream ss; 82 | ss.clear(); 83 | ss << std::boolalpha; 84 | (ss << ... << args); 85 | return ss.str(); 86 | } 87 | 88 | string join2str(const vector t, const char delm = ' '); 89 | string join2str(const int argc, char **argv, const char delm = ' '); 90 | bool startWith(string prefix); 91 | 92 | bool validCgroup(const string cgroup); 93 | bool validCgroup(const vector cgroup); 94 | bool validPid(const string pid); 95 | bool validPort(const int port); 96 | 97 | bool fileExist(const string &path); 98 | bool dirExist(const string &path); 99 | vector bash_pidof(const string &path); 100 | string bash_which(const string &name); 101 | string bash_readlink(const string &path); 102 | string getRealExistPath(const string &name); 103 | 104 | /** 105 | * whether cg1 belongs to cg2 106 | */ 107 | bool belongToCgroup(string cg1, string cg2); 108 | bool belongToCgroup(string cg1, vector cg2); 109 | string getCgroup(const pid_t &pid); 110 | string getCgroup(const string &pid); 111 | 112 | #endif 113 | -------------------------------------------------------------------------------- /src/common.cpp: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | bool enable_debug = false; 8 | bool enable_info = true; 9 | 10 | string join2str(const vector t, const char delm) { 11 | string s; 12 | for (const auto &e : t) e != *(t.end() - 1) ? s += e + delm : s += e; 13 | return s; 14 | } 15 | 16 | string join2str(const int argc, char **argv, const char delm) { 17 | string s; 18 | for (int i = 0; i < argc; i++) { 19 | s += argv[i]; 20 | if (i != argc - 1) s += delm; 21 | } 22 | return s; 23 | } 24 | 25 | bool startWith(string s, string prefix) { return s.rfind(prefix, 0) == 0; } 26 | 27 | bool validCgroup(const string cgroup) { 28 | return regex_match(cgroup, regex("^/[a-zA-Z0-9\\-_./@]*$")); 29 | } 30 | 31 | bool validCgroup(const vector cgroup) { 32 | for (auto &e : cgroup) { 33 | if (!regex_match(e, regex("^/[a-zA-Z0-9\\-_./@]*$"))) { return false; } 34 | } 35 | return true; 36 | } 37 | 38 | bool validPid(const string pid) { return regex_match(pid, regex("^[0-9]+$")); } 39 | 40 | bool validPort(const int port) { return port > 0; } 41 | 42 | bool fileExist(const string &path) { 43 | struct stat st; 44 | return (stat(path.c_str(), &st) == 0 && S_ISREG(st.st_mode)); 45 | } 46 | 47 | bool dirExist(const string &path) { 48 | struct stat st; 49 | return (stat(path.c_str(), &st) == 0 && S_ISDIR(st.st_mode)); 50 | } 51 | 52 | vector bash_pidof(const string &path) { 53 | vector pids; 54 | unique_ptr fp(popen(to_str("pidof ", path).c_str(), "r"), 55 | &pclose); 56 | if (!fp) return pids; 57 | int pid; 58 | while (fscanf(fp.get(), "%d", &pid) != EOF) { pids.push_back(pid); } 59 | return pids; 60 | } 61 | 62 | string bash_which(const string &name) { 63 | stringstream buffer; 64 | unique_ptr fp(popen(to_str("which ", name).c_str(), "r"), 65 | &pclose); 66 | if (!fp) return ""; 67 | char buf[READ_SIZE_MAX]; 68 | while (fgets(buf, READ_SIZE_MAX, fp.get()) != NULL) { buffer << buf; } 69 | string s = buffer.str(); 70 | if (!s.empty()) s.pop_back(); // remove newline character 71 | return s; 72 | } 73 | 74 | string bash_readlink(const string &path) { 75 | stringstream buffer; 76 | unique_ptr fp(popen(to_str("readlink -e ", path).c_str(), "r"), 77 | &pclose); 78 | if (!fp) return ""; 79 | char buf[READ_SIZE_MAX]; 80 | while (fgets(buf, READ_SIZE_MAX, fp.get()) != NULL) { buffer << buf; } 81 | string s = buffer.str(); 82 | if (!s.empty()) s.pop_back(); // remove newline character 83 | return s; 84 | } 85 | 86 | string getRealExistPath(const string &name) { 87 | if (name[0] == '/' && fileExist(name)) return name; 88 | string path; 89 | path = bash_which(name); 90 | if (path.empty()) return ""; 91 | path = bash_readlink(path); 92 | if (!fileExist(path)) return ""; 93 | return path; 94 | } 95 | 96 | bool belongToCgroup(string cg1, string cg2) { return startWith(cg1 + '/', cg2 + '/'); } 97 | 98 | bool belongToCgroup(string cg1, vector cg2) { 99 | for (const auto &s : cg2) { 100 | if (startWith(cg1 + '/', s + '/')) return true; 101 | } 102 | return false; 103 | } 104 | 105 | string getCgroup(const pid_t &pid) { return getCgroup(to_str(pid)); } 106 | 107 | string getCgroup(const string &pid) { 108 | string cgroup_f = to_str("/proc/", pid, "/cgroup"); 109 | if (!fileExist(cgroup_f)) return ""; 110 | 111 | string cgroup, line; 112 | ifstream ifs(cgroup_f); 113 | debug("prcessing file %s", cgroup_f.c_str()); 114 | while (ifs.good() && getline(ifs, line)) { 115 | // debug("process line: %s", line.c_str()); 116 | if (line[0] == '0') { 117 | cgroup = line.substr(3); 118 | debug("get cgroup of %s: %s", pid.c_str(), cgroup.c_str()); 119 | break; 120 | } 121 | } 122 | ifs.close(); 123 | return cgroup; 124 | } 125 | -------------------------------------------------------------------------------- /src/config.cpp: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include "common.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | using json = nlohmann::json; 9 | 10 | #define add2json(v) j[#v] = v; 11 | #define tryassign(v) \ 12 | try { \ 13 | j.at(#v).get_to(v); \ 14 | } catch (exception & e) {} 15 | #define merge(v) \ 16 | { \ 17 | v.erase(std::remove(v.begin(), v.end(), v##_preserved), v.end()); \ 18 | v.insert(v.begin(), v##_preserved); \ 19 | } 20 | 21 | namespace CGPROXY::CONFIG { 22 | 23 | void Config::toEnv() { 24 | setenv("cgroup_mount_point", CGROUP2_MOUNT_POINT, 1); 25 | setenv("program_proxy", join2str(program_proxy, ':').c_str(), 1); 26 | setenv("program_noproxy", join2str(program_noproxy, ':').c_str(), 1); 27 | setenv("cgroup_proxy", join2str(cgroup_proxy, ':').c_str(), 1); 28 | setenv("cgroup_noproxy", join2str(cgroup_noproxy, ':').c_str(), 1); 29 | setenv("enable_gateway", to_str(enable_gateway).c_str(), 1); 30 | setenv("port", to_str(port).c_str(), 1); 31 | setenv("enable_dns", to_str(enable_dns).c_str(), 1); 32 | setenv("enable_tcp", to_str(enable_tcp).c_str(), 1); 33 | setenv("enable_udp", to_str(enable_udp).c_str(), 1); 34 | setenv("enable_ipv4", to_str(enable_ipv4).c_str(), 1); 35 | setenv("enable_ipv6", to_str(enable_ipv6).c_str(), 1); 36 | setenv("table", to_str(table).c_str(), 1); 37 | setenv("fwmark", to_str(fwmark).c_str(), 1); 38 | setenv("mark_newin", to_str(mark_newin).c_str(), 1); 39 | } 40 | 41 | int Config::saveToFile(const string f) { 42 | ofstream o(f); 43 | if (!o.is_open()) return FILE_ERROR; 44 | string js = toJsonStr(); 45 | o << setw(4) << js << endl; 46 | o.close(); 47 | return 0; 48 | } 49 | 50 | string Config::toJsonStr() { 51 | json j; 52 | add2json(program_proxy); 53 | add2json(program_noproxy); 54 | add2json(cgroup_proxy); 55 | add2json(cgroup_noproxy); 56 | add2json(enable_gateway); 57 | add2json(port); 58 | add2json(enable_dns); 59 | add2json(enable_tcp); 60 | add2json(enable_udp); 61 | add2json(enable_ipv4); 62 | add2json(enable_ipv6); 63 | add2json(table); 64 | add2json(fwmark); 65 | add2json(mark_newin); 66 | return j.dump(); 67 | } 68 | 69 | int Config::loadFromFile(const string f) { 70 | debug("loading config: %s", f.c_str()); 71 | ifstream ifs(f); 72 | if (ifs.is_open()) { 73 | string js = to_str(ifs.rdbuf()); 74 | ifs.close(); 75 | return loadFromJsonStr(js); 76 | } else { 77 | error("open failed: %s", f.c_str()); 78 | return FILE_ERROR; 79 | } 80 | } 81 | 82 | int Config::loadFromJsonStr(const string js) { 83 | if (!validateJsonStr(js)) { 84 | error("json validate fail"); 85 | return PARAM_ERROR; 86 | } 87 | json j = json::parse(js); 88 | tryassign(program_proxy); 89 | tryassign(program_noproxy); 90 | tryassign(cgroup_proxy); 91 | tryassign(cgroup_noproxy); 92 | tryassign(enable_gateway); 93 | tryassign(port); 94 | tryassign(enable_dns); 95 | tryassign(enable_tcp); 96 | tryassign(enable_udp); 97 | tryassign(enable_ipv4); 98 | tryassign(enable_ipv6); 99 | tryassign(table); 100 | tryassign(fwmark); 101 | tryassign(mark_newin); 102 | 103 | // e.g. v2ray -> /usr/bin/v2ray -> /usr/lib/v2ray/v2ray 104 | toRealProgramPath(program_noproxy); 105 | toRealProgramPath(program_proxy); 106 | 107 | mergeReserved(); 108 | 109 | return 0; 110 | } 111 | 112 | void Config::mergeReserved() { 113 | merge(cgroup_proxy); 114 | merge(cgroup_noproxy); 115 | } 116 | 117 | bool Config::validateJsonStr(const string js) { 118 | json j = json::parse(js); 119 | bool status = true; 120 | const set boolset = {"enable_gateway", "enable_dns", "enable_tcp", 121 | "enable_udp", "enable_ipv4", "enable_ipv6"}; 122 | const set allowset = {"program_proxy", "program_noproxy", "comment", "table", "fwmark", "mark_newin"}; 123 | for (auto &[key, value] : j.items()) { 124 | if (key == "cgroup_proxy" || key == "cgroup_noproxy") { 125 | if (value.is_string() && !validCgroup((string)value)) status = false; 126 | // TODO what if vector etc. 127 | if (value.is_array() && !validCgroup((vector)value)) status = false; 128 | if (!value.is_string() && !value.is_array()) status = false; 129 | } else if (key == "port") { 130 | if (!validPort(value)) status = false; 131 | } else if (boolset.find(key) != boolset.end()) { 132 | if (!value.is_boolean()) status = false; 133 | } else if (allowset.find(key) != allowset.end()) { 134 | 135 | } else { 136 | error("unknown key: %s", key.c_str()); 137 | return false; 138 | } 139 | if (!status) { 140 | error("invalid value for key: %s", key.c_str()); 141 | return false; 142 | } 143 | } 144 | return true; 145 | } 146 | 147 | void Config::print_summary() { 148 | info("noproxy program: %s", join2str(program_noproxy).c_str()); 149 | info("proxied program: %s", join2str(program_proxy).c_str()); 150 | info("noproxy cgroup: %s", join2str(cgroup_noproxy).c_str()); 151 | info("proxied cgroup: %s", join2str(cgroup_proxy).c_str()); 152 | info("table: %d, fwmark: %d, mark_newin: %d", table, fwmark, mark_newin); 153 | } 154 | 155 | void Config::toRealProgramPath(vector &v) { 156 | vector tmp; 157 | for (auto &p : v) { 158 | auto rpath = getRealExistPath(p); 159 | if (!rpath.empty()) tmp.push_back(rpath); 160 | else 161 | warning("%s not exist or broken link", p.c_str()); 162 | } 163 | v = tmp; 164 | } 165 | 166 | #undef tryassign 167 | #undef add2json 168 | #undef merge 169 | 170 | } // namespace CGPROXY::CONFIG -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H 2 | #define CONFIG_H 3 | #include "common.h" 4 | #include 5 | #include 6 | #include 7 | using namespace std; 8 | 9 | namespace CGPROXY::CONFIG { 10 | 11 | class Config { 12 | public: 13 | const string cgroup_proxy_preserved = CGROUP_PROXY_PRESVERED; 14 | const string cgroup_noproxy_preserved = CGROUP_NOPROXY_PRESVERED; 15 | 16 | vector program_proxy = {cgroup_proxy_preserved}; 17 | vector program_noproxy = {cgroup_noproxy_preserved}; 18 | vector cgroup_proxy; 19 | vector cgroup_noproxy; 20 | bool enable_gateway = false; 21 | int port = 12345; 22 | bool enable_dns = true; 23 | bool enable_tcp = true; 24 | bool enable_udp = true; 25 | bool enable_ipv4 = true; 26 | bool enable_ipv6 = true; 27 | 28 | // for iptables 29 | int table=10007; 30 | int fwmark=0x9973; 31 | int mark_newin=0x9967; 32 | 33 | void toEnv(); 34 | int saveToFile(const string f); 35 | string toJsonStr(); 36 | int loadFromFile(const string f); 37 | int loadFromJsonStr(const string js); 38 | void print_summary(); 39 | 40 | private: 41 | void mergeReserved(); 42 | bool validateJsonStr(const string js); 43 | void toRealProgramPath(vector &v); 44 | }; 45 | 46 | } // namespace CGPROXY::CONFIG 47 | #endif -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "cgproxy.hpp" 2 | #include "cgproxyd.hpp" 3 | 4 | bool as_cgproxyd = false; 5 | void processArgs(const int argc, char *argv[]) { 6 | for (int i = 1; i < argc; i++) { 7 | if (strcmp(argv[i], "--daemon") == 0) { as_cgproxyd = true; } 8 | if (argv[i][0] != '-') { break; } 9 | } 10 | } 11 | 12 | int main(int argc, char *argv[]) { 13 | processArgs(argc, argv); 14 | if (as_cgproxyd) ::CGPROXY::CGPROXYD::main(argc, argv); 15 | else 16 | ::CGPROXY::CGPROXY::main(argc, argv); 17 | } -------------------------------------------------------------------------------- /src/socket_client.cpp: -------------------------------------------------------------------------------- 1 | #include "socket_client.h" 2 | #include "common.h" 3 | #include 4 | #include 5 | #include 6 | 7 | #define return_if_error(flag, msg) \ 8 | if (flag == -1) { \ 9 | perror(msg); \ 10 | status = CONN_ERROR; \ 11 | close(sfd); \ 12 | return; \ 13 | } 14 | 15 | namespace CGPROXY::SOCKET { 16 | 17 | void send(const char *msg, int &status) { 18 | debug("send msg: %s", msg); 19 | status = UNKNOWN_ERROR; 20 | 21 | int flag; 22 | int sfd = socket(AF_UNIX, SOCK_STREAM, 0); 23 | 24 | struct sockaddr_un unix_socket; 25 | memset(&unix_socket, '\0', sizeof(struct sockaddr_un)); 26 | unix_socket.sun_family = AF_UNIX; 27 | strncpy(unix_socket.sun_path, SOCKET_PATH, sizeof(unix_socket.sun_path) - 1); 28 | 29 | flag = connect(sfd, (struct sockaddr *)&unix_socket, sizeof(struct sockaddr_un)); 30 | return_if_error(flag, "connect"); 31 | 32 | int msg_len = strlen(msg); 33 | flag = write(sfd, &msg_len, sizeof(int)); 34 | return_if_error(flag, "write length"); 35 | flag = write(sfd, msg, msg_len * sizeof(char)); 36 | return_if_error(flag, "write msg"); 37 | 38 | flag = read(sfd, &status, sizeof(int)); 39 | return_if_error(flag, "read return value"); 40 | 41 | close(sfd); 42 | } 43 | 44 | void send(const string msg, int &status) { 45 | send(msg.c_str(), status); 46 | debug("return status: %d", status); 47 | } 48 | 49 | } // namespace CGPROXY::SOCKET -------------------------------------------------------------------------------- /src/socket_client.h: -------------------------------------------------------------------------------- 1 | #ifndef SOCKET_CLIENT_H 2 | #define SOCKET_CLIENT_H 3 | 4 | #include 5 | #include 6 | using namespace std; 7 | 8 | namespace CGPROXY::SOCKET { 9 | 10 | void send(const char *msg, int &status); 11 | void send(const string msg, int &status); 12 | 13 | } // namespace CGPROXY::SOCKET 14 | #endif -------------------------------------------------------------------------------- /src/socket_server.cpp: -------------------------------------------------------------------------------- 1 | #include "socket_server.h" 2 | #include "common.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace fs = std::filesystem; 10 | 11 | namespace CGPROXY::SOCKET { 12 | 13 | void SocketServer::socketListening(function callback, promise status) { 14 | debug("starting socket listening"); 15 | sfd = socket(AF_UNIX, SOCK_STREAM, 0); 16 | 17 | if (fs::exists(SOCKET_PATH) && unlink(SOCKET_PATH) == -1) { 18 | error("%s exist, and can't unlink", SOCKET_PATH); 19 | return; 20 | } 21 | memset(&unix_socket, '\0', sizeof(struct sockaddr_un)); 22 | unix_socket.sun_family = AF_UNIX; 23 | strncpy(unix_socket.sun_path, SOCKET_PATH, sizeof(unix_socket.sun_path) - 1); 24 | 25 | bind(sfd, (struct sockaddr *)&unix_socket, sizeof(struct sockaddr_un)); 26 | 27 | listen(sfd, LISTEN_BACKLOG); 28 | chmod(SOCKET_PATH, S_IRWXU | S_IRWXG | S_IRWXO); 29 | 30 | status.set_value(); 31 | 32 | while (true) { 33 | close(cfd); 34 | cfd = accept(sfd, NULL, NULL); 35 | continue_if_error(cfd, "accept"); 36 | debug("accept connection: %d", cfd); 37 | 38 | // read length 39 | int msg_len; 40 | flag = read(cfd, &msg_len, sizeof(int)); 41 | continue_if_error(flag, "read length"); 42 | // read msg 43 | auto msg = (char *)malloc(msg_len + 1); 44 | flag = read(cfd, msg, msg_len * sizeof(char)); 45 | continue_if_error(flag, "read msg"); 46 | msg[msg_len] = '\0'; 47 | // handle msg 48 | int status = callback(msg); 49 | free(msg); 50 | // send back flag 51 | flag = write(cfd, &status, sizeof(int)); 52 | continue_if_error(flag, "write back"); 53 | } 54 | } 55 | 56 | SocketServer::~SocketServer() { 57 | close(sfd); 58 | close(cfd); 59 | unlink(SOCKET_PATH); 60 | } 61 | 62 | void startThread(function callback, promise status) { 63 | SocketServer server; 64 | server.socketListening(callback, move(status)); 65 | } 66 | 67 | } // namespace CGPROXY::SOCKET -------------------------------------------------------------------------------- /src/socket_server.h: -------------------------------------------------------------------------------- 1 | #ifndef SOCKET_SERVER_H 2 | #define SOCKET_SERVER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | using namespace std; 9 | 10 | namespace CGPROXY::SOCKET { 11 | 12 | #define continue_if_error(flag, msg) \ 13 | if (flag == -1) { \ 14 | perror(msg); \ 15 | continue; \ 16 | } 17 | 18 | class SocketServer { 19 | public: 20 | int sfd = -1, cfd = -1, flag = -1; 21 | struct sockaddr_un unix_socket; 22 | 23 | void socketListening(function callback, promise status); 24 | ~SocketServer(); 25 | }; 26 | 27 | void startThread(function callback, promise status); 28 | 29 | } // namespace CGPROXY::SOCKET 30 | 31 | #endif -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories(${PROJECT_SOURCE_DIR}) 2 | include_directories(${PROJECT_SOURCE_DIR}/src) 3 | 4 | find_package(nlohmann_json REQUIRED) 5 | add_executable(client_test socket_client_test.cpp 6 | ../src/socket_client.cpp ../src/common.cpp ../src/config.cpp) 7 | target_link_libraries(client_test nlohmann_json::nlohmann_json) -------------------------------------------------------------------------------- /test/socket_client_test.cpp: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include "config.h" 3 | #include "socket_client.h" 4 | #include 5 | 6 | using namespace std; 7 | using json = nlohmann::json; 8 | using namespace CGPROXY; 9 | using namespace CGPROXY::CONFIG; 10 | 11 | void send_config(Config &config, int &status) { 12 | json j; 13 | j["type"] = MSG_TYPE_CONFIG_JSON; 14 | j["data"] = config.toJsonStr(); 15 | SOCKET::send(j.dump(), status); 16 | } 17 | 18 | void send_config_path(const string s, int &status) { 19 | json j; 20 | j["type"] = MSG_TYPE_CONFIG_PATH; 21 | j["data"] = s; 22 | SOCKET::send(j.dump(), status); 23 | } 24 | 25 | void send_pid(const pid_t pid, bool proxy, int &status) { 26 | json j; 27 | j["type"] = proxy ? MSG_TYPE_PROXY_PID : MSG_TYPE_NOPROXY_PID; 28 | j["data"] = pid; 29 | SOCKET::send(j.dump(), status); 30 | } 31 | 32 | void test_config() { 33 | Config config; 34 | config.cgroup_proxy = {"/"}; 35 | int status; 36 | send_config(config, status); 37 | } 38 | 39 | void test_config_path() { 40 | string path = "/etc/cgproxy/config.json"; 41 | int status; 42 | send_config_path(path, status); 43 | } 44 | 45 | int main() { 46 | test_config(); 47 | return 0; 48 | } -------------------------------------------------------------------------------- /tools/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories(${PROJECT_SOURCE_DIR}) 2 | include_directories(${PROJECT_SOURCE_DIR}/src) 3 | 4 | add_executable(cgattach cgattach.cpp ../src/cgroup_attach.cpp ../src/common.cpp) 5 | install(TARGETS cgattach DESTINATION ${CMAKE_INSTALL_FULL_BINDIR} PERMISSIONS ${basic_permission}) -------------------------------------------------------------------------------- /tools/cgattach.cpp: -------------------------------------------------------------------------------- 1 | #include "cgroup_attach.h" 2 | #include "common.h" 3 | #include 4 | #include 5 | using namespace std; 6 | 7 | void print_usage() { fprintf(stdout, "usage: cgattach \n"); } 8 | 9 | int main(int argc, char *argv[]) { 10 | int flag = setuid(0); 11 | if (flag != 0) { 12 | perror("cgattach need root"); 13 | exit(EXIT_FAILURE); 14 | } 15 | 16 | if (argc != 3) { 17 | error("need exact 2 paramaters"); 18 | print_usage(); 19 | exit(EXIT_FAILURE); 20 | } 21 | 22 | string pid = string(argv[1]); 23 | string cgroup_target = string(argv[2]); 24 | 25 | if (validPid(pid) && validCgroup(cgroup_target)) { 26 | CGPROXY::CGROUP::attach(pid, cgroup_target); 27 | } else { 28 | error("param not valid"); 29 | exit(EXIT_FAILURE); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /v2ray_config/00_log.json: -------------------------------------------------------------------------------- 1 | { 2 | "log": { 3 | "loglevel": "none" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /v2ray_config/01_api.json: -------------------------------------------------------------------------------- 1 | { 2 | "api": { 3 | "services": [ 4 | "HandlerService", 5 | "LoggerService", 6 | "StatsService" 7 | ], 8 | "tag": "API" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /v2ray_config/02_dns.json: -------------------------------------------------------------------------------- 1 | { 2 | "dns": { 3 | "hosts": { 4 | "geosite:category-ads": "127.0.0.1" 5 | }, 6 | "servers": [ 7 | "https+local://223.5.5.5/dns-query", 8 | "https://1.1.1.1/dns-query", 9 | { 10 | "address": "localhost", 11 | "port": 53, 12 | "domains": [ 13 | "geosite:cn" 14 | ], 15 | "expectIPs": [ 16 | "geoip:cn" 17 | ] 18 | } 19 | ], 20 | "tag": "dns_inbound" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /v2ray_config/03_policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "policy": { 3 | "system": { 4 | "statsInboundDownlink": true, 5 | "statsInboundUplink": true 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /v2ray_config/04_routing_00.json: -------------------------------------------------------------------------------- 1 | { 2 | "routing": { 3 | "domainStrategy": "IPIfNonMatch", 4 | "rules": [ 5 | { 6 | "domain": [ 7 | "geosite:category-ads-all" 8 | ], 9 | "outboundTag": "outBound_BLACKHOLE", 10 | "type": "field" 11 | }, 12 | { 13 | "inboundTag": [ 14 | "inbound_API" 15 | ], 16 | "outboundTag": "API", 17 | "type": "field" 18 | }, 19 | { 20 | "outboundTag": "dns-out", 21 | "port": "53", 22 | "type": "field" 23 | }, 24 | { 25 | "domain": [ 26 | "geosite:google", 27 | "geosite:github", 28 | "geosite:netflix", 29 | "geosite:steam", 30 | "geosite:telegram", 31 | "geosite:tumblr", 32 | "geosite:bbc" 33 | ], 34 | "outboundTag": "outBound_PROXY", 35 | "type": "field" 36 | }, 37 | { 38 | "domain": [ 39 | "geosite:cn" 40 | ], 41 | "outboundTag": "outBound_DIRECT", 42 | "type": "field" 43 | }, 44 | { 45 | "ip": [ 46 | "geoip:cn", 47 | "geoip:private" 48 | ], 49 | "outboundTag": "outBound_DIRECT", 50 | "type": "field" 51 | } 52 | ] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /v2ray_config/05_inbounds_00_api.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "listen": "127.0.0.1", 5 | "port": 15490, 6 | "protocol": "dokodemo-door", 7 | "settings": { 8 | "address": "127.0.0.1" 9 | }, 10 | "sniffing": {}, 11 | "tag": "inbound_API" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /v2ray_config/05_inbounds_01_tproxy_ipv4lo.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "listen": "127.0.0.1", 5 | "port": 12345, 6 | "protocol": "dokodemo-door", 7 | "settings": { 8 | "address": "", 9 | "followRedirect": true, 10 | "network": "tcp,udp", 11 | "port": 0, 12 | "timeout": 300, 13 | "userLevel": 0 14 | }, 15 | "sniffing": { 16 | "destOverride": [ 17 | "http", 18 | "tls" 19 | ], 20 | "enabled": true 21 | }, 22 | "streamSettings": { 23 | "sockopt": { 24 | "tproxy": "tproxy" 25 | } 26 | }, 27 | "tag": "tproxy_IN_ipv4lo" 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /v2ray_config/05_inbounds_02_tproxy_ipv6lo.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "listen": "::1", 5 | "port": 12345, 6 | "protocol": "dokodemo-door", 7 | "settings": { 8 | "address": "", 9 | "followRedirect": true, 10 | "network": "tcp,udp", 11 | "port": 0, 12 | "timeout": 300, 13 | "userLevel": 0 14 | }, 15 | "sniffing": { 16 | "destOverride": [ 17 | "http", 18 | "tls" 19 | ], 20 | "enabled": true 21 | }, 22 | "streamSettings": { 23 | "sockopt": { 24 | "tproxy": "tproxy" 25 | } 26 | }, 27 | "tag": "tproxy_IN_ipv6lo" 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /v2ray_config/05_inbounds_03_http.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "listen": "127.0.0.1", 5 | "port": 8888, 6 | "protocol": "http", 7 | "sniffing": { 8 | "enabled": false 9 | }, 10 | "tag": "http_IN" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /v2ray_config/05_inbounds_04_socks5.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbounds": [ 3 | { 4 | "listen": "127.0.0.1", 5 | "port": 1080, 6 | "protocol": "socks", 7 | "settings": { 8 | "udp": true, 9 | "auth": "noauth", 10 | "userLevel": 0 11 | }, 12 | "sniffing": { 13 | "enabled": false 14 | }, 15 | "tag": "socks_IN" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /v2ray_config/06_outbounds_00_blackhole.json: -------------------------------------------------------------------------------- 1 | { 2 | "outbounds": [ 3 | { 4 | "protocol": "blackhole", 5 | "sendThrough": "0.0.0.0", 6 | "settings": { 7 | "response": { 8 | "type": "none" 9 | } 10 | }, 11 | "streamSettings": { 12 | "sockopt": { 13 | "mark": 255 14 | } 15 | }, 16 | "tag": "outBound_BLACKHOLE" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /v2ray_config/06_outbounds_01_freedom.json: -------------------------------------------------------------------------------- 1 | { 2 | "outbounds": [ 3 | { 4 | "protocol": "freedom", 5 | "sendThrough": "0.0.0.0", 6 | "settings": { 7 | "domainStrategy": "UseIP", 8 | "redirect": ":0", 9 | "userLevel": 0 10 | }, 11 | "streamSettings": { 12 | "sockopt": { 13 | "mark": 255 14 | } 15 | }, 16 | "tag": "outBound_DIRECT" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /v2ray_config/06_outbounds_02_dns.json: -------------------------------------------------------------------------------- 1 | { 2 | "outbounds": [ 3 | { 4 | "protocol": "dns", 5 | "streamSettings": { 6 | "sockopt": { 7 | "mark": 255 8 | } 9 | }, 10 | "tag": "dns-out" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /v2ray_config/06_outbounds_10_myproxy.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /v2ray_config/07_transport.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /v2ray_config/08_stats.json: -------------------------------------------------------------------------------- 1 | { 2 | "stats": {} 3 | } 4 | -------------------------------------------------------------------------------- /v2ray_config/09_reverse.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /v2ray_config/merge.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | jq -rs 'reduce .[] as $item ({}; . + $item + {inbounds: (.inbounds + $item.inbounds)} + {outbounds: ($item.outbounds + .outbounds)})' *.json |sudo tee /etc/v2ray/config.json > /dev/null 3 | -------------------------------------------------------------------------------- /v2ray_config/readme.md: -------------------------------------------------------------------------------- 1 | ## Usage 2 | - Fill `06_outbounds_myproxy.json` with your VMess proxy config with tag `outBound_PROXY`. 3 | - Start with `sudo v2ray -confdir .` 4 | 5 | ## Reference 6 | 7 | - [v2ray multi-file config](https://www.v2fly.org/config/multiple_config.html) 8 | 9 | -------------------------------------------------------------------------------- /v2ray_config/v2ray.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=V2Ray - A unified platform for anti-censorship 3 | Documentation=https://v2ray.com https://guide.v2fly.org 4 | After=network.target nss-lookup.target 5 | Wants=network-online.target 6 | 7 | [Service] 8 | Type=exec 9 | ExecStart=/usr/lib/v2ray/v2ray -config /etc/v2ray/config.json 10 | DynamicUser=yes 11 | AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE 12 | NoNewPrivileges=yes 13 | Restart=on-failure 14 | # Don't restart in the case of configuration error 15 | RestartPreventExitStatus=23 16 | 17 | [Install] 18 | WantedBy=multi-user.target 19 | --------------------------------------------------------------------------------