7 Commits
v0.10 ... v0.11

Author SHA1 Message Date
fancy
78c10e3e3e bump version 2020-05-18 21:34:30 +08:00
fancy
99b29195f2 spearete package pack 2020-05-18 21:34:30 +08:00
fancy
53fccbe72d single cgproxyd process only 2020-05-18 21:34:30 +08:00
fancy
db4757316a fix print usage and clang format 2020-05-18 21:34:30 +08:00
fancy
3a4e62b3c2 remove no-need depency 2020-05-18 21:34:30 +08:00
fancy
4525d83a53 reorganize project 2020-05-18 21:34:30 +08:00
fancy
c5ec1027ad clang format 2020-05-17 17:32:12 +08:00
26 changed files with 804 additions and 560 deletions

1
.gitignore vendored
View File

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

View File

@@ -1,62 +1,26 @@
cmake_minimum_required(VERSION 3.10)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_BUILD_TYPE DEBUG)
# set(CMAKE_BUILD_TYPE RELEASE)
project(cgproxy VERSION 4.0)
project(cgproxy VERSION 4.1)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-result")
find_package(Threads REQUIRED)
find_package(nlohmann_json REQUIRED)
include_directories(${PROJECT_SOURCE_DIR})
add_executable(cgattach cgattach.cpp)
add_executable(cgproxyd cgproxyd.cpp)
add_executable(cgproxy cgproxy.cpp)
add_executable(cgnoproxy cgnoproxy.cpp)
target_link_libraries(cgproxyd Threads::Threads nlohmann_json::nlohmann_json)
target_link_libraries(cgproxy nlohmann_json::nlohmann_json)
target_link_libraries(cgnoproxy nlohmann_json::nlohmann_json)
# add_executable(client_test socket_client_test.cpp)
# target_link_libraries(client_test nlohmann_json::nlohmann_json)
set(build_tools OFF)
set(build_test OFF)
set(basic_permission OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
install(TARGETS cgattach DESTINATION /usr/bin PERMISSIONS ${basic_permission})
install(TARGETS cgproxyd DESTINATION /usr/bin PERMISSIONS ${basic_permission})
install(TARGETS cgproxy DESTINATION /usr/bin PERMISSIONS ${basic_permission})
install(TARGETS cgnoproxy DESTINATION /usr/bin PERMISSIONS ${basic_permission})
add_subdirectory(src)
add_subdirectory(pack)
if (build_tools)
add_subdirectory(tools)
endif()
if (build_test)
add_subdirectory(test)
endif()
install(FILES cgnoproxy DESTINATION /usr/bin PERMISSIONS ${basic_permission})
install(FILES cgproxy.service DESTINATION /usr/lib/systemd/system/)
install(FILES config.json DESTINATION /etc/cgproxy/)
install(FILES cgroup-tproxy.sh DESTINATION /usr/share/cgproxy/scripts/)
install(FILES readme.md DESTINATION /share/doc/cgproxy/)
## package for deb and rpm
set(CPACK_GENERATOR "DEB;RPM")
set(CPACK_PACKAGE_NAME "cgproxy")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "cgproxy will transparent proxy anything running in specific cgroup.It aslo supports global transparent proxy and gateway proxy")
## deb pack
set(CPACK_DEBIAN_PACKAGE_NAME "cgproxy")
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "x86_64")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "systemd" "nlohmann-json3-dev")
set(CPACK_DEBIAN_PACKAGE_SECTION "network")
set(CPACK_DEBIAN_PACKAGE_PRIORITY "Optional")
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/springzfx/cgproxy")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "springzfx@gmail.com")
set(CONTROL_DIR ${CMAKE_SOURCE_DIR}/control)
set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CONTROL_DIR}/postinst;${CONTROL_DIR}/prerm")
## rpm pack
set(CPACK_RPM_PACKAGE_ARCHITECTURE, "x86_64")
set(CPACK_RPM_PACKAGE_REQUIRES "systemd" "json-devel")
set(CPACK_RPM_PACKAGE_GROUP "network")
set(CPACK_RPM_PACKAGE_URL "https://github.com/springzfx/cgproxy")
set(CONTROL_DIR ${CMAKE_SOURCE_DIR}/control)
set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${CONTROL_DIR}/postinst")
set(CPACK_RPM_PRE_UNINSTALL_SCRIPT_FILE "${CONTROL_DIR}/prerm")
include(CPack)
install(FILES readme.md DESTINATION /usr/share/doc/cgproxy/)

137
_clang-format Normal file
View File

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

2
cgnoproxy Normal file
View File

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

View File

@@ -1,31 +0,0 @@
#include <nlohmann/json.hpp>
#include "socket_client.hpp"
using json = nlohmann::json;
using namespace CGPROXY;
bool attach2cgproxy(){
pid_t pid=getpid();
json j;
j["type"] = MSG_TYPE_NOPROXY_PID;
j["data"] = pid;
int status;
SOCKET::send(j.dump(), status);
return status==0;
}
int main(int argc, char *argv[]){
int shift=1;
if (argc==1){
error("usage: cgnoproxy [--debug] <CMD>\nexample: cgnoproxy curl -I https://www.google.com");
exit(EXIT_FAILURE);
}
processArgs(argc,argv,shift);
if (!attach2cgproxy()){
error("attach process failed");
exit(EXIT_FAILURE);
}
string s=join2str(argc-shift,argv+shift,' ');
return system(s.c_str());
}

View File

@@ -1,31 +0,0 @@
#include <nlohmann/json.hpp>
#include "socket_client.hpp"
using json = nlohmann::json;
using namespace CGPROXY;
bool attach2cgproxy(){
pid_t pid=getpid();
json j;
j["type"] = MSG_TYPE_PROXY_PID;
j["data"] = pid;
int status;
SOCKET::send(j.dump(), status);
return status==0;
}
int main(int argc, char *argv[]){
int shift=1;
if (argc==1){
error("usage: cgproxy [--debug] <CMD>\nexample: cgroxy curl -I https://www.google.com");
exit(EXIT_FAILURE);
}
processArgs(argc,argv,shift);
if (!attach2cgproxy()){
error("attach process failed");
exit(EXIT_FAILURE);
}
string s=join2str(argc-shift,argv+shift,' ');
return system(s.c_str());
}

View File

@@ -1,140 +0,0 @@
#include "common.hpp"
#include "socket_server.hpp"
#include <fstream>
#include <iostream>
#include <libconfig.h++>
#include <nlohmann/json.hpp>
#include <pthread.h>
#include <sstream>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <csignal>
#include "config.hpp"
#include "cgroup_attach.hpp"
using namespace std;
using json = nlohmann::json;
using namespace CGPROXY::SOCKET;
using namespace CGPROXY::CONFIG;
using namespace CGPROXY::CGROUP;
namespace CGPROXY{
class cgproxyd{
thread_arg arg_t;
Config config;
pthread_t socket_thread_id = -1;
static cgproxyd* instance;
static int handle_msg_static(char* msg){
if (!instance) {
error("no cgproxyd instance assigned");
return ERROR;
}
return instance->handle_msg(msg);
}
static void signalHandler( int signum ){
debug("Signal %d received.", &signum);
if (!instance){ error("no cgproxyd instance assigned");}
else { instance->stop(); }
exit(signum);
}
int handle_msg(char *msg) {
debug("received msg: %s", msg);
json j;
try{ j = json::parse(msg); }catch(exception& e){debug("msg paser error");return MSG_ERROR;}
int type, status;
int pid, cgroup_target;
try {
type = j.at("type").get<int>();
switch (type)
{
case MSG_TYPE_JSON:
status=config.loadFromJson(j.at("data"));
if (status==SUCCESS) status=applyConfig(&config);
return status;
break;
case MSG_TYPE_CONFIG_PATH:
status=config.loadFromFile(j.at("data").get<string>());
if (status==SUCCESS) status=applyConfig(&config);
return status;
break;
case MSG_TYPE_PROXY_PID:
pid=j.at("data").get<int>();
status=attach(pid, config.cgroup_proxy_preserved);
return status;
break;
case MSG_TYPE_NOPROXY_PID:
pid=j.at("data").get<int>();
status=attach(pid, config.cgroup_noproxy_preserved);
return status;
break;
default:
return MSG_ERROR;
break;
};
} catch (out_of_range &e) {
return MSG_ERROR;
} catch (exception &e){
return ERROR;
}
}
pthread_t startSocketListeningThread() {
arg_t.handle_msg = &handle_msg_static;
pthread_t thread_id;
int status =
pthread_create(&thread_id, NULL, &SocketServer::startThread, &arg_t);
if (status != 0)
error("socket thread create failed");
return thread_id;
}
void assignStaticInstance(){
instance=this;
}
public:
int start(int argc, char* argv[]) {
signal(SIGINT, &signalHandler);
signal(SIGTERM,&signalHandler);
signal(SIGHUP,&signalHandler);
int shift=1;
processArgs(argc,argv,shift);
config.loadFromFile(DEFAULT_CONFIG_FILE);
applyConfig(&config);
assignStaticInstance();
socket_thread_id = startSocketListeningThread();
pthread_join(socket_thread_id, NULL);
return 0;
}
int applyConfig(Config *c) {
system(TPROXY_IPTABLS_CLEAN);
c->toEnv();
system(TPROXY_IPTABLS_START);
// no need to track running status
return 0;
}
void stop(){
debug("stopping");
system(TPROXY_IPTABLS_CLEAN);
}
~cgproxyd(){
stop();
}
};
cgproxyd* cgproxyd::instance=NULL;
}
int main(int argc, char* argv[]) {
CGPROXY::cgproxyd d;
return d.start(argc,argv);
}

View File

@@ -1,107 +0,0 @@
#ifndef COMMON_H
#define COMMON_H 1
#define TPROXY_IPTABLS_START "sh /usr/share/cgproxy/scripts/cgroup-tproxy.sh"
#define TPROXY_IPTABLS_CLEAN "sh /usr/share/cgproxy/scripts/cgroup-tproxy.sh stop"
#define SOCKET_PATH "/tmp/cgproxy_unix_socket"
#define LISTEN_BACKLOG 64
#define DEFAULT_CONFIG_FILE "/etc/cgproxy/config.json"
#define CGROUP_PROXY_PRESVERED "/proxy.slice"
#define CGROUP_NOPROXY_PRESVERED "/noproxy.slice"
#define MSG_TYPE_JSON 1
#define MSG_TYPE_CONFIG_PATH 2
#define MSG_TYPE_PROXY_PID 3
#define MSG_TYPE_NOPROXY_PID 4
#define UNKNOWN_ERROR 99
#define ERROR -1
#define SUCCESS 0
#define CONN_ERROR 1
#define MSG_ERROR 2
#define PARSE_ERROR 3
#define PARAM_ERROR 4
#define APPLY_ERROR 5
#define CGROUP_ERROR 6
#define FILE_ERROR 7
#include <iostream>
#include <sstream>
#include <string>
#include <regex>
using namespace std;
static bool enable_debug=false;
static bool print_help=false;
#define error(...) {fprintf(stderr, __VA_ARGS__);fprintf(stderr, "\n");}
#define debug(...) if (enable_debug) {fprintf(stdout, __VA_ARGS__);fprintf(stdout, "\n");}
#define return_error return -1;
#define return_success return 0;
void processArgs(const int argc, char *argv[], int &shift){
for (int i=1;i<argc;i++){
if (strcmp(argv[i], "--debug")==0){
enable_debug=true;
}
if (strcmp(argv[i], "--help")==0){
print_help=true;
}
if (argv[i][0]!='-') {
break;
}
shift+=1;
}
}
template <typename... T>
string to_str(T... args) {
stringstream ss;
ss.clear();
ss << std::boolalpha;
(ss << ... << args);
return ss.str();
}
string join2str(const vector<string> t, const char delm=' '){
string s;
for (const auto &e : t)
e!=*(t.end()-1)?s+=e+delm:s+=e;
return s;
}
string join2str(const int argc,char** argv, const char delm=' '){
string s;
for (int i=0;i<argc;i++){
s+=argv[i];
if (i!=argc-1) s+=delm;
}
return s;
}
bool validCgroup(const string cgroup){
return regex_match(cgroup, regex("^/[a-zA-Z0-9\\-_./@]*$"));
}
bool validCgroup(const vector<string> cgroup){
for (auto &e:cgroup){
if (!regex_match(e, regex("^/[a-zA-Z0-9\\-_./@]*$"))){
return false;
}
}
return true;
}
bool validPid(const string pid){
return regex_match(pid, regex("^[0-9]+$"));
}
bool validPort(const int port){
return port>0;
}
#endif

View File

@@ -1,143 +0,0 @@
#ifndef CONFIG_H
#define CONFIG_H
#include "common.hpp"
#include "socket_server.hpp"
#include <fstream>
#include <iostream>
#include <nlohmann/json.hpp>
#include <pthread.h>
#include <sstream>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <vector>
#include <set>
#include <iomanip>
using namespace std;
using json = nlohmann::json;
namespace CGPROXY::CONFIG{
struct Config {
public:
const string cgroup_proxy_preserved=CGROUP_PROXY_PRESVERED;
const string cgroup_noproxy_preserved=CGROUP_NOPROXY_PRESVERED;
private:
vector<string> cgroup_proxy;
vector<string> cgroup_noproxy;
bool enable_gateway = false;
int port = 12345;
bool enable_dns = true;
bool enable_tcp = true;
bool enable_udp = true;
bool enable_ipv4 = true;
bool enable_ipv6 = true;
public:
void toEnv() {
mergeReserved();
setenv("cgroup_proxy", join2str(cgroup_proxy,':').c_str(), 1);
setenv("cgroup_noproxy", join2str(cgroup_noproxy, ':').c_str(), 1);
setenv("enable_gateway", to_str(enable_gateway).c_str(), 1);
setenv("port", to_str(port).c_str(), 1);
setenv("enable_dns", to_str(enable_dns).c_str(), 1);
setenv("enable_tcp", to_str(enable_tcp).c_str(), 1);
setenv("enable_udp", to_str(enable_udp).c_str(), 1);
setenv("enable_ipv4", to_str(enable_ipv4).c_str(), 1);
setenv("enable_ipv6", to_str(enable_ipv6).c_str(), 1);
}
int saveToFile(const string f){
ofstream o(f);
if (!o.is_open()) return FILE_ERROR;
json j=toJson();
o << setw(4) << j << endl;
o.close();
return 0;
}
json toJson(){
json j;
#define add2json(v) j[#v]=v;
add2json(cgroup_proxy);
add2json(cgroup_noproxy);
add2json(enable_gateway);
add2json(port);
add2json(enable_dns);
add2json(enable_tcp);
add2json(enable_udp);
add2json(enable_ipv4);
add2json(enable_ipv6);
#undef add2json
return j;
}
int loadFromFile(const string f) {
debug("loading config: %s", f.c_str());
ifstream ifs(f);
if (ifs.is_open()){
json j;
try { ifs >> j; }catch (exception& e){error("parse error: %s", f.c_str());ifs.close();return PARSE_ERROR;}
ifs.close();
return loadFromJson(j);
}else{
error("open failed: %s",f.c_str());
return FILE_ERROR;
}
}
int loadFromJson(const json &j) {
if (!validateJson(j)) {error("json validate fail"); return PARAM_ERROR;}
#define tryassign(v) try{j.at(#v).get_to(v);}catch(exception &e){}
tryassign(cgroup_proxy);
tryassign(cgroup_noproxy);
tryassign(enable_gateway);
tryassign(port);
tryassign(enable_dns);
tryassign(enable_tcp);
tryassign(enable_udp);
tryassign(enable_ipv4);
tryassign(enable_ipv6);
#undef assign
return 0;
}
void mergeReserved(){
#define merge(v) { \
v.erase(std::remove(v.begin(), v.end(), v ## _preserved), v.end()); \
v.insert(v.begin(), v ## _preserved); \
}
merge(cgroup_proxy);
merge(cgroup_noproxy);
#undef merge
}
bool validateJson(const json &j){
bool status=true;
const set<string> boolset={"enable_gateway","enable_dns","enable_tcp","enable_udp","enable_ipv4","enable_ipv6"};
for (auto& [key, value] : j.items()) {
if (key=="cgroup_proxy"||key=="cgroup_noproxy"){
if (value.is_string()&&!validCgroup((string)value)) status=false;
// TODO what if vector<int> etc.
if (value.is_array()&&!validCgroup((vector<string>)value)) status=false;
if (!value.is_string()&&!value.is_array()) status=false;
}else if (key=="port"){
if (!validPort(value)) status=false;
}else if (boolset.find(key)!=boolset.end()){
if (!value.is_boolean()) status=false;
}else{
error("unknown key: %s", key.c_str());
return false;
}
if (!status) {
error("invalid value for key: %s", key.c_str());
return false;
}
}
return true;
}
};
}
#endif

24
pack/CMakeLists.txt Normal file
View File

@@ -0,0 +1,24 @@
## package for deb and rpm
set(CPACK_GENERATOR "DEB;RPM")
set(CPACK_PACKAGE_NAME "cgproxy")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "cgproxy will transparent proxy anything running in specific cgroup.It aslo supports global transparent proxy and gateway proxy")
## deb pack
set(CPACK_DEBIAN_PACKAGE_NAME "cgproxy")
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "x86_64")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "systemd")
set(CPACK_DEBIAN_PACKAGE_SECTION "network")
set(CPACK_DEBIAN_PACKAGE_PRIORITY "Optional")
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/springzfx/cgproxy")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "springzfx@gmail.com")
set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_CURRENT_SOURCE_DIR}/postinst;${CMAKE_CURRENT_SOURCE_DIR}/prerm")
## rpm pack
set(CPACK_RPM_PACKAGE_ARCHITECTURE, "x86_64")
set(CPACK_RPM_PACKAGE_REQUIRES "systemd")
set(CPACK_RPM_PACKAGE_GROUP "network")
set(CPACK_RPM_PACKAGE_URL "https://github.com/springzfx/cgproxy")
set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/postinst")
set(CPACK_RPM_PRE_UNINSTALL_SCRIPT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/prerm")
include(CPack)

View File

@@ -67,7 +67,7 @@ mkdir build && cd build && cmake .. && make && make install
- For example, test proxy
```bash
cgproxy curl -I https://www.google.com
cgproxy curl -vI https://www.google.com
```
- To completely stop
@@ -153,7 +153,7 @@ sudo systemctl restart cgproxy.service
## NOTES
- v2ray TPROXY need root or special permission
- v2ray TPROXY need root or special permission, use [service](https://github.com/springzfx/cgproxy/blob/v3.x/v2ray_config/v2ray.service) or
```bash
sudo setcap "cap_net_admin,cap_net_bind_service=ep" /usr/lib/v2ray/v2ray

12
src/CMakeLists.txt Normal file
View File

@@ -0,0 +1,12 @@
find_package(Threads REQUIRED)
find_package(nlohmann_json REQUIRED)
include_directories(${PROJECT_SOURCE_DIR})
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
add_executable(cgproxyd cgproxyd.cpp)
add_executable(cgproxy cgproxy.cpp)
target_link_libraries(cgproxyd PRIVATE Threads::Threads nlohmann_json::nlohmann_json)
target_link_libraries(cgproxy PRIVATE nlohmann_json::nlohmann_json)
install(TARGETS cgproxyd DESTINATION /usr/bin PERMISSIONS ${basic_permission})
install(TARGETS cgproxy DESTINATION /usr/bin PERMISSIONS ${basic_permission})

49
src/cgproxy.cpp Normal file
View File

@@ -0,0 +1,49 @@
#include "common.hpp"
#include "socket_client.hpp"
#include <nlohmann/json.hpp>
using json = nlohmann::json;
using namespace CGPROXY;
bool print_help = false, proxy = true;
void print_usage() {
fprintf(stdout, "Usage: cgproxy [--help] [--debug] [--noproxy] <CMD>\n");
fprintf(stdout, "Alias: cgnoproxy = cgproxy --noproxy\n");
}
void processArgs(const int argc, char *argv[], int &shift) {
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--noproxy") == 0) { proxy = false; }
if (strcmp(argv[i], "--debug") == 0) { enable_debug = true; }
if (strcmp(argv[i], "--help") == 0) { print_help = true; }
if (argv[i][0] != '-') { break; }
shift += 1;
}
}
bool attach2cgroup(pid_t pid, bool proxy) {
json j;
j["type"] = proxy ? MSG_TYPE_PROXY_PID : MSG_TYPE_NOPROXY_PID;
j["data"] = pid;
int status;
SOCKET::send(j.dump(), status);
return status == 0;
}
int main(int argc, char *argv[]) {
int shift = 1;
processArgs(argc, argv, shift);
if (argc == shift || print_help) {
print_usage();
exit(0);
}
pid_t pid = getpid();
if (!attach2cgroup(pid, proxy)) {
error("attach process failed");
exit(EXIT_FAILURE);
}
string s = join2str(argc - shift, argv + shift, ' ');
return system(s.c_str());
}

184
src/cgproxyd.cpp Normal file
View File

@@ -0,0 +1,184 @@
#include "cgroup_attach.hpp"
#include "common.hpp"
#include "config.hpp"
#include "socket_server.hpp"
#include <csignal>
#include <errno.h>
#include <fstream>
#include <iostream>
#include <nlohmann/json.hpp>
#include <pthread.h>
#include <sstream>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <sys/file.h>
using namespace std;
using json = nlohmann::json;
using namespace CGPROXY::SOCKET;
using namespace CGPROXY::CONFIG;
using namespace CGPROXY::CGROUP;
namespace CGPROXY {
class cgproxyd {
thread_arg arg_t;
Config config;
pthread_t socket_thread_id = -1;
static cgproxyd *instance;
static int handle_msg_static(char *msg) {
if (!instance) {
error("no cgproxyd instance assigned");
return ERROR;
}
return instance->handle_msg(msg);
}
static void signalHandler(int signum) {
debug("Signal %d received.", signum);
if (!instance) {
error("no cgproxyd instance assigned");
} else {
instance->stop();
}
exit(signum);
}
// single process instance
int lock_fd;
void lock() {
lock_fd = open(PID_LOCK_FILE, O_CREAT | O_RDWR, 0666);
int rc = flock(lock_fd, LOCK_EX | LOCK_NB);
if (rc == -1) {
perror(PID_LOCK_FILE);
error("maybe another cgproxyd is running");
exit(EXIT_FAILURE);
} else {
ofstream ofs(PID_LOCK_FILE);
ofs << getpid() << endl;
ofs.close();
}
}
void unlock() {
close(lock_fd);
unlink(PID_LOCK_FILE);
}
int handle_msg(char *msg) {
debug("received msg: %s", msg);
json j;
try {
j = json::parse(msg);
} catch (exception &e) {
debug("msg paser error");
return MSG_ERROR;
}
int type, status;
int pid, cgroup_target;
try {
type = j.at("type").get<int>();
switch (type) {
case MSG_TYPE_JSON:
status = config.loadFromJson(j.at("data"));
if (status == SUCCESS) status = applyConfig(&config);
return status;
break;
case MSG_TYPE_CONFIG_PATH:
status = config.loadFromFile(j.at("data").get<string>());
if (status == SUCCESS) status = applyConfig(&config);
return status;
break;
case MSG_TYPE_PROXY_PID:
pid = j.at("data").get<int>();
status = attach(pid, config.cgroup_proxy_preserved);
return status;
break;
case MSG_TYPE_NOPROXY_PID:
pid = j.at("data").get<int>();
status = attach(pid, config.cgroup_noproxy_preserved);
return status;
break;
default: return MSG_ERROR; break;
};
} catch (out_of_range &e) { return MSG_ERROR; } catch (exception &e) {
return ERROR;
}
}
pthread_t startSocketListeningThread() {
arg_t.handle_msg = &handle_msg_static;
pthread_t thread_id;
int status = pthread_create(&thread_id, NULL, &SocketServer::startThread, &arg_t);
if (status != 0) error("socket thread create failed");
return thread_id;
}
void assignStaticInstance() { instance = this; }
public:
int start() {
lock();
signal(SIGINT, &signalHandler);
signal(SIGTERM, &signalHandler);
signal(SIGHUP, &signalHandler);
config.loadFromFile(DEFAULT_CONFIG_FILE);
applyConfig(&config);
assignStaticInstance();
socket_thread_id = startSocketListeningThread();
pthread_join(socket_thread_id, NULL);
return 0;
}
int applyConfig(Config *c) {
system(TPROXY_IPTABLS_CLEAN);
c->toEnv();
system(TPROXY_IPTABLS_START);
// no need to track running status
return 0;
}
void stop() {
debug("stopping");
system(TPROXY_IPTABLS_CLEAN);
unlock();
}
~cgproxyd() { stop(); }
};
cgproxyd *cgproxyd::instance = NULL;
} // namespace CGPROXY
bool print_help = false;
void print_usage() { printf("cgproxyd [--help] [--debug]\n"); }
void processArgs(const int argc, char *argv[]) {
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--debug") == 0) { enable_debug = true; }
if (strcmp(argv[i], "--help") == 0) { print_help = true; }
if (argv[i][0] != '-') { break; }
}
}
int main(int argc, char *argv[]) {
processArgs(argc, argv);
if (print_help) {
print_usage();
exit(0);
}
if (getuid() != 0) {
error("permission denied, need root");
print_usage();
exit(EXIT_FAILURE);
}
CGPROXY::cgproxyd d;
return d.start();
}

View File

@@ -1,66 +1,63 @@
#ifndef CGPROUP_ATTACH_H
#define CGPROUP_ATTACH_H
#include "common.hpp"
#include <errno.h>
#include <fstream>
#include <iostream>
#include <regex>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include "common.hpp"
using namespace std;
namespace CGPROXY::CGROUP{
namespace CGPROXY::CGROUP {
bool exist(string path) {
struct stat st;
if (stat(path.c_str(), &st) != -1) {
return S_ISDIR(st.st_mode);
}
if (stat(path.c_str(), &st) != -1) { return S_ISDIR(st.st_mode); }
return false;
}
bool validate(string pid, string cgroup) {
bool pid_v = validPid(pid);
bool cg_v = validCgroup(cgroup);
if (pid_v && cg_v)
return true;
if (pid_v && cg_v) return true;
error("attach paramater validate error");
return_error
}
string get_cgroup2_mount_point(int &status){
char cgroup2_mount_point[100]="";
FILE* fp = popen("findmnt -t cgroup2 -n -o TARGET", "r");
int count=fscanf(fp,"%s",&cgroup2_mount_point);
string get_cgroup2_mount_point(int &status) {
char cgroup2_mount_point[100] = "";
FILE *fp = popen("findmnt -t cgroup2 -n -o TARGET", "r");
int count = fscanf(fp, "%s", cgroup2_mount_point);
fclose(fp);
if (count=0){
if (count == 0) {
error("cgroup2 not supported");
status=-1;
status = -1;
return NULL;
}
status=0;
status = 0;
return cgroup2_mount_point;
}
int attach(const string pid, const string cgroup_target) {
if (getuid()!=0) {
if (getuid() != 0) {
error("need root to attach cgroup");
return_error
}
debug("attaching %s to %s",pid.c_str(),cgroup_target.c_str());
debug("attaching %s to %s", pid.c_str(), cgroup_target.c_str());
int status;
if (!validate(pid, cgroup_target)) return_error
string cgroup_mount_point = get_cgroup2_mount_point(status);
if (status!=0) return_error
string cgroup_target_path = cgroup_mount_point + cgroup_target;
if (!validate(pid, cgroup_target))
return_error string cgroup_mount_point = get_cgroup2_mount_point(status);
if (status != 0)
return_error string cgroup_target_path = cgroup_mount_point + cgroup_target;
string cgroup_target_procs = cgroup_target_path + "/cgroup.procs";
// check if exist, we will create it if not exist
@@ -87,17 +84,17 @@ int attach(const string pid, const string cgroup_target) {
// maybe there some write error, for example process pid may not exist
if (!procs) {
error("write %s to %s failed, maybe process %s not exist",
pid.c_str(), cgroup_target_procs.c_str(), pid.c_str());
error("write %s to %s failed, maybe process %s not exist", pid.c_str(),
cgroup_target_procs.c_str(), pid.c_str());
return_error
}
return_success
}
int attach(const int pid, const string cgroup_target){
int attach(const int pid, const string cgroup_target) {
return attach(to_str(pid), cgroup_target);
}
}
} // namespace CGPROXY::CGROUP
#endif

90
src/common.hpp Normal file
View File

@@ -0,0 +1,90 @@
#ifndef COMMON_H
#define COMMON_H 1
#define TPROXY_IPTABLS_START "sh /usr/share/cgproxy/scripts/cgroup-tproxy.sh"
#define TPROXY_IPTABLS_CLEAN "sh /usr/share/cgproxy/scripts/cgroup-tproxy.sh stop"
#define PID_LOCK_FILE "/var/run/cgproxyd.pid"
#define SOCKET_PATH "/tmp/cgproxy_unix_socket"
#define LISTEN_BACKLOG 64
#define DEFAULT_CONFIG_FILE "/etc/cgproxy/config.json"
#define CGROUP_PROXY_PRESVERED "/proxy.slice"
#define CGROUP_NOPROXY_PRESVERED "/noproxy.slice"
#define MSG_TYPE_JSON 1
#define MSG_TYPE_CONFIG_PATH 2
#define MSG_TYPE_PROXY_PID 3
#define MSG_TYPE_NOPROXY_PID 4
#define UNKNOWN_ERROR 99
#define ERROR -1
#define SUCCESS 0
#define CONN_ERROR 1
#define MSG_ERROR 2
#define PARSE_ERROR 3
#define PARAM_ERROR 4
#define APPLY_ERROR 5
#define CGROUP_ERROR 6
#define FILE_ERROR 7
#include <iostream>
#include <regex>
#include <sstream>
#include <string>
using namespace std;
static bool enable_debug = false;
#define error(...) \
{ \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, "\n"); \
}
#define debug(...) \
if (enable_debug) { \
fprintf(stdout, __VA_ARGS__); \
fprintf(stdout, "\n"); \
}
#define return_error return -1;
#define return_success return 0;
template <typename... T> string to_str(T... args) {
stringstream ss;
ss.clear();
ss << std::boolalpha;
(ss << ... << args);
return ss.str();
}
string join2str(const vector<string> t, const char delm = ' ') {
string s;
for (const auto &e : t) e != *(t.end() - 1) ? s += e + delm : s += e;
return s;
}
string join2str(const int argc, char **argv, const char delm = ' ') {
string s;
for (int i = 0; i < argc; i++) {
s += argv[i];
if (i != argc - 1) s += delm;
}
return s;
}
bool validCgroup(const string cgroup) {
return regex_match(cgroup, regex("^/[a-zA-Z0-9\\-_./@]*$"));
}
bool validCgroup(const vector<string> cgroup) {
for (auto &e : cgroup) {
if (!regex_match(e, regex("^/[a-zA-Z0-9\\-_./@]*$"))) { return false; }
}
return true;
}
bool validPid(const string pid) { return regex_match(pid, regex("^[0-9]+$")); }
bool validPort(const int port) { return port > 0; }
#endif

155
src/config.hpp Normal file
View File

@@ -0,0 +1,155 @@
#ifndef CONFIG_H
#define CONFIG_H
#include "common.hpp"
#include <fstream>
#include <iomanip>
#include <iostream>
#include <nlohmann/json.hpp>
#include <set>
#include <sstream>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <vector>
using namespace std;
using json = nlohmann::json;
namespace CGPROXY::CONFIG {
struct Config {
public:
const string cgroup_proxy_preserved = CGROUP_PROXY_PRESVERED;
const string cgroup_noproxy_preserved = CGROUP_NOPROXY_PRESVERED;
private:
vector<string> cgroup_proxy;
vector<string> cgroup_noproxy;
bool enable_gateway = false;
int port = 12345;
bool enable_dns = true;
bool enable_tcp = true;
bool enable_udp = true;
bool enable_ipv4 = true;
bool enable_ipv6 = true;
public:
void toEnv() {
mergeReserved();
setenv("cgroup_proxy", join2str(cgroup_proxy, ':').c_str(), 1);
setenv("cgroup_noproxy", join2str(cgroup_noproxy, ':').c_str(), 1);
setenv("enable_gateway", to_str(enable_gateway).c_str(), 1);
setenv("port", to_str(port).c_str(), 1);
setenv("enable_dns", to_str(enable_dns).c_str(), 1);
setenv("enable_tcp", to_str(enable_tcp).c_str(), 1);
setenv("enable_udp", to_str(enable_udp).c_str(), 1);
setenv("enable_ipv4", to_str(enable_ipv4).c_str(), 1);
setenv("enable_ipv6", to_str(enable_ipv6).c_str(), 1);
}
int saveToFile(const string f) {
ofstream o(f);
if (!o.is_open()) return FILE_ERROR;
json j = toJson();
o << setw(4) << j << endl;
o.close();
return 0;
}
#define add2json(v) j[#v] = v;
json toJson() {
json j;
add2json(cgroup_proxy);
add2json(cgroup_noproxy);
add2json(enable_gateway);
add2json(port);
add2json(enable_dns);
add2json(enable_tcp);
add2json(enable_udp);
add2json(enable_ipv4);
add2json(enable_ipv6);
return j;
}
#undef add2json
int loadFromFile(const string f) {
debug("loading config: %s", f.c_str());
ifstream ifs(f);
if (ifs.is_open()) {
json j;
try {
ifs >> j;
} catch (exception &e) {
error("parse error: %s", f.c_str());
ifs.close();
return PARSE_ERROR;
}
ifs.close();
return loadFromJson(j);
} else {
error("open failed: %s", f.c_str());
return FILE_ERROR;
}
}
#define tryassign(v) \
try { \
j.at(#v).get_to(v); \
} catch (exception & e) {}
int loadFromJson(const json &j) {
if (!validateJson(j)) {
error("json validate fail");
return PARAM_ERROR;
}
tryassign(cgroup_proxy);
tryassign(cgroup_noproxy);
tryassign(enable_gateway);
tryassign(port);
tryassign(enable_dns);
tryassign(enable_tcp);
tryassign(enable_udp);
tryassign(enable_ipv4);
tryassign(enable_ipv6);
return 0;
}
#undef assign
#define merge(v) \
{ \
v.erase(std::remove(v.begin(), v.end(), v##_preserved), v.end()); \
v.insert(v.begin(), v##_preserved); \
}
void mergeReserved() {
merge(cgroup_proxy);
merge(cgroup_noproxy);
}
#undef merge
bool validateJson(const json &j) {
bool status = true;
const set<string> boolset = {"enable_gateway", "enable_dns", "enable_tcp",
"enable_udp", "enable_ipv4", "enable_ipv6"};
for (auto &[key, value] : j.items()) {
if (key == "cgroup_proxy" || key == "cgroup_noproxy") {
if (value.is_string() && !validCgroup((string)value)) status = false;
// TODO what if vector<int> etc.
if (value.is_array() && !validCgroup((vector<string>)value)) status = false;
if (!value.is_string() && !value.is_array()) status = false;
} else if (key == "port") {
if (!validPort(value)) status = false;
} else if (boolset.find(key) != boolset.end()) {
if (!value.is_boolean()) status = false;
} else {
error("unknown key: %s", key.c_str());
return false;
}
if (!status) {
error("invalid value for key: %s", key.c_str());
return false;
}
}
return true;
}
};
} // namespace CGPROXY::CONFIG
#endif

View File

@@ -1,6 +1,7 @@
#ifndef SOCKET_CLIENT_H
#define SOCKET_CLIENT_H
#include "common.hpp"
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
@@ -9,18 +10,17 @@
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#include "common.hpp"
using namespace std;
namespace CGPROXY::SOCKET{
namespace CGPROXY::SOCKET {
#define return_if_error(flag, msg) \
if (flag == -1) { \
perror(msg); \
status = CONN_ERROR; \
close(sfd); \
return; \
#define return_if_error(flag, msg) \
if (flag == -1) { \
perror(msg); \
status = CONN_ERROR; \
close(sfd); \
return; \
}
void send(const char *msg, int &status) {
@@ -35,8 +35,7 @@ void send(const char *msg, int &status) {
unix_socket.sun_family = AF_UNIX;
strncpy(unix_socket.sun_path, SOCKET_PATH, sizeof(unix_socket.sun_path) - 1);
flag =
connect(sfd, (struct sockaddr *)&unix_socket, sizeof(struct sockaddr_un));
flag = connect(sfd, (struct sockaddr *)&unix_socket, sizeof(struct sockaddr_un));
return_if_error(flag, "connect");
int msg_len = strlen(msg);
@@ -60,5 +59,5 @@ void send(const string msg, int &status) {
debug("return status: %d", status);
}
}
} // namespace CGPROXY::SOCKET
#endif

View File

@@ -1,6 +1,8 @@
#ifndef SOCKET_SERVER_H
#define SOCKET_SERVER_H
#include "common.hpp"
#include <filesystem>
#include <functional>
#include <iostream>
#include <signal.h>
@@ -8,21 +10,19 @@
#include <stdlib.h>
#include <string>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#include <sys/stat.h>
#include <filesystem>
#include "common.hpp"
using namespace std;
namespace fs = std::filesystem;
namespace CGPROXY::SOCKET{
namespace CGPROXY::SOCKET {
#define continue_if_error(flag, msg) \
if (flag == -1) { \
perror(msg); \
continue; \
#define continue_if_error(flag, msg) \
if (flag == -1) { \
perror(msg); \
continue; \
}
struct thread_arg {
@@ -38,19 +38,18 @@ public:
debug("starting socket listening");
sfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fs::exists(SOCKET_PATH)&&unlink(SOCKET_PATH)==-1){
error("%s exist, and can't unlink",SOCKET_PATH);
if (fs::exists(SOCKET_PATH) && unlink(SOCKET_PATH) == -1) {
error("%s exist, and can't unlink", SOCKET_PATH);
return;
}
memset(&unix_socket, '\0', sizeof(struct sockaddr_un));
unix_socket.sun_family = AF_UNIX;
strncpy(unix_socket.sun_path, SOCKET_PATH,
sizeof(unix_socket.sun_path) - 1);
strncpy(unix_socket.sun_path, SOCKET_PATH, sizeof(unix_socket.sun_path) - 1);
bind(sfd, (struct sockaddr *)&unix_socket, sizeof(struct sockaddr_un));
listen(sfd, LISTEN_BACKLOG);
chmod(SOCKET_PATH,S_IRWXU|S_IRWXG|S_IRWXO);
chmod(SOCKET_PATH, S_IRWXU | S_IRWXG | S_IRWXO);
while (true) {
close(cfd);
@@ -66,7 +65,7 @@ public:
char msg[msg_len];
flag = read(cfd, msg, msg_len * sizeof(char));
continue_if_error(flag, "read msg");
msg[msg_len]='\0';
msg[msg_len] = '\0';
// handle msg
int status = callback(msg);
// send back flag
@@ -89,6 +88,6 @@ public:
}
};
}
} // namespace CGPROXY::SOCKET
#endif

6
test/CMakeLists.txt Normal file
View File

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

View File

@@ -0,0 +1,48 @@
#include "socket_client.hpp"
#include <nlohmann/json.hpp>
using namespace std;
using json = nlohmann::json;
using namespace CGPROXY;
void test_json() {
json j;
j["type"] = MSG_TYPE_JSON;
j["data"]["cgroup_proxy"] = "/";
j["data"]["enable_dns"] = false;
int status;
SOCKET::send(j.dump(), status);
}
void test_json_array() {
json j;
j["type"] = MSG_TYPE_JSON;
j["data"]["cgroup_proxy"] = "/proxy.slice";
j["data"]["cgroup_noproxy"] = {"/noproxy.slice", "/system.slice/v2ray.service"};
int status;
SOCKET::send(j.dump(), status);
}
void test_file() {
json j;
j["type"] = MSG_TYPE_CONFIG_PATH;
j["data"] = "/etc/cgproxy.conf";
int status;
SOCKET::send(j.dump(), status);
}
void test_pid() {
json j;
j["type"] = MSG_TYPE_PROXY_PID;
j["data"] = "9999";
int status;
SOCKET::send(j.dump(), status);
}
int main() {
test_json_array();
test_file();
test_json();
test_pid();
return 0;
}

18
test/test.cpp Normal file
View File

@@ -0,0 +1,18 @@
// #include "common.h"
#include "config.hpp"
#include <fstream>
#include <iostream>
#include <pthread.h>
#include <sstream>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <vector>
using namespace std;
using namespace CGPROXY::CONFIG;
int main() {
Config c;
c.saveToFile("./config.json");
return 0;
}

4
tools/CMakeLists.txt Normal file
View File

@@ -0,0 +1,4 @@
include_directories(${PROJECT_SOURCE_DIR})
include_directories(${PROJECT_SOURCE_DIR}/src)
add_executable(cgattach cgattach.cpp)
install(TARGETS cgattach DESTINATION /usr/bin PERMISSIONS ${basic_permission})

View File

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