#define _GNU_SOURCE #include "nfqws.h" #include "sec.h" #include "desync.h" #include "helpers.h" #include "checksum.h" #include "params.h" #include "protocol.h" #include "hostlist.h" #include "ipset.h" #include "gzip.h" #include "pools.h" #include "lua.h" #include "crypto/aes.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __CYGWIN__ #include "win.h" #endif #ifdef USE_SYSTEMD #include #endif #ifdef __linux__ #include #define NF_DROP 0 #define NF_ACCEPT 1 #endif #define MAX_CONFIG_FILE_SIZE 16384 struct params_s params; static volatile sig_atomic_t bReload = false; volatile sig_atomic_t bQuit = false; static void onhup(int sig) { // async safe if (bQuit) return; const char *msg = "HUP received ! Lists will be reloaded.\n"; size_t wr = write(1, msg, strlen(msg)); bReload = true; } static void ReloadCheck() { if (bReload) { ResetAllHostlistsModTime(); if (!LoadAllHostLists()) { DLOG_ERR("hostlists load failed. this is fatal.\n"); exit(200); } ResetAllIpsetModTime(); if (!LoadAllIpsets()) { DLOG_ERR("ipset load failed. this is fatal.\n"); exit(200); } bReload = false; } } static void onusr1(int sig) { // this is debug-only signal. no async safety if (bQuit) return; printf("\nCONNTRACK DUMP\n"); ConntrackPoolDump(¶ms.conntrack); printf("\n"); } static void onusr2(int sig) { // this is debug-only signal. no async safety if (bQuit) return; printf("\nHOSTFAIL POOL DUMP\n"); struct desync_profile_list *dpl; LIST_FOREACH(dpl, ¶ms.desync_profiles, next) { printf("\nDESYNC profile %u (%s)\n", dpl->dp.n, PROFILE_NAME(&dpl->dp)); HostFailPoolDump(dpl->dp.hostlist_auto_fail_counters); } printf("\nIPCACHE\n"); ipcachePrint(¶ms.ipcache); printf("\n"); } static void onint(int sig) { // theoretically lua_sethook is not async-safe. but it's one-time signal if (bQuit) return; const char *msg = "INT received !\n"; size_t wr = write(1, msg, strlen(msg)); bQuit = true; lua_req_quit(); } static void onterm(int sig) { // theoretically lua_sethook is not async-safe. but it's one-time signal if (bQuit) return; const char *msg = "TERM received !\n"; size_t wr = write(1, msg, strlen(msg)); bQuit = true; lua_req_quit(); } static void catch_signals(void) { struct sigaction sa; sigemptyset(&sa.sa_mask); sa.sa_sigaction = NULL; sa.sa_flags = 0; sa.sa_handler = onhup; sigaction(SIGHUP, &sa, NULL); sa.sa_handler = onusr1; sigaction(SIGUSR1, &sa, NULL); sa.sa_handler = onusr2; sigaction(SIGUSR2, &sa, NULL); sa.sa_handler = onint; sigaction(SIGINT, &sa, NULL); sa.sa_handler = onterm; sigaction(SIGTERM, &sa, NULL); } static uint8_t processPacketData(uint32_t *mark, const char *ifin, const char *ifout, const uint8_t *data_pkt, size_t len_pkt, uint8_t *mod_pkt, size_t *len_mod_pkt) { #ifdef __linux__ if (*mark & params.desync_fwmark) { DLOG("ignoring generated packet\n"); return VERDICT_PASS; } #endif return dpi_desync_packet(*mark, ifin, ifout, data_pkt, len_pkt, mod_pkt, len_mod_pkt); } #define FUZZ_MAX_PACKET_SIZE (RECONSTRUCT_MAX_SIZE+4096) static void fuzzPacketData(unsigned int count) { uint8_t *packet,mod[RECONSTRUCT_MAX_SIZE+4096]; size_t len, modlen; unsigned int k; uint32_t mark=0; uint8_t verdict; for(k=0;kfilename && !file_open_test(hfile->filename, O_RDONLY)) { DLOG_PERROR("file_open_test"); DLOG_ERR("cannot access hostlist file '%s'\n", hfile->filename); return false; } LIST_FOREACH(ifile, ¶ms.ipsets, next) if (ifile->filename && !file_open_test(ifile->filename, O_RDONLY)) { DLOG_PERROR("file_open_test"); DLOG_ERR("cannot access ipset file '%s'\n", ifile->filename); return false; } return true; } // writes and closes pidfile static int write_pidfile(FILE **Fpid) { if (*Fpid) { int r = fprintf(*Fpid, "%d", getpid()); if (r <= 0) { DLOG_PERROR("write pidfile"); fclose(*Fpid); *Fpid = NULL; return false; } fclose(*Fpid); *Fpid = NULL; } return true; } #ifdef __linux__ // cookie must point to mod buffer with size RECONSTRUCT_MAX_SIZE static int nfq_cb(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *cookie) { int id, ilen; size_t len; struct nfqnl_msg_packet_hdr *ph; uint8_t *data; uint32_t ifidx_out, ifidx_in; char ifout[IFNAMSIZ], ifin[IFNAMSIZ]; size_t modlen; uint8_t *mod = (uint8_t*)cookie; uint32_t mark; ph = nfq_get_msg_packet_hdr(nfa); id = ph ? ntohl(ph->packet_id) : 0; mark = nfq_get_nfmark(nfa); ilen = nfq_get_payload(nfa, &data); ifidx_out = nfq_get_outdev(nfa); *ifout = 0; if (ifidx_out) if_indextoname(ifidx_out, ifout); ifidx_in = nfq_get_indev(nfa); *ifin = 0; if (ifidx_in) if_indextoname(ifidx_in, ifin); DLOG("\npacket: id=%d len=%d mark=%08X ifin=%s(%u) ifout=%s(%u)\n", id, ilen, mark, ifin, ifidx_in, ifout, ifidx_out); if (ilen >= 0) { len = ilen; modlen = RECONSTRUCT_MAX_SIZE; // there's no space to grow packet in recv blob from nfqueue. it can contain multiple packets with no extra buffer length for modifications. // to support increased sizes use separate mod buffer // this is not a problem because only LUA code can trigger VERDICT_MODIFY (and postnat workaround too, once a connection if first packet is dropped) // in case of VERIDCT_MODIFY packet is always reconstructed from dissect, so no difference where to save the data => no performance loss uint8_t verdict = processPacketData(&mark, ifin, ifout, data, len, mod, &modlen); switch (verdict & VERDICT_MASK) { case VERDICT_MODIFY: DLOG("packet: id=%d pass modified. len %zu => %zu\n", id, len, modlen); return nfq_set_verdict2(qh, id, NF_ACCEPT, mark, (uint32_t)modlen, mod); case VERDICT_DROP: DLOG("packet: id=%d drop\n", id); return nfq_set_verdict2(qh, id, NF_DROP, mark, 0, NULL); } } DLOG("packet: id=%d pass unmodified\n", id); return nfq_set_verdict2(qh, id, NF_ACCEPT, mark, 0, NULL); } static void nfq_deinit(struct nfq_handle **h, struct nfq_q_handle **qh) { if (*qh) { DLOG_CONDUP("unbinding from queue %u\n", params.qnum); nfq_destroy_queue(*qh); *qh = NULL; } if (*h) { DLOG_CONDUP("closing nfq library handle\n"); nfq_close(*h); *h = NULL; } } static bool nfq_init(struct nfq_handle **h, struct nfq_q_handle **qh, uint8_t *mod_buffer) { nfq_deinit(h, qh); DLOG_CONDUP("opening nfq library handle\n"); *h = nfq_open(); if (!*h) { DLOG_PERROR("nfq_open()"); goto exiterr; } // linux 3.8 - bind calls are NOOP. linux 3.8- - secondary bind to AF_INET6 will fail // old kernels seem to require both binds to ipv4 and ipv6. may not work without unbind DLOG_CONDUP("unbinding existing nf_queue handler for AF_INET (if any)\n"); if (nfq_unbind_pf(*h, AF_INET) < 0) { DLOG_PERROR("nfq_unbind_pf(AF_INET)"); goto exiterr; } DLOG_CONDUP("binding nfnetlink_queue as nf_queue handler for AF_INET\n"); if (nfq_bind_pf(*h, AF_INET) < 0) { DLOG_PERROR("nfq_bind_pf(AF_INET)"); goto exiterr; } DLOG_CONDUP("unbinding existing nf_queue handler for AF_INET6 (if any)\n"); if (nfq_unbind_pf(*h, AF_INET6) < 0) { DLOG_PERROR("nfq_unbind_pf(AF_INET6)"); } DLOG_CONDUP("binding nfnetlink_queue as nf_queue handler for AF_INET6\n"); if (nfq_bind_pf(*h, AF_INET6) < 0) { DLOG_PERROR("nfq_bind_pf(AF_INET6)"); } DLOG_CONDUP("binding this socket to queue '%u'\n", params.qnum); *qh = nfq_create_queue(*h, params.qnum, &nfq_cb, mod_buffer); if (!*qh) { DLOG_PERROR("nfq_create_queue()"); goto exiterr; } DLOG_CONDUP("setting copy_packet mode\n"); if (nfq_set_mode(*qh, NFQNL_COPY_PACKET, 0xffff) < 0) { DLOG_PERROR("can't set packet_copy mode"); goto exiterr; } if (nfq_set_queue_maxlen(*qh, Q_MAXLEN) < 0) { DLOG_PERROR("can't set queue maxlen"); goto exiterr; } // accept packets if they cant be handled if (nfq_set_queue_flags(*qh, NFQA_CFG_F_FAIL_OPEN, NFQA_CFG_F_FAIL_OPEN)) { DLOG_ERR("can't set queue flags. its OK on linux <3.6\n"); // dot not fail. not supported in old linuxes <3.6 } nfnl_rcvbufsiz(nfq_nfnlh(*h), Q_RCVBUF); int yes = 1, fd = nfq_fd(*h); #if defined SOL_NETLINK && defined NETLINK_NO_ENOBUFS if (setsockopt(fd, SOL_NETLINK, NETLINK_NO_ENOBUFS, &yes, sizeof(yes)) == -1) DLOG_PERROR("setsockopt(NETLINK_NO_ENOBUFS)"); #endif return true; exiterr: nfq_deinit(h, qh); return false; } static void notify_ready(void) { #ifdef USE_SYSTEMD int r = sd_notify(0, "READY=1"); if (r < 0) DLOG_ERR("sd_notify: %s\n", strerror(-r)); #endif } // extra space for netlink headers #define NFQ_MAX_RECV_SIZE (RECONSTRUCT_MAX_SIZE+512) static int nfq_main(void) { struct nfq_handle *h = NULL; struct nfq_q_handle *qh = NULL; int res, fd, e; ssize_t rd; FILE *Fpid = NULL; uint8_t *buf=NULL, *mod=NULL; if (*params.pidfile && !(Fpid = fopen(params.pidfile, "w"))) { DLOG_PERROR("create pidfile"); return 1; } if (params.droproot && !droproot(params.uid, params.user, params.gid, params.gid_count) || !dropcaps()) goto err; print_id(); if (params.droproot && !test_list_files()) goto err; if (!lua_test_init_script_files()) goto err; sec_harden(); DLOG_CONDUP("initializing raw sockets bind-fix4=%u bind-fix6=%u\n", params.bind_fix4, params.bind_fix6); if (!rawsend_preinit(params.bind_fix4, params.bind_fix6)) goto err; if (!params.intercept) { if (params.daemon) daemonize(); if (!write_pidfile(&Fpid)) goto err; notify_ready(); } catch_signals(); if (!lua_init()) goto err; do_fuzz(); if (!params.intercept) { DLOG_CONDUP("no intercept quit\n"); goto exok; } if (!(buf = malloc(NFQ_MAX_RECV_SIZE)) || !(mod = malloc(RECONSTRUCT_MAX_SIZE))) { DLOG_ERR("out of memory\n"); goto err; } if (!nfq_init(&h, &qh, mod)) goto err; #ifdef HAS_FILTER_SSID if (params.filter_ssid_present) { if (!wlan_info_init()) { DLOG_ERR("cannot initialize wlan info capture\n"); goto err; } DLOG("wlan info capture initialized\n"); } #endif if (params.daemon) daemonize(); if (!write_pidfile(&Fpid)) goto err; notify_ready(); fd = nfq_fd(h); do { if (bQuit) goto quit; while ((rd = recv(fd, buf, NFQ_MAX_RECV_SIZE, 0)) >= 0) { if (!rd) { DLOG_ERR("recv from nfq returned 0 !\n"); goto err; } ReloadCheck(); lua_do_gc(); #ifdef HAS_FILTER_SSID if (params.filter_ssid_present) if (!wlan_info_get_rate_limited()) DLOG_ERR("cannot get wlan info\n"); #endif int r = nfq_handle_packet(h, (char *)buf, (int)rd); if (r<0) DLOG_ERR("nfq_handle_packet result %d, errno %d : %s\n", r, errno, strerror(errno)); if (bQuit) goto quit; } if (errno==EINTR) continue; e = errno; DLOG_ERR("recv: recv=%zd errno %d\n", rd, e); errno = e; DLOG_PERROR("recv"); // do not fail on ENOBUFS } while (e == ENOBUFS); exok: res=0; ex: free(mod); free(buf); nfq_deinit(&h, &qh); lua_shutdown(); #ifdef HAS_FILTER_SSID wlan_info_deinit(); #endif rawsend_cleanup(); return res; err: if (Fpid) fclose(Fpid); res=1; goto ex; quit: DLOG_CONDUP("quit requested\n"); goto exok; } #elif defined(BSD) static int dvt_main(void) { struct sockaddr_storage sa_from; int fd[2] = { -1,-1 }; // 4,6 int i, r, res = 1, fdct = 1, fdmax; unsigned int id = 0; socklen_t socklen; ssize_t rd, wr; fd_set fdset; FILE *Fpid = NULL; struct sockaddr_in bp4; struct sockaddr_in6 bp6; uint8_t buf[RECONSTRUCT_MAX_SIZE] __attribute__((aligned)); if (*params.pidfile && !(Fpid = fopen(params.pidfile, "w"))) { DLOG_PERROR("create pidfile"); return 1; } if (params.intercept) { bp4.sin_family = AF_INET; bp4.sin_port = htons(params.port); bp4.sin_addr.s_addr = INADDR_ANY; DLOG_CONDUP("creating divert4 socket\n"); fd[0] = socket_divert(AF_INET); if (fd[0] == -1) { DLOG_PERROR("socket (DIVERT4)"); goto exiterr; } DLOG_CONDUP("binding divert4 socket\n"); if (bind(fd[0], (struct sockaddr*)&bp4, sizeof(bp4)) < 0) { DLOG_PERROR("bind (DIVERT4)"); goto exiterr; } #ifdef __OpenBSD__ // in OpenBSD must use separate divert sockets for ipv4 and ipv6 memset(&bp6, 0, sizeof(bp6)); bp6.sin6_family = AF_INET6; bp6.sin6_port = htons(params.port); DLOG_CONDUP("creating divert6 socket\n"); fd[1] = socket_divert(AF_INET6); if (fd[1] == -1) { DLOG_PERROR("socket (DIVERT6)"); goto exiterr; } DLOG_CONDUP("binding divert6 socket\n"); if (bind(fd[1], (struct sockaddr*)&bp6, sizeof(bp6)) < 0) { DLOG_PERROR("bind (DIVERT6)"); goto exiterr; } fdct++; #endif fdmax = (fd[0] > fd[1] ? fd[0] : fd[1]) + 1; } DLOG_CONDUP("initializing raw sockets\n"); if (!rawsend_preinit(false, false)) goto exiterr; if (params.droproot && !droproot(params.uid, params.user, params.gid, params.gid_count)) goto exiterr; print_id(); if (params.droproot && !test_list_files()) goto exiterr; if (!lua_test_init_script_files()) goto exiterr; catch_signals(); if (!params.intercept) { if (params.daemon) daemonize(); if (!write_pidfile(&Fpid)) goto exiterr; } if (!lua_init()) goto exiterr; do_fuzz(); if (!params.intercept) { DLOG("no intercept quit\n"); goto exitok; } if (params.daemon) daemonize(); if (!write_pidfile(&Fpid)) goto exiterr; for (;;) { if (bQuit) { DLOG_CONDUP("quit requested\n"); goto exitok; } FD_ZERO(&fdset); for (i = 0; i < fdct; i++) FD_SET(fd[i], &fdset); r = select(fdmax, &fdset, NULL, NULL, NULL); if (bQuit) { DLOG_CONDUP("quit requested\n"); goto exitok; } if (r == -1) { if (errno == EINTR) continue; DLOG_PERROR("select"); goto exiterr; } for (i = 0; i < fdct; i++) { if (FD_ISSET(fd[i], &fdset)) { socklen = sizeof(sa_from); while ((rd = recvfrom(fd[i], buf, sizeof(buf), 0, (struct sockaddr*)&sa_from, &socklen))<0 && errno==EINTR); if (rd < 0) { DLOG_PERROR("recvfrom"); goto exiterr; } else if (rd > 0) { uint32_t mark = 0; uint8_t verdict; size_t modlen, len = rd; const char *ifin, *ifout; ReloadCheck(); lua_do_gc(); // in any BSD addr of incoming packet is set to the first addr of the interface. addr of outgoing packet is set to zero bool bIncoming = sa_has_addr((struct sockaddr*)&sa_from); ifin = bIncoming ? "unknown" : ""; ifout = bIncoming ? "" : "unknown"; #ifdef __FreeBSD__ // FreeBSD passes ifname of incoming interface in 8 bytes after sin_addr // it always sets family to AF_INET despite of ip version char ifname[9]; if (bIncoming && sa_from.ss_family==AF_INET) { const char *p = ((char*)&((struct sockaddr_in *)&sa_from)->sin_addr)+sizeof(struct in_addr); if (*p) { memcpy(ifname, p, 8); ifname[8] = 0; ifin = ifname; } } #endif DLOG("\npacket: id=%u len=%zu ifin=%s ifout=%s\n", id, len, ifin, ifout); modlen = sizeof(buf); verdict = processPacketData(&mark, ifin, ifout, buf, len, buf, &modlen); switch (verdict & VERDICT_MASK) { case VERDICT_PASS: case VERDICT_MODIFY: if ((verdict & VERDICT_MASK) == VERDICT_PASS) { DLOG("packet: id=%u reinject unmodified\n", id); modlen = len; } else DLOG("packet: id=%u reinject modified len %zu => %zu\n", id, len, modlen); wr = sendto(fd[i], buf, modlen, 0, (struct sockaddr*)&sa_from, socklen); if (wr < 0) DLOG_PERROR("reinject sendto"); else if (wr != modlen) DLOG_ERR("reinject sendto: not all data was reinjected. received %zu, sent %zd\n", len, wr); break; default: DLOG("packet: id=%u drop\n", id); } id++; } else { DLOG("unexpected zero size recvfrom\n"); } } } } exitok: res = 0; exiterr: if (Fpid) fclose(Fpid); if (fd[0] != -1) close(fd[0]); if (fd[1] != -1) close(fd[1]); lua_shutdown(); rawsend_cleanup(); return res; } #elif defined (__CYGWIN__) #define WINDIVERT_BULK_MAX 128 // do not make it less than 65536 - loopback packets can be up to 64K #define WINDIVERT_PACKET_BUF_SIZE 196608 // 3*64K, 128*1500=192000 static int win_main() { size_t len, packet_len, left, modlen; unsigned int id; uint8_t verdict; bool bOutbound; uint32_t mark; char ifname[IFNAMSIZ]; int res=0; WINDIVERT_ADDRESS wa[WINDIVERT_BULK_MAX]; uint8_t *packets = NULL, *packet, *mod=NULL; unsigned int n,wa_count; // windows emulated fork logic does not cover objects outside of cygwin world. have to daemonize before inits if (params.daemon) daemonize(); if (*params.pidfile && !writepid(params.pidfile)) { DLOG_ERR("could not write pidfile"); return ERROR_TOO_MANY_OPEN_FILES; // code 4 = The system cannot open the file } if (!win_dark_init(¶ms.ssid_filter, ¶ms.nlm_filter)) { DLOG_ERR("win_dark_init failed. win32 error %u (0x%08X)\n", w_win32_error, w_win32_error); res=w_win32_error; goto ex; } if (!(packets = malloc(WINDIVERT_PACKET_BUF_SIZE)) || !(mod = malloc(RECONSTRUCT_MAX_SIZE))) { res=ERROR_NOT_ENOUGH_MEMORY; goto ex; } catch_signals(); for (;;) { if (!logical_net_filter_match()) { DLOG_CONDUP("logical network is not present. waiting it to appear.\n"); do { if (bQuit) { DLOG("quit requested\n"); goto ex; } usleep(500000); } while (!logical_net_filter_match()); DLOG_CONDUP("logical network now present\n"); } if (!windivert_init(params.windivert_filter)) { res=w_win32_error; goto ex; } DLOG_CONDUP(params.intercept ? "windivert initialized. capture is started.\n" : "windivert initialized\n"); if (!win_sandbox()) { res=w_win32_error; DLOG_ERR("Cannot init Windows sandbox\n"); goto ex; } // init LUA only here because of possible sandbox. no LUA code with high privs if (!params.L && !lua_init()) { res=ERROR_INVALID_PARAMETER; goto ex; } do_fuzz(); if (!params.intercept) { DLOG("no intercept quit\n"); goto ex; } for (id = 0;;) { len = WINDIVERT_PACKET_BUF_SIZE; wa_count = WINDIVERT_BULK_MAX; if (!windivert_recv(packets, &len, wa, &wa_count)) { if (errno == ENOBUFS) { DLOG("windivert: ignoring too large packet\n"); continue; // too large packet } else if (errno == ENODEV) { DLOG_CONDUP("\nlogical network disappeared. deinitializing windivert.\n"); rawsend_cleanup(); break; } else if (errno == EINTR) { DLOG("quit requested\n"); goto ex; } DLOG_ERR("windivert: recv failed. errno %d\n", errno); res=w_win32_error; goto ex; } ReloadCheck(); lua_do_gc(); for (n=0, packet=packets, left = len ; nLength))) { DLOG_ERR("invalid ipv6 packet\n"); break; } } else { if (leftLength))) { DLOG_ERR("invalid ipv4 packet\n"); break; } } *ifname = 0; snprintf(ifname, sizeof(ifname), "%u.%u", wa[n].Network.IfIdx, wa[n].Network.SubIfIdx); DLOG("\npacket: id=%u len=%zu %s IPv6=%u IPChecksum=%u TCPChecksum=%u UDPChecksum=%u IfIdx=%u.%u\n", id, packet_len, wa[n].Outbound ? "outbound" : "inbound", wa[n].IPv6, wa[n].IPChecksum, wa[n].TCPChecksum, wa[n].UDPChecksum, wa[n].Network.IfIdx, wa[n].Network.SubIfIdx); if (wa[n].Impostor) { DLOG("windivert: passing impostor packet\n"); verdict = VERDICT_PASS; } else { mark = 0; modlen = RECONSTRUCT_MAX_SIZE; verdict = processPacketData(&mark, wa[n].Outbound ? "" : ifname, wa[n].Outbound ? ifname : "", packet, packet_len, mod, &modlen); } switch (verdict & VERDICT_MASK) { case VERDICT_PASS: DLOG("packet: id=%u reinject unmodified\n", id); if (!windivert_send(packet, packet_len, wa+n)) DLOG_ERR("windivert: reinject of packet id=%u failed\n", id); break; case VERDICT_MODIFY: DLOG("packet: id=%u reinject modified len %zu => %zu\n", id, packet_len, modlen); if (!windivert_send(mod, modlen, wa+n)) DLOG_ERR("windivert: reinject of packet id=%u failed\n", id); break; default: DLOG("packet: id=%u drop\n", id); } } } } ex: free(mod); free(packets); win_dark_deinit(); lua_shutdown(); rawsend_cleanup(); return res; } #endif // multiple OS divert handlers static void exit_clean(int code) { cleanup_params(¶ms); close_std_and_exit(code); } static bool is_hexstring(const char *filename) { return filename[0] == '0' && filename[1] == 'x'; } static bool parse_filespec(const char **filename, unsigned long long *ofs) { *ofs = 0; if (**filename == '+') { (*filename)++; if (sscanf(*filename, "%llu", ofs) != 1) { DLOG("offset read error: %s\n", *filename); return false; } while (**filename && **filename != '@') (*filename)++; if (**filename == '@') (*filename)++; } else if (**filename == '@') (*filename)++; return true; } static void load_file_or_exit(const char *filename, void *buf, size_t *size) { unsigned long long ofs; // 0xaabbcc // filename // @filename // +123@filename if (is_hexstring(filename)) { if (!parse_hex_str(filename + 2, buf, size) || !*size) { DLOG_ERR("invalid hex string: %s\n", filename + 2); exit_clean(1); } DLOG("read %zu bytes from hex string\n", *size); } else { if (!parse_filespec(&filename, &ofs)) exit_clean(1); if (!load_file(filename, ofs, buf, size)) { DLOG_ERR("could not read '%s'\n", filename); exit_clean(1); } DLOG("read %zu bytes from '%s'. offset=%zu\n", *size, filename, ofs); } } static char* item_name(char **str) { char *s,*p; size_t l; l = (s = strchr(*str,':')) ? s-*str : strlen(*str); if (!(p = malloc(l+1))) { DLOG_ERR("out of memory\n"); return NULL; } memcpy(p,*str,l); p[l]=0; if (!is_identifier(p)) { DLOG_ERR("bad identifier '%s'\n",p); free(p); return NULL; } *str = s ? s+1 : *str+l; return p; } static struct blob_item *load_blob_to_collection(const char *filename, struct blob_collection_head *blobs, size_t max_size, size_t size_reserve) { struct blob_item *blob = blob_collection_add(blobs); uint8_t *p; char *name; if (!(name = item_name((char**)&filename))) exit_clean(1); if (!is_hexstring(filename)) { const char *fn = filename; unsigned long long ofs; off_t fsize; if (!parse_filespec(&fn, &ofs)) { free(name); exit_clean(1); } if (!file_size(fn,&fsize)) { free(name); DLOG_ERR("cannot access file '%s'\n",fn); exit_clean(1); } if (fsize) { if (ofs >= fsize) { free(name); DLOG_ERR("offset %llu is beyond file size %llu\n", ofs, (uint64_t)fsize); exit_clean(1); } max_size = fsize - ofs; } } if (blob_collection_search_name(blobs,name)) { DLOG_ERR("duplicate blob name '%s'\n",name); free(name); exit_clean(1); } if (!blob || (!(blob->data = malloc(max_size + size_reserve)))) { free(name); DLOG_ERR("out of memory\n"); exit_clean(1); } blob->size = max_size; blob->name = name; load_file_or_exit(filename, blob->data, &blob->size); p = realloc(blob->data, blob->size + size_reserve); if (!p) { DLOG_ERR("out of memory\n"); exit_clean(1); } blob->data = p; blob->size_buf = blob->size + size_reserve; return blob; } static struct blob_item *load_const_blob_to_collection(const char *name, const void *data, size_t sz, struct blob_collection_head *blobs, size_t size_reserve) { if (blob_collection_search_name(blobs,name)) { DLOG_ERR("duplicate blob name '%s'\n",name); exit_clean(1); } struct blob_item *blob = blob_collection_add(blobs); if (!blob || (!(blob->data = malloc(sz + size_reserve))) || !(blob->name = strdup(name))) { DLOG_ERR("out of memory\n"); exit_clean(1); } blob->size = sz; blob->size_buf = sz + size_reserve; memcpy(blob->data, data, sz); return blob; } static bool parse_uid(char *opt, uid_t *uid, gid_t *gid, int *gid_count, int max_gids) { unsigned int u; char c, *p, *e; *gid_count = 0; if ((e = strchr(opt, ':'))) *e++ = 0; if (sscanf(opt, "%u", &u) != 1) return false; *uid = (uid_t)u; for (p = e; p; ) { if ((e = strchr(p, ','))) { c = *e; *e = 0; } if (p) { if (sscanf(p, "%u", &u) != 1) return false; if (*gid_count >= max_gids) return false; gid[(*gid_count)++] = (gid_t)u; } if (e) *e++ = c; p = e; } return true; } static bool parse_l7_list(char *opt, uint64_t *l7) { char *e, *p, c; t_l7proto proto; for (p = opt, *l7 = 0; p; ) { if ((e = strchr(p, ','))) { c = *e; *e = 0; } if ((proto=l7proto_from_name(p))==L7_INVALID) return false; else if (proto==L7_ALL) { *l7 = 0; break; } else *l7 |= 1ULL<str1 = strdup(opt); if (p) { arg->str2 = strdup(p+1); *p = c; if (!arg->str2) return false; } return arg->str1; } struct func_list *parse_lua_call(char *opt, struct func_list_head *flist) { char *name, *e, *p, c; bool b,last; struct func_list *f = NULL; if (!(name = item_name(&opt))) return false; if (!is_identifier(name) || !(f=funclist_add_tail(flist,name))) goto err; for (p = opt; p && *p; ) { for(e=p; *e && *e!=':'; e++) { if (e[0]=='\\' && e[1]==':') memmove(e,e+1,strlen(e)); // swallow escape symbol } last = !*e; c = *e; *e = 0; b = lua_call_param_add(p, &f->args); if (!last) *e++ = c; if (!b) goto err; p = e; } free(name); return f; err: free(name); return NULL; } static bool wf_make_l3(char *opt, bool *ipv4, bool *ipv6) { char *e, *p, c; // do not overwrite ipv4 and ipv6 old values. OR instead - simulate adding to the list for (p = opt; p; ) { if ((e = strchr(p, ','))) { c = *e; *e = 0; } if (!strcmp(p, "ipv4")) *ipv4 = true; else if (!strcmp(p, "ipv6")) *ipv6 = true; else return false; if (e) *e++ = c; p = e; } return true; } static bool parse_domain_list(char *opt, hostlist_pool **pp) { char *e, *p, c; for (p = opt; p; ) { if ((e = strchr(p, ','))) { c = *e; *e = 0; } if (*p && !AppendHostlistItem(pp, p)) return false; if (e) *e++ = c; p = e; } return true; } static bool parse_ip_list(char *opt, ipset *pp) { char *e, *p, c; for (p = opt; p; ) { if ((e = strchr(p, ','))) { c = *e; *e = 0; } if (*p && !AppendIpsetItem(pp, p)) return false; if (e) *e++ = c; p = e; } return true; } static bool parse_strlist(char *opt, struct str_list_head *list) { char *e, *p = opt; while (p) { e = strchr(p, ','); if (e) *e++ = 0; if (*p && !strlist_add(list, p)) return false; p = e; } return true; } static void BlobDebug() { struct blob_item *blob; LIST_FOREACH(blob, ¶ms.blobs, next) { DLOG("blob '%s' : size=%zu alloc=%zu\n",blob->name,blob->size,blob->size_buf); } } static void LuaDesyncDebug(struct desync_profile *dp, const char *entity) { if (params.debug) { struct func_list *func; struct str2_list *arg; int n,i; LIST_FOREACH(func, &dp->lua_desync, next) { DLOG("%s %u (%s) lua %s(",entity,dp->n,PROFILE_NAME(dp),func->func); n=0; LIST_FOREACH(arg, &func->args, next) { if (n) DLOG(","); DLOG(arg->str2 ? "%s=\"%s\"" : "%s=\"\"", arg->str1, arg->str2); n++; } DLOG(" range_in=%c%u%c%c%u range_out=%c%u%c%c%u payload_type=", func->range_in.from.mode,func->range_in.from.pos, func->range_in.upper_cutoff ? '<' : '-', func->range_in.to.mode,func->range_in.to.pos, func->range_out.from.mode,func->range_out.from.pos, func->range_out.upper_cutoff ? '<' : '-', func->range_out.to.mode,func->range_out.to.pos); if (func->payload_type) { for(i=0;ipayload_type & (1ULL<b_filter_l3) dp->filter_ipv4 = dp->filter_ipv6 = true; // if any filter is set - deny all unset if (!LIST_EMPTY(&dp->pf_tcp) || !LIST_EMPTY(&dp->pf_udp) || !LIST_EMPTY(&dp->icf) || !LIST_EMPTY(&dp->ipf)) { return port_filters_deny_if_empty(&dp->pf_tcp) && port_filters_deny_if_empty(&dp->pf_udp) && icmp_filters_deny_if_empty(&dp->icf) && ipp_filters_deny_if_empty(&dp->ipf); } return true; } #ifdef __CYGWIN__ static bool wf_make_pf(char *opt, const char *l4, const char *portname, char *buf, size_t len) { char *e, *p, c, s1[64]; port_filter pf; int n; if (len < 3) return false; for (n = 0, p = opt, *buf = '(', buf[1] = 0; p; n++) { if ((e = strchr(p, ','))) { c = *e; *e = 0; } if (!pf_parse(p, &pf)) return false; if (pf.from == pf.to) snprintf(s1, sizeof(s1), "(%s.%s %s %u)", l4, portname, pf.neg ? "!=" : "==", pf.from); else snprintf(s1, sizeof(s1), "(%s.%s %s %u %s %s.%s %s %u)", l4, portname, pf.neg ? "<" : ">=", pf.from, pf.neg ? "or" : "and", l4, portname, pf.neg ? ">" : "<=", pf.to); if (n) strncat(buf, " or ", len - strlen(buf) - 1); strncat(buf, s1, len - strlen(buf) - 1); if (e) *e++ = c; p = e; } strncat(buf, ")", len - strlen(buf) - 1); return true; } static bool wf_make_icf(char *opt, char *buf, size_t len) { char *e, *p, c, s1[80]; icmp_filter icf; if (len < 3) return false; for (p = opt, *buf = '(', buf[1] = 0; p;) { if ((e = strchr(p, ','))) { c = *e; *e = 0; } if (!icf_parse(p, &icf)) return false; switch(icf.mode) { case FLTMODE_FILTER: if (icf.code_valid) snprintf(s1, sizeof(s1), "icmp.Type==%u and icmp.Code==%u or icmpv6.Type==%u and icmpv6.Code==%u", icf.type, icf.code, icf.type, icf.code); else snprintf(s1, sizeof(s1), "icmp.Type==%u or icmpv6.Type==%u", icf.type, icf.type); break; case FLTMODE_ANY: snprintf(s1, sizeof(s1), "icmp or icmpv6"); break; default: goto dont_add; } if (buf[1]) strncat(buf, " or ", len - strlen(buf) - 1); strncat(buf, s1, len - strlen(buf) - 1); dont_add: if (e) *e++ = c; p = e; } if (!buf[1]) return false; // nothing added strncat(buf, ")", len - strlen(buf) - 1); return true; } static bool wf_make_ipf(char *opt, char *buf, size_t len) { char *e, *p, c, s1[40]; ipp_filter ipf; if (len < 3) return false; for (p = opt, *buf = '(', buf[1] = 0; p;) { if ((e = strchr(p, ','))) { c = *e; *e = 0; } if (!ipp_parse(p, &ipf)) return false; switch(ipf.mode) { case FLTMODE_FILTER: // NOTE: windivert can't walk ipv6 extension headers. instead of real protocol first ext header type will be matched snprintf(s1, sizeof(s1), "ip.Protocol==%u or ipv6.NextHdr==%u", ipf.proto, ipf.proto); break; case FLTMODE_ANY: snprintf(s1, sizeof(s1), "ip or ipv6"); break; default: goto dont_add; } if (buf[1]) strncat(buf, " or ", len - strlen(buf) - 1); strncat(buf, s1, len - strlen(buf) - 1); dont_add: if (e) *e++ = c; p = e; } if (!buf[1]) return false; // nothing added strncat(buf, ")", len - strlen(buf) - 1); return true; } #define DIVERT_NO_LOCALNETSv4_DST "(" \ "(ip.DstAddr < 10.0.0.0 or ip.DstAddr > 10.255.255.255) and " \ "(ip.DstAddr < 192.168.0.0 or ip.DstAddr > 192.168.255.255) and " \ "(ip.DstAddr < 172.16.0.0 or ip.DstAddr > 172.31.255.255) and " \ "(ip.DstAddr < 169.254.0.0 or ip.DstAddr > 169.254.255.255))" #define DIVERT_NO_LOCALNETSv4_SRC "(" \ "(ip.SrcAddr < 10.0.0.0 or ip.SrcAddr > 10.255.255.255) and " \ "(ip.SrcAddr < 192.168.0.0 or ip.SrcAddr > 192.168.255.255) and " \ "(ip.SrcAddr < 172.16.0.0 or ip.SrcAddr > 172.31.255.255) and " \ "(ip.SrcAddr < 169.254.0.0 or ip.SrcAddr > 169.254.255.255))" #define DIVERT_NO_LOCALNETSv6_DST "(" \ "(ipv6.DstAddr < 2001::0 or ipv6.DstAddr >= 2001:1::0) and " \ "(ipv6.DstAddr < fc00::0 or ipv6.DstAddr >= fe00::0) and " \ "(ipv6.DstAddr < fe80::0 or ipv6.DstAddr >= fec0::0) and " \ "(ipv6.DstAddr < ff00::0 or ipv6.DstAddr >= ffff::0))" #define DIVERT_NO_LOCALNETSv6_SRC "(" \ "(ipv6.SrcAddr < 2001::0 or ipv6.SrcAddr >= 2001:1::0) and " \ "(ipv6.SrcAddr < fc00::0 or ipv6.SrcAddr >= fe00::0) and " \ "(ipv6.SrcAddr < fe80::0 or ipv6.SrcAddr >= fec0::0) and " \ "(ipv6.SrcAddr < ff00::0 or ipv6.SrcAddr >= ffff::0))" #define DIVERT_NO_LOCALNETS_SRC "(" DIVERT_NO_LOCALNETSv4_SRC " or " DIVERT_NO_LOCALNETSv6_SRC ")" #define DIVERT_NO_LOCALNETS_DST "(" DIVERT_NO_LOCALNETSv4_DST " or " DIVERT_NO_LOCALNETSv6_DST ")" #define DIVERT_TCP_NOT_EMPTY "(!tcp or tcp.Syn or tcp.Rst or tcp.Fin or tcp.PayloadLength>0)" #define DIVERT_TCP_ALWAYS "(tcp.Syn or tcp.Rst or tcp.Fin)" // HTTP/1.? 30(2|7) #define DIVERT_HTTP_REDIRECT "tcp.PayloadLength>=12 and tcp.Payload32[0]==0x48545450 and tcp.Payload16[2]==0x2F31 and tcp.Payload[6]==0x2E and tcp.Payload16[4]==0x2033 and tcp.Payload[10]==0x30 and (tcp.Payload[11]==0x32 or tcp.Payload[11]==0x37)" #define DIVERT_PROLOG "!impostor" static bool wf_make_filter( char *wf, size_t len, unsigned int IfIdx, unsigned int SubIfIdx, bool ipv4, bool ipv6, bool bTcpEmpty, const char *pf_tcp_src_out, const char *pf_tcp_dst_out, const char *pf_tcp_src_in, const char *pf_tcp_dst_in, const char *pf_udp_src_in, const char *pf_udp_dst_out, const char *icf_out, const char *icf_in, const char *ipf_out, const char *ipf_in, const char *wf_raw_filter, const struct str_list_head *wf_raw_part, bool bFilterOutLAN, bool bFilterOutLoopback) { struct str_list *wfpart; bool bHaveTCP = *pf_tcp_src_in || *pf_tcp_dst_out; snprintf(wf, len, "%s", DIVERT_PROLOG); if (bFilterOutLoopback) snprintf(wf + strlen(wf), len - strlen(wf), "\nand !loopback"); if (IfIdx) snprintf(wf + strlen(wf), len - strlen(wf), "\nand ifIdx=%u and subIfIdx=%u", IfIdx, SubIfIdx); if (ipv4 ^ ipv6) snprintf(wf + strlen(wf), len - strlen(wf), "\nand %s", ipv4 ? "ip" : "ipv6"); if (bHaveTCP && !bTcpEmpty) snprintf(wf + strlen(wf), len - strlen(wf), "\nand " DIVERT_TCP_NOT_EMPTY); if (*wf_raw_filter) snprintf(wf + strlen(wf), len - strlen(wf), "\nand\n(\n%s\n)", wf_raw_filter); snprintf(wf + strlen(wf), len - strlen(wf), "\nand\n(\n false"); if (bHaveTCP) { // may be required by orchestrators - always redirect // if (dp_list_have_autohostlist(¶ms.desync_profiles)) snprintf(wf + strlen(wf), len - strlen(wf), " or\n " DIVERT_HTTP_REDIRECT); } if (!LIST_EMPTY(wf_raw_part)) { LIST_FOREACH(wfpart, wf_raw_part, next) { snprintf(wf + strlen(wf), len - strlen(wf), " or\n (\n%s\n )", wfpart->str); } } if (*pf_tcp_dst_out) { snprintf(wf + strlen(wf), len - strlen(wf), " or\n outbound and %s", pf_tcp_dst_out); // always redirect opposite syn,fin,rst for conntrack snprintf(wf + strlen(wf), len - strlen(wf), " or\n inbound and " DIVERT_TCP_ALWAYS " and %s", pf_tcp_src_out); } if (*pf_tcp_src_in) { snprintf(wf + strlen(wf), len - strlen(wf), " or\n inbound and %s", pf_tcp_src_in); // always redirect opposite syn,fin,rst for conntrack snprintf(wf + strlen(wf), len - strlen(wf), " or\n outbound and " DIVERT_TCP_ALWAYS " and %s", pf_tcp_dst_in); } if (*pf_udp_dst_out) snprintf(wf + strlen(wf), len - strlen(wf), " or\n outbound and %s", pf_udp_dst_out); if (*pf_udp_src_in) snprintf(wf + strlen(wf), len - strlen(wf), " or\n inbound and %s", pf_udp_src_in); if (*icf_in) snprintf(wf + strlen(wf), len - strlen(wf), " or\n inbound and %s", icf_in); if (*icf_out) snprintf(wf + strlen(wf), len - strlen(wf), " or\n outbound and %s", icf_out); if (*ipf_in) snprintf(wf + strlen(wf), len - strlen(wf), " or\n inbound and %s", ipf_in); if (*ipf_out) snprintf(wf + strlen(wf), len - strlen(wf), " or\n outbound and %s", ipf_out); snprintf(wf + strlen(wf), len - strlen(wf), "\n)"); if (bFilterOutLAN) snprintf(wf + strlen(wf), len - strlen(wf), "\nand\n(\n outbound and %s\n or\n inbound and %s\n)\n", ipv4 ? ipv6 ? DIVERT_NO_LOCALNETS_DST : DIVERT_NO_LOCALNETSv4_DST : DIVERT_NO_LOCALNETSv6_DST, ipv4 ? ipv6 ? DIVERT_NO_LOCALNETS_SRC : DIVERT_NO_LOCALNETSv4_SRC : DIVERT_NO_LOCALNETSv6_SRC); return true; } static unsigned int hash_jen(const void *data, unsigned int len) { unsigned int hash; HASH_JEN(data, len, hash); return hash; } #endif static void exithelp(void) { char all_payloads[1024], all_protos[512]; *all_payloads=0; for (t_l7payload pl=0 ; pl|$\t\t\t\t; read file for options. must be the only argument. other options are ignored.\n\n" #endif #ifdef __ANDROID__ " --debug=0|1|syslog|android|@\n" #else " --debug=0|1|syslog|@\n" #endif " --version\t\t\t\t\t\t; print version and exit\n" " --dry-run\t\t\t\t\t\t; verify parameters and exit with code 0 if successful\n" " --comment=any_text\n" " --intercept=0|1\t\t\t\t\t; enable interception. if disabled - run lua-init and exit\n" #ifdef __linux__ " --qnum=\n" #elif defined(BSD) " --port=\t\t\t\t\t\t; divert port\n" #endif " --daemon\t\t\t\t\t\t; daemonize\n" " --chdir[=path]\t\t\t\t\t\t; change current directory. if no path specified use EXEDIR\n" " --pidfile=\t\t\t\t\t; write pid to file\n" #ifndef __CYGWIN__ " --user=\t\t\t\t\t; drop root privs\n" " --uid=uid[:gid1,gid2,...]\t\t\t\t; drop root privs\n" #endif #ifdef __linux__ " --bind-fix4\t\t\t\t\t\t; apply outgoing interface selection fix for generated ipv4 packets\n" " --bind-fix6\t\t\t\t\t\t; apply outgoing interface selection fix for generated ipv6 packets\n" " --fwmark=\t\t\t\t\t; override fwmark for generated packets. default = 0x%08X (%u)\n" #elif defined(SO_USER_COOKIE) " --sockarg=\t\t\t\t\t; override sockarg (SO_USER_COOKIE) for generated packets. default = 0x%08X (%u)\n" #endif " --ctrack-timeouts=S:E:F[:U]\t\t\t\t; internal conntrack timeouts for TCP SYN, ESTABLISHED, FIN stages, UDP timeout. default %u:%u:%u:%u\n" " --ctrack-disable=[0|1]\t\t\t\t\t; 1 or no argument disables conntrack\n" " --payload-disable=[type[,type]]\t\t\t; do not discover these payload types. for available payload types see '--payload'. disable all if no argument.\n" " --server=[0|1]\t\t\t\t\t\t; change multiple aspects of src/dst ip/port handling for incoming connections\n" " --ipcache-lifetime=\t\t\t\t; time in seconds to keep cached hop count and domain name (default %u). 0 = no expiration\n" " --ipcache-hostname=[0|1]\t\t\t\t; 1 or no argument enables ip->hostname caching\n" " --reasm-disable=[type[,type]]\t\t\t\t; disable reasm for these L7 payloads : tls_client_hello quic_initial . if no argument - disable all reasm.\n" #ifdef __CYGWIN__ "\nWINDIVERT FILTER:\n" " --wf-iface=[.]\t\t\t\t; numeric network interface and subinterface indexes\n" " --wf-l3=ipv4|ipv6\t\t\t\t\t; L3 protocol filter. multiple comma separated values allowed.\n" " --wf-tcp-in=[~]port1[-port2]\t\t\t\t; TCP in port filter. ~ means negation. multiple comma separated values allowed.\n" " --wf-tcp-out=[~]port1[-port2]\t\t\t\t; TCP out port filter. ~ means negation. multiple comma separated values allowed.\n" " --wf-udp-in=[~]port1[-port2]\t\t\t\t; UDP in port filter. ~ means negation. multiple comma separated values allowed.\n" " --wf-udp-out=[~]port1[-port2]\t\t\t\t; UDP out port filter. ~ means negation. multiple comma separated values allowed.\n" " --wf-tcp-empty=[0|1]\t\t\t\t\t; enable processing of empty tcp packets without flags SYN,RST,FIN (default : 0)\n" " --wf-icmp-in=type[:code]\t\t\t\t; ICMP out filter. multiple comma separated values allowed.\n" " --wf-icmp-out=type[:code]\t\t\t\t; ICMP in filter. multiple comma separated values allowed.\n" " --wf-ipp-in=proto\t\t\t\t\t; IP protocol in filter. multiple comma separated values allowed.\n" " --wf-ipp-out=proto\t\t\t\t\t; IP protocol out filter. multiple comma separated values allowed.\n" " --wf-raw-part=|@\t\t\t; partial raw windivert filter combined by OR. multiple allowed\n" " --wf-raw-filter=|@\t\t\t; partial raw windivert filter combined by AND. only one allowed\n" " --wf-filter-lan=0|1\t\t\t\t\t; add excluding filter for non-global IP (default : 1)\n" " --wf-filter-loopback=0|1\t\t\t\t; add excluding filter for loopback (default : 1)\n" " --wf-raw=|@\t\t\t\t; full raw windivert filter string or filename. replaces --wf-tcp,--wf-udp,--wf-raw-part\n" " --wf-dup-check=0|1\t\t\t\t\t; 1 (default) = do not allow duplicate winws2 instances with the same wf filter\n" " --wf-save=\t\t\t\t\t; save windivert filter string to a file and exit\n" "\nLOGICAL NETWORK FILTER:\n" " --ssid-filter=ssid1[,ssid2,ssid3,...]\t\t\t; enable winws2 only if any of specified wifi SSIDs connected\n" " --nlm-filter=net1[,net2,net3,...]\t\t\t; enable winws2 only if any of specified NLM network is connected. names and GUIDs are accepted.\n" " --nlm-list[=all]\t\t\t\t\t; list Network List Manager (NLM) networks. connected only or all.\n" #endif "\nDESYNC ENGINE INIT:\n" " --writeable[=]\t\t\t\t; create writeable dir for LUA scripts and pass it in WRITEABLE env variable (only one dir possible)\n" " --blob=:[+ofs]@|0xHEX\t\t; load blob to LUA var \n" " --lua-init=@|\t\t\t; load LUA program from a file or string. if multiple parameters present order of execution is preserved. gzipped files are supported.\n" " --lua-gc=\t\t\t\t\t\t; forced garbage collection every N sec. default %u sec. triggers only when a packet arrives. 0 = disable.\n" "\nMULTI-STRATEGY:\n" " --new[=]\t\t\t\t\t\t; begin new profile. optionally set name\n" " --skip\t\t\t\t\t\t\t; do not use this profile\n" " --name=\t\t\t\t\t\t; set profile name\n" " --template[=]\t\t\t\t\t; use this profile as template (must be named or will be useless)\n" " --cookie[=]\t\t\t\t\t; pass this profile-bound string to LUA\n" " --import=\t\t\t\t\t; populate current profile with template data\n" " --filter-l3=ipv4|ipv6\t\t\t\t\t; L3 protocol filter. multiple comma separated values allowed.\n" " --filter-tcp=[~]port1[-port2]|*\t\t\t; TCP port filter. ~ means negation. setting tcp filter and not setting others denies others. comma separated list allowed.\n" " --filter-udp=[~]port1[-port2]|*\t\t\t; UDP port filter. ~ means negation. setting udp filter and not setting others denies others. comma separated list allowed.\n" " --filter-icmp=type[:code]|*\t\t\t\t; ICMP type+code filter. setting icmp filter and not setting others denies others. comma separated list allowed.\n" " --filter-ipp=proto\t\t\t\t\t; IP protocol filter. setting up ipp filter and not setting others denies others. comma separated list allowed.\n" " --filter-l7=proto[,proto]\t\t\t\t; L6-L7 protocol filter : %s\n" #ifdef HAS_FILTER_SSID " --filter-ssid=ssid1[,ssid2,ssid3,...]\t\t\t; per profile wifi SSID filter\n" #endif " --ipset=\t\t\t\t\t; ipset include filter (one ip/CIDR per line, ipv4 and ipv6 accepted, gzip supported, multiple ipsets allowed)\n" " --ipset-ip=\t\t\t\t\t; comma separated fixed subnet list\n" " --ipset-exclude=\t\t\t\t; ipset exclude filter (one ip/CIDR per line, ipv4 and ipv6 accepted, gzip supported, multiple ipsets allowed)\n" " --ipset-exclude-ip=\t\t\t\t; comma separated fixed subnet list\n" " --hostlist=\t\t\t\t\t; apply dpi desync only to the listed hosts (one host per line, subdomains auto apply, gzip supported, multiple hostlists allowed)\n" " --hostlist-domains=\t\t\t; comma separated fixed domain list\n" " --hostlist-exclude=\t\t\t\t; do not apply dpi desync to the listed hosts (one host per line, subdomains auto apply, gzip supported, multiple hostlists allowed)\n" " --hostlist-exclude-domains=\t\t; comma separated fixed domain list\n" " --hostlist-auto=\t\t\t\t; detect DPI blocks and build hostlist automatically\n" " --hostlist-auto-fail-threshold=\t\t\t; how many failed attempts cause hostname to be added to auto hostlist (default : %d)\n" " --hostlist-auto-fail-time=\t\t\t; all failed attemps must be within these seconds (default : %d)\n" " --hostlist-auto-retrans-threshold=\t\t; how many request retransmissions cause attempt to fail (default : %d)\n" " --hostlist-auto-retrans-maxseq=\t\t\t; count retransmissions only within this relative sequence (default : %u)\n" " --hostlist-auto-retrans-reset=[0|1]\t\t\t; send RST to retransmitter to break long wait (default: 1)\n" " --hostlist-auto-incoming-maxseq=\t\t\t; treat tcp connection as successful if incoming relative sequence exceedes this threshold (default : %u)\n" " --hostlist-auto-udp-out=\t\t\t\t; udp failure condition : sent at least `udp_out` packets (default : %u)\n" " --hostlist-auto-udp-in=\t\t\t\t; udp failure condition : received not more than `udp_in` packets (default : %u)\n" " --hostlist-auto-debug=\t\t\t; debug auto hostlist positives (global parameter)\n" "\nLUA PACKET PASS MODE:\n" " --payload=type[,type]\t\t\t\t\t; set payload types following LUA functions should process : %s\n" " --out-range=[(n|a|d|s|p)](-|<)[(n|a|d|s|p)]\t; set outgoing packet range for following LUA functions. '-' - include end pos, '<' - not include. prefix meaning : n - packet number, d - data packet number, s - relative sequence, p - data position relative sequence, b - byte count, x - never, a - always\n" " --in-range=[(n|a|d|s|p)](-|<)[(n|a|d|s|p)]\t; set incoming packet range for following LUA functions. '-' - include end pos, '<' - not include. prefix meaning : n - packet number, d - data packet number, s - relative sequence, p - data position relative sequence, b - byte count, x - never, a - always\n" "\nLUA DESYNC ACTION:\n" " --lua-desync=[:param1=val1[:param2=val2]]\t; call LUA function when packet received\n", #if defined(__linux__) || defined(SO_USER_COOKIE) DPI_DESYNC_FWMARK_DEFAULT,DPI_DESYNC_FWMARK_DEFAULT, #endif CTRACK_T_SYN, CTRACK_T_EST, CTRACK_T_FIN, CTRACK_T_UDP, IPCACHE_LIFETIME, LUA_GC_INTERVAL, all_protos, HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT, HOSTLIST_AUTO_FAIL_TIME_DEFAULT, HOSTLIST_AUTO_RETRANS_THRESHOLD_DEFAULT, HOSTLIST_AUTO_RETRANS_MAXSEQ, HOSTLIST_AUTO_INCOMING_MAXSEQ, HOSTLIST_AUTO_UDP_OUT, HOSTLIST_AUTO_UDP_IN, all_payloads ); close_std_and_exit(1); } static void exithelp_clean(void) { cleanup_params(¶ms); exithelp(); } #if !defined( __OpenBSD__) && !defined(__ANDROID__) // no static to not allow optimizer to inline this func (save stack) void config_from_file(const char *filename) { // config from a file char buf[MAX_CONFIG_FILE_SIZE]; buf[0] = 'x'; // fake argv[0] buf[1] = ' '; size_t bufsize = sizeof(buf) - 3; if (!load_file(filename, 0, buf + 2, &bufsize)) { DLOG_ERR("could not load config file '%s'\n", filename); exit_clean(1); } buf[bufsize + 2] = 0; // wordexp fails if it sees \t \n \r between args replace_char(buf, '\n', ' '); replace_char(buf, '\r', ' '); replace_char(buf, '\t', ' '); if (wordexp(buf, ¶ms.wexp, WRDE_NOCMD)) { DLOG_ERR("failed to split command line options from file '%s'\n", filename); exit_clean(1); } } #endif static void ApplyDefaultBlobs(struct blob_collection_head *blobs) { load_const_blob_to_collection("fake_default_tls",fake_tls_clienthello_default,sizeof(fake_tls_clienthello_default),blobs,BLOB_EXTRA_BYTES); load_const_blob_to_collection("fake_default_http",fake_http_request_default,strlen(fake_http_request_default),blobs,0); uint8_t buf[620]; memset(buf,0,sizeof(buf)); buf[0]=0x40; load_const_blob_to_collection("fake_default_quic",buf,620,blobs,0); } enum opt_indices { IDX_DEBUG, IDX_DRY_RUN, IDX_INTERCEPT, IDX_FUZZ, IDX_VERSION, IDX_COMMENT, #ifdef __linux__ IDX_QNUM, IDX_BIND_FIX4, IDX_BIND_FIX6, #elif defined(BSD) IDX_PORT, #endif IDX_DAEMON, IDX_CHDIR, IDX_PIDFILE, #ifndef __CYGWIN__ IDX_USER, IDX_UID, #endif IDX_CTRACK_TIMEOUTS, IDX_CTRACK_DISABLE, IDX_PAYLOAD_DISABLE, IDX_SERVER, IDX_IPCACHE_LIFETIME, IDX_IPCACHE_HOSTNAME, IDX_REASM_DISABLE, #ifdef __linux__ IDX_FWMARK, #elif defined(SO_USER_COOKIE) IDX_SOCKARG, #endif IDX_WRITEABLE, IDX_BLOB, IDX_LUA_INIT, IDX_LUA_GC, IDX_HOSTLIST, IDX_HOSTLIST_DOMAINS, IDX_HOSTLIST_EXCLUDE, IDX_HOSTLIST_EXCLUDE_DOMAINS, IDX_HOSTLIST_AUTO, IDX_HOSTLIST_AUTO_FAIL_THRESHOLD, IDX_HOSTLIST_AUTO_FAIL_TIME, IDX_HOSTLIST_AUTO_RETRANS_THRESHOLD, IDX_HOSTLIST_AUTO_RETRANS_MAXSEQ, IDX_HOSTLIST_AUTO_RETRANS_RESET, IDX_HOSTLIST_AUTO_INCOMING_MAXSEQ, IDX_HOSTLIST_AUTO_UDP_IN, IDX_HOSTLIST_AUTO_UDP_OUT, IDX_HOSTLIST_AUTO_DEBUG, IDX_NEW, IDX_SKIP, IDX_NAME, IDX_TEMPLATE, IDX_IMPORT, IDX_COOKIE, IDX_FILTER_L3, IDX_FILTER_TCP, IDX_FILTER_UDP, IDX_FILTER_ICMP, IDX_FILTER_IPP, IDX_FILTER_L7, #ifdef HAS_FILTER_SSID IDX_FILTER_SSID, #endif IDX_IPSET, IDX_IPSET_IP, IDX_IPSET_EXCLUDE, IDX_IPSET_EXCLUDE_IP, IDX_PAYLOAD, IDX_IN_RANGE, IDX_OUT_RANGE, IDX_LUA_DESYNC, #ifdef __CYGWIN__ IDX_WF_IFACE, IDX_WF_L3, IDX_WF_TCP_IN, IDX_WF_TCP_OUT, IDX_WF_UDP_IN, IDX_WF_UDP_OUT, IDX_WF_TCP_EMPTY, IDX_WF_ICMP_IN, IDX_WF_ICMP_OUT, IDX_WF_IPP_IN, IDX_WF_IPP_OUT, IDX_WF_RAW, IDX_WF_RAW_PART, IDX_WF_RAW_FILTER, IDX_WF_FILTER_LAN, IDX_WF_FILTER_LOOPBACK, IDX_WF_DUP_CHECK, IDX_WF_SAVE, IDX_SSID_FILTER, IDX_NLM_FILTER, IDX_NLM_LIST, #endif IDX_LAST }; static const struct option long_options[] = { [IDX_DEBUG] = {"debug", optional_argument, 0, 0}, [IDX_DRY_RUN] = {"dry-run", no_argument, 0, 0}, [IDX_INTERCEPT] = {"intercept", optional_argument, 0, 0}, [IDX_FUZZ] = {"fuzz", required_argument, 0, 0}, [IDX_VERSION] = {"version", no_argument, 0, 0}, [IDX_COMMENT] = {"comment", optional_argument, 0, 0}, #ifdef __linux__ [IDX_QNUM] = {"qnum", required_argument, 0, 0}, [IDX_BIND_FIX4] = {"bind-fix4", no_argument, 0, 0}, [IDX_BIND_FIX6] = {"bind-fix6", no_argument, 0, 0}, #elif defined(BSD) [IDX_PORT] = {"port", required_argument, 0, 0}, #endif [IDX_DAEMON] = {"daemon", no_argument, 0, 0}, [IDX_CHDIR] = {"chdir", optional_argument, 0, 0}, [IDX_PIDFILE] = {"pidfile", required_argument, 0, 0}, #ifndef __CYGWIN__ [IDX_USER] = {"user", required_argument, 0, 0}, [IDX_UID] = {"uid", required_argument, 0, 0}, #endif [IDX_CTRACK_TIMEOUTS] = {"ctrack-timeouts", required_argument, 0, 0}, [IDX_CTRACK_DISABLE] = {"ctrack-disable", optional_argument, 0, 0}, [IDX_PAYLOAD_DISABLE] = {"payload-disable", optional_argument, 0, 0}, [IDX_SERVER] = {"server", optional_argument, 0, 0}, [IDX_IPCACHE_LIFETIME] = {"ipcache-lifetime", required_argument, 0, 0}, [IDX_IPCACHE_HOSTNAME] = {"ipcache-hostname", optional_argument, 0, 0}, [IDX_REASM_DISABLE] = {"reasm-disable", optional_argument, 0, 0}, #ifdef __linux__ [IDX_FWMARK] = {"fwmark", required_argument, 0, 0}, #elif defined(SO_USER_COOKIE) [IDX_SOCKARG] = {"sockarg", required_argument, 0, 0}, #endif [IDX_WRITEABLE] = {"writeable", optional_argument, 0, 0}, [IDX_BLOB] = {"blob", required_argument, 0, 0}, [IDX_LUA_INIT] = {"lua-init", required_argument, 0, 0}, [IDX_LUA_GC] = {"lua-gc", required_argument, 0, 0}, [IDX_HOSTLIST] = {"hostlist", required_argument, 0, 0}, [IDX_HOSTLIST_DOMAINS] = {"hostlist-domains", required_argument, 0, 0}, [IDX_HOSTLIST_EXCLUDE] = {"hostlist-exclude", required_argument, 0, 0}, [IDX_HOSTLIST_EXCLUDE_DOMAINS] = {"hostlist-exclude-domains", required_argument, 0, 0}, [IDX_HOSTLIST_AUTO] = {"hostlist-auto", required_argument, 0, 0}, [IDX_HOSTLIST_AUTO_FAIL_THRESHOLD] = {"hostlist-auto-fail-threshold", required_argument, 0, 0}, [IDX_HOSTLIST_AUTO_FAIL_TIME] = {"hostlist-auto-fail-time", required_argument, 0, 0}, [IDX_HOSTLIST_AUTO_RETRANS_THRESHOLD] = {"hostlist-auto-retrans-threshold", required_argument, 0, 0}, [IDX_HOSTLIST_AUTO_RETRANS_MAXSEQ] = {"hostlist-auto-retrans-maxseq", required_argument, 0, 0}, [IDX_HOSTLIST_AUTO_RETRANS_RESET] = {"hostlist-auto-retrans-reset", optional_argument, 0, 0}, [IDX_HOSTLIST_AUTO_INCOMING_MAXSEQ] = {"hostlist-auto-incoming-maxseq", required_argument, 0, 0}, [IDX_HOSTLIST_AUTO_UDP_IN] = {"hostlist-auto-udp-in", required_argument, 0, 0}, [IDX_HOSTLIST_AUTO_UDP_OUT] = {"hostlist-auto-udp-out", required_argument, 0, 0}, [IDX_HOSTLIST_AUTO_DEBUG] = {"hostlist-auto-debug", required_argument, 0, 0}, [IDX_NEW] = {"new", optional_argument, 0, 0}, [IDX_SKIP] = {"skip", no_argument, 0, 0}, [IDX_NAME] = {"name", required_argument, 0, 0}, [IDX_TEMPLATE] = {"template", optional_argument, 0, 0}, [IDX_IMPORT] = {"import", required_argument, 0, 0}, [IDX_COOKIE] = {"cookie", required_argument, 0, 0}, [IDX_FILTER_L3] = {"filter-l3", required_argument, 0, 0}, [IDX_FILTER_TCP] = {"filter-tcp", required_argument, 0, 0}, [IDX_FILTER_UDP] = {"filter-udp", required_argument, 0, 0}, [IDX_FILTER_ICMP] = {"filter-icmp", required_argument, 0, 0}, [IDX_FILTER_IPP] = {"filter-ipp", required_argument, 0, 0}, [IDX_FILTER_L7] = {"filter-l7", required_argument, 0, 0}, #ifdef HAS_FILTER_SSID [IDX_FILTER_SSID] = {"filter-ssid", required_argument, 0, 0}, #endif [IDX_IPSET] = {"ipset", required_argument, 0, 0}, [IDX_IPSET_IP] = {"ipset-ip", required_argument, 0, 0}, [IDX_IPSET_EXCLUDE] = {"ipset-exclude", required_argument, 0, 0}, [IDX_IPSET_EXCLUDE_IP] = {"ipset-exclude-ip", required_argument, 0, 0}, [IDX_PAYLOAD] = {"payload", required_argument, 0, 0}, [IDX_IN_RANGE] = {"in-range", required_argument, 0, 0}, [IDX_OUT_RANGE] = {"out-range", required_argument, 0, 0}, [IDX_LUA_DESYNC] = {"lua-desync", required_argument, 0, 0}, #ifdef __CYGWIN__ [IDX_WF_IFACE] = {"wf-iface", required_argument, 0, 0}, [IDX_WF_L3] = {"wf-l3", required_argument, 0, 0}, [IDX_WF_TCP_IN] = {"wf-tcp-in", required_argument, 0, 0}, [IDX_WF_TCP_OUT] = {"wf-tcp-out", required_argument, 0, 0}, [IDX_WF_UDP_IN] = {"wf-udp-in", required_argument, 0, 0}, [IDX_WF_UDP_OUT] = {"wf-udp-out", required_argument, 0, 0}, [IDX_WF_TCP_EMPTY] = {"wf-tcp-empty", optional_argument, 0, 0}, [IDX_WF_ICMP_IN] = {"wf-icmp-in", required_argument, 0, 0}, [IDX_WF_ICMP_OUT] = {"wf-icmp-out", required_argument, 0, 0}, [IDX_WF_IPP_IN] = {"wf-ipp-in", required_argument, 0, 0}, [IDX_WF_IPP_OUT] = {"wf-ipp-out", required_argument, 0, 0}, [IDX_WF_RAW] = {"wf-raw", required_argument, 0, 0}, [IDX_WF_RAW_PART] = {"wf-raw-part", required_argument, 0, 0}, [IDX_WF_RAW_FILTER] = {"wf-raw-filter", required_argument, 0, 0}, [IDX_WF_FILTER_LAN] = {"wf-filter-lan", required_argument, 0, 0}, [IDX_WF_FILTER_LOOPBACK] = {"wf-filter-loopback", required_argument, 0, 0}, [IDX_WF_SAVE] = {"wf-save", required_argument, 0, 0}, [IDX_WF_DUP_CHECK] = {"wf-dup-check", optional_argument, 0, 0}, [IDX_SSID_FILTER] = {"ssid-filter", required_argument, 0, 0}, [IDX_NLM_FILTER] = {"nlm-filter", required_argument, 0, 0}, [IDX_NLM_LIST] = {"nlm-list", optional_argument, 0, 0}, #endif [IDX_LAST] = {NULL, 0, NULL, 0}, }; #ifdef __CYGWIN__ #define TITLE_ICON MAKEINTRESOURCE(1) static void WinSetIcon(void) { HWND hConsole = GetConsoleWindow(); HICON hIcon,hIconOld; if (hConsole) { if ((hIcon = LoadImage(GetModuleHandle(NULL),TITLE_ICON,IMAGE_ICON,32,32,LR_DEFAULTCOLOR|LR_SHARED))) { hIconOld = (HICON)SendMessage(hConsole, WM_SETICON, ICON_SMALL, (LPARAM)hIcon); if (hIconOld) DestroyIcon(hIconOld); } if ((hIcon = LoadImage(GetModuleHandle(NULL),TITLE_ICON,IMAGE_ICON,0,0,LR_DEFAULTCOLOR|LR_SHARED))) { hIconOld = (HICON)SendMessage(hConsole, WM_SETICON, ICON_BIG, (LPARAM)hIcon); if (hIconOld) DestroyIcon(hIconOld); } } } #endif #define STRINGIFY(x) #x #define TOSTRING(x) STRINGIFY(x) #if defined(ZAPRET_GH_VER) || defined (ZAPRET_GH_HASH) #ifdef __ANDROID__ #define MAKE_VER(s,size) snprintf(s,size,"github android version %s (%s) lua_compat_ver %u", TOSTRING(ZAPRET_GH_VER), TOSTRING(ZAPRET_GH_HASH), LUA_COMPAT_VER) #else #define MAKE_VER(s,size) snprintf(s,size,"github version %s (%s) lua_compat_ver %u", TOSTRING(ZAPRET_GH_VER), TOSTRING(ZAPRET_GH_HASH), LUA_COMPAT_VER) #endif #else #ifdef __ANDROID__ #define MAKE_VER(s,size) snprintf(s,size,"self-built android version %s %s lua_compat_ver %u", __DATE__, __TIME__, LUA_COMPAT_VER) #else #define MAKE_VER(s,size) snprintf(s,size,"self-built version %s %s lua_compat_ver %u", __DATE__, __TIME__, LUA_COMPAT_VER) #endif #endif enum {WF_TCP_IN, WF_UDP_IN, WF_TCP_OUT, WF_UDP_OUT, WF_ICMP_IN, WF_ICMP_OUT, WF_IPP_IN, WF_IPP_OUT, WF_RAW, WF_RAWF_PART, WF_RAWF_FILTER, GLOBAL_SSID_FILTER, GLOBAL_NLM_FILTER, WF_RAWF, WF_COUNT} t_wf_index; int main(int argc, char **argv) { #ifdef __CYGWIN__ if (service_run(argc, argv)) { // we were running as service. now exit. return 0; } WinSetIcon(); #endif MAKE_VER(params.verstr, sizeof(params.verstr)); printf("%s\n\n",params.verstr); int result, v; int option_index = 0; bool bSkip = false, bDry = false, bDupCheck = true, bTemplate; struct hostlist_file *anon_hl = NULL, *anon_hl_exclude = NULL; struct ipset_file *anon_ips = NULL, *anon_ips_exclude = NULL; uint64_t payload_type=0; struct packet_range range_in = PACKET_RANGE_NEVER, range_out = PACKET_RANGE_ALWAYS; #ifdef __CYGWIN__ char wf_save_file[256]=""; bool wf_ipv4 = true, wf_ipv6 = true, wf_filter_lan = true, wf_filter_loopback = true, wf_tcp_empty = false; unsigned int IfIdx = 0, SubIfIdx = 0; unsigned int hash_wf[WF_COUNT]; #endif if (argc < 2) exithelp(); srandom(time(NULL)); aes_init_keygen_tables(); // required for aes set_env_exedir(argv[0]); set_console_io_buffering(); #ifdef __CYGWIN__ mask_from_bitcount6_prepare(); memset(hash_wf,0,sizeof(hash_wf)); prepare_low_appdata(); #endif init_params(¶ms); ApplyDefaultBlobs(¶ms.blobs); struct desync_profile_list *dpl; struct desync_profile *dp; unsigned int desync_profile_count = 0, desync_template_count = 0; bTemplate = false; if (!(dpl = dp_list_add(¶ms.desync_profiles))) { DLOG_ERR("desync_profile_add: out of memory\n"); exit_clean(1); } dp = &dpl->dp; dp->n = ++desync_profile_count; #if !defined( __OpenBSD__) && !defined(__ANDROID__) if (argc >= 2 && (argv[1][0] == '@' || argv[1][0] == '$')) { config_from_file(argv[1] + 1); argv = params.wexp.we_wordv; argc = params.wexp.we_wordc; } #endif #ifdef __CYGWIN__ params.windivert_filter = malloc(WINDIVERT_MAX); if (!params.windivert_filter || !alloc_windivert_portfilters(¶ms)) { DLOG_ERR("out of memory\n"); exit_clean(1); } #endif while ((v = getopt_long_only(argc, argv, "", long_options, &option_index)) != -1) { if (v) { if (bDry) exit_clean(1); else exithelp_clean(); } switch (option_index) { case IDX_DEBUG: if (optarg) { if (*optarg == '@') { if (!realpath_any(optarg+1,params.debug_logfile)) { DLOG_ERR("bad file '%s'\n",optarg+1); exit_clean(1); } FILE *F = fopen(params.debug_logfile, "wt"); if (!F) { fprintf(stderr, "cannot create %s\n", params.debug_logfile); exit_clean(1); } fclose(F); params.debug = true; params.debug_target = LOG_TARGET_FILE; } else if (!strcmp(optarg, "syslog")) { params.debug = true; params.debug_target = LOG_TARGET_SYSLOG; openlog(progname, LOG_PID, LOG_USER); } #ifdef __ANDROID__ else if (!strcmp(optarg, "android")) { if (!params.debug) params.debug = 1; params.debug_target = LOG_TARGET_ANDROID; } #endif else if (optarg[0] >= '0' && optarg[0] <= '1') { params.debug = atoi(optarg); params.debug_target = LOG_TARGET_CONSOLE; } else { fprintf(stderr, "invalid debug mode : %s\n", optarg); exit_clean(1); } } else { params.debug = true; params.debug_target = LOG_TARGET_CONSOLE; } break; case IDX_DRY_RUN: bDry = true; break; case IDX_INTERCEPT: params.intercept = !optarg || atoi(optarg); break; case IDX_FUZZ: params.fuzz = atoi(optarg); params.intercept = false; break; case IDX_VERSION: exit_clean(0); break; case IDX_COMMENT: break; #ifdef __linux__ case IDX_QNUM: params.qnum = atoi(optarg); if (params.qnum < 0 || params.qnum>65535) { DLOG_ERR("bad qnum\n"); exit_clean(1); } break; case IDX_BIND_FIX4: params.bind_fix4 = true; break; case IDX_BIND_FIX6: params.bind_fix6 = true; break; #elif defined(BSD) case IDX_PORT: { int i = atoi(optarg); if (i <= 0 || i > 65535) { DLOG_ERR("bad port number\n"); exit_clean(1); } params.port = (uint16_t)i; } break; #endif case IDX_DAEMON: params.daemon = true; break; case IDX_CHDIR: { const char *d = optarg ? optarg : getenv("EXEDIR"); if (!d) { DLOG_ERR("chdir: directory unknown\n"); exit_clean(1); } DLOG("changing dir to '%s'\n",d); if (chdir(d)) { DLOG_PERROR("chdir"); exit_clean(1); } } break; case IDX_PIDFILE: if (!realpath_any(optarg,params.pidfile)) { DLOG_ERR("bad file '%s'\n",optarg); exit_clean(1); } break; #ifndef __CYGWIN__ case IDX_USER: { free(params.user); params.user = NULL; struct passwd *pwd = getpwnam(optarg); if (!pwd) { DLOG_ERR("non-existent username supplied\n"); exit_clean(1); } params.uid = pwd->pw_uid; params.gid[0] = pwd->pw_gid; params.gid_count = 1; if (!(params.user = strdup(optarg))) { DLOG_ERR("strdup: out of memory\n"); exit_clean(1); } params.droproot = true; break; } case IDX_UID: free(params.user); params.user = NULL; if (!parse_uid(optarg, ¶ms.uid, params.gid, ¶ms.gid_count, MAX_GIDS)) { DLOG_ERR("--uid should be : uid[:gid,gid,...]\n"); exit_clean(1); } if (!params.gid_count) { params.gid[0] = 0x7FFFFFFF; params.gid_count = 1; } params.droproot = true; break; #endif case IDX_CTRACK_TIMEOUTS: if (sscanf(optarg, "%u:%u:%u:%u", ¶ms.ctrack_t_syn, ¶ms.ctrack_t_est, ¶ms.ctrack_t_fin, ¶ms.ctrack_t_udp) < 3) { DLOG_ERR("invalid ctrack-timeouts value\n"); exit_clean(1); } break; case IDX_CTRACK_DISABLE: params.ctrack_disable = !optarg || atoi(optarg); break; case IDX_SERVER: params.server = !optarg || atoi(optarg); break; case IDX_IPCACHE_LIFETIME: if (sscanf(optarg, "%u", ¶ms.ipcache_lifetime) != 1) { DLOG_ERR("invalid ipcache-lifetime value\n"); exit_clean(1); } break; case IDX_IPCACHE_HOSTNAME: params.cache_hostname = !optarg || atoi(optarg); break; case IDX_PAYLOAD_DISABLE: if (optarg) { if (!parse_l7p_list(optarg, ¶ms.payload_disable)) { DLOG_ERR("Invalid payload filter : %s\n", optarg); exit_clean(1); } } else params.payload_disable = L7P_ALL; break; case IDX_REASM_DISABLE: if (optarg) { if (!parse_l7p_list(optarg, ¶ms.reasm_payload_disable)) { DLOG_ERR("Invalid l7 protocol list : %s\n", optarg); exit_clean(1); } } else params.reasm_payload_disable = L7P_ALL; break; #if defined(__linux__) case IDX_FWMARK: #elif defined(SO_USER_COOKIE) case IDX_SOCKARG: #endif #if defined(__linux__) || defined(SO_USER_COOKIE) params.desync_fwmark = 0; if (sscanf(optarg, "0x%X", ¶ms.desync_fwmark) <= 0) sscanf(optarg, "%u", ¶ms.desync_fwmark); if (!params.desync_fwmark) { DLOG_ERR("fwmark/sockarg should be decimal or 0xHEX and should not be zero\n"); exit_clean(1); } break; #endif case IDX_WRITEABLE: params.writeable_dir_enable = true; if (optarg) { if (!realpath_any(optarg, params.writeable_dir)) { DLOG_ERR("bad file '%s'\n",optarg); exit_clean(1); } } else *params.writeable_dir = 0; break; case IDX_BLOB: load_blob_to_collection(optarg, ¶ms.blobs, MAX_BLOB_SIZE, BLOB_EXTRA_BYTES); break; case IDX_LUA_INIT: { char pabs[PATH_MAX+1], *p=optarg; if (*p=='@') { if (!realpath_any(p+1,pabs+1)) { DLOG_ERR("bad file '%s'\n",p+1); exit_clean(1); } *(p=pabs)='@'; } if (!strlist_add_tail(¶ms.lua_init_scripts, p)) { DLOG_ERR("out of memory\n"); exit_clean(1); } } break; case IDX_LUA_GC: params.lua_gc = atoi(optarg); if (params.lua_gc<0) { DLOG_ERR("lua-gc must be >=0\n"); exit_clean(1); } break; case IDX_HOSTLIST: if (bSkip) break; if (!RegisterHostlist(dp, false, optarg)) { DLOG_ERR("failed to register hostlist '%s'\n", optarg); exit_clean(1); } break; case IDX_HOSTLIST_DOMAINS: if (bSkip) break; if (!anon_hl && !(anon_hl = RegisterHostlist(dp, false, NULL))) { DLOG_ERR("failed to register anonymous hostlist\n"); exit_clean(1); } if (!parse_domain_list(optarg, &anon_hl->hostlist)) { DLOG_ERR("failed to add domains to anonymous hostlist\n"); exit_clean(1); } break; case IDX_HOSTLIST_EXCLUDE: if (bSkip) break; if (!RegisterHostlist(dp, true, optarg)) { DLOG_ERR("failed to register hostlist '%s'\n", optarg); exit_clean(1); } break; case IDX_HOSTLIST_EXCLUDE_DOMAINS: if (bSkip) break; if (!anon_hl_exclude && !(anon_hl_exclude = RegisterHostlist(dp, true, NULL))) { DLOG_ERR("failed to register anonymous hostlist\n"); exit_clean(1); } if (!parse_domain_list(optarg, &anon_hl_exclude->hostlist)) { DLOG_ERR("failed to add domains to anonymous hostlist\n"); exit_clean(1); } break; case IDX_HOSTLIST_AUTO: if (bSkip) break; if (dp->hostlist_auto) { DLOG_ERR("only one auto hostlist per profile is supported\n"); exit_clean(1); } { FILE *F = fopen(optarg, "a+b"); if (!F) { DLOG_ERR("cannot create %s\n", optarg); exit_clean(1); } bool bGzip = is_gzip(F); fclose(F); if (bGzip) { DLOG_ERR("gzipped auto hostlists are not supported\n"); exit_clean(1); } } if (!(dp->hostlist_auto = RegisterHostlist(dp, false, optarg))) { DLOG_ERR("failed to register hostlist '%s'\n", optarg); exit_clean(1); } break; case IDX_HOSTLIST_AUTO_FAIL_THRESHOLD: dp->hostlist_auto_fail_threshold = (uint8_t)atoi(optarg); if (dp->hostlist_auto_fail_threshold < 1 || dp->hostlist_auto_fail_threshold>20) { DLOG_ERR("auto hostlist fail threshold must be within 1..20\n"); exit_clean(1); } dp->b_hostlist_auto_fail_threshold = true; break; case IDX_HOSTLIST_AUTO_FAIL_TIME: dp->hostlist_auto_fail_time = (uint8_t)atoi(optarg); if (dp->hostlist_auto_fail_time < 1) { DLOG_ERR("auto hostlist fail time is not valid\n"); exit_clean(1); } dp->b_hostlist_auto_fail_time = true; break; case IDX_HOSTLIST_AUTO_RETRANS_THRESHOLD: dp->hostlist_auto_retrans_threshold = (uint8_t)atoi(optarg); if (dp->hostlist_auto_retrans_threshold < 2 || dp->hostlist_auto_retrans_threshold>10) { DLOG_ERR("auto hostlist fail threshold must be within 2..10\n"); exit_clean(1); } dp->b_hostlist_auto_retrans_threshold = true; break; case IDX_HOSTLIST_AUTO_RETRANS_MAXSEQ: dp->hostlist_auto_retrans_maxseq = (uint32_t)atoi(optarg); dp->b_hostlist_auto_retrans_maxseq = true; break; case IDX_HOSTLIST_AUTO_INCOMING_MAXSEQ: dp->hostlist_auto_incoming_maxseq = (uint32_t)atoi(optarg); dp->b_hostlist_auto_incoming_maxseq = true; break; case IDX_HOSTLIST_AUTO_RETRANS_RESET: dp->hostlist_auto_retrans_reset = !optarg || !!atoi(optarg); dp->b_hostlist_auto_retrans_reset = true; break; case IDX_HOSTLIST_AUTO_UDP_OUT: dp->hostlist_auto_udp_out = atoi(optarg); dp->b_hostlist_auto_udp_out = true; break; case IDX_HOSTLIST_AUTO_UDP_IN: dp->hostlist_auto_udp_in = atoi(optarg); dp->b_hostlist_auto_udp_in = true; break; case IDX_HOSTLIST_AUTO_DEBUG: { if (!realpath_any(optarg,params.hostlist_auto_debuglog)) { DLOG_ERR("bad file '%s'\n",optarg); exit_clean(1); } FILE *F = fopen(params.hostlist_auto_debuglog, "a+t"); if (!F) { DLOG_ERR("cannot create %s\n", optarg); exit_clean(1); } fclose(F); } break; case IDX_NEW: if (bSkip) { dp_clear(dp); dp_init(dp); dp->n = desync_profile_count; bSkip = false; } else { if (bTemplate) { if (dp->name && dp_list_search_name(¶ms.desync_templates, dp->name)) { DLOG_ERR("template '%s' already present\n", dp->name); exit_clean(1); } dpl->dp.n = ++desync_template_count; dp_list_move(¶ms.desync_templates, dpl); } else { desync_profile_count++; } if (!(dpl = dp_list_add(¶ms.desync_profiles))) { DLOG_ERR("desync_profile_add: out of memory\n"); exit_clean(1); } dp = &dpl->dp; dp->n = desync_profile_count; } if (optarg && !(dp->name = strdup(optarg))) { DLOG_ERR("out of memory\n"); exit_clean(1); } anon_hl = anon_hl_exclude = NULL; anon_ips = anon_ips_exclude = NULL; payload_type = 0; range_in = PACKET_RANGE_NEVER; range_out = PACKET_RANGE_ALWAYS; bTemplate = false; break; case IDX_SKIP: bSkip = true; break; case IDX_TEMPLATE: bTemplate = true; case IDX_NAME: if (optarg) { free(dp->name); if (!(dp->name = strdup(optarg))) { DLOG_ERR("out of memory\n"); exit_clean(1); } } break; case IDX_COOKIE: free(dp->cookie); if (!(dp->cookie = strdup(optarg))) { DLOG_ERR("out of memory\n"); exit_clean(1); } break; case IDX_IMPORT: { struct desync_profile_list *tpl = dp_list_search_name(¶ms.desync_templates, optarg); if (!tpl) { DLOG_ERR("template '%s' not found\n", optarg); exit_clean(1); } if (!dp_copy(dp, &tpl->dp)) { DLOG_ERR("could not copy template\n"); exit_clean(1); } dp->n = desync_profile_count; } break; case IDX_FILTER_L3: if (!wf_make_l3(optarg, &dp->filter_ipv4, &dp->filter_ipv6)) { DLOG_ERR("bad value for --filter-l3\n"); exit_clean(1); } dp->b_filter_l3 = true; break; case IDX_FILTER_TCP: if (!parse_pf_list(optarg, &dp->pf_tcp)) { DLOG_ERR("Invalid port filter : %s\n", optarg); exit_clean(1); } break; case IDX_FILTER_UDP: if (!parse_pf_list(optarg, &dp->pf_udp)) { DLOG_ERR("Invalid port filter : %s\n", optarg); exit_clean(1); } break; case IDX_FILTER_ICMP: if (!parse_icf_list(optarg, &dp->icf)) { DLOG_ERR("Invalid icmp filter : %s\n", optarg); exit_clean(1); } break; case IDX_FILTER_IPP: if (!parse_ipp_list(optarg, &dp->ipf)) { DLOG_ERR("Invalid ip protocol filter : %s\n", optarg); exit_clean(1); } break; case IDX_FILTER_L7: if (!parse_l7_list(optarg, &dp->filter_l7)) { DLOG_ERR("Invalid l7 filter : %s\n", optarg); exit_clean(1); } dp->b_filter_l7 = true; break; #ifdef HAS_FILTER_SSID case IDX_FILTER_SSID: if (!parse_strlist(optarg, &dp->filter_ssid)) { DLOG_ERR("strlist_add failed\n"); exit_clean(1); } params.filter_ssid_present = true; break; #endif case IDX_IPSET: if (bSkip) break; if (!RegisterIpset(dp, false, optarg)) { DLOG_ERR("failed to register ipset '%s'\n", optarg); exit_clean(1); } break; case IDX_IPSET_IP: if (bSkip) break; if (!anon_ips && !(anon_ips = RegisterIpset(dp, false, NULL))) { DLOG_ERR("failed to register anonymous ipset\n"); exit_clean(1); } if (!parse_ip_list(optarg, &anon_ips->ipset)) { DLOG_ERR("failed to add subnets to anonymous ipset\n"); exit_clean(1); } break; case IDX_IPSET_EXCLUDE: if (bSkip) break; if (!RegisterIpset(dp, true, optarg)) { DLOG_ERR("failed to register ipset '%s'\n", optarg); exit_clean(1); } break; case IDX_IPSET_EXCLUDE_IP: if (bSkip) break; if (!anon_ips_exclude && !(anon_ips_exclude = RegisterIpset(dp, true, NULL))) { DLOG_ERR("failed to register anonymous ipset\n"); exit_clean(1); } if (!parse_ip_list(optarg, &anon_ips_exclude->ipset)) { DLOG_ERR("failed to add subnets to anonymous ipset\n"); exit_clean(1); } break; case IDX_PAYLOAD: if (!parse_l7p_list(optarg, &payload_type)) { DLOG_ERR("Invalid payload filter : %s\n", optarg); exit_clean(1); } break; case IDX_OUT_RANGE: if (!packet_range_parse(optarg, &range_out)) { DLOG_ERR("invalid packet range value : %s\n",optarg); exit_clean(1); } break; case IDX_IN_RANGE: if (!packet_range_parse(optarg, &range_in)) { DLOG_ERR("invalid packet range value : %s\n",optarg); exit_clean(1); } break; case IDX_LUA_DESYNC: { struct func_list *f; if (!(f=parse_lua_call(optarg, &dp->lua_desync))) { DLOG_ERR("invalid lua function call : %s\n", optarg); exit_clean(1); } f->payload_type = payload_type; f->range_in = range_in; f->range_out = range_out; } break; #ifdef __CYGWIN__ case IDX_WF_IFACE: if (!sscanf(optarg, "%u.%u", &IfIdx, &SubIfIdx)) { DLOG_ERR("bad value for --wf-iface\n"); exit_clean(1); } break; case IDX_WF_L3: if (!wf_make_l3(optarg, &wf_ipv4, &wf_ipv6)) { DLOG_ERR("bad value for --wf-l3\n"); exit_clean(1); } break; case IDX_WF_TCP_IN: hash_wf[WF_TCP_IN] = hash_jen(optarg, strlen(optarg)); if (!wf_make_pf(optarg, "tcp", "SrcPort", params.wf_pf_tcp_src_in, WINDIVERT_PORTFILTER_MAX) || !wf_make_pf(optarg, "tcp", "DstPort", params.wf_pf_tcp_dst_in, WINDIVERT_PORTFILTER_MAX)) { DLOG_ERR("bad value for --wf-tcp-in\n"); exit_clean(1); } break; case IDX_WF_TCP_OUT: hash_wf[WF_TCP_OUT] = hash_jen(optarg, strlen(optarg)); if (!wf_make_pf(optarg, "tcp", "SrcPort", params.wf_pf_tcp_src_out, WINDIVERT_PORTFILTER_MAX) || !wf_make_pf(optarg, "tcp", "DstPort", params.wf_pf_tcp_dst_out, WINDIVERT_PORTFILTER_MAX)) { DLOG_ERR("bad value for --wf-tcp-out\n"); exit_clean(1); } break; case IDX_WF_UDP_IN: hash_wf[WF_UDP_IN] = hash_jen(optarg, strlen(optarg)); if (!wf_make_pf(optarg, "udp", "SrcPort", params.wf_pf_udp_src_in, WINDIVERT_PORTFILTER_MAX) || !wf_make_pf(optarg, "udp", "DstPort", params.wf_pf_udp_dst_in, WINDIVERT_PORTFILTER_MAX)) { DLOG_ERR("bad value for --wf-udp-in\n"); exit_clean(1); } break; case IDX_WF_UDP_OUT: hash_wf[WF_UDP_OUT] = hash_jen(optarg, strlen(optarg)); if (!wf_make_pf(optarg, "udp", "SrcPort", params.wf_pf_udp_src_out, WINDIVERT_PORTFILTER_MAX) || !wf_make_pf(optarg, "udp", "DstPort", params.wf_pf_udp_dst_out, WINDIVERT_PORTFILTER_MAX)) { DLOG_ERR("bad value for --wf-udp-out\n"); exit_clean(1); } break; case IDX_WF_ICMP_IN: hash_wf[WF_ICMP_IN] = hash_jen(optarg, strlen(optarg)); if (!wf_make_icf(optarg, params.wf_icf_in, WINDIVERT_PORTFILTER_MAX)) { DLOG_ERR("bad value for --wf-icmp-in\n"); exit_clean(1); } break; case IDX_WF_ICMP_OUT: hash_wf[WF_ICMP_OUT] = hash_jen(optarg, strlen(optarg)); if (!wf_make_icf(optarg, params.wf_icf_out, WINDIVERT_PORTFILTER_MAX)) { DLOG_ERR("bad value for --wf-icmp-out\n"); exit_clean(1); } break; case IDX_WF_IPP_IN: hash_wf[WF_IPP_IN] = hash_jen(optarg, strlen(optarg)); if (!wf_make_ipf(optarg, params.wf_ipf_in, WINDIVERT_PORTFILTER_MAX)) { DLOG_ERR("bad value for --wf-ipp-in\n"); exit_clean(1); } break; case IDX_WF_IPP_OUT: hash_wf[WF_IPP_OUT] = hash_jen(optarg, strlen(optarg)); if (!wf_make_ipf(optarg, params.wf_ipf_out, WINDIVERT_PORTFILTER_MAX)) { DLOG_ERR("bad value for --wf-ipp-out\n"); exit_clean(1); } break; case IDX_WF_RAW: if (optarg[0] == '@') { size_t sz = WINDIVERT_MAX-1; load_file_or_exit(optarg, params.windivert_filter, &sz); params.windivert_filter[sz] = 0; } else snprintf(params.windivert_filter, WINDIVERT_MAX, "%s", optarg); hash_wf[WF_RAWF] = hash_jen(params.windivert_filter, strlen(params.windivert_filter)); break; case IDX_WF_TCP_EMPTY: wf_tcp_empty = !optarg || atoi(optarg); break; case IDX_WF_RAW_PART: { char *wfpart = malloc(WINDIVERT_MAX); if (!wfpart) { DLOG_ERR("out of memory\n"); exit_clean(1); } if (optarg[0] == '@') { size_t sz = WINDIVERT_MAX - 1; load_file_or_exit(optarg, wfpart, &sz); wfpart[sz] = 0; } else snprintf(wfpart, WINDIVERT_MAX, "%s", optarg); hash_wf[WF_RAWF_PART] ^= hash_jen(wfpart, strlen(wfpart)); if (!strlist_add(¶ms.wf_raw_part, wfpart)) { free(wfpart); DLOG_ERR("out of memory\n"); exit_clean(1); } free(wfpart); } break; case IDX_WF_RAW_FILTER: hash_wf[WF_RAWF_FILTER] = hash_jen(optarg, strlen(optarg)); if (optarg[0] == '@') { size_t sz = WINDIVERT_MAX-1; load_file_or_exit(optarg, params.wf_raw_filter, &sz); params.wf_raw_filter[sz] = 0; hash_wf[WF_RAWF] = hash_jen(params.windivert_filter, sz); } else snprintf(params.wf_raw_filter, WINDIVERT_MAX, "%s", optarg); hash_wf[WF_RAWF] = hash_jen(params.wf_raw_filter, strlen(params.wf_raw_filter)); break; case IDX_WF_FILTER_LAN: wf_filter_lan = !!atoi(optarg); break; case IDX_WF_FILTER_LOOPBACK: wf_filter_loopback = !!atoi(optarg); break; case IDX_WF_SAVE: strncpy(wf_save_file, optarg, sizeof(wf_save_file)); wf_save_file[sizeof(wf_save_file) - 1] = '\0'; break; case IDX_WF_DUP_CHECK: bDupCheck = !optarg || !!atoi(optarg); break; case IDX_SSID_FILTER: hash_wf[GLOBAL_SSID_FILTER] = hash_jen(optarg, strlen(optarg)); if (!parse_strlist(optarg, ¶ms.ssid_filter)) { DLOG_ERR("strlist_add failed\n"); exit_clean(1); } break; case IDX_NLM_FILTER: hash_wf[GLOBAL_NLM_FILTER] = hash_jen(optarg, strlen(optarg)); if (!parse_strlist(optarg, ¶ms.nlm_filter)) { DLOG_ERR("strlist_add failed\n"); exit_clean(1); } break; case IDX_NLM_LIST: if (!nlm_list(optarg && !strcmp(optarg, "all"))) { DLOG_ERR("could not get list of NLM networks\n"); exit_clean(1); } exit_clean(0); #endif } } if (bSkip) { LIST_REMOVE(dpl, next); dp_entry_destroy(dpl); desync_profile_count--; } else { if (bTemplate) { if (dp->name && dp_list_search_name(¶ms.desync_templates, dp->name)) { DLOG_ERR("template '%s' already present\n", dp->name); exit_clean(1); } dpl->dp.n = ++desync_template_count; dp_list_move(¶ms.desync_templates, dpl); desync_profile_count--; } } // do not need args from file anymore #if !defined( __OpenBSD__) && !defined(__ANDROID__) cleanup_args(¶ms); #endif argv = NULL; argc = 0; if (params.intercept) { #ifdef __linux__ if (params.qnum < 0) { DLOG_ERR("Need queue number (--qnum)\n"); exit_clean(1); } #elif defined(BSD) if (!params.port) { DLOG_ERR("Need divert port (--port)\n"); exit_clean(1); } #endif } DLOG("adding low-priority default empty desync profile\n"); // add default empty profile if (!(dpl = dp_list_add(¶ms.desync_profiles)) || !(dpl->dp.name=strdup("no_action"))) { DLOG_ERR("desync_profile_add: out of memory\n"); exit_clean(1); } DLOG_CONDUP("we have %u user defined desync profile(s) and default low priority profile 0\n", desync_profile_count); DLOG_CONDUP("we have %u user defined desync template(s)\n", desync_template_count); if (params.writeable_dir_enable) { if (!make_writeable_dir()) { DLOG_ERR("could not make writeable dir for LUA\n"); exit_clean(1); } DLOG("LUA writeable dir : %s\n", getenv("WRITEABLE")); } #ifndef __CYGWIN__ if (params.droproot) #endif { if (params.debug_target == LOG_TARGET_FILE && !ensure_file_access(params.debug_logfile)) DLOG_ERR("could not make '%s' accessible. log file may not be writable after privilege drop\n", params.debug_logfile); if (*params.hostlist_auto_debuglog && !ensure_file_access(params.hostlist_auto_debuglog)) DLOG_ERR("could not make '%s' accessible. auto hostlist debug log may not be writable after privilege drop\n", params.hostlist_auto_debuglog); } LIST_FOREACH(dpl, ¶ms.desync_profiles, next) { dp = &dpl->dp; if (params.server && dp->hostlist_auto) { DLOG_ERR("autohostlists not supported in server mode\n"); exit_clean(1); } #ifndef __CYGWIN__ if (params.droproot) #endif { if (dp->hostlist_auto && !ensure_file_access(dp->hostlist_auto->filename)) DLOG_ERR("could not make '%s' accessible. auto hostlist file may not be writable after privilege drop\n", dp->hostlist_auto->filename); } if (!filter_defaults(dp)) exit_clean(1); LuaDesyncDebug(dp,"profile"); } LIST_FOREACH(dpl, ¶ms.desync_templates, next) { dp = &dpl->dp; LuaDesyncDebug(dp,"template"); } if (!test_list_files()) exit_clean(1); if (!lua_test_init_script_files()) exit_clean(1); if (!LoadAllHostLists()) { DLOG_ERR("hostlists load failed\n"); exit_clean(1); } if (!LoadAllIpsets()) { DLOG_ERR("ipset load failed\n"); exit_clean(1); } DLOG("\nlists summary:\n"); HostlistsDebug(); IpsetsDebug(); DLOG("\nblobs summary:\n"); BlobDebug(); DLOG("\n"); // not required anymore. free memory dp_list_destroy(¶ms.desync_templates); #ifdef __CYGWIN__ if (params.intercept) { if (!*params.windivert_filter) { if (!*params.wf_pf_tcp_src_in && !*params.wf_pf_udp_src_in && !*params.wf_pf_tcp_src_out && !*params.wf_pf_udp_src_out && !*params.wf_icf_in && !*params.wf_icf_out && !*params.wf_ipf_in && !*params.wf_ipf_out && LIST_EMPTY(¶ms.wf_raw_part)) { DLOG_ERR("windivert filter : must specify port or/and partial raw filter\n"); exit_clean(1); } // exchange src/dst ports in server mode bool b = params.server ? wf_make_filter(params.windivert_filter, WINDIVERT_MAX, IfIdx, SubIfIdx, wf_ipv4, wf_ipv6, wf_tcp_empty, params.wf_pf_tcp_dst_out, params.wf_pf_tcp_src_out, params.wf_pf_tcp_dst_in, params.wf_pf_tcp_src_in, params.wf_pf_udp_dst_in, params.wf_pf_udp_src_out, params.wf_icf_out, params.wf_icf_in, params.wf_ipf_out, params.wf_ipf_in, params.wf_raw_filter, ¶ms.wf_raw_part, wf_filter_lan, wf_filter_loopback) : wf_make_filter(params.windivert_filter, WINDIVERT_MAX, IfIdx, SubIfIdx, wf_ipv4, wf_ipv6, wf_tcp_empty, params.wf_pf_tcp_src_out, params.wf_pf_tcp_dst_out, params.wf_pf_tcp_src_in, params.wf_pf_tcp_dst_in, params.wf_pf_udp_src_in, params.wf_pf_udp_dst_out, params.wf_icf_out, params.wf_icf_in, params.wf_ipf_out, params.wf_ipf_in, params.wf_raw_filter, ¶ms.wf_raw_part, wf_filter_lan, wf_filter_loopback); cleanup_windivert_portfilters(¶ms); if (!b) { DLOG_ERR("windivert filter : could not make filter\n"); exit_clean(1); } // free unneeded extra memory char *p = realloc(params.windivert_filter, strlen(params.windivert_filter)+1); if (p) params.windivert_filter=p; } } else { // do not intercept anything. only required for rawsend snprintf(params.windivert_filter,WINDIVERT_MAX,"false"); } DLOG("windivert filter size: %zu\nwindivert filter:\n%s\n", strlen(params.windivert_filter), params.windivert_filter); if (*wf_save_file) { if (save_file(wf_save_file, params.windivert_filter, strlen(params.windivert_filter))) { DLOG_ERR("windivert filter: raw filter saved to %s\n", wf_save_file); exit_clean(0); } else { DLOG_ERR("windivert filter: could not save raw filter to %s\n", wf_save_file); exit_clean(1); } } HANDLE hMutexArg = NULL; if (bDupCheck && params.intercept) { char mutex_name[32]; snprintf(mutex_name, sizeof(mutex_name), "Global\\winws2_arg_%u", hash_jen(hash_wf, sizeof(hash_wf))); hMutexArg = CreateMutexA(NULL, TRUE, mutex_name); if (hMutexArg && GetLastError() == ERROR_ALREADY_EXISTS) { CloseHandle(hMutexArg); hMutexArg = NULL; DLOG_ERR("A copy of winws2 is already running with the same filter\n"); goto exiterr; } } #endif if (bDry) { #ifndef __CYGWIN__ if (params.droproot) { if (!droproot(params.uid, params.user, params.gid, params.gid_count)) exit_clean(1); #ifdef __linux__ if (!dropcaps()) exit_clean(1); #endif print_id(); if (!test_list_files() || !lua_test_init_script_files()) exit_clean(1); } #endif DLOG_CONDUP("command line parameters verified\n"); exit_clean(0); } if (params.ctrack_disable) DLOG_CONDUP("conntrack disabled ! some functions will not work. make sure it's what you want.\n"); else { DLOG("initializing conntrack with timeouts tcp=%u:%u:%u udp=%u\n", params.ctrack_t_syn, params.ctrack_t_est, params.ctrack_t_fin, params.ctrack_t_udp); ConntrackPoolInit(¶ms.conntrack, 10, params.ctrack_t_syn, params.ctrack_t_est, params.ctrack_t_fin, params.ctrack_t_udp); } DLOG("ipcache lifetime %us\n", params.ipcache_lifetime); #ifdef __linux__ result = nfq_main(); #elif defined(BSD) result = dvt_main(); #elif defined(__CYGWIN__) result = win_main(); #else #error unsupported OS #endif ex: cleanup_params(¶ms); #ifdef __CYGWIN__ if (hMutexArg) { ReleaseMutex(hMutexArg); CloseHandle(hMutexArg); } #endif close_std(); return result; exiterr: result = 1; goto ex; }