From 28c2c05c5d303f02dfd256d1676187cf40af4361 Mon Sep 17 00:00:00 2001 From: csujedihy Date: Wed, 19 Aug 2015 23:06:17 -0500 Subject: [PATCH] added VPN mode --- proximac-cli/jconf.c | 32 +++-- proximac-cli/jconf.h | 2 + proximac-cli/local.c | 51 ++++--- proximac-cli/local.h | 3 + proximac-cli/utils.h | 125 +++--------------- proximac.xcodeproj/project.pbxproj | 5 +- .../xcshareddata/proximac.xccheckout | 4 +- .../UserInterfaceState.xcuserstate | Bin 27818 -> 29725 bytes .../xcschemes/proximac-cli.xcscheme | 9 +- .../xcschemes/proximac.xcscheme | 2 +- proximac/proximac.c | 92 ++++++++++--- proximac/proximac.h | 5 +- 12 files changed, 167 insertions(+), 163 deletions(-) diff --git a/proximac-cli/jconf.c b/proximac-cli/jconf.c index 78ac799..405d1a4 100644 --- a/proximac-cli/jconf.c +++ b/proximac-cli/jconf.c @@ -18,7 +18,10 @@ void read_conf(char* configfile, conf_t* conf) char* configbuf = NULL; char localport_buf[6] = { 0 }; char proximac_port_buf[6] = { 0 }; + char vpn_mode_buf[6] = { 0 }; int vlen = 0; + + /* need to reset these bufs to zero */ FILE* f = fopen(configfile, "rb"); if (f == NULL) { @@ -60,25 +63,38 @@ void read_conf(char* configfile, conf_t* conf) pid_to_insert->name[vlen] = '\0'; pid_to_insert->pid = hash(pid_to_insert->name); RB_INSERT(pid_tree, &pid_list, pid_to_insert); - LOGI("%d. %s hash %x", index, pid_to_insert->name, pid_to_insert->pid); + LOGI("%d. %s hash %d", index, pid_to_insert->name, pid_to_insert->pid); } conf->total_process_num = index; } - JSONPARSE("proximac_listen_address") - { - conf->proximac_listen_address = (char*)malloc(vlen + 1); - memcpy(conf->proximac_listen_address, val, vlen); - conf->proximac_listen_address[vlen] = '\0'; - } - JSONPARSE("proximac_port") { memcpy(proximac_port_buf, val, vlen); conf->proximac_port = atoi(proximac_port_buf); } + + JSONPARSE("VPN_mode") + { + memcpy(vpn_mode_buf, val, vlen); + conf->vpn_mode = atoi(vpn_mode_buf); + } + JSONPARSE("proxyapp_name") + { + struct pid* proxyapp_hash = malloc(sizeof(struct pid)); + proxyapp_hash->name = calloc(1, vlen + 1); + memcpy(proxyapp_hash->name, val, vlen); + proxyapp_hash->name[vlen] = '\0'; + proxyapp_hash->pid = hash(proxyapp_hash->name); + LOGI("Proxy App name is %s", proxyapp_hash->name); + LOGD("Proxy App name is %s, hash %d", proxyapp_hash->name, proxyapp_hash->pid); + + conf->proxyapp_hash = proxyapp_hash->pid; + free(proxyapp_hash); + } + JSONPARSE("local_port") { memcpy(localport_buf, val, vlen); diff --git a/proximac-cli/jconf.h b/proximac-cli/jconf.h index 47c7583..981a422 100644 --- a/proximac-cli/jconf.h +++ b/proximac-cli/jconf.h @@ -25,6 +25,8 @@ typedef struct { uint16_t proximac_port; int pid; int total_process_num; + int vpn_mode; + int proxyapp_hash; } conf_t; extern void read_conf(char* configfile, conf_t* conf); diff --git a/proximac-cli/local.c b/proximac-cli/local.c index 9c94281..61c80a3 100644 --- a/proximac-cli/local.c +++ b/proximac-cli/local.c @@ -118,8 +118,7 @@ static void remote_read_cb(uv_stream_t* stream, ssize_t nread, const uv_buf_t* b wr->req.data = server_ctx; unsigned char username_len = strlen(conf.username); unsigned char password_len = strlen(conf.password); - int len = 1 /* fixed 1 byte */ + 2 /* two bytes indicate the length of username and password */ + username_len + password_len; - int offset = 0; + int len = 1 /* fixed 1 byte */ + 2 /* 2 bytes for username and password */ + username_len + password_len; char* socks5req = malloc(len); socks5req[0] = 0x01; /* version of auth */ socks5req[1] = username_len; @@ -297,13 +296,12 @@ static void server_read_cb(uv_stream_t* stream, ssize_t nread, const uv_buf_t* b server_ctx->buf = tmpbuf; } else { - server_ctx->buf_len = NULL; + server_ctx->buf_len = 0; server_ctx->buf = NULL; } free(buf->base); - LOGD("server_ctx %x addrlen = %d addr = %s port = %d", server_ctx, server_ctx->addrlen, server_ctx->remote_addr, server_ctx->port); struct sockaddr_in remote_addr; memset(&remote_addr, 0, sizeof(remote_addr)); @@ -353,11 +351,10 @@ int tell_kernel_to_hook() struct ctl_info ctl_info; struct sockaddr_ctl sc; errno_t retval = 0; - LOGI("tell kernel"); gSocket = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL); if (gSocket < 0) { - LOGE("socket SYSPROTO_CONTROL"); + LOGI("socket() failed."); exit(EXIT_FAILURE); } @@ -377,16 +374,28 @@ int tell_kernel_to_hook() sc.sc_unit = 0; if (connect(gSocket, (struct sockaddr*)&sc, sizeof(struct sockaddr_ctl))) { - LOGE("connect"); + LOGI("Connection to kernel failed. The kernel module may not be correctly loaded."); exit(EXIT_FAILURE); } - int tmp = 0; - retval = setsockopt(gSocket, SYSPROTO_CONTROL, PROXIMAC_ON, &tmp, sizeof(tmp)); + + int vpn_mode = 0; + if (conf.vpn_mode == 1) + vpn_mode = 1; + + retval = setsockopt(gSocket, SYSPROTO_CONTROL, PROXIMAC_ON, &vpn_mode, sizeof(vpn_mode)); if (retval) { LOGE("setsockopt failure PROXIMAC_ON"); return retval; } + + if (vpn_mode == 1) { + retval = setsockopt(gSocket, SYSPROTO_CONTROL, NOT_TO_HOOK, &conf.proxyapp_hash, sizeof(conf.proxyapp_hash)); + if (retval) { + LOGE("setsockopt failure NOT_TO_HOOK"); + return retval; + } + } struct pid* pid_tmp = NULL; int pidset_checksum = 0; @@ -422,7 +431,10 @@ int tell_kernel_to_hook() return retval; } - LOGI("pid_num = %d", pid_num); + if (conf.vpn_mode == 1) + LOGI("All traffic will be redirected to this SOCKS5 proxy"); + else + LOGI("The total number of process that will be hooked = %d", pid_num); return retval; } @@ -451,7 +463,7 @@ int main(int argc, char** argv) { int c, option_index = 0, daemon = 0; char* configfile = NULL; - char* logfile_path = "/tmp/proximac.log"; + char* logfile_path = "./proximac.log"; RB_INIT(&pid_list); opterr = 0; static struct option long_options[] = { @@ -481,6 +493,10 @@ int main(int argc, char** argv) usage(); exit(EXIT_FAILURE); } + + if (log_to_file) { + USE_LOGFILE(logfile_path); + } if (configfile) { read_conf(configfile, &conf); @@ -497,8 +513,7 @@ int main(int argc, char** argv) if (daemon == 1) init_daemon(); - if (log_to_file) - USE_LOGFILE(logfile_path); + struct sockaddr_in bind_addr; loop = malloc(sizeof *loop); @@ -508,16 +523,16 @@ int main(int argc, char** argv) uv_tcp_init(loop, &listener->handle); uv_tcp_nodelay(&listener->handle, 1); - r = uv_ip4_addr(conf.proximac_listen_address, conf.proximac_port, &bind_addr); + r = uv_ip4_addr("127.0.0.1", conf.proximac_port, &bind_addr); if (r) - LOGE("address error"); + LOGE("Translate address error"); r = uv_tcp_bind(&listener->handle, (struct sockaddr*)&bind_addr, 0); if (r) - LOGI("bind error"); + LOGI("Bind error"); r = uv_listen((uv_stream_t*)&listener->handle, 128 /*backlog*/, server_accept_cb); if (r) - LOGI("listen error port"); - LOGI("Listening on %s:%d", conf.proximac_listen_address, conf.proximac_port); + LOGI("Listen error"); + LOGI("Listening on %d", conf.proximac_port); signal(SIGPIPE, SIG_IGN); uv_signal_t sigint, sigstp, sigkil; diff --git a/proximac-cli/local.h b/proximac-cli/local.h index 1dc739c..dda9be1 100644 --- a/proximac-cli/local.h +++ b/proximac-cli/local.h @@ -7,6 +7,8 @@ #define CTL_INIT 0x01 #define CTL_NORMAL 0 +#define LOCALHOST "127.0.0.1" + // packet related MACROs #define MAX_PKT_SIZE 8192 #define ID_LEN 4 @@ -30,6 +32,7 @@ #define HOOK_PID 2 #define PIDLIST_STATUS 3 #define PROXIMAC_OFF 4 +#define NOT_TO_HOOK 5 #include "tree.h" diff --git a/proximac-cli/utils.h b/proximac-cli/utils.h index 2812c09..3613a95 100644 --- a/proximac-cli/utils.h +++ b/proximac-cli/utils.h @@ -7,6 +7,7 @@ #include #include #include + extern FILE * logfile; #if __GNUC__ >= 3 @@ -47,102 +48,15 @@ extern FILE * logfile; assert(0); \ } while(0) -#ifdef XCODE_DEBUG -#define LOGI(format, ...) \ -do { \ - time_t now = time(NULL); \ - char timestr[20]; \ - strftime(timestr, 20, TIME_FORMAT, localtime(&now)); \ - fprintf(stderr, " %s INFO: " format "\n", timestr, \ - ## __VA_ARGS__); \ - fflush(stderr); \ -} \ -while (0) -#else -#define LOGI(format, ...) \ -do { \ - time_t now = time(NULL); \ - char timestr[20]; \ - strftime(timestr, 20, TIME_FORMAT, localtime(&now)); \ - fprintf(stderr, "\x1b[32m %s INFO: \e[0m" format "\n", \ - timestr,## __VA_ARGS__); \ - fflush(stderr); \ -} \ -while (0) -#endif -#ifdef XCODE_DEBUG -#define LOGW(format, ...) \ -do { \ - time_t now = time(NULL); \ - char timestr[20]; \ - strftime(timestr, 20, TIME_FORMAT, localtime(&now)); \ - fprintf(stderr, " %s WARN: " format "\n", \ - timestr,## __VA_ARGS__); \ - fflush(stderr); \ -} \ -while (0) -#else -#define LOGW(format, ...) \ -do { \ - time_t now = time(NULL); \ - char timestr[20]; \ - strftime(timestr, 20, TIME_FORMAT, localtime(&now)); \ - if (logfile != NULL) { \ - fprintf(logfile, " %s WARN: " format "\n", \ - timestr,## __VA_ARGS__); \ - fflush(logfile); \ - } \ - else { \ - fprintf(stderr, "\x1b[33m %s WARN: \e[0m" format "\n", \ - timestr,## __VA_ARGS__); \ - fflush(stderr); \ - } \ -} \ -while (0) -#endif -#ifdef XCODE_DEBUG -#define LOGD(format, ...) \ -do { \ - time_t now = time(NULL); \ - char timestr[20]; \ - strftime(timestr, 20, TIME_FORMAT, localtime(&now)); \ - fprintf(stderr, " %s INFO: " format "\n", timestr, \ - ## __VA_ARGS__); \ - fflush(stderr); \ - } \ -while (0) -#else -#define LOGD(format, ...) \ -do { \ - if (logfile != NULL) { \ - time_t now = time(NULL); \ - char timestr[20]; \ - strftime(timestr, 20, TIME_FORMAT, localtime(&now)); \ - fprintf(logfile, " %s INFO: " format "\n", timestr, \ - ## __VA_ARGS__); \ - fflush(logfile); \ - } \ - else { \ - time_t now = time(NULL); \ - char timestr[20]; \ - strftime(timestr, 20, TIME_FORMAT, localtime(&now)); \ - fprintf(stderr, "\x1b[32m %s INFO: \e[0m" format "\n", timestr, \ - ## __VA_ARGS__); \ - fflush(stderr); \ - } \ -} \ -while (0) -#endif - -#define FATAL(format, ...) \ +#define _LOG(color, tag, format, ...) \ do { \ if (logfile != NULL) { \ time_t now = time(NULL); \ char timestr[20]; \ strftime(timestr, 20, TIME_FORMAT, localtime(&now)); \ - fprintf(logfile, " %s FATAL: " format "\n", timestr, \ + fprintf(logfile, " %s " tag ": " format "\n", timestr, \ ## __VA_ARGS__); \ fflush(logfile); \ } \ @@ -150,32 +64,29 @@ do { \ time_t now = time(NULL); \ char timestr[20]; \ strftime(timestr, 20, TIME_FORMAT, localtime(&now)); \ - fprintf(stderr, "\x1b[31m %s FATAL: \e[0m" format "\n", timestr, \ + fprintf(stderr, color " %s " tag ": \e[0m" format "\n", timestr, \ ## __VA_ARGS__); \ fflush(stderr); \ } \ - exit(EXIT_FAILURE); \ } \ while (0) -#define LOGE(format, ...) \ -do { \ - time_t now = time(NULL); \ - char timestr[20]; \ - strftime(timestr, 20, TIME_FORMAT, localtime(&now)); \ - if (logfile != NULL) { \ - fprintf(logfile, " %s ERROR: " format "\n", \ - timestr,## __VA_ARGS__); \ - fflush(logfile); \ - } \ - else { \ - fprintf(stderr, "\x1b[31m %s ERROR: \e[0m" format "\n", \ - timestr,## __VA_ARGS__); \ - fflush(stderr); \ - } \ -} \ +#define LOGI(format, ...) \ +do { \ + time_t now = time(NULL); \ + char timestr[20]; \ + strftime(timestr, 20, TIME_FORMAT, localtime(&now)); \ + fprintf(stderr, "\x1b[32m" " %s INFO: \e[0m" format "\n", timestr, \ + ## __VA_ARGS__); \ + fflush(stderr); \ +} \ while (0) +#define LOGD(format, ...) _LOG("\x1b[32m", "Debug", format, ##__VA_ARGS__) +#define LOGW(format, ...) _LOG("\x1b[33m", "Warning", format, ##__VA_ARGS__) +#define LOGE(format, ...) _LOG("\x1b[31m", "Error", format, ##__VA_ARGS__) +#define FATAL(format, ...) _LOG("\x1b[31m", "Fatal", format, ##__VA_ARGS__) + #define SHOW_BUFFER(buf, len) \ do { \ for (int i = 0; iIDESourceControlProjectOriginsDictionary 8BD223DEF30FBE5E513BC5235F66567CE49ADFB9 - https://github.com/csujedihy/proximac + github.com:csujedihy/proximac.git IDESourceControlProjectPath proximac.xcodeproj @@ -21,7 +21,7 @@ ../.. IDESourceControlProjectURL - https://github.com/csujedihy/proximac + github.com:csujedihy/proximac.git IDESourceControlProjectVersion 111 IDESourceControlProjectWCCIdentifier diff --git a/proximac.xcodeproj/project.xcworkspace/xcuserdata/jedihy.xcuserdatad/UserInterfaceState.xcuserstate b/proximac.xcodeproj/project.xcworkspace/xcuserdata/jedihy.xcuserdatad/UserInterfaceState.xcuserstate index 0798a246b59c6bcc597455c4329c3082148d4aa8..6db5713bb9ce99e9e4e1b3e4d243c3ea454b8d35 100644 GIT binary patch delta 15800 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 X#j9FhtU%cMm*-T|+GzV-yypJ_KK3}X diff --git a/proximac.xcodeproj/xcuserdata/jedihy.xcuserdatad/xcschemes/proximac-cli.xcscheme b/proximac.xcodeproj/xcuserdata/jedihy.xcuserdatad/xcschemes/proximac-cli.xcscheme index 7d9ac29..b8577d8 100644 --- a/proximac.xcodeproj/xcuserdata/jedihy.xcuserdatad/xcschemes/proximac-cli.xcscheme +++ b/proximac.xcodeproj/xcuserdata/jedihy.xcuserdatad/xcschemes/proximac-cli.xcscheme @@ -42,14 +42,14 @@ - + - + diff --git a/proximac/proximac.c b/proximac/proximac.c index bbb2ed3..40e8a8e 100644 --- a/proximac/proximac.c +++ b/proximac/proximac.c @@ -35,6 +35,7 @@ static kern_ctl_ref g_proximac_ctl_ref = NULL; static int g_pid_num = 0; static int g_proximac_mode = PROXIMAC_MODE_OFF; +static int g_proxy_hash = 0; static bool g_proximac_tcp_filter_registered = false; static bool g_proximac_tcp_unreg_started = false; @@ -227,7 +228,7 @@ static struct kern_ctl_reg proximac_ctl_reg = { MYBUNDLEID, /* use a reverse dns name which includes a name unique to your comany */ 0, /* set to 0 for dynamically assigned control ID - CTL_FLAG_REG_ID_UNIT not set */ 0, /* ctl_unit - ignored when CTL_FLAG_REG_ID_UNIT not set */ - CTL_FLAG_PRIVILEGED, /* privileged access required to access this filter */ + 0, /* privileged access required to access this filter */ 0, /* use default send size buffer */ 0, /* use default receive size buffer */ proximac_ctl_connect_cb, /* called when a connection request is accepted (requied field)*/ @@ -296,6 +297,7 @@ static errno_t proximac_ctl_connect_cb( #define HOOK_PID 2 #define PIDLIST_STATUS 3 #define PROXIMAC_OFF 4 +#define NOT_TO_HOOK 5 static errno_t proximac_ctl_setopt_cb( kern_ctl_ref kctlref, @@ -309,6 +311,7 @@ static errno_t proximac_ctl_setopt_cb( int intval; switch (opt) { case PROXIMAC_ON: + { lck_rw_lock_exclusive(g_pidlist_lock); if (g_pid_num != 0) { @@ -335,6 +338,8 @@ static errno_t proximac_ctl_setopt_cb( lck_rw_unlock_exclusive(g_pidlist_lock); lck_rw_lock_exclusive(g_mode_lock); + + // install socket filters if (g_proximac_mode == PROXIMAC_MODE_OFF) { retval = install_proximac_tcp_filter(); @@ -343,10 +348,16 @@ static errno_t proximac_ctl_setopt_cb( lck_rw_unlock_exclusive(g_mode_lock); return retval; } - g_proximac_mode = PROXIMAC_MODE_ON; + intval = *(int *)data; + if(intval == 1){ + g_proximac_mode = PROXIMAC_MODE_ALL; + LOGI("In VPN mode"); + } + else + g_proximac_mode = PROXIMAC_MODE_ON; } lck_rw_unlock_exclusive(g_mode_lock); - + } break; // case PROXIMAC_OFF: // lck_rw_lock_exclusive(g_mode_lock); @@ -357,11 +368,12 @@ static errno_t proximac_ctl_setopt_cb( // return retval; // break; case HOOK_PID: + { if (len < sizeof(int)) { retval = EINVAL; break; } - intval = *(int *)data; + intval = *(int*)data; lck_rw_lock_exclusive(g_pidlist_lock); struct pid *pid_to_insert = _MALLOC(sizeof(struct pid), M_TEMP, M_WAITOK| M_ZERO); pid_to_insert->pid = intval; @@ -370,7 +382,22 @@ static errno_t proximac_ctl_setopt_cb( g_pid_num++; lck_rw_unlock_exclusive(g_pidlist_lock); break; + } + case NOT_TO_HOOK: + { + if (len < sizeof(int)) { + retval = EINVAL; + break; + } + intval = *(int*)data; + lck_rw_lock_exclusive(g_mode_lock); + g_proxy_hash = intval; + LOGI("proxy's hash has been set to %d", g_proxy_hash); + lck_rw_unlock_exclusive(g_mode_lock); + break; + + } default: break; } @@ -443,6 +470,8 @@ typedef struct proximac_cookie { struct sockaddr_in6 addr6; /* ipv6 remote addr */ } remote_addr; int protocol; /* IPv4 or IPv6 */ + int forward_flag; /* Forward Flag */ + int pid; } proximac_cookie_t; const static struct sflt_filter proximac_tcp_filter = { @@ -495,6 +524,8 @@ proximac_tcp_connect_out_cb( const struct sockaddr * to) { proximac_cookie_t * proximac_cookie = (proximac_cookie_t *)cookie; + assert(cookie); + lck_rw_lock_shared(g_mode_lock); if (g_proximac_mode == PROXIMAC_MODE_OFF) { @@ -503,30 +534,53 @@ proximac_tcp_connect_out_cb( } lck_rw_unlock_shared(g_mode_lock); - assert(cookie); - - // Make sure address family is correct assert(to->sa_family == AF_INET); assert(sizeof(struct sockaddr_in) <= to->sa_len); - assert((to->sa_family == AF_INET) || (to->sa_family == AF_INET6)); /*verify that the address is AF_INET/AF_INET6 */ - + assert((to->sa_family == AF_INET) || (to->sa_family == AF_INET6)); /* verify that the address is AF_INET/AF_INET6 */ assert (sizeof(proximac_cookie->remote_addr.addr4) >= to->sa_len); /* verify that there is enough room to store data */ + /* save the remote address in the tli_remote field */ bcopy(to, &(proximac_cookie->remote_addr.addr4), to->sa_len); struct sockaddr_in *remote_addr; remote_addr = (struct sockaddr_in*)to; proximac_cookie->remote_addr.addr4.sin_port = ntohs(proximac_cookie->remote_addr.addr4.sin_port); + + /* see if this is a local stream, then we just ignore */ + int local_tcp_flag = 0; + if (remote_addr->sin_addr.s_addr == LOCALHOST) + return 0; + + /* do not forward any traffic from proximac-cli or SOCKS5 proxy. Otherwise, traffic will be trapped in a loop */ + if (proximac_cookie->pid == 0 || proximac_cookie->pidhash_value == pid_hash(MYAPPNAME) || proximac_cookie->pidhash_value == g_proxy_hash) { + LOGI("Traffic from this process is not allowed to be forwarded. PID = %d", proximac_cookie->pid); + return 0; + } + + /* see if we're in forward all mode which is like VPN */ + if (g_proximac_mode == PROXIMAC_MODE_ALL) { + if (proximac_cookie->pid != 0 && local_tcp_flag == 0) { + proximac_cookie->forward_flag = 1; + LOGI("A process is now hooked"); + remote_addr->sin_port = htons(8558); + remote_addr->sin_addr.s_addr = LOCALHOST; + LOGI("forward flag has been set to 1"); + } + return 0; + } + struct pid find_pid; find_pid.pid = proximac_cookie->pidhash_value; lck_rw_lock_exclusive(g_pidlist_lock); struct pid *exist = RB_FIND(pid_tree, &pid_list, &find_pid); LOGI("after RB_FIND pid = %d pid_num %d\n", find_pid.pid, g_pid_num); lck_rw_unlock_exclusive(g_pidlist_lock); + if (exist != NULL) { - LOGI("found existed PID\n"); + proximac_cookie->forward_flag = 1; + LOGI("A process is now hooked"); remote_addr->sin_port = htons(8558); - remote_addr->sin_addr.s_addr = 0x100007f; + remote_addr->sin_addr.s_addr = LOCALHOST; } return 0; @@ -553,10 +607,13 @@ proximac_tcp_attach_cb(void ** cookie, socket_t so) } proximac_cookie_t * proximac_cookie = (proximac_cookie_t *)(*cookie); + proximac_cookie->forward_flag = 0; + char proc_name[64] = {0}; proc_selfname(proc_name, 63); + proximac_cookie->pid = proc_selfpid(); proximac_cookie->pidhash_value = pid_hash(proc_name); - LOGI("pid hash value = %d\n", proximac_cookie->pidhash_value); + LOGI("pid hash value = %d proc_name = %s\n", proximac_cookie->pidhash_value, proc_name); LOGI("Proximac TCP filter has been attached to a socket"); return 0; } @@ -575,14 +632,9 @@ proximac_tcp_notify_cb(void *cookie, socket_t so, sflt_event_t event, void *para inet_ntop(AF_INET, remoteAddr, (char*)addrString, sizeof(addrString)); // added prepend proximac_hdr for proximac - struct pid find_pid; - find_pid.pid = proximac_cookie->pidhash_value; - lck_rw_lock_exclusive(g_pidlist_lock); - struct pid *exist = RB_FIND(pid_tree, &pid_list, &find_pid); - LOGI("notify_cb -- after RB_FIND pid = %d pid_num = %d\n", find_pid.pid, g_pid_num); - lck_rw_unlock_exclusive(g_pidlist_lock); - if (exist != NULL) { - LOGI("notify_cb -- do hook operations to pid = %d\n", find_pid.pid); + + if (proximac_cookie->forward_flag == 1) { + LOGI("notify_cb -- do hook operations to pid"); mbuf_t proximac_hdr_data = NULL; mbuf_t proximac_hdr_control = NULL; errno_t retval; diff --git a/proximac/proximac.h b/proximac/proximac.h index 513d666..3658153 100644 --- a/proximac/proximac.h +++ b/proximac/proximac.h @@ -11,7 +11,10 @@ #define MYBUNDLEID "com.proximac.kext" #define PROXIMAC_TCP_FILTER_HANDLE 0x2e33677d -#define PROXIMAC_MODE_ON 1 #define PROXIMAC_MODE_OFF 0 +#define PROXIMAC_MODE_ON 1 +#define PROXIMAC_MODE_ALL 2 +#define LOCALHOST 0x100007f +#define MYAPPNAME "proximac-cli" #endif