From 78b3baa03f12ec44ef7f0867e5662c62c62d9b54 Mon Sep 17 00:00:00 2001 From: bol-van Date: Mon, 26 Jan 2026 14:22:38 +0300 Subject: [PATCH] icmp and ipp support --- nfq2/Makefile | 6 +- nfq2/checksum.c | 25 +- nfq2/checksum.h | 18 + nfq2/conntrack.c | 11 +- nfq2/conntrack.h | 2 +- nfq2/crypto/hkdf.c | 9 +- nfq2/darkmagic.c | 368 +++++++++++++- nfq2/darkmagic.h | 46 +- nfq2/desync.c | 377 ++++++++++++-- nfq2/filter.c | 236 +++++++++ nfq2/filter.h | 64 +++ nfq2/helpers.c | 237 +++------ nfq2/helpers.h | 53 +- nfq2/hostlist.c | 2 +- nfq2/ipset.c | 27 +- nfq2/ipset.h | 5 +- nfq2/lua.c | 849 ++++++++++++++++++++++++++++---- nfq2/lua.h | 17 +- nfq2/nfqws.c | 560 +++++++++++++++------ nfq2/nfqws.h | 6 +- nfq2/params.c | 28 +- nfq2/params.h | 6 +- nfq2/pools.c | 81 ++- nfq2/pools.h | 22 +- nfq2/protocol.c | 1 + nfq2/protocol.h | 2 + nfq2/windows/netinet/icmp6.h | 347 +++++++++++++ nfq2/windows/netinet/ip_icmp.h | 298 +++++++++++ nfq2/windows/nthacks.h | 40 ++ nfq2/windows/res/howto.txt | 4 + nfq2/windows/res/winws.ico | Bin 0 -> 29109 bytes nfq2/windows/res/winws.manifest | 24 + nfq2/windows/res/winws.rc | 3 + nfq2/windows/res/winws_res32.o | Bin 0 -> 30830 bytes nfq2/windows/res/winws_res64.o | Bin 0 -> 30830 bytes 35 files changed, 3213 insertions(+), 561 deletions(-) create mode 100644 nfq2/filter.c create mode 100644 nfq2/filter.h create mode 100644 nfq2/windows/netinet/icmp6.h create mode 100644 nfq2/windows/netinet/ip_icmp.h create mode 100644 nfq2/windows/nthacks.h create mode 100644 nfq2/windows/res/howto.txt create mode 100644 nfq2/windows/res/winws.ico create mode 100644 nfq2/windows/res/winws.manifest create mode 100644 nfq2/windows/res/winws.rc create mode 100644 nfq2/windows/res/winws_res32.o create mode 100644 nfq2/windows/res/winws_res64.o diff --git a/nfq2/Makefile b/nfq2/Makefile index 4cc2c8c..3354f04 100644 --- a/nfq2/Makefile +++ b/nfq2/Makefile @@ -10,11 +10,11 @@ LIBS = LIBS_LINUX = -lz -lnetfilter_queue -lnfnetlink -lmnl -lm LIBS_SYSTEMD = -lsystemd LIBS_BSD = -lz -lm -LIBS_CYGWIN = -lz -Lwindows/windivert -Iwindows -lwlanapi -lole32 -loleaut32 +LIBS_CYGWIN = -lz -Lwindows/windivert -Iwindows -lwlanapi -lole32 -loleaut32 -liphlpapi -lntdll LIBS_CYGWIN32 = -lwindivert32 LIBS_CYGWIN64 = -lwindivert64 -RES_CYGWIN32 = windows/res/32/winmanifest.o windows/res/32/winicon.o -RES_CYGWIN64 = windows/res/64/winmanifest.o windows/res/64/winicon.o +RES_CYGWIN32 = windows/res/winws_res32.o +RES_CYGWIN64 = windows/res/winws_res64.o SRC_FILES = *.c crypto/*.c LUA_JIT?=1 diff --git a/nfq2/checksum.c b/nfq2/checksum.c index 3775114..3d8c620 100644 --- a/nfq2/checksum.c +++ b/nfq2/checksum.c @@ -95,10 +95,7 @@ static uint16_t do_csum(const uint8_t *buff, size_t len) return u16; } -uint16_t csum_partial(const void *buff, size_t len) -{ - return do_csum(buff, len); -} +#define csum_partial(buff, len) do_csum((const uint8_t*)buff,len) uint16_t csum_tcpudp_magic(uint32_t saddr, uint32_t daddr, size_t len, uint8_t proto, uint16_t sum) { @@ -107,7 +104,7 @@ uint16_t csum_tcpudp_magic(uint32_t saddr, uint32_t daddr, size_t len, uint8_t p uint16_t ip4_compute_csum(const void *buff, size_t len) { - return ~from64to16(do_csum(buff, len)); + return ~csum_partial(buff, len); } void ip4_fix_checksum(struct ip *ip) { @@ -158,3 +155,21 @@ void udp_fix_checksum(struct udphdr *udp, size_t len, const struct ip *ip, const else if (ip6hdr) udp6_fix_checksum(udp, len, &ip6hdr->ip6_src, &ip6hdr->ip6_dst); } + +void icmp4_fix_checksum(struct icmp46 *icmp, size_t len) +{ + icmp->icmp_cksum = 0; + icmp->icmp_cksum = ~csum_partial(icmp, len); +} +void icmp6_fix_checksum(struct icmp46 *icmp, size_t len, const struct ip6_hdr *ip6hdr) +{ + icmp->icmp_cksum = 0; + icmp->icmp_cksum = csum_ipv6_magic(&ip6hdr->ip6_src, &ip6hdr->ip6_dst, len, IPPROTO_ICMPV6, csum_partial(icmp, len)); +} +void icmp_fix_checksum(struct icmp46 *icmp, size_t len, const struct ip6_hdr *ip6hdr) +{ + if (ip6hdr) + icmp6_fix_checksum(icmp, len, ip6hdr); + else + icmp4_fix_checksum(icmp, len); +} diff --git a/nfq2/checksum.h b/nfq2/checksum.h index c33831e..611927c 100644 --- a/nfq2/checksum.h +++ b/nfq2/checksum.h @@ -11,6 +11,20 @@ #include #include +// icmp 4 and 6 are basically compatible although checksums are calculated differently +// do not use version specific structs +struct icmp46 +{ + uint8_t icmp_type, icmp_code; + uint16_t icmp_cksum; + union + { + uint32_t icmp_data32; + uint16_t icmp_data16[2]; + uint8_t icmp_data8[4]; + }; +}; + uint16_t csum_partial(const void *buff, size_t len); uint16_t csum_tcpudp_magic(uint32_t saddr, uint32_t daddr, size_t len, uint8_t proto, uint16_t sum); uint16_t csum_ipv6_magic(const void *saddr, const void *daddr, size_t len, uint8_t proto, uint16_t sum); @@ -25,3 +39,7 @@ void tcp_fix_checksum(struct tcphdr *tcp,size_t len,const struct ip *ip,const st void udp4_fix_checksum(struct udphdr *udp,size_t len, const struct in_addr *src_addr, const struct in_addr *dest_addr); void udp6_fix_checksum(struct udphdr *udp,size_t len, const struct in6_addr *src_addr, const struct in6_addr *dest_addr); void udp_fix_checksum(struct udphdr *udp,size_t len,const struct ip *ip,const struct ip6_hdr *ip6hdr); + +void icmp4_fix_checksum(struct icmp46 *icmp, size_t len); +void icmp6_fix_checksum(struct icmp46 *icmp, size_t len, const struct ip6_hdr *ip6hdr); +void icmp_fix_checksum(struct icmp46 *icmp, size_t len, const struct ip6_hdr *ip6hdr); diff --git a/nfq2/conntrack.c b/nfq2/conntrack.c index 2ee4c54..88d35b9 100644 --- a/nfq2/conntrack.c +++ b/nfq2/conntrack.c @@ -70,7 +70,7 @@ void ConntrackPoolInit(t_conntrack *p, time_t purge_interval, uint32_t timeout_s p->pool = NULL; } -void ConntrackExtractConn(t_conn *c, bool bReverse, const struct dissect *dis) +bool ConntrackExtractConn(t_conn *c, bool bReverse, const struct dissect *dis) { memset(c, 0, sizeof(*c)); if (dis->ip) @@ -86,8 +86,9 @@ void ConntrackExtractConn(t_conn *c, bool bReverse, const struct dissect *dis) c->src.ip6 = bReverse ? dis->ip6->ip6_dst : dis->ip6->ip6_src; } else - c->l3proto = -1; + return false; extract_ports(dis->tcp, dis->udp, &c->l4proto, bReverse ? &c->dport : &c->sport, bReverse ? &c->sport : &c->dport); + return c->l4proto!=IPPROTO_NONE; } @@ -225,7 +226,7 @@ static bool ConntrackPoolDoubleSearchPool(t_conntrack_pool **pp, const struct di t_conn conn, connswp; t_conntrack_pool *ctr; - ConntrackExtractConn(&conn, false, dis); + if (!ConntrackExtractConn(&conn, false, dis)) return false; if ((ctr = ConntrackPoolSearch(*pp, &conn))) { if (bReverse) *bReverse = false; @@ -256,7 +257,7 @@ static bool ConntrackPoolFeedPool(t_conntrack_pool **pp, const struct dissect *d bool b_rev; uint8_t proto = dis->tcp ? IPPROTO_TCP : dis->udp ? IPPROTO_UDP : IPPROTO_NONE; - ConntrackExtractConn(&conn, false, dis); + if (!ConntrackExtractConn(&conn, false, dis)) return false; if ((ctr = ConntrackPoolSearch(*pp, &conn))) { ConntrackFeedPacket(&ctr->track, (b_rev = false), dis); @@ -296,7 +297,7 @@ static bool ConntrackPoolDropPool(t_conntrack_pool **pp, const struct dissect *d { t_conn conn, connswp; t_conntrack_pool *t; - ConntrackExtractConn(&conn, false, dis); + if (!ConntrackExtractConn(&conn, false, dis)) return false; if (!(t = ConntrackPoolSearch(*pp, &conn))) { connswap(&conn, &connswp); diff --git a/nfq2/conntrack.h b/nfq2/conntrack.h index 01f99af..62467b8 100644 --- a/nfq2/conntrack.h +++ b/nfq2/conntrack.h @@ -105,7 +105,7 @@ bool ConntrackPoolFeed(t_conntrack *p, const struct dissect *dis, t_ctrack **ctr // do not create, do not update. only find existing bool ConntrackPoolDoubleSearch(t_conntrack *p, const struct dissect *dis, t_ctrack **ctrack, bool *bReverse); bool ConntrackPoolDrop(t_conntrack *p, const struct dissect *dis); -void ConntrackExtractConn(t_conn *c, bool bReverse, const struct dissect *dis); +bool ConntrackExtractConn(t_conn *c, bool bReverse, const struct dissect *dis); void ConntrackPoolDump(const t_conntrack *p); void ConntrackPoolPurge(t_conntrack *p); void ConntrackClearHostname(t_ctrack *track); diff --git a/nfq2/crypto/hkdf.c b/nfq2/crypto/hkdf.c index 266cb37..5e16de1 100644 --- a/nfq2/crypto/hkdf.c +++ b/nfq2/crypto/hkdf.c @@ -103,9 +103,6 @@ int hkdfExtract(SHAversion whichSha, salt_len = USHAHashSize(whichSha); memset(nullSalt, '\0', salt_len); } - else if (salt_len < 0) { - return shaBadParam; - } return hmac(whichSha, ikm, ikm_len, salt, salt_len, prk); } @@ -154,11 +151,7 @@ int hkdfExpand(SHAversion whichSha, const uint8_t prk[], size_t prk_len, info = (const unsigned char *)""; info_len = 0; } - else if (info_len < 0) { - return shaBadParam; - } - if (okm_len <= 0) return shaBadParam; - if (!okm) return shaBadParam; + if (!okm || !okm_len) return shaBadParam; hash_len = USHAHashSize(whichSha); if (prk_len < hash_len) return shaBadParam; diff --git a/nfq2/darkmagic.c b/nfq2/darkmagic.c index fa3d56e..e27043a 100644 --- a/nfq2/darkmagic.c +++ b/nfq2/darkmagic.c @@ -33,6 +33,8 @@ #define ERROR_INVALID_IMAGE_HASH __MSABI_LONG(577) #endif +#include "nthacks.h" + #endif #ifdef __linux__ @@ -98,11 +100,11 @@ bool tcp_syn_segment(const struct tcphdr *tcphdr) } -void extract_ports(const struct tcphdr *tcphdr, const struct udphdr *udphdr, uint8_t *proto, uint16_t *sport, uint16_t *dport) +void extract_ports(const struct tcphdr *tcphdr, const struct udphdr *udphdr,uint8_t *proto, uint16_t *sport, uint16_t *dport) { if (sport) *sport = htons(tcphdr ? tcphdr->th_sport : udphdr ? udphdr->uh_sport : 0); if (dport) *dport = htons(tcphdr ? tcphdr->th_dport : udphdr ? udphdr->uh_dport : 0); - if (proto) *proto = tcphdr ? IPPROTO_TCP : udphdr ? IPPROTO_UDP : -1; + if (proto) *proto = tcphdr ? IPPROTO_TCP : udphdr ? IPPROTO_UDP : IPPROTO_NONE; } bool extract_dst(const uint8_t *data, size_t len, struct sockaddr* dst) @@ -174,6 +176,11 @@ void extract_endpoints(const struct ip *ip,const struct ip6_hdr *ip6hdr,const st si->sin6_scope_id = 0; } } + else + { + memset(src,0,sizeof(*src)); + memset(dst,0,sizeof(*dst)); + } } const char *proto_name(uint8_t proto) @@ -192,8 +199,6 @@ const char *proto_name(uint8_t proto) return "igmp"; case IPPROTO_ESP: return "esp"; - case IPPROTO_AH: - return "ah"; case IPPROTO_IPV6: return "6in4"; case IPPROTO_IPIP: @@ -210,7 +215,7 @@ const char *proto_name(uint8_t proto) return NULL; } } -static void str_proto_name(char *s, size_t s_len, uint8_t proto) +void str_proto_name(char *s, size_t s_len, uint8_t proto) { const char *name = proto_name(proto); if (name) @@ -228,6 +233,56 @@ uint16_t family_from_proto(uint8_t l3proto) } } +const char *icmp_type_name(bool v6, uint8_t type) +{ + if (v6) + { + switch(type) + { + case ICMP6_ECHO_REQUEST: return "echo_req6"; + case ICMP6_ECHO_REPLY: return "echo_reply6"; + case ICMP6_DST_UNREACH: return "dest_unreach6"; + case ICMP6_PACKET_TOO_BIG: return "packet_too_big"; + case ICMP6_TIME_EXCEEDED: return "time_exceeded6"; + case ICMP6_PARAM_PROB: return "param_problem6"; + case MLD_LISTENER_QUERY: return "mld_listener_query"; + case MLD_LISTENER_REPORT: return "mld_listener_report"; + case MLD_LISTENER_REDUCTION: return "mld_listener_reduction"; + case ND_ROUTER_SOLICIT: return "router_sol"; + case ND_ROUTER_ADVERT: return "router_adv"; + case ND_NEIGHBOR_SOLICIT: return "neigh_sol"; + case ND_NEIGHBOR_ADVERT: return "neigh_adv"; + case ND_REDIRECT: return "redirect6"; + } + } + else + { + switch(type) + { + case ICMP_ECHOREPLY: return "echo_reply"; + case ICMP_DEST_UNREACH: return "dest_unreach"; + case ICMP_REDIRECT: return "redirect"; + case ICMP_ECHO: return "echo_req"; + case ICMP_TIME_EXCEEDED: return "time_exceeded"; + case ICMP_PARAMETERPROB: return "param_problem"; + case ICMP_TIMESTAMP: return "ts"; + case ICMP_TIMESTAMPREPLY: return "ts_reply"; + case ICMP_INFO_REQUEST: return "info_req"; + case ICMP_INFO_REPLY: return "info_reply"; + } + } + return NULL; +} +void str_icmp_type_name(char *s, size_t s_len, bool v6, uint8_t type) +{ + const char *name = icmp_type_name(v6, type); + if (name) + snprintf(s,s_len,"%s",name); + else + snprintf(s,s_len,"%u",type); +} + + static void str_srcdst_ip(char *s, size_t s_len, const void *saddr,const void *daddr) { char s_ip[16],d_ip[16]; @@ -298,7 +353,21 @@ void print_udphdr(const struct udphdr *udphdr) str_udphdr(s,sizeof(s),udphdr); printf("%s",s); } - +void str_icmphdr(char *s, size_t s_len, bool v6, const struct icmp46 *icmp) +{ + char stype[32]; + str_icmp_type_name(stype,sizeof(stype),v6,icmp->icmp_type); + if (icmp->icmp_type==ICMP_ECHO || icmp->icmp_type==ICMP_ECHOREPLY || icmp->icmp_type==ICMP6_ECHO_REQUEST || icmp->icmp_type==ICMP6_ECHO_REPLY) + snprintf(s,s_len,"icmp_type=%s icmp_code=%u id=0x%04X seq=%u",stype,icmp->icmp_code,ntohs(icmp->icmp_data16[0]),ntohs(icmp->icmp_data16[1])); + else + snprintf(s,s_len,"icmp_type=%s icmp_code=%u data=0x%08X",stype,icmp->icmp_code,ntohl(icmp->icmp_data32)); +} +void print_icmphdr(const struct icmp46 *icmp, bool v6) +{ + char s[48]; + str_icmphdr(s,sizeof(s),v6,icmp); + printf("%s",s); +} @@ -340,7 +409,15 @@ void proto_skip_udp(const uint8_t **data, size_t *len) *data += sizeof(struct udphdr); *len -= sizeof(struct udphdr); } - +bool proto_check_icmp(const uint8_t *data, size_t len) +{ + return len >= sizeof(struct icmp46); +} +void proto_skip_icmp(const uint8_t **data, size_t *len) +{ + *data += sizeof(struct icmp46); + *len -= sizeof(struct icmp46); +} bool proto_check_ipv6(const uint8_t *data, size_t len) { return len >= sizeof(struct ip6_hdr) && (data[0] & 0xF0) == 0x60; @@ -449,7 +526,7 @@ uint8_t *proto_find_ip6_exthdr(struct ip6_hdr *ip6, size_t len, uint8_t proto) return NULL; } -void proto_dissect_l3l4(const uint8_t *data, size_t len, struct dissect *dis) +void proto_dissect_l3l4(const uint8_t *data, size_t len, struct dissect *dis, bool no_payload_check) { const uint8_t *p; @@ -466,7 +543,7 @@ void proto_dissect_l3l4(const uint8_t *data, size_t len, struct dissect *dis) proto_skip_ipv4(&data, &len); dis->len_l3 = data-p; } - else if (proto_check_ipv6(data, len) && proto_check_ipv6_payload(data, len)) + else if (proto_check_ipv6(data, len) && (no_payload_check || proto_check_ipv6_payload(data, len))) { dis->ip6 = (const struct ip6_hdr *) data; p = data; @@ -478,31 +555,36 @@ void proto_dissect_l3l4(const uint8_t *data, size_t len, struct dissect *dis) return; } + dis->transport_len = len; + if (dis->proto==IPPROTO_TCP && proto_check_tcp(data, len)) { dis->tcp = (const struct tcphdr *) data; - dis->transport_len = len; - p = data; proto_skip_tcp(&data, &len); dis->len_l4 = data-p; - - dis->data_payload = data; - dis->len_payload = len; - } - else if (dis->proto==IPPROTO_UDP && proto_check_udp(data, len) && proto_check_udp_payload(data, len)) + else if (dis->proto==IPPROTO_UDP && proto_check_udp(data, len) && (no_payload_check || proto_check_udp_payload(data, len))) { dis->udp = (const struct udphdr *) data; - dis->transport_len = len; - p = data; proto_skip_udp(&data, &len); dis->len_l4 = data-p; - - dis->data_payload = data; - dis->len_payload = len; } + else if ((dis->proto==IPPROTO_ICMP || dis->proto==IPPROTO_ICMPV6) && proto_check_icmp(data, len)) + { + dis->icmp = (const struct icmp46 *) data; + p = data; + proto_skip_icmp(&data, &len); + dis->len_l4 = data-p; + } + else + { + dis->len_l4 = 0; + } + + dis->data_payload = data; + dis->len_payload = len; } void reverse_ip(struct ip *ip, struct ip6_hdr *ip6) @@ -539,12 +621,137 @@ uint8_t ttl46(const struct ip *ip, const struct ip6_hdr *ip6) } +bool get_source_ip4(const struct in_addr *target, struct in_addr *source) +{ + int sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) return false; + + struct sockaddr_in serv,name; + socklen_t namelen; + + memset(&serv,0,sizeof(serv)); // or valgrind complains about uninitialized + serv.sin_family = AF_INET; + serv.sin_addr = *target; + serv.sin_port = 0xFFFF; + + // Connect triggers the kernel's route lookup + if (!connect(sock, (const struct sockaddr*)&serv, sizeof(serv))) + { + namelen = sizeof(name); + if (!getsockname(sock, (struct sockaddr*)&name, &namelen)) + { + close(sock); + *source = name.sin_addr; + return true; + } + } + close(sock); + return false; +} +bool get_source_ip6(const struct in6_addr *target, struct in6_addr *source) +{ + int sock = socket(AF_INET6, SOCK_DGRAM, 0); + if (sock < 0) return false; + + struct sockaddr_in6 serv,name; + socklen_t namelen; + + memset(&serv,0,sizeof(serv)); // or valgrind complains about uninitialized + serv.sin6_family = AF_INET6; + serv.sin6_addr = *target; + serv.sin6_port = 0xFFFF; + + // Connect triggers the kernel's route lookup + if (!connect(sock, (const struct sockaddr*)&serv, sizeof(serv))) + { + namelen = sizeof(name); + if (!getsockname(sock, (struct sockaddr*)&name, &namelen)) + { + close(sock); + *source = name.sin6_addr; + return true; + } + } + + close(sock); + return false; +} #ifdef __CYGWIN__ uint32_t w_win32_error=0; +BOOL AdjustPrivileges(HANDLE hToken, const LPCTSTR *privs, BOOL bEnable) +{ + DWORD dwSize, k, n; + PTOKEN_PRIVILEGES TokenPrivsData; + LUID luid; + + dwSize = 0; + GetTokenInformation(hToken, TokenPrivileges, NULL, 0, &dwSize ); // will fail + w_win32_error = GetLastError(); + if (w_win32_error == ERROR_INSUFFICIENT_BUFFER) + { + w_win32_error = 0; + if (TokenPrivsData = (PTOKEN_PRIVILEGES)LocalAlloc(LMEM_FIXED, dwSize)) + { + if (GetTokenInformation(hToken, TokenPrivileges, TokenPrivsData, dwSize, &dwSize)) + { + n = 0; + while (privs[n]) + { + if (LookupPrivilegeValue(NULL, privs[n], &luid)) + { + w_win32_error = ERROR_PRIVILEGE_NOT_HELD; + for (k = 0; k < TokenPrivsData->PrivilegeCount; k++) + if (!memcmp(&TokenPrivsData->Privileges[k].Luid, &luid, sizeof(LUID))) + { + if (bEnable) + TokenPrivsData->Privileges[k].Attributes |= SE_PRIVILEGE_ENABLED; + else + TokenPrivsData->Privileges[k].Attributes &= ~SE_PRIVILEGE_ENABLED; + w_win32_error = 0; + break; + } + } + else + w_win32_error = GetLastError(); + if (w_win32_error) break; + n++; + } + if (!w_win32_error) + { + if (!AdjustTokenPrivileges(hToken, FALSE, TokenPrivsData, 0, NULL, NULL)) + w_win32_error = GetLastError(); + } + } + else + w_win32_error = GetLastError(); + LocalFree(TokenPrivsData); + } + else + w_win32_error = GetLastError(); + } + return !w_win32_error; +} +BOOL AdjustPrivilegesForCurrentProcess(const LPCTSTR *privs, BOOL bEnable) +{ + HANDLE hTokenThisProcess; + + w_win32_error = 0; + if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hTokenThisProcess)) + { + if (!AdjustPrivileges(hTokenThisProcess, privs, bEnable)) + w_win32_error = GetLastError(); + CloseHandle(hTokenThisProcess); + } + else + w_win32_error = GetLastError(); + + return !w_win32_error; +} + static BOOL RemoveTokenPrivs(void) { BOOL bRes = FALSE; @@ -670,6 +877,26 @@ err: if (!bRes) w_win32_error = GetLastError(); return bRes; } +BOOL SetMandatoryLabelObject(HANDLE h, SE_OBJECT_TYPE ObjType, DWORD dwMandatoryLabelRID, DWORD dwAceRevision, DWORD dwAceFlags) +{ + BOOL bRes = FALSE; + DWORD dwErr; + char buf_label[16], buf_pacl[32]; + PSID label = (PSID)buf_label; + PACL pacl = (PACL)buf_pacl; + + InitializeSid(label, &label_authority, 1); + *GetSidSubAuthority(label, 0) = dwMandatoryLabelRID; + if (InitializeAcl(pacl, sizeof(buf_pacl), ACL_REVISION) && AddMandatoryAce(pacl, dwAceRevision, dwAceFlags, SYSTEM_MANDATORY_LABEL_NO_WRITE_UP, label)) + { + dwErr = SetSecurityInfo(h, ObjType, LABEL_SECURITY_INFORMATION, NULL, NULL, NULL, pacl); + SetLastError(dwErr); + bRes = dwErr == ERROR_SUCCESS; + } + if (!bRes) w_win32_error = GetLastError(); + return bRes; +} + bool ensure_file_access(const char *filename) { @@ -703,6 +930,85 @@ bool prepare_low_appdata() return b; } +// cygwin uses nt directory to store it's state. low mandatory breaks write access there and cause some functions to fail +// it's not possible to reproduce exact directory names, have to iterate and set low integrity to all +BOOL RelaxCygwinNTDir() +{ + NTSTATUS status; + PROCESS_SESSION_INFORMATION psi; + WCHAR bno_name[32], cyg_name[256]; + CHAR scyg_name[256]; + UNICODE_STRING bno_us, cyg_us; + OBJECT_ATTRIBUTES attr; + HANDLE hDir, hDirCyg; + BYTE buf[4096]; + ULONG context, rsize; + BOOL b,ball,restart; + POBJECT_DIRECTORY_INFORMATION pdir; + + LPCTSTR Privs[] = { SE_TAKE_OWNERSHIP_NAME , NULL }; + + if (!AdjustPrivilegesForCurrentProcess(Privs, TRUE)) + return FALSE; + + status = NtQueryInformationProcess(GetCurrentProcess(), ProcessSessionInformation, &psi, sizeof psi, NULL); + if (NT_SUCCESS(status)) + swprintf(bno_name, sizeof(bno_name)/sizeof(*bno_name), L"\\Sessions\\BNOLINKS\\%u", psi.SessionId); + else + swprintf(bno_name, sizeof(bno_name)/sizeof(*bno_name), L"\\BaseNamedObjects"); + + RtlInitUnicodeString(&bno_us, bno_name); + InitializeObjectAttributes(&attr, &bno_us, 0, NULL, NULL); + + ball = TRUE; + w_win32_error = 0; + status = NtOpenDirectoryObject(&hDir, DIRECTORY_QUERY, &attr); + if (NT_SUCCESS(status)) + { + context = 0; + restart = TRUE; + while (NT_SUCCESS(status = NtQueryDirectoryObject(hDir, buf, sizeof(buf), restart, FALSE, &context, &rsize))) + { + + for (pdir = (POBJECT_DIRECTORY_INFORMATION)buf; pdir->Name.Length; pdir++) + if (pdir->TypeName.Length == 18 && !memcmp(pdir->TypeName.Buffer, L"Directory", 18) && + pdir->Name.Length >= 14 && !memcmp(pdir->Name.Buffer, L"cygwin1", 14)) + { + swprintf(cyg_name, sizeof(cyg_name)/sizeof(*cyg_name), L"%ls\\%ls", bno_name, pdir->Name.Buffer); + if (!WideCharToMultiByte(CP_ACP, 0, cyg_name, -1, scyg_name, sizeof(scyg_name), NULL, NULL)) + *scyg_name=0; + RtlInitUnicodeString(&cyg_us, cyg_name); + InitializeObjectAttributes(&attr, &cyg_us, 0, NULL, NULL); + status = NtOpenDirectoryObject(&hDirCyg, WRITE_OWNER, &attr); + if (NT_SUCCESS(status)) + { + b = SetMandatoryLabelObject(hDirCyg, SE_KERNEL_OBJECT, SECURITY_MANDATORY_LOW_RID, ACL_REVISION_DS, 0); + if (!b) + { + w_win32_error = GetLastError(); + DLOG_ERR("could not set integrity label on '%s' . error %u\n", scyg_name, w_win32_error); + } + else + DLOG("set low integrity label on '%s'\n", scyg_name); + ball = ball && b; + NtClose(hDirCyg); + } + } + restart = FALSE; + } + NtClose(hDir); + } + else + { + w_win32_error = RtlNtStatusToDosError(status); + return FALSE; + } + + return ball; +} + + + BOOL JobSandbox() { BOOL bRes = FALSE; @@ -734,6 +1040,9 @@ bool win_sandbox(void) // there's no way to return privs if (!b_sandbox_set) { + if (!RelaxCygwinNTDir()) + DLOG_ERR("could not set low mandatory label on cygwin NT directory. some functions may not work. error %u\n", w_win32_error); + if (!RemoveTokenPrivs()) return FALSE; @@ -1961,6 +2270,23 @@ void verdict_udp_csum_fix(uint8_t verdict, struct udphdr *udphdr, size_t transpo #endif } +void verdict_icmp_csum_fix(uint8_t verdict, struct icmp46 *icmphdr, size_t transport_len, const struct ip6_hdr *ip6hdr) +{ + // always fix csum for windivert. original can be partial or bad + // FreeBSD tend to pass ipv6 frames with wrong checksum (OBSERVED EARLIER, MAY BE FIXED NOW) + // Linux passes correct checksums +#ifndef __linux__ + if (!(verdict & VERDICT_NOCSUM) && (verdict & VERDICT_MASK)==VERDICT_PASS) + { + #ifdef __FreeBSD__ + if (ip6hdr) + #endif + DLOG("fixing icmp checksum\n"); + icmp_fix_checksum(icmphdr,transport_len,ip6hdr); + } +#endif +} + void dbgprint_socket_buffers(int fd) { if (params.debug) diff --git a/nfq2/darkmagic.h b/nfq2/darkmagic.h index 5a12b42..cba8604 100644 --- a/nfq2/darkmagic.h +++ b/nfq2/darkmagic.h @@ -25,6 +25,11 @@ #ifdef __CYGWIN__ #define INITGUID #include "windivert/windivert.h" +#include "netinet/icmp6.h" +#include "netinet/ip_icmp.h" +#else +#include +#include #endif #ifndef IPPROTO_DIVERT @@ -59,6 +64,31 @@ #define IPPROTO_SHIM6 140 #endif +#ifndef ICMP_DEST_UNREACH +#define ICMP_DEST_UNREACH 3 +#endif +#ifndef ICMP_TIME_EXCEEDED +#define ICMP_TIME_EXCEEDED 11 +#endif +#ifndef ICMP_PARAMETERPROB +#define ICMP_PARAMETERPROB 12 +#endif +#ifndef ICMP_TIMESTAMP +#define ICMP_TIMESTAMP 13 +#endif +#ifndef ICMP_TIMESTAMPREPLY +#define ICMP_TIMESTAMPREPLY 14 +#endif +#ifndef ICMP_INFO_REQUEST +#define ICMP_INFO_REQUEST 15 +#endif +#ifndef ICMP_INFO_REPLY +#define ICMP_INFO_REPLY 16 +#endif +#ifndef MLD_LISTENER_REDUCTION +#define MLD_LISTENER_REDUCTION 132 +#endif + // returns netorder value uint32_t net32_add(uint32_t netorder_value, uint32_t cpuorder_increment); uint32_t net16_add(uint16_t netorder_value, uint16_t cpuorder_increment); @@ -85,6 +115,7 @@ uint16_t tcp_find_mss(const struct tcphdr *tcp); bool tcp_synack_segment(const struct tcphdr *tcphdr); bool tcp_syn_segment(const struct tcphdr *tcphdr); + bool make_writeable_dir(); bool ensure_file_access(const char *filename); #ifdef __CYGWIN__ @@ -121,16 +152,21 @@ int socket_divert(sa_family_t family); #endif const char *proto_name(uint8_t proto); +void str_proto_name(char *s, size_t s_len, uint8_t proto); +const char *icmp_type_name(bool v6, uint8_t type); +void str_icmp_type_name(char *s, size_t s_len, bool v6, uint8_t type); uint16_t family_from_proto(uint8_t l3proto); void print_ip(const struct ip *ip); void print_ip6hdr(const struct ip6_hdr *ip6hdr, uint8_t proto); void print_tcphdr(const struct tcphdr *tcphdr); void print_udphdr(const struct udphdr *udphdr); +void print_icmphdr(const struct icmp46 *icmp, bool v6); void str_ip(char *s, size_t s_len, const struct ip *ip); void str_ip6hdr(char *s, size_t s_len, const struct ip6_hdr *ip6hdr, uint8_t proto); void str_srcdst_ip6(char *s, size_t s_len, const void *saddr,const void *daddr); void str_tcphdr(char *s, size_t s_len, const struct tcphdr *tcphdr); void str_udphdr(char *s, size_t s_len, const struct udphdr *udphdr); +void str_icmphdr(char *s, size_t s_len, bool v6, const struct icmp46 *icmp); bool proto_check_ipv4(const uint8_t *data, size_t len); void proto_skip_ipv4(const uint8_t **data, size_t *len); @@ -143,6 +179,9 @@ void proto_skip_tcp(const uint8_t **data, size_t *len); bool proto_check_udp(const uint8_t *data, size_t len); bool proto_check_udp_payload(const uint8_t *data, size_t len); void proto_skip_udp(const uint8_t **data, size_t *len); +bool proto_check_icmp(const uint8_t *data, size_t len); +void proto_skip_icmp(const uint8_t **data, size_t *len); + struct dissect { const uint8_t *data_pkt; @@ -153,19 +192,24 @@ struct dissect uint8_t proto; const struct tcphdr *tcp; const struct udphdr *udp; + const struct icmp46 *icmp; size_t len_l4; size_t transport_len; const uint8_t *data_payload; size_t len_payload; }; -void proto_dissect_l3l4(const uint8_t *data, size_t len, struct dissect *dis); +void proto_dissect_l3l4(const uint8_t *data, size_t len, struct dissect *dis, bool no_payload_check); void reverse_ip(struct ip *ip, struct ip6_hdr *ip6); void reverse_tcp(struct tcphdr *tcp); uint8_t ttl46(const struct ip *ip, const struct ip6_hdr *ip6); +bool get_source_ip4(const struct in_addr *target, struct in_addr *source); +bool get_source_ip6(const struct in6_addr *target, struct in6_addr *source); + void verdict_tcp_csum_fix(uint8_t verdict, struct tcphdr *tcphdr, size_t transport_len, const struct ip *ip, const struct ip6_hdr *ip6hdr); void verdict_udp_csum_fix(uint8_t verdict, struct udphdr *udphdr, size_t transport_len, const struct ip *ip, const struct ip6_hdr *ip6hdr); +void verdict_icmp_csum_fix(uint8_t verdict, struct icmp46 *icmphdr, size_t transport_len, const struct ip6_hdr *ip6hdr); void dbgprint_socket_buffers(int fd); bool set_socket_buffers(int fd, int rcvbuf, int sndbuf); diff --git a/nfq2/desync.c b/nfq2/desync.c index 7914b0e..21c300c 100644 --- a/nfq2/desync.c +++ b/nfq2/desync.c @@ -132,11 +132,79 @@ static void TLSDebug(const uint8_t *tls, size_t sz) TLSDebugHandshake(tls + 5, sz - 5); } +static void packet_debug(bool replay, const struct dissect *dis) +{ + if (params.debug) + { + if (replay) DLOG("REPLAY "); + if (dis->ip) + { + char s[66]; + str_ip(s, sizeof(s), dis->ip); + DLOG("IP4: %s", s); + } + else if (dis->ip6) + { + char s[128]; + str_ip6hdr(s, sizeof(s), dis->ip6, dis->proto); + DLOG("IP6: %s", s); + } + if (dis->tcp) + { + char s[80]; + str_tcphdr(s, sizeof(s), dis->tcp); + DLOG(" %s\n", s); + if (dis->len_payload) + { + DLOG("TCP: len=%zu : ", dis->len_payload); + hexdump_limited_dlog(dis->data_payload, dis->len_payload, PKTDATA_MAXDUMP); + DLOG("\n"); + } + } + else if (dis->udp) + { + char s[30]; + str_udphdr(s, sizeof(s), dis->udp); + DLOG(" %s\n", s); + if (dis->len_payload) + { + DLOG("UDP: len=%zu : ", dis->len_payload); + hexdump_limited_dlog(dis->data_payload, dis->len_payload, PKTDATA_MAXDUMP); + DLOG("\n"); + } + } + else if (dis->icmp) + { + char s[72]; + str_icmphdr(s, sizeof(s), !!dis->ip6, dis->icmp); + DLOG(" %s\nICMP: len=%zu : ", s, dis->len_payload); + hexdump_limited_dlog(dis->data_payload, dis->len_payload, PKTDATA_MAXDUMP); + DLOG("\n"); + } + else + { + if (dis->len_payload) + { + char s_proto[16]; + str_proto_name(s_proto,sizeof(s_proto),dis->proto); + DLOG("\nIP PROTO %s: len=%zu : ", s_proto, dis->len_payload); + hexdump_limited_dlog(dis->data_payload, dis->len_payload, PKTDATA_MAXDUMP); + DLOG("\n"); + } + else + DLOG("\n"); + } + } +} + +// ipr,ipr6 - reverse ip - ip of the other side of communication static bool dp_match( struct desync_profile *dp, uint8_t l3proto, - const struct in_addr *ip, const struct in6_addr *ip6, uint16_t port, + const struct in_addr *ip, const struct in6_addr *ip6, + const struct in_addr *ipr, const struct in6_addr *ipr6, + uint16_t port, uint8_t icmp_type, uint8_t icmp_code, const char *hostname, bool bNoSubdom, t_l7proto l7proto, const char *ssid, bool *bCheckDone, bool *bCheckResult, bool *bExcluded) { @@ -150,8 +218,23 @@ static bool dp_match( // L3 filter does not match return false; - if ((l3proto == IPPROTO_TCP && !port_filters_in_range(&dp->pf_tcp, port)) || (l3proto == IPPROTO_UDP && !port_filters_in_range(&dp->pf_udp, port))) - // L4 filter does not match + switch(l3proto) + { + case IPPROTO_TCP: + if (!port_filters_match(&dp->pf_tcp, port)) return false; + break; + case IPPROTO_UDP: + if (!port_filters_match(&dp->pf_udp, port)) return false; + break; + case IPPROTO_ICMP: + if (!icmp_filters_match(&dp->icf, icmp_type, icmp_code)) return false; + break; + default: + if (!ipp_filters_match(&dp->ipf, l3proto)) return false; + } + + if (l3proto == IPPROTO_ICMP && !icmp_filters_match(&dp->icf, icmp_type, icmp_code)) + // icmp filter does not match return false; if (!l7_proto_match(l7proto, dp->filter_l7)) @@ -166,7 +249,7 @@ static bool dp_match( if (!dp->hostlist_auto && !hostname && !bHostlistsEmpty) // avoid cpu consuming ipset check. profile cannot win if regular hostlists are present without auto hostlist and hostname is unknown. return false; - if (!IpsetCheck(dp, ip, ip6)) + if (!IpsetCheck(dp, ip, ip6, ipr, ipr6)) // target ip does not match return false; @@ -197,7 +280,9 @@ static bool dp_match( static struct desync_profile *dp_find( struct desync_profile_list_head *head, uint8_t l3proto, - const struct in_addr *ip, const struct in6_addr *ip6, uint16_t port, + const struct in_addr *ip, const struct in6_addr *ip6, + const struct in_addr *ipr, const struct in6_addr *ipr6, + uint16_t port, uint8_t icmp_type, uint8_t icmp_code, const char *hostname, bool bNoSubdom, t_l7proto l7proto, const char *ssid, bool *bCheckDone, bool *bCheckResult, bool *bExcluded) { @@ -206,12 +291,21 @@ static struct desync_profile *dp_find( { char s[40]; ntopa46(ip, ip6, s, sizeof(s)); - DLOG("desync profile search for %s ip=%s port=%u l7proto=%s ssid='%s' hostname='%s'\n", proto_name(l3proto), s, port, l7proto_str(l7proto), ssid ? ssid : "", hostname ? hostname : ""); + if (ipr || ipr6) + { + char sr[40]; + ntopa46(ipr, ipr6, sr, sizeof(sr)); + DLOG("desync profile search for %s ip1=%s ip2=%s port=%u icmp=%u:%u l7proto=%s ssid='%s' hostname='%s'\n", + proto_name(l3proto), s, sr, port, icmp_type, icmp_code, l7proto_str(l7proto), ssid ? ssid : "", hostname ? hostname : ""); + } + else + DLOG("desync profile search for %s ip=%s port=%u icmp=%u:%u l7proto=%s ssid='%s' hostname='%s'\n", + proto_name(l3proto), s, port, icmp_type, icmp_code, l7proto_str(l7proto), ssid ? ssid : "", hostname ? hostname : ""); } if (bCheckDone) *bCheckDone = false; LIST_FOREACH(dpl, head, next) { - if (dp_match(&dpl->dp, l3proto, ip, ip6, port, hostname, bNoSubdom, l7proto, ssid, bCheckDone, bCheckResult, bExcluded)) + if (dp_match(&dpl->dp, l3proto, ip, ip6, ipr, ipr6, port, icmp_type, icmp_code, hostname, bNoSubdom, l7proto, ssid, bCheckDone, bCheckResult, bExcluded)) { DLOG("desync profile %u (%s) matches\n", dpl->dp.n, PROFILE_NAME(&dpl->dp)); return &dpl->dp; @@ -455,8 +549,8 @@ static bool reasm_start(t_ctrack *ctrack, t_reassemble *reasm, uint8_t proto, ui static bool reasm_client_start(t_ctrack *ctrack, uint8_t proto, size_t sz, size_t szMax, const uint8_t *data_payload, size_t len_payload) { if (!ctrack) return false; - // if winsize_calc==0 it means we dont know server window size - no incoming packets redirected ? - if (proto==IPPROTO_TCP && ctrack->pos.server.winsize_calc && (ctrack->pos.server.winsize_calc < sz)) + // if pcounter==0 it means we dont know server window size - no incoming packets redirected ? + if (proto==IPPROTO_TCP && ctrack->pos.server.pcounter && (ctrack->pos.server.winsize_calc < sz)) { // this is rare but possible situation // server gave us too small tcp window @@ -640,6 +734,12 @@ static bool ipcache_get_hostname(const struct in_addr *a4, const struct in6_addr *hostname = 0; return true; } + if (params.debug) + { + char s[40]; + ntopa46(a4, a6, s, sizeof(s)); + DLOG("ipcache hostname search for %s\n", s); + } ip_cache_item *ipc = ipcacheTouch(¶ms.ipcache, a4, a6, NULL); if (!ipc) { @@ -648,7 +748,12 @@ static bool ipcache_get_hostname(const struct in_addr *a4, const struct in6_addr } if (ipc->hostname) { - DLOG("got cached hostname (is_ip=%u): %s\n", ipc->hostname_is_ip, ipc->hostname); + if (params.debug) + { + char s[40]; + ntopa46(a4, a6, s, sizeof(s)); + DLOG("got cached hostname for %s : %s (is_ip=%u)\n", s, ipc->hostname, ipc->hostname_is_ip); + } snprintf(hostname, hostname_buf_len, "%s", ipc->hostname); if (hostname_is_ip) *hostname_is_ip = ipc->hostname_is_ip; } @@ -968,7 +1073,7 @@ static uint8_t desync( } else { - b = lua_reconstruct_dissect(params.L, -1, mod_pkt, len_mod_pkt, false, false); + b = lua_reconstruct_dissect(params.L, -1, mod_pkt, len_mod_pkt, false, false, false); lua_pop(params.L, 2); if (!b) { @@ -1116,7 +1221,7 @@ static bool play_prolog( DLOG("using cached desync profile %u (%s)\n", ps->dp->n, PROFILE_NAME(ps->dp)); else if (!ps->ctrack_replay->dp_search_complete) { - ps->dp = ps->ctrack_replay->dp = dp_find(¶ms.desync_profiles, dis->proto, ps->sdip4, ps->sdip6, ps->sdport, ps->ctrack_replay->hostname, ps->ctrack_replay->hostname_is_ip, ps->l7proto, ps->ssid, NULL, NULL, NULL); + ps->dp = ps->ctrack_replay->dp = dp_find(¶ms.desync_profiles, dis->proto, ps->sdip4, ps->sdip6, NULL, NULL, ps->sdport, 0xFF, 0xFF, ps->ctrack_replay->hostname, ps->ctrack_replay->hostname_is_ip, ps->l7proto, ps->ssid, NULL, NULL, NULL); ps->ctrack_replay->dp_search_complete = true; } if (!ps->dp) @@ -1164,7 +1269,7 @@ static bool play_prolog( DLOG_ERR("strdup(host): out of memory\n"); } } - ps->dp = dp_find(¶ms.desync_profiles, dis->proto, ps->sdip4, ps->sdip6, ps->sdport, hostname, hostname_is_ip, ps->l7proto, ps->ssid, NULL, NULL, NULL); + ps->dp = dp_find(¶ms.desync_profiles, dis->proto, ps->sdip4, ps->sdip6, NULL, NULL, ps->sdport, 0xFF, 0xFF, hostname, hostname_is_ip, ps->l7proto, ps->ssid, NULL, NULL, NULL); if (ps->ctrack) { ps->ctrack->dp = ps->dp; @@ -1249,7 +1354,7 @@ static bool dp_rediscovery(struct play_state *ps) { struct desync_profile *dp_prev = ps->dp; // search for desync profile again. it may have changed. - ps->dp = dp_find(¶ms.desync_profiles, ps->dis->proto, ps->sdip4, ps->sdip6, ps->sdport, + ps->dp = dp_find(¶ms.desync_profiles, ps->dis->proto, ps->sdip4, ps->sdip6, NULL, NULL, ps->sdport, 0xFF, 0xFF, ps->ctrack_replay ? ps->ctrack_replay->hostname : ps->bHaveHost ? ps->host : NULL, ps->ctrack_replay ? ps->ctrack_replay->hostname_is_ip : bHostIsIp, ps->l7proto, ps->ssid, @@ -1782,41 +1887,200 @@ pass_reasm_cancel: goto pass; } - -static void packet_debug(bool replay, const struct dissect *dis) +// conntrack is supported only for RELATED icmp +// ip matched in both directions if conntrack is unavailable +static uint8_t dpi_desync_icmp_packet( + uint32_t fwmark, + const char *ifin, const char *ifout, + const struct dissect *dis, + uint8_t *mod_pkt, size_t *len_mod_pkt) { - if (params.debug) + uint8_t verdict = VERDICT_PASS; + + // additional safety check + if (!!dis->ip == !!dis->ip6) return verdict; + + const uint8_t *pkt_attached; + size_t len_attached; + const char *ifname; + struct sockaddr_storage src, dst; + const char *ssid = NULL; + struct desync_profile *dp = NULL; + t_l7payload l7payload = L7P_UNKNOWN; + t_ctrack *ctrack = NULL; + bool bReverse, bReverseFixed; + + extract_endpoints(dis->ip, dis->ip6, NULL, NULL, &src, &dst); + + switch(dis->icmp->icmp_type) { - if (replay) DLOG("REPLAY "); - if (dis->ip) - { - char s[66]; - str_ip(s, sizeof(s), dis->ip); - DLOG("IP4: %s", s); - } - else if (dis->ip6) - { - char s[128]; - str_ip6hdr(s, sizeof(s), dis->ip6, dis->proto); - DLOG("IP6: %s", s); - } - if (dis->tcp) - { - char s[80]; - str_tcphdr(s, sizeof(s), dis->tcp); - DLOG(" %s\n", s); - if (dis->len_payload) { DLOG("TCP: len=%zu : ", dis->len_payload); hexdump_limited_dlog(dis->data_payload, dis->len_payload, PKTDATA_MAXDUMP); DLOG("\n"); } - } - else if (dis->udp) - { - char s[30]; - str_udphdr(s, sizeof(s), dis->udp); - DLOG(" %s\n", s); - if (dis->len_payload) { DLOG("UDP: len=%zu : ", dis->len_payload); hexdump_limited_dlog(dis->data_payload, dis->len_payload, PKTDATA_MAXDUMP); DLOG("\n"); } - } - else - DLOG("\n"); + case ICMP_DEST_UNREACH: + case ICMP_TIME_EXCEEDED: + case ICMP_PARAMETERPROB: + case ICMP_REDIRECT: + case ICMP6_DST_UNREACH: + //case ICMP6_TIME_EXCEEDED: // same as ICMP_TIME_EXCEEDED = 3 + case ICMP6_PACKET_TOO_BIG: + case ICMP6_PARAM_PROB: + pkt_attached = dis->data_payload; + break; + default: + pkt_attached = NULL; } + if (pkt_attached) + { + struct dissect adis; + + len_attached = pkt_attached - dis->data_payload + dis->len_payload; + proto_dissect_l3l4(pkt_attached, len_attached, &adis, true); // dissect without payload length checks - can be partial + if (!dis->ip && !dis->ip6) + DLOG("attached packet is invalid\n"); + else + { + l7payload = dis->ip ? L7P_IPV4 : L7P_IPV6; + DLOG("attached packet\n"); + packet_debug(false, &adis); + if (ConntrackPoolDoubleSearch(¶ms.conntrack, &adis, &ctrack, &bReverse)) + { + // invert direction. they are answering to this packet + bReverse = !bReverse; + DLOG("found conntrack entry. inverted reverse=%u\n",bReverse); + if (ctrack->dp_search_complete) + { + // RELATED icmp processed within base connection profile + dp = ctrack->dp; + DLOG("using desync profile %u (%s) from conntrack entry\n", dp->n, PROFILE_NAME(dp)); + } + } + else + DLOG("conntrack entry not found\n"); + } + } + + bReverseFixed = ctrack ? (bReverse ^ params.server) : (bReverse = ifin && *ifin && (!ifout || !*ifout)); + +#ifdef HAS_FILTER_SSID + ifname = bReverse ? ifin : ifout; + if ((ssid = wlan_ssid_search_ifname(ifname))) + DLOG("found ssid for %s : %s\n", ifname, ssid); + else if (!ctrack) + { + // we dont know direction for sure + // search opposite interface + ifname = bReverse ? ifout : ifin; + if ((ssid = wlan_ssid_search_ifname(ifname))) + DLOG("found ssid for %s : %s\n", ifname, ssid); + } +#endif + if (!dp) + { + bool hostname_is_ip = false; + char host[256]; + const char *hostname = NULL; + if (ctrack && ctrack->hostname) + { +printf("ZZZZZz4 %p\n",ctrack->hostname); + hostname = ctrack->hostname; + hostname_is_ip = ctrack->hostname_is_ip; + } + else if (ipcache_get_hostname(dis->ip ? &dis->ip->ip_dst : NULL, dis->ip6 ? &dis->ip6->ip6_dst : NULL, host, sizeof(host), &hostname_is_ip) && *host || + ipcache_get_hostname(dis->ip ? &dis->ip->ip_src : NULL, dis->ip6 ? &dis->ip6->ip6_src : NULL, host, sizeof(host), &hostname_is_ip) && *host) + { + hostname = host; + } + + dp = dp_find( + ¶ms.desync_profiles, + dis->proto, + dis->ip ? &dis->ip->ip_dst : NULL, dis->ip6 ? &dis->ip6->ip6_dst : NULL, + dis->ip ? &dis->ip->ip_src : NULL, dis->ip6 ? &dis->ip6->ip6_src : NULL, + 0, dis->icmp->icmp_type, dis->icmp->icmp_code, + hostname, hostname_is_ip, + L7_UNKNOWN, ssid, NULL, NULL, NULL); + if (!dp) + { + DLOG("matching desync profile not found\n"); + return verdict; + } + } + + const struct in_addr *sdip4; + const struct in6_addr *sdip6; + sdip6 = dis->ip6 ? bReverseFixed ? &dis->ip6->ip6_src : &dis->ip6->ip6_dst : NULL; + sdip4 = dis->ip ? bReverseFixed ? &dis->ip->ip_src : &dis->ip->ip_dst : NULL; + + verdict = desync( + dp, fwmark, ifin, ifout, bReverseFixed, ctrack, NULL, l7payload, L7_UNKNOWN, + dis, sdip4, sdip6, 0, mod_pkt, len_mod_pkt, 0, 0, 0, NULL, 0, NULL, 0); + + return verdict; +} + +// undissected l4+ +// conntrack is unsupported +// ip matched in both directions +static uint8_t dpi_desync_ip_packet( + uint32_t fwmark, + const char *ifin, const char *ifout, + const struct dissect *dis, + uint8_t *mod_pkt, size_t *len_mod_pkt) +{ + uint8_t verdict = VERDICT_PASS; + + // additional safety check + if (!!dis->ip == !!dis->ip6) return verdict; + + struct sockaddr_storage src, dst; + const char *ssid; + struct desync_profile *dp; + + extract_endpoints(dis->ip, dis->ip6, NULL, NULL, &src, &dst); +#ifdef HAS_FILTER_SSID + if ((ssid = wlan_ssid_search_ifname(ifin))) + DLOG("found ssid for %s : %s\n", ifin, ssid); + else + { + // we dont know direction for sure + // search opposite interface + if ((ssid = wlan_ssid_search_ifname(ifout))) + DLOG("found ssid for %s : %s\n", ifout, ssid); + } +#endif + + bool hostname_is_ip = false; + const char *hostname = NULL; + char host[256]; + if (ipcache_get_hostname(dis->ip ? &dis->ip->ip_dst : NULL, dis->ip6 ? &dis->ip6->ip6_dst : NULL, host, sizeof(host), &hostname_is_ip) && *host || + ipcache_get_hostname(dis->ip ? &dis->ip->ip_src : NULL, dis->ip6 ? &dis->ip6->ip6_src : NULL, host, sizeof(host), &hostname_is_ip) && *host) + { + hostname = host; + } + dp = dp_find( + ¶ms.desync_profiles, + dis->proto, + dis->ip ? &dis->ip->ip_dst : NULL, dis->ip6 ? &dis->ip6->ip6_dst : NULL, + dis->ip ? &dis->ip->ip_src : NULL, dis->ip6 ? &dis->ip6->ip6_src : NULL, + 0, 0xFF, 0xFF, + hostname, hostname_is_ip, + L7_UNKNOWN, ssid, NULL, NULL, NULL); + if (!dp) + { + DLOG("matching desync profile not found\n"); + return verdict; + } + + bool bReverse = ifin && *ifin && (!ifout || !*ifout); + + const struct in_addr *sdip4; + const struct in6_addr *sdip6; + sdip6 = dis->ip6 ? bReverse ? &dis->ip6->ip6_src : &dis->ip6->ip6_dst : NULL; + sdip4 = dis->ip ? bReverse ? &dis->ip->ip_src : &dis->ip->ip_dst : NULL; + + verdict = desync( + dp, fwmark, ifin, ifout, bReverse, NULL, NULL, L7P_UNKNOWN, L7_UNKNOWN, + dis, sdip4, sdip6, 0, mod_pkt, len_mod_pkt, 0, 0, 0, NULL, 0, NULL, 0); + + return verdict; } @@ -1831,19 +2095,19 @@ static uint8_t dpi_desync_packet_play( // NOTE ! OS can pass wrong checksum to queue. cannot rely on it ! - proto_dissect_l3l4(data_pkt, len_pkt, &dis); + proto_dissect_l3l4(data_pkt, len_pkt, &dis, false); if (!!dis.ip != !!dis.ip6) { packet_debug(!!replay_piece_count, &dis); + // fix csum if unmodified and if OS can pass wrong csum to queue (depends on OS) + // modified means we have already fixed the checksum or made it invalid intentionally + // this is the only point we VIOLATE const to fix the checksum in the original buffer to avoid copying to mod_pkt switch (dis.proto) { case IPPROTO_TCP: if (dis.tcp) { verdict = dpi_desync_tcp_packet_play(replay_piece, replay_piece_count, reasm_offset, fwmark, ifin, ifout, tpos, &dis, mod_pkt, len_mod_pkt); - // fix csum if unmodified and if OS can pass wrong csum to queue (depends on OS) - // modified means we have already fixed the checksum or made it invalid intentionally - // this is the only point we VIOLATE const to fix the checksum in the original buffer to avoid copying to mod_pkt verdict_tcp_csum_fix(verdict, (struct tcphdr *)dis.tcp, dis.transport_len, dis.ip, dis.ip6); } break; @@ -1851,12 +2115,19 @@ static uint8_t dpi_desync_packet_play( if (dis.udp) { verdict = dpi_desync_udp_packet_play(replay_piece, replay_piece_count, reasm_offset, fwmark, ifin, ifout, tpos, &dis, mod_pkt, len_mod_pkt); - // fix csum if unmodified and if OS can pass wrong csum to queue (depends on OS) - // modified means we have already fixed the checksum or made it invalid intentionally - // this is the only point we VIOLATE const to fix the checksum in the original buffer to avoid copying to mod_pkt verdict_udp_csum_fix(verdict, (struct udphdr *)dis.udp, dis.transport_len, dis.ip, dis.ip6); } break; + case IPPROTO_ICMP: + case IPPROTO_ICMPV6: + if (dis.icmp) + { + verdict = dpi_desync_icmp_packet(fwmark, ifin, ifout, &dis, mod_pkt, len_mod_pkt); + verdict_icmp_csum_fix(verdict, (struct icmp46 *)dis.icmp, dis.transport_len, dis.ip6); + } + break; + default: + verdict = dpi_desync_ip_packet(fwmark, ifin, ifout, &dis, mod_pkt, len_mod_pkt); } } return verdict; diff --git a/nfq2/filter.c b/nfq2/filter.c new file mode 100644 index 0000000..8a08d3d --- /dev/null +++ b/nfq2/filter.c @@ -0,0 +1,236 @@ +#include "filter.h" +#include +#include +#include +#include +#include + +bool pf_match(uint16_t port, const port_filter *pf) +{ + return port && (((!pf->from && !pf->to) || (port>=pf->from && port<=pf->to)) ^ pf->neg); +} +bool pf_parse(const char *s, port_filter *pf) +{ + unsigned int v1,v2; + char c; + + if (!s) return false; + if (*s=='*' && s[1]==0) + { + pf->from=1; pf->to=0xFFFF; + return true; + } + if (*s=='~') + { + pf->neg=true; + s++; + } + else + pf->neg=false; + if (sscanf(s,"%u-%u%c",&v1,&v2,&c)==2) + { + if (v1>65535 || v2>65535 || v1>v2) return false; + pf->from=(uint16_t)v1; + pf->to=(uint16_t)v2; + } + else if (sscanf(s,"%u%c",&v1,&c)==1) + { + if (v1>65535) return false; + pf->to=pf->from=(uint16_t)v1; + } + else + return false; + // deny all case + if (!pf->from && !pf->to) pf->neg=true; + return true; +} + +static bool fltmode_parse(const char *s, uint8_t *mode) +{ + if (*s=='*' && !s[1]) + { + *mode = FLTMODE_ANY; + return true; + } + else if (*s=='-' && !s[1]) + { + *mode = FLTMODE_SKIP; + return true; + } + *mode = FLTMODE_SKIP; + return false; +} + +bool icf_match(uint8_t type, uint8_t code, const icmp_filter *icf) +{ + return icf->mode==FLTMODE_ANY || icf->mode==FLTMODE_FILTER && icf->type==type && (!icf->code_valid || icf->code==code); +} +bool icf_parse(const char *s, icmp_filter *icf) +{ + unsigned int u1,u2; + char c1,c2; + + icf->type = icf->code = 0; + icf->code_valid = false; + if (fltmode_parse(s, &icf->mode)) return true; + switch(sscanf(s,"%u%c%u%c",&u1,&c1,&u2,&c2)) + { + case 1: + if (u1>0xFF) return false; + icf->type = (uint8_t)u1; + icf->mode = FLTMODE_FILTER; + break; + case 3: + if (c1!=':' || (u1>0xFF) || (u2>0xFF)) return false; + icf->type = (uint8_t)u1; + icf->code = (uint8_t)u2; + icf->code_valid = true; + icf->mode = FLTMODE_FILTER; + break; + default: + icf->mode = FLTMODE_SKIP; + return false; + } + return true; +} + +bool ipp_match(uint8_t proto, const ipp_filter *ipp) +{ + return ipp->mode==FLTMODE_ANY || ipp->mode==FLTMODE_FILTER && ipp->proto==proto; +} +bool ipp_parse(const char *s, ipp_filter *ipp) +{ + unsigned int u1; + char c; + + ipp->proto = 0xFF; + if (fltmode_parse(s, &ipp->mode)) return true; + if (sscanf(s,"%u%c",&u1,&c)!=1 || u1>0xFF) return false; + ipp->proto = (uint8_t)u1; + ipp->mode = FLTMODE_FILTER; + return true; +} + +bool packet_pos_parse(const char *s, struct packet_pos *pos) +{ + if (*s!='n' && *s!='d' && *s!='s' && *s!='p' && *s!='b' && *s!='x' && *s!='a') return false; + pos->mode=*s; + if (pos->mode=='x' || pos->mode=='a') + { + pos->pos=0; + return true; + } + return sscanf(s+1,"%u",&pos->pos)==1; +} +bool packet_range_parse(const char *s, struct packet_range *range) +{ + const char *p; + + range->upper_cutoff = false; + if (*s=='-' || *s=='<') + { + range->from = PACKET_POS_ALWAYS; + range->upper_cutoff = *s=='<'; + } + else + { + if (!packet_pos_parse(s,&range->from)) return false; + if (range->from.mode=='x') + { + range->to = range->from; + return true; + } + if (!(p = strchr(s,'-'))) + p = strchr(s,'<'); + if (p) + { + s = p; + range->upper_cutoff = *s=='<'; + } + else + { + if (range->from.mode=='a') + { + range->to = range->from; + return true; + } + return false; + } + } + s++; + if (*s) + { + return packet_pos_parse(s,&range->to); + } + else + { + range->to = PACKET_POS_ALWAYS; + return true; + } +} + + +void str_cidr4(char *s, size_t s_len, const struct cidr4 *cidr) +{ + char s_ip[16]; + *s_ip=0; + inet_ntop(AF_INET, &cidr->addr, s_ip, sizeof(s_ip)); + snprintf(s,s_len,cidr->preflen<32 ? "%s/%u" : "%s", s_ip, cidr->preflen); +} +void print_cidr4(const struct cidr4 *cidr) +{ + char s[19]; + str_cidr4(s,sizeof(s),cidr); + printf("%s",s); +} +void str_cidr6(char *s, size_t s_len, const struct cidr6 *cidr) +{ + char s_ip[40]; + *s_ip=0; + inet_ntop(AF_INET6, &cidr->addr, s_ip, sizeof(s_ip)); + snprintf(s,s_len,cidr->preflen<128 ? "%s/%u" : "%s", s_ip, cidr->preflen); +} +void print_cidr6(const struct cidr6 *cidr) +{ + char s[44]; + str_cidr6(s,sizeof(s),cidr); + printf("%s",s); +} +bool parse_cidr4(char *s, struct cidr4 *cidr) +{ + char *p,d; + bool b; + unsigned int plen; + + if ((p = strchr(s, '/'))) + { + if (sscanf(p + 1, "%u", &plen)!=1 || plen>32) + return false; + cidr->preflen = (uint8_t)plen; + d=*p; *p=0; // backup char + } + else + cidr->preflen = 32; + b = (inet_pton(AF_INET, s, &cidr->addr)==1); + if (p) *p=d; // restore char + return b; +} +bool parse_cidr6(char *s, struct cidr6 *cidr) +{ + char *p,d; + bool b; + unsigned int plen; + + if ((p = strchr(s, '/'))) + { + if (sscanf(p + 1, "%u", &plen)!=1 || plen>128) + return false; + cidr->preflen = (uint8_t)plen; + d=*p; *p=0; // backup char + } + else + cidr->preflen = 128; + b = (inet_pton(AF_INET6, s, &cidr->addr)==1); + if (p) *p=d; // restore char + return b; +} diff --git a/nfq2/filter.h b/nfq2/filter.h new file mode 100644 index 0000000..2a9f9ed --- /dev/null +++ b/nfq2/filter.h @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include + +typedef struct +{ + uint16_t from,to; + bool neg; +} port_filter; +bool pf_match(uint16_t port, const port_filter *pf); +bool pf_parse(const char *s, port_filter *pf); + +#define FLTMODE_SKIP 0 +#define FLTMODE_ANY 1 +#define FLTMODE_FILTER 2 +typedef struct +{ + uint8_t mode, type, code; + bool code_valid; +} icmp_filter; +bool icf_match(uint8_t type, uint8_t code, const icmp_filter *icf); +bool icf_parse(const char *s, icmp_filter *icf); + +typedef struct +{ + uint8_t mode, proto; +} ipp_filter; +bool ipp_match(uint8_t proto, const ipp_filter *ipp); +bool ipp_parse(const char *s, ipp_filter *ipp); + +struct packet_pos +{ + char mode; // n - packets, d - data packets, s - relative sequence + unsigned int pos; +}; +struct packet_range +{ + struct packet_pos from, to; + bool upper_cutoff; // true - do not include upper limit, false - include upper limit +}; +#define PACKET_POS_NEVER (struct packet_pos){'x',0} +#define PACKET_POS_ALWAYS (struct packet_pos){'a',0} +#define PACKET_RANGE_NEVER (struct packet_range){PACKET_POS_NEVER,PACKET_POS_NEVER} +#define PACKET_RANGE_ALWAYS (struct packet_range){PACKET_POS_ALWAYS,PACKET_POS_ALWAYS} +bool packet_range_parse(const char *s, struct packet_range *range); + +struct cidr4 +{ + struct in_addr addr; + uint8_t preflen; +}; +struct cidr6 +{ + struct in6_addr addr; + uint8_t preflen; +}; +void str_cidr4(char *s, size_t s_len, const struct cidr4 *cidr); +void print_cidr4(const struct cidr4 *cidr); +void str_cidr6(char *s, size_t s_len, const struct cidr6 *cidr); +void print_cidr6(const struct cidr6 *cidr); +bool parse_cidr4(char *s, struct cidr4 *cidr); +bool parse_cidr6(char *s, struct cidr6 *cidr); diff --git a/nfq2/helpers.c b/nfq2/helpers.c index 67ccc1f..58dc86a 100644 --- a/nfq2/helpers.c +++ b/nfq2/helpers.c @@ -448,107 +448,6 @@ bool file_open_test(const char *filename, int flags) return false; } -bool pf_in_range(uint16_t port, const port_filter *pf) -{ - return port && (((!pf->from && !pf->to) || (port>=pf->from && port<=pf->to)) ^ pf->neg); -} -bool pf_parse(const char *s, port_filter *pf) -{ - unsigned int v1,v2; - char c; - - if (!s) return false; - if (*s=='*' && s[1]==0) - { - pf->from=1; pf->to=0xFFFF; - return true; - } - if (*s=='~') - { - pf->neg=true; - s++; - } - else - pf->neg=false; - if (sscanf(s,"%u-%u%c",&v1,&v2,&c)==2) - { - if (v1>65535 || v2>65535 || v1>v2) return false; - pf->from=(uint16_t)v1; - pf->to=(uint16_t)v2; - } - else if (sscanf(s,"%u%c",&v1,&c)==1) - { - if (v1>65535) return false; - pf->to=pf->from=(uint16_t)v1; - } - else - return false; - // deny all case - if (!pf->from && !pf->to) pf->neg=true; - return true; -} -bool pf_is_empty(const port_filter *pf) -{ - return !pf->neg && !pf->from && !pf->to; -} - -bool packet_pos_parse(const char *s, struct packet_pos *pos) -{ - if (*s!='n' && *s!='d' && *s!='s' && *s!='p' && *s!='b' && *s!='x' && *s!='a') return false; - pos->mode=*s; - if (pos->mode=='x' || pos->mode=='a') - { - pos->pos=0; - return true; - } - return sscanf(s+1,"%u",&pos->pos)==1; -} -bool packet_range_parse(const char *s, struct packet_range *range) -{ - const char *p; - - range->upper_cutoff = false; - if (*s=='-' || *s=='<') - { - range->from = PACKET_POS_ALWAYS; - range->upper_cutoff = *s=='<'; - } - else - { - if (!packet_pos_parse(s,&range->from)) return false; - if (range->from.mode=='x') - { - range->to = range->from; - return true; - } - if (!(p = strchr(s,'-'))) - p = strchr(s,'<'); - if (p) - { - s = p; - range->upper_cutoff = *s=='<'; - } - else - { - if (range->from.mode=='a') - { - range->to = range->from; - return true; - } - return false; - } - } - s++; - if (*s) - { - return packet_pos_parse(s,&range->to); - } - else - { - range->to = PACKET_POS_ALWAYS; - return true; - } -} void fill_random_bytes(uint8_t *p,size_t sz) { @@ -581,12 +480,55 @@ bool fill_crypto_random_bytes(uint8_t *p,size_t sz) return b; } +#if defined(__GNUC__) && !defined(__llvm__) +__attribute__((optimize ("no-strict-aliasing"))) +#endif +void bxor(const uint8_t *x1, const uint8_t *x2, uint8_t *result, size_t sz) +{ + for (; sz>=8 ; x1+=8, x2+=8, result+=8, sz-=8) + *(uint64_t*)result = *(uint64_t*)x1 ^ *(uint64_t*)x2; + for (; sz ; x1++, x2++, result++, sz--) + *result = *x1 ^ *x2; +} +#if defined(__GNUC__) && !defined(__llvm__) +__attribute__((optimize ("no-strict-aliasing"))) +#endif +void bor(const uint8_t *x1, const uint8_t *x2, uint8_t *result, size_t sz) +{ + for (; sz>=8 ; x1+=8, x2+=8, result+=8, sz-=8) + *(uint64_t*)result = *(uint64_t*)x1 | *(uint64_t*)x2; + for (; sz ; x1++, x2++, result++, sz--) + *result = *x1 | *x2; +} +#if defined(__GNUC__) && !defined(__llvm__) +__attribute__((optimize ("no-strict-aliasing"))) +#endif +void band(const uint8_t *x1, const uint8_t *x2, uint8_t *result, size_t sz) +{ + for (; sz>=8 ; x1+=8, x2+=8, result+=8, sz-=8) + *(uint64_t*)result = *(uint64_t*)x1 & *(uint64_t*)x2; + for (; sz ; x1++, x2++, result++, sz--) + *result = *x1 & *x2; +} + + void set_console_io_buffering(void) { setvbuf(stdout, NULL, _IOLBF, 0); setvbuf(stderr, NULL, _IOLBF, 0); } +void close_std(void) +{ + // free memory allocated by setvbuf + fclose(stdout); + fclose(stderr); +} +void close_std_and_exit(int code) +{ + close_std(); + exit(code); +} bool set_env_exedir(const char *argv0) { @@ -601,73 +543,6 @@ bool set_env_exedir(const char *argv0) return bOK; } - - -void str_cidr4(char *s, size_t s_len, const struct cidr4 *cidr) -{ - char s_ip[16]; - *s_ip=0; - inet_ntop(AF_INET, &cidr->addr, s_ip, sizeof(s_ip)); - snprintf(s,s_len,cidr->preflen<32 ? "%s/%u" : "%s", s_ip, cidr->preflen); -} -void print_cidr4(const struct cidr4 *cidr) -{ - char s[19]; - str_cidr4(s,sizeof(s),cidr); - printf("%s",s); -} -void str_cidr6(char *s, size_t s_len, const struct cidr6 *cidr) -{ - char s_ip[40]; - *s_ip=0; - inet_ntop(AF_INET6, &cidr->addr, s_ip, sizeof(s_ip)); - snprintf(s,s_len,cidr->preflen<128 ? "%s/%u" : "%s", s_ip, cidr->preflen); -} -void print_cidr6(const struct cidr6 *cidr) -{ - char s[44]; - str_cidr6(s,sizeof(s),cidr); - printf("%s",s); -} -bool parse_cidr4(char *s, struct cidr4 *cidr) -{ - char *p,d; - bool b; - unsigned int plen; - - if ((p = strchr(s, '/'))) - { - if (sscanf(p + 1, "%u", &plen)!=1 || plen>32) - return false; - cidr->preflen = (uint8_t)plen; - d=*p; *p=0; // backup char - } - else - cidr->preflen = 32; - b = (inet_pton(AF_INET, s, &cidr->addr)==1); - if (p) *p=d; // restore char - return b; -} -bool parse_cidr6(char *s, struct cidr6 *cidr) -{ - char *p,d; - bool b; - unsigned int plen; - - if ((p = strchr(s, '/'))) - { - if (sscanf(p + 1, "%u", &plen)!=1 || plen>128) - return false; - cidr->preflen = (uint8_t)plen; - d=*p; *p=0; // backup char - } - else - cidr->preflen = 128; - b = (inet_pton(AF_INET6, s, &cidr->addr)==1); - if (p) *p=d; // restore char - return b; -} - bool parse_int16(const char *p, int16_t *v) { if (*p == '+' || *p == '-' || *p >= '0' && *p <= '9') @@ -678,3 +553,29 @@ bool parse_int16(const char *p, int16_t *v) } return false; } + +uint32_t mask_from_bitcount(uint32_t zct) +{ + return zct<32 ? ~((1u << zct) - 1) : 0; +} +static void mask_from_bitcount6_make(uint32_t zct, struct in6_addr *a) +{ + if (zct >= 128) + memset(a->s6_addr,0x00,16); + else + { + int32_t n = (127 - zct) >> 3; + memset(a->s6_addr,0xFF,n); + memset(a->s6_addr+n,0x00,16-n); + a->s6_addr[n] = ~((1u << (zct & 7)) - 1); + } +} +static struct in6_addr ip6_mask[129]; +void mask_from_bitcount6_prepare(void) +{ + for (int zct=0;zct<=128;zct++) mask_from_bitcount6_make(zct, ip6_mask+zct); +} +const struct in6_addr *mask_from_bitcount6(uint32_t zct) +{ + return ip6_mask+zct; +} diff --git a/nfq2/helpers.h b/nfq2/helpers.h index ff922bc..ad0841d 100644 --- a/nfq2/helpers.h +++ b/nfq2/helpers.h @@ -85,55 +85,22 @@ time_t file_mod_time(const char *filename); bool file_size(const char *filename, off_t *size); bool file_open_test(const char *filename, int flags); -typedef struct -{ - uint16_t from,to; - bool neg; -} port_filter; -bool pf_in_range(uint16_t port, const port_filter *pf); -bool pf_parse(const char *s, port_filter *pf); -bool pf_is_empty(const port_filter *pf); - -struct packet_pos -{ - char mode; // n - packets, d - data packets, s - relative sequence - unsigned int pos; -}; -struct packet_range -{ - struct packet_pos from, to; - bool upper_cutoff; // true - do not include upper limit, false - include upper limit -}; -#define PACKET_POS_NEVER (struct packet_pos){'x',0} -#define PACKET_POS_ALWAYS (struct packet_pos){'a',0} -#define PACKET_RANGE_NEVER (struct packet_range){PACKET_POS_NEVER,PACKET_POS_NEVER} -#define PACKET_RANGE_ALWAYS (struct packet_range){PACKET_POS_ALWAYS,PACKET_POS_ALWAYS} -bool packet_range_parse(const char *s, struct packet_range *range); - void fill_random_bytes(uint8_t *p,size_t sz); void fill_random_az(uint8_t *p,size_t sz); void fill_random_az09(uint8_t *p,size_t sz); bool fill_crypto_random_bytes(uint8_t *p,size_t sz); +void bxor(const uint8_t *x1, const uint8_t *x2, uint8_t *result, size_t sz); +void band(const uint8_t *x1, const uint8_t *x2, uint8_t *result, size_t sz); +void bor(const uint8_t *x1, const uint8_t *x2, uint8_t *result, size_t sz); + void set_console_io_buffering(void); +void close_std(void); +void close_std_and_exit(int code); bool set_env_exedir(const char *argv0); - -struct cidr4 -{ - struct in_addr addr; - uint8_t preflen; -}; -struct cidr6 -{ - struct in6_addr addr; - uint8_t preflen; -}; -void str_cidr4(char *s, size_t s_len, const struct cidr4 *cidr); -void print_cidr4(const struct cidr4 *cidr); -void str_cidr6(char *s, size_t s_len, const struct cidr6 *cidr); -void print_cidr6(const struct cidr6 *cidr); -bool parse_cidr4(char *s, struct cidr4 *cidr); -bool parse_cidr6(char *s, struct cidr6 *cidr); - bool parse_int16(const char *p, int16_t *v); + +uint32_t mask_from_bitcount(uint32_t zct); +void mask_from_bitcount6_prepare(void); +const struct in6_addr *mask_from_bitcount6(uint32_t zct); diff --git a/nfq2/hostlist.c b/nfq2/hostlist.c index ec31b60..fcc71da 100644 --- a/nfq2/hostlist.c +++ b/nfq2/hostlist.c @@ -42,7 +42,7 @@ bool AppendHostlistItem(hostlist_pool **hostlist, char *s) bool AppendHostList(hostlist_pool **hostlist, const char *filename) { - char *p, *e, s[256], *zbuf; + char *p, *e, s[4096], *zbuf; size_t zsize; int ct = 0; FILE *F; diff --git a/nfq2/ipset.c b/nfq2/ipset.c index 115f00d..55add05 100644 --- a/nfq2/ipset.c +++ b/nfq2/ipset.c @@ -59,7 +59,7 @@ bool AppendIpsetItem(ipset *ips, char *ip) static bool AppendIpset(ipset *ips, const char *filename) { - char *p, *e, s[256], *zbuf; + char *p, *e, s[4096], *zbuf; size_t zsize; int ct = 0; FILE *F; @@ -215,7 +215,11 @@ bool IpsetsReloadCheckForProfile(const struct desync_profile *dp) return IpsetsReloadCheck(&dp->ips_collection) && IpsetsReloadCheck(&dp->ips_collection_exclude); } -static bool IpsetCheck_(const struct ipset_collection_head *ips, const struct ipset_collection_head *ips_exclude, const struct in_addr *ipv4, const struct in6_addr *ipv6) +static bool IpsetCheck_( + const struct ipset_collection_head *ips, const struct ipset_collection_head *ips_exclude, + const struct in_addr *ipv4, const struct in6_addr *ipv6, + const struct in_addr *ipv4r, const struct in6_addr *ipv6r +) { struct ipset_item *item; @@ -227,6 +231,12 @@ static bool IpsetCheck_(const struct ipset_collection_head *ips, const struct ip DLOG("[%s] exclude ",item->hfile->filename ? item->hfile->filename : "fixed"); if (SearchIpset(&item->hfile->ipset, ipv4, ipv6)) return false; + if (ipv4r || ipv6r) + { + DLOG("[%s] exclude ",item->hfile->filename ? item->hfile->filename : "fixed"); + if (SearchIpset(&item->hfile->ipset, ipv4r, ipv6r)) + return false; + } } // old behavior compat: all include lists are empty means check passes if (!ipset_collection_is_empty(ips)) @@ -236,17 +246,26 @@ static bool IpsetCheck_(const struct ipset_collection_head *ips, const struct ip DLOG("[%s] include ",item->hfile->filename ? item->hfile->filename : "fixed"); if (SearchIpset(&item->hfile->ipset, ipv4, ipv6)) return true; + if (ipv4r || ipv6r) + { + DLOG("[%s] include ",item->hfile->filename ? item->hfile->filename : "fixed"); + if (SearchIpset(&item->hfile->ipset, ipv4r, ipv6r)) + return true; + } } return false; } return true; } -bool IpsetCheck(const struct desync_profile *dp, const struct in_addr *ipv4, const struct in6_addr *ipv6) +bool IpsetCheck( + const struct desync_profile *dp, + const struct in_addr *ipv4, const struct in6_addr *ipv6, + const struct in_addr *ipv4r, const struct in6_addr *ipv6r) { if (PROFILE_IPSETS_ABSENT(dp)) return true; DLOG("* ipset check for profile %u (%s)\n",dp->n,PROFILE_NAME(dp)); - return IpsetCheck_(&dp->ips_collection,&dp->ips_collection_exclude,ipv4,ipv6); + return IpsetCheck_(&dp->ips_collection,&dp->ips_collection_exclude,ipv4,ipv6,ipv4r,ipv6r); } diff --git a/nfq2/ipset.h b/nfq2/ipset.h index b711ac5..36bd7dd 100644 --- a/nfq2/ipset.h +++ b/nfq2/ipset.h @@ -6,7 +6,10 @@ #include "pools.h" bool LoadAllIpsets(); -bool IpsetCheck(const struct desync_profile *dp, const struct in_addr *ipv4, const struct in6_addr *ipv6); +bool IpsetCheck( + const struct desync_profile *dp, + const struct in_addr *ipv4, const struct in6_addr *ipv6, + const struct in_addr *ipv4r, const struct in6_addr *ipv6r); struct ipset_file *RegisterIpset(struct desync_profile *dp, bool bExclude, const char *filename); void IpsetsDebug(); bool AppendIpsetItem(ipset *ips, char *ip); diff --git a/nfq2/lua.c b/nfq2/lua.c index c623f91..4509d33 100644 --- a/nfq2/lua.c +++ b/nfq2/lua.c @@ -1,15 +1,37 @@ #include #include #include +#include #include -#ifdef __FreeBSD__ -#include -#elif defined(__linux__) -#include -#elif defined(__CYGWIN__) -#include -#endif +#include #include +#include +#include + +#ifdef __FreeBSD__ + +#include + +#elif defined(__linux__) + +#include + +#elif defined(__CYGWIN__) + +#include + +// header hell conflicts between unix and win32 +typedef struct in6_addr IN6_ADDR, *PIN6_ADDR, *LPIN6_ADDR; +typedef struct sockaddr *LPSOCKADDR; +typedef struct _SOCKET_ADDRESS { + LPSOCKADDR lpSockaddr; + int iSockaddrLength; +} SOCKET_ADDRESS,*PSOCKET_ADDRESS,*LPSOCKET_ADDRESS; +#define _WINSOCK2API_ +#define _NETIOAPI_H_ +#include + +#endif #include "lua.h" #include "params.h" @@ -67,6 +89,18 @@ static int luacall_DLOG_CONDUP(lua_State *L) return 0; } +const char *lua_reqlstring(lua_State *L,int idx,size_t *len) +{ + luaL_checktype(L,idx,LUA_TSTRING); + return lua_tolstring(L,idx,len); +} +const char *lua_reqstring(lua_State *L,int idx) +{ + luaL_checktype(L,idx,LUA_TSTRING); + return lua_tostring(L,idx); +} + + static int luacall_bitlshift(lua_State *L) { lua_check_argc(L,"bitlshift",2); @@ -213,7 +247,7 @@ static int luacall_u8(lua_State *L) int argc=lua_gettop(L); size_t l; lua_Integer offset; - const uint8_t *p = (uint8_t*)luaL_checklstring(L,1,&l); + const uint8_t *p = (uint8_t*)lua_reqlstring(L,1,&l); offset = (argc>=2 && lua_type(L,2)!=LUA_TNIL) ? luaL_checkinteger(L,2)-1 : 0; if (offset<0 || (offset+1)>l) luaL_error(L, "out of range"); @@ -227,7 +261,7 @@ static int luacall_u16(lua_State *L) int argc=lua_gettop(L); size_t l; lua_Integer offset; - const uint8_t *p = (uint8_t*)luaL_checklstring(L,1,&l); + const uint8_t *p = (uint8_t*)lua_reqlstring(L,1,&l); offset = (argc>=2 && lua_type(L,2)!=LUA_TNIL) ? luaL_checkinteger(L,2)-1 : 0; if (offset<0 || (offset+2)>l) luaL_error(L, "out of range"); @@ -241,7 +275,7 @@ static int luacall_u24(lua_State *L) int argc=lua_gettop(L); size_t l; lua_Integer offset; - const uint8_t *p = (uint8_t*)luaL_checklstring(L,1,&l); + const uint8_t *p = (uint8_t*)lua_reqlstring(L,1,&l); offset = (argc>=2 && lua_type(L,2)!=LUA_TNIL) ? luaL_checkinteger(L,2)-1 : 0; if (offset<0 || (offset+3)>l) luaL_error(L, "out of range"); @@ -255,7 +289,7 @@ static int luacall_u32(lua_State *L) int argc=lua_gettop(L); size_t l; lua_Integer offset; - const uint8_t *p = (uint8_t*)luaL_checklstring(L,1,&l); + const uint8_t *p = (uint8_t*)lua_reqlstring(L,1,&l); offset = (argc>=2 && lua_type(L,2)!=LUA_TNIL) ? luaL_checkinteger(L,2)-1 : 0; if (offset<0 || (offset+4)>l) luaL_error(L, "out of range"); @@ -269,7 +303,7 @@ static int luacall_u48(lua_State *L) int argc=lua_gettop(L); size_t l; lua_Integer offset; - const uint8_t *p = (uint8_t*)luaL_checklstring(L,1,&l); + const uint8_t *p = (uint8_t*)lua_reqlstring(L,1,&l); offset = (argc>=2 && lua_type(L,2)!=LUA_TNIL) ? luaL_checkinteger(L,2)-1 : 0; if (offset<0 || (offset+6)>l) luaL_error(L, "out of range"); @@ -495,7 +529,7 @@ static int luacall_parse_hex(lua_State *L) LUA_STACK_GUARD_ENTER(L) size_t l; - const char *hex = luaL_checklstring(L,1,&l); + const char *hex = lua_reqlstring(L,1,&l); if ((l&1)) goto err; l>>=1; uint8_t *p = malloc(l); @@ -553,6 +587,40 @@ static int luacall_bcryptorandom(lua_State *L) LUA_STACK_GUARD_RETURN(L,1) } + +static int luac_bop(lua_State *L, const char *name, void (*op)(const uint8_t *x1, const uint8_t *x2, uint8_t *result, size_t sz)) +{ + lua_check_argc(L,name,2); + + LUA_STACK_GUARD_ENTER(L) + + size_t sz1,sz2; + const uint8_t *d1 = (const uint8_t*)lua_reqlstring(L,1,&sz1); + const uint8_t *d2 = (const uint8_t*)lua_reqlstring(L,2,&sz2); + if (sz1!=sz2) luaL_error(L, "string lengths must be the same\n"); + uint8_t *d3 = malloc(sz1); + if (!d3) luaL_error(L, "out of memory"); + + op(d1,d2,d3,sz1); + + lua_pushlstring(L,(char*)d3,sz1); + free(d3); + + LUA_STACK_GUARD_RETURN(L,1) +} +static int luacall_bxor(lua_State *L) +{ + return luac_bop(L,"bxor",bxor); +} +static int luacall_bor(lua_State *L) +{ + return luac_bop(L,"bor",bor); +} +static int luacall_band(lua_State *L) +{ + return luac_bop(L,"band",band); +} + static int luacall_hash(lua_State *L) { // hash(hash_type, data) returns hash @@ -564,7 +632,7 @@ static int luacall_hash(lua_State *L) SHAversion sha_ver = lua_hash_type(L, s_hash_type); size_t data_len; - const uint8_t *data = (uint8_t*)luaL_checklstring(L,2,&data_len); + const uint8_t *data = (uint8_t*)lua_reqlstring(L,2,&data_len); unsigned char hash[USHAMaxHashSize]; USHAContext tcontext; @@ -585,11 +653,11 @@ static int luacall_aes(lua_State *L) bool bEncrypt = lua_toboolean(L,1); size_t key_len; - const uint8_t *key = (uint8_t*)luaL_checklstring(L,2,&key_len); + const uint8_t *key = (uint8_t*)lua_reqlstring(L,2,&key_len); if (key_len!=16 && key_len!=24 && key_len!=32) luaL_error(L, "aes: wrong key length %u. should be 16,24,32.", (unsigned)key_len); size_t input_len; - const uint8_t *input = (uint8_t*)luaL_checklstring(L,3,&input_len); + const uint8_t *input = (uint8_t*)lua_reqlstring(L,3,&input_len); if (input_len!=16) luaL_error(L, "aes: wrong data length %u. should be 16.", (unsigned)input_len); @@ -613,17 +681,17 @@ static int luacall_aes_gcm(lua_State *L) int argc = lua_gettop(L); bool bEncrypt = lua_toboolean(L,1); size_t key_len; - const uint8_t *key = (uint8_t*)luaL_checklstring(L,2,&key_len); + const uint8_t *key = (uint8_t*)lua_reqlstring(L,2,&key_len); if (key_len!=16 && key_len!=24 && key_len!=32) luaL_error(L, "aes_gcm: wrong key length %u. should be 16,24,32.", (unsigned)key_len); size_t iv_len; - const uint8_t *iv = (uint8_t*)luaL_checklstring(L,3,&iv_len); + const uint8_t *iv = (uint8_t*)lua_reqlstring(L,3,&iv_len); if (iv_len!=12) luaL_error(L, "aes_gcm: wrong iv length %u. should be 12.", (unsigned)iv_len); size_t input_len; - const uint8_t *input = (uint8_t*)luaL_checklstring(L,4,&input_len); + const uint8_t *input = (uint8_t*)lua_reqlstring(L,4,&input_len); size_t add_len=0; - const uint8_t *add = lua_isnoneornil(L,5) ? NULL : (uint8_t*)luaL_checklstring(L,5,&add_len); + const uint8_t *add = lua_isnoneornil(L,5) ? NULL : (uint8_t*)lua_reqlstring(L,5,&add_len); uint8_t atag[16]; uint8_t *output = malloc(input_len); @@ -652,12 +720,12 @@ static int luacall_aes_ctr(lua_State *L) LUA_STACK_GUARD_ENTER(L) size_t key_len; - const uint8_t *key = (uint8_t*)luaL_checklstring(L,1,&key_len); + const uint8_t *key = (uint8_t*)lua_reqlstring(L,1,&key_len); if (key_len!=16 && key_len!=24 && key_len!=32) luaL_error(L, "aes_ctr: wrong key length %u. should be 16,24,32.", (unsigned)key_len); size_t iv_len; - const uint8_t *iv = (uint8_t*)luaL_checklstring(L,2,&iv_len); + const uint8_t *iv = (uint8_t*)lua_reqlstring(L,2,&iv_len); if (iv_len!=16) luaL_error(L, "aes_ctr: wrong iv length %u. should be 16.", (unsigned)iv_len); @@ -1175,6 +1243,32 @@ void lua_pushf_blob(lua_State *L, int idx_desync, const char *field, const char lua_rawset(L,-3); } +void lua_push_ipaddr(lua_State *L, const struct sockaddr *sa) +{ + switch(sa ? sa->sa_family : 0) + { + case AF_INET: + lua_pushlstring(L, (const char*)&((struct sockaddr_in*)sa)->sin_addr, sizeof(struct in_addr)); + break; + case AF_INET6: + lua_pushlstring(L, (const char*)&((struct sockaddr_in6*)sa)->sin6_addr, sizeof(struct in6_addr)); + break; + default: + lua_pushnil(L); + } +} +void lua_pushf_ipaddr(lua_State *L, const char *field, const struct sockaddr *sa) +{ + lua_pushstring(L, field); + lua_push_ipaddr(L,sa); + lua_rawset(L,-3); +} +void lua_pushi_ipaddr(lua_State *L, lua_Integer idx, const struct sockaddr *sa) +{ + lua_pushinteger(L, idx); + lua_push_ipaddr(L,sa); + lua_rawset(L,-3); +} void lua_pushf_tcphdr_options(lua_State *L, const struct tcphdr *tcp, size_t len) { @@ -1217,11 +1311,10 @@ void lua_pushf_tcphdr_options(lua_State *L, const struct tcphdr *tcp, size_t len LUA_STACK_GUARD_LEAVE(L, 0) } -void lua_pushf_tcphdr(lua_State *L, const struct tcphdr *tcp, size_t len) +void lua_push_tcphdr(lua_State *L, const struct tcphdr *tcp, size_t len) { LUA_STACK_GUARD_ENTER(L) - lua_pushliteral(L, "tcp"); if (tcp && len>=sizeof(struct tcphdr)) { lua_createtable(L, 0, 11); @@ -1239,15 +1332,37 @@ void lua_pushf_tcphdr(lua_State *L, const struct tcphdr *tcp, size_t len) } else lua_pushnil(L); + + LUA_STACK_GUARD_LEAVE(L, 1) +} +void lua_pushf_tcphdr(lua_State *L, const struct tcphdr *tcp, size_t len) +{ + LUA_STACK_GUARD_ENTER(L) + + lua_pushliteral(L, "tcp"); + lua_push_tcphdr(L,tcp,len); lua_rawset(L,-3); LUA_STACK_GUARD_LEAVE(L, 0) } -void lua_pushf_udphdr(lua_State *L, const struct udphdr *udp, size_t len) +static int luacall_dissect_tcphdr(lua_State *L) +{ + // dissect_tcphdr(tcphdr_data) + lua_check_argc(L,"dissect_tcphdr",1); + + LUA_STACK_GUARD_ENTER(L) + + size_t len; + const uint8_t *data = (const uint8_t*)lua_reqlstring(L, 1, &len); + + lua_push_tcphdr(L, (struct tcphdr*)data, len); + + LUA_STACK_GUARD_RETURN(L,1) +} +void lua_push_udphdr(lua_State *L, const struct udphdr *udp, size_t len) { LUA_STACK_GUARD_ENTER(L) - lua_pushliteral(L, "udp"); if (udp && len>=sizeof(struct udphdr)) { lua_createtable(L, 0, 4); @@ -1258,15 +1373,77 @@ void lua_pushf_udphdr(lua_State *L, const struct udphdr *udp, size_t len) } else lua_pushnil(L); + + LUA_STACK_GUARD_LEAVE(L, 1) +} +void lua_pushf_udphdr(lua_State *L, const struct udphdr *udp, size_t len) +{ + LUA_STACK_GUARD_ENTER(L) + + lua_pushliteral(L, "udp"); + lua_push_udphdr(L,udp,len); lua_rawset(L,-3); LUA_STACK_GUARD_LEAVE(L, 0) } -void lua_pushf_iphdr(lua_State *L, const struct ip *ip, size_t len) +static int luacall_dissect_udphdr(lua_State *L) +{ + // dissect_udphdr(udphdr_data) + lua_check_argc(L,"dissect_udphdr",1); + + LUA_STACK_GUARD_ENTER(L) + + size_t len; + const uint8_t *data = (const uint8_t*)lua_reqlstring(L, 1, &len); + + lua_push_udphdr(L, (struct udphdr*)data, len); + + LUA_STACK_GUARD_RETURN(L,1) +} +void lua_push_icmphdr(lua_State *L, const struct icmp46 *icmp, size_t len) { LUA_STACK_GUARD_ENTER(L) - lua_pushliteral(L, "ip"); + if (icmp && len>=sizeof(struct icmp46)) + { + lua_createtable(L, 0, 4); + lua_pushf_int(L,"icmp_type",icmp->icmp_type); + lua_pushf_int(L,"icmp_code",icmp->icmp_code); + lua_pushf_int(L,"icmp_cksum",ntohs(icmp->icmp_cksum)); + lua_pushf_int(L,"icmp_data",ntohl(icmp->icmp_data32)); + } + else + lua_pushnil(L); + + LUA_STACK_GUARD_LEAVE(L, 1) +} +void lua_pushf_icmphdr(lua_State *L, const struct icmp46 *icmp, size_t len) +{ + LUA_STACK_GUARD_ENTER(L) + + lua_pushliteral(L, "icmp"); + lua_push_icmphdr(L,icmp,len); + lua_rawset(L,-3); + + LUA_STACK_GUARD_LEAVE(L, 0) +} +static int luacall_dissect_icmphdr(lua_State *L) +{ + // dissect_icmphdr(icmphdr_data) + lua_check_argc(L,"dissect_icmphdr",1); + + LUA_STACK_GUARD_ENTER(L) + + size_t len; + const uint8_t *data = (const uint8_t*)lua_reqlstring(L, 1, &len); + + lua_push_icmphdr(L, (struct icmp46*)data, len); + + LUA_STACK_GUARD_RETURN(L,1) +} +void lua_push_iphdr(lua_State *L, const struct ip *ip, size_t len) +{ + LUA_STACK_GUARD_ENTER(L) if (ip && len>=sizeof(struct ip)) { uint16_t hl = ip->ip_hl<<2; @@ -1288,10 +1465,31 @@ void lua_pushf_iphdr(lua_State *L, const struct ip *ip, size_t len) } else lua_pushnil(L); - lua_rawset(L,-3); + LUA_STACK_GUARD_LEAVE(L, 1) +} +void lua_pushf_iphdr(lua_State *L, const struct ip *ip, size_t len) +{ + LUA_STACK_GUARD_ENTER(L) + lua_pushliteral(L, "ip"); + lua_push_iphdr(L,ip,len); + lua_rawset(L,-3); LUA_STACK_GUARD_LEAVE(L, 0) } +static int luacall_dissect_iphdr(lua_State *L) +{ + // dissect_iphdr(iphdr_data) + lua_check_argc(L,"dissect_iphdr",1); + + LUA_STACK_GUARD_ENTER(L) + + size_t len; + const uint8_t *data = (const uint8_t*)lua_reqlstring(L, 1, &len); + + lua_push_iphdr(L, (struct ip*)data, len); + + LUA_STACK_GUARD_RETURN(L,1) +} void lua_pushf_ip6exthdr(lua_State *L, const struct ip6_hdr *ip6, size_t len) { LUA_STACK_GUARD_ENTER(L); @@ -1358,11 +1556,10 @@ end: LUA_STACK_GUARD_LEAVE(L, 0) } -void lua_pushf_ip6hdr(lua_State *L, const struct ip6_hdr *ip6, size_t len) +void lua_push_ip6hdr(lua_State *L, const struct ip6_hdr *ip6, size_t len) { LUA_STACK_GUARD_ENTER(L) - lua_pushliteral(L, "ip6"); if (ip6) { lua_createtable(L, 0, 7); @@ -1376,21 +1573,45 @@ void lua_pushf_ip6hdr(lua_State *L, const struct ip6_hdr *ip6, size_t len) } else lua_pushnil(L); + + LUA_STACK_GUARD_LEAVE(L, 1) +} +void lua_pushf_ip6hdr(lua_State *L, const struct ip6_hdr *ip6, size_t len) +{ + LUA_STACK_GUARD_ENTER(L) + + lua_pushliteral(L, "ip6"); + lua_push_ip6hdr(L,ip6,len); lua_rawset(L,-3); LUA_STACK_GUARD_LEAVE(L, 0) } +static int luacall_dissect_ip6hdr(lua_State *L) +{ + // dissect_iphdr(ip6hdr_data) + lua_check_argc(L,"dissect_ip6hdr",1); + + LUA_STACK_GUARD_ENTER(L) + + size_t len; + const uint8_t *data = (const uint8_t*)lua_reqlstring(L, 1, &len); + + lua_push_ip6hdr(L, (struct ip6_hdr*)data, len); + + LUA_STACK_GUARD_RETURN(L,1) +} void lua_push_dissect(lua_State *L, const struct dissect *dis) { LUA_STACK_GUARD_ENTER(L) if (dis) { - lua_createtable(L, 0, 9); + lua_createtable(L, 0, 10); lua_pushf_iphdr(L,dis->ip, dis->len_l3); lua_pushf_ip6hdr(L,dis->ip6, dis->len_l3); lua_pushf_tcphdr(L,dis->tcp, dis->len_l4); lua_pushf_udphdr(L,dis->udp, dis->len_l4); + lua_pushf_icmphdr(L,dis->icmp, dis->len_l4); lua_pushf_int(L,"l4proto",dis->proto); lua_pushf_int(L,"transport_len",dis->transport_len); lua_pushf_int(L,"l3_len",dis->len_l3); @@ -1455,7 +1676,7 @@ void lua_pushf_ctrack(lua_State *L, const t_ctrack *ctrack, const t_ctrack_posit lua_pushf_nil(L, "incoming_ttl"); lua_pushf_str(L, "l7proto", l7proto_str(ctrack->l7proto)); lua_pushf_str(L, "hostname", ctrack->hostname); - lua_pushf_bool(L, "hostname_is_ip", ctrack->hostname_is_ip); + if (ctrack->hostname) lua_pushf_bool(L, "hostname_is_ip", ctrack->hostname_is_ip); lua_pushf_reg(L, "lua_state", ctrack->lua_state); lua_pushf_bool(L, "lua_in_cutoff", ctrack->b_lua_in_cutoff); lua_pushf_bool(L, "lua_out_cutoff", ctrack->b_lua_out_cutoff); @@ -1573,10 +1794,11 @@ void lua_pushf_range(lua_State *L, const char *name, const struct packet_range * } -static void lua_reconstruct_extract_options(lua_State *L, int idx, bool *badsum, bool *ip6_preserve_next, uint8_t *ip6_last_proto) +static void lua_reconstruct_extract_options(lua_State *L, int idx, bool *keepsum, bool *badsum, bool *ip6_preserve_next, uint8_t *ip6_last_proto) { if (lua_isnoneornil(L,idx)) { + if (keepsum) *keepsum = false; if (badsum) *badsum = false; if (ip6_preserve_next) *ip6_preserve_next = false; if (ip6_last_proto) *ip6_last_proto = IPPROTO_NONE; @@ -1584,6 +1806,12 @@ static void lua_reconstruct_extract_options(lua_State *L, int idx, bool *badsum, else { luaL_checktype(L, idx, LUA_TTABLE); + if (keepsum) + { + lua_getfield(L, idx,"keepsum"); + *keepsum = lua_type(L,-1)!=LUA_TNIL && (lua_type(L,-1)!=LUA_TBOOLEAN || lua_toboolean(L,-1)); + lua_pop(L,1); + } if (badsum) { lua_getfield(L, idx,"badsum"); @@ -1659,6 +1887,7 @@ static bool lua_reconstruct_ip6exthdr(lua_State *L, int idx, struct ip6_hdr *ip6 } } } + // set last header proto if (!preserve_next) *last_proto = proto; @@ -1684,11 +1913,6 @@ bool lua_reconstruct_ip6hdr(lua_State *L, int idx, struct ip6_hdr *ip6, size_t * ip6->ip6_ctlun.ip6_un1.ip6_un1_flow = htonl(lua_type(L,-1)==LUA_TNUMBER ? (uint32_t)lua_tolint(L,-1) : 0x60000000); lua_pop(L, 1); - lua_getfield(L,idx,"ip6_plen"); - ip6->ip6_ctlun.ip6_un1.ip6_un1_plen = htons((uint16_t)lua_tointeger(L,-1)); - - lua_pop(L, 1); - lua_getfield(L,idx,"ip6_nxt"); ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt = (uint8_t)lua_tointeger(L,-1); lua_pop(L, 1); @@ -1711,12 +1935,28 @@ bool lua_reconstruct_ip6hdr(lua_State *L, int idx, struct ip6_hdr *ip6, size_t * ip6->ip6_dst = *(struct in6_addr*)p; lua_pop(L, 1); + bool have_plen = false; + lua_getfield(L,idx,"ip6_plen"); + switch (lua_type(L,-1)) + { + case LUA_TNIL: + break; + case LUA_TNUMBER: + ip6->ip6_ctlun.ip6_un1.ip6_un1_plen = htons((uint16_t)luaL_checkinteger(L,-1)); + have_plen = true; + break; + default: + luaL_error(L,"reconstruct_ip6hdr: ip6_plen wrong type"); + } + lua_pop(L, 1); + bool b = lua_reconstruct_ip6exthdr(L, idx, ip6, len, last_proto, preserve_next); + if (b && !have_plen) ip6->ip6_ctlun.ip6_un1.ip6_un1_plen = htons((uint16_t)(*len-sizeof(struct ip6_hdr))); + LUA_STACK_GUARD_LEAVE(L, 0) return b; err: lua_pop(L, 1); - LUA_STACK_GUARD_LEAVE(L, 0) return false; } @@ -1732,7 +1972,7 @@ static int luacall_reconstruct_ip6hdr(lua_State *L) uint8_t last_proto; bool preserve_next; - lua_reconstruct_extract_options(L, 2, NULL, &preserve_next, &last_proto); + lua_reconstruct_extract_options(L, 2, NULL, NULL, &preserve_next, &last_proto); if (!lua_reconstruct_ip6hdr(L, 1,(struct ip6_hdr*)data, &len, last_proto, preserve_next)) luaL_error(L, "invalid data for ip6hdr"); @@ -2035,6 +2275,51 @@ static int luacall_reconstruct_udphdr(lua_State *L) LUA_STACK_GUARD_RETURN(L,1) } +bool lua_reconstruct_icmphdr(lua_State *L, int idx, struct icmp46 *icmp) +{ + if (lua_type(L,-1)!=LUA_TTABLE) return false; + + LUA_STACK_GUARD_ENTER(L) + + lua_getfield(L,idx,"icmp_type"); + if (lua_type(L,-1)!=LUA_TNUMBER) goto err; + icmp->icmp_type = (uint8_t)lua_tointeger(L,-1); + lua_pop(L, 1); + + lua_getfield(L,idx,"icmp_code"); + if (lua_type(L,-1)!=LUA_TNUMBER) goto err; + icmp->icmp_code = (uint8_t)lua_tointeger(L,-1); + lua_pop(L, 1); + + lua_getfield(L,idx,"icmp_data"); + if (lua_type(L,-1)!=LUA_TNUMBER) goto err; + icmp->icmp_data32 = htonl((uint32_t)lua_tointeger(L,-1)); + lua_pop(L, 1); + + lua_getfield(L,idx,"icmp_cksum"); + icmp->icmp_cksum = htons((uint16_t)lua_tointeger(L,-1)); + lua_pop(L, 1); + + LUA_STACK_GUARD_LEAVE(L, 0) + return true; +err: + lua_pop(L, 1); + LUA_STACK_GUARD_LEAVE(L, 0) + return false; +} +static int luacall_reconstruct_icmphdr(lua_State *L) +{ + LUA_STACK_GUARD_ENTER(L) + + lua_check_argc(L,"reconstruct_icmphdr",1); + struct icmp46 icmp; + if (!lua_reconstruct_icmphdr(L,1,&icmp)) + luaL_error(L, "invalid data for icmphdr"); + lua_pushlstring(L,(char*)&icmp,sizeof(icmp)); + + LUA_STACK_GUARD_RETURN(L,1) +} + uint8_t lua_ip6_l4proto_from_dissect(lua_State *L, int idx) { int type; @@ -2047,10 +2332,28 @@ uint8_t lua_ip6_l4proto_from_dissect(lua_State *L, int idx) lua_getfield(L,idx,"udp"); type=lua_type(L,-1); lua_pop(L,1); - return type==LUA_TTABLE ? IPPROTO_UDP : IPPROTO_NONE; + if (type==LUA_TTABLE) return IPPROTO_UDP; + + lua_getfield(L,idx,"icmp"); + type=lua_type(L,-1); + lua_pop(L,1); + if (type==LUA_TTABLE) + { + lua_getfield(L,idx,"ip"); + type=lua_type(L,-1); + lua_pop(L,1); + if (type==LUA_TTABLE) return IPPROTO_ICMP; + + lua_getfield(L,idx,"ip6"); + type=lua_type(L,-1); + lua_pop(L,1); + if (type==LUA_TTABLE) return IPPROTO_ICMPV6; + } + + return IPPROTO_NONE; } -bool lua_reconstruct_dissect(lua_State *L, int idx, uint8_t *buf, size_t *len, bool badsum, bool ip6_preserve_next) +bool lua_reconstruct_dissect(lua_State *L, int idx, uint8_t *buf, size_t *len, bool keepsum, bool badsum, bool ip6_preserve_next) { uint8_t *data = buf; size_t sz,l,lpayload,l3,left = *len; @@ -2058,6 +2361,7 @@ bool lua_reconstruct_dissect(lua_State *L, int idx, uint8_t *buf, size_t *len, b struct ip6_hdr *ip6=NULL; struct tcphdr *tcp=NULL; struct udphdr *udp=NULL; + struct icmp46 *icmp=NULL; const char *p; LUA_STACK_GUARD_ENTER(L) @@ -2093,9 +2397,10 @@ bool lua_reconstruct_dissect(lua_State *L, int idx, uint8_t *buf, size_t *len, b lua_pop(L, 1); lua_getfield(L,idx,"tcp"); - l = left; + l=0; if (lua_type(L,-1)==LUA_TTABLE) { + l = left; tcp = (struct tcphdr*)data; if (!lua_reconstruct_tcphdr(L, -1, tcp, &l)) { @@ -2107,13 +2412,30 @@ bool lua_reconstruct_dissect(lua_State *L, int idx, uint8_t *buf, size_t *len, b { lua_pop(L, 1); lua_getfield(L,idx,"udp"); - l = sizeof(struct udphdr); - if (lua_type(L,-1)!=LUA_TTABLE || left0xFFFF) + if (tcp) { - DLOG_ERR("reconstruct_dissect: invalid payload length\n"); - goto err; + tcp_fix_checksum(tcp,l-l3,ip,ip6); + if (badsum) tcp->th_sum ^= 1 + (random() % 0xFFFF); + } + else if (udp) + { + sz = (uint16_t)(lpayload+sizeof(struct udphdr)); + if (sz>0xFFFF) + { + DLOG_ERR("reconstruct_dissect: invalid payload length\n"); + goto err; + } + udp->uh_ulen = htons((uint16_t)sz); + udp_fix_checksum(udp,l-l3,ip,ip6); + if (badsum) udp->uh_sum ^= 1 + (random() % 0xFFFF); + } + else if (icmp) + { + icmp_fix_checksum(icmp,l-l3,ip6); + if (badsum) icmp->icmp_cksum ^= 1 + (random() % 0xFFFF); } - udp->uh_ulen = htons((uint16_t)sz); - udp_fix_checksum(udp,l-l3,ip,ip6); - if (badsum) udp->uh_sum ^= 1 + (random() % 0xFFFF); - } - if (tcp) - { - tcp_fix_checksum(tcp,l-l3,ip,ip6); - if (badsum) tcp->th_sum ^= 1 + (random() % 0xFFFF); } if (ip) @@ -2227,10 +2558,10 @@ static int luacall_reconstruct_dissect(lua_State *L) uint8_t buf[RECONSTRUCT_MAX_SIZE] __attribute__((aligned(16))); l = sizeof(buf); - bool ip6_preserve_next, badsum; - lua_reconstruct_extract_options(L, 2, &badsum, &ip6_preserve_next, NULL); + bool ip6_preserve_next, badsum, keepsum; + lua_reconstruct_extract_options(L, 2, &keepsum, &badsum, &ip6_preserve_next, NULL); - if (!lua_reconstruct_dissect(L, 1, buf, &l, badsum, ip6_preserve_next)) + if (!lua_reconstruct_dissect(L, 1, buf, &l, keepsum, badsum, ip6_preserve_next)) luaL_error(L, "invalid dissect data"); lua_pushlstring(L,(char*)buf,l); @@ -2245,10 +2576,10 @@ static int luacall_dissect(lua_State *L) LUA_STACK_GUARD_ENTER(L) size_t len; - const uint8_t *data = (const uint8_t*)luaL_checklstring(L, 1, &len); + const uint8_t *data = (const uint8_t*)lua_reqlstring(L, 1, &len); struct dissect dis; - proto_dissect_l3l4(data, len, &dis); + proto_dissect_l3l4(data, len, &dis, false); lua_push_dissect(L, &dis); @@ -2263,7 +2594,7 @@ static int luacall_csum_ip4_fix(lua_State *L) LUA_STACK_GUARD_ENTER(L) size_t l; - const uint8_t *data = (const uint8_t*)luaL_checklstring(L, 1, &l); + const uint8_t *data = (const uint8_t*)lua_reqlstring(L, 1, &l); if (l>60 || !proto_check_ipv4(data, l)) luaL_error(L, "invalid ip header"); @@ -2283,7 +2614,7 @@ static int luacall_csum_tcp_fix(lua_State *L) LUA_STACK_GUARD_ENTER(L) size_t l_ip; - const uint8_t *b_ip = (const uint8_t*)luaL_checklstring(L, 1, &l_ip); + const uint8_t *b_ip = (const uint8_t*)lua_reqlstring(L, 1, &l_ip); const struct ip *ip=NULL; const struct ip6_hdr *ip6=NULL; @@ -2295,12 +2626,12 @@ static int luacall_csum_tcp_fix(lua_State *L) luaL_error(L, "invalid ip header"); size_t l_tcp; - const uint8_t *b_tcp = (const uint8_t*)luaL_checklstring(L, 2, &l_tcp); + const uint8_t *b_tcp = (const uint8_t*)lua_reqlstring(L, 2, &l_tcp); if (!proto_check_tcp(b_tcp, l_tcp)) luaL_error(L, "invalid tcp header"); size_t l_pl; - const uint8_t *b_pl = (const uint8_t*)luaL_checklstring(L, 3, &l_pl); + const uint8_t *b_pl = (const uint8_t*)lua_reqlstring(L, 3, &l_pl); if (l_pl>0xFFFF) luaL_error(L, "invalid payload length"); @@ -2326,7 +2657,7 @@ static int luacall_csum_udp_fix(lua_State *L) LUA_STACK_GUARD_ENTER(L) size_t l_ip; - const uint8_t *b_ip = (const uint8_t*)luaL_checklstring(L, 1, &l_ip); + const uint8_t *b_ip = (const uint8_t*)lua_reqlstring(L, 1, &l_ip); const struct ip *ip=NULL; const struct ip6_hdr *ip6=NULL; @@ -2338,12 +2669,12 @@ static int luacall_csum_udp_fix(lua_State *L) luaL_error(L, "invalid ip header"); size_t l_udp; - const uint8_t *b_udp = (const uint8_t*)luaL_checklstring(L, 2, &l_udp); + const uint8_t *b_udp = (const uint8_t*)lua_reqlstring(L, 2, &l_udp); if (!proto_check_udp(b_udp, l_udp)) luaL_error(L, "invalid udp header"); size_t l_pl; - const uint8_t *b_pl = (const uint8_t*)luaL_checklstring(L, 3, &l_pl); + const uint8_t *b_pl = (const uint8_t*)lua_reqlstring(L, 3, &l_pl); if (l_pl>0xFFFF) luaL_error(L, "invalid payload length"); @@ -2361,6 +2692,49 @@ static int luacall_csum_udp_fix(lua_State *L) LUA_STACK_GUARD_RETURN(L,1) } +static int luacall_csum_icmp_fix(lua_State *L) +{ + // csum_icmp_fix(ip_header, icmp_header, payload) returns icmp_header + lua_check_argc(L,"csum_icmp_fix",3); + + LUA_STACK_GUARD_ENTER(L) + + size_t l_ip; + const uint8_t *b_ip = (const uint8_t*)lua_reqlstring(L, 1, &l_ip); + const struct ip *ip=NULL; + const struct ip6_hdr *ip6=NULL; + + if (proto_check_ipv4(b_ip, l_ip)) + ip = (struct ip*)b_ip; + else if (proto_check_ipv6(b_ip, l_ip)) + ip6 = (struct ip6_hdr*)b_ip; + else + luaL_error(L, "invalid ip header"); + + size_t l_icmp; + const uint8_t *b_icmp = (const uint8_t*)lua_reqlstring(L, 2, &l_icmp); + if (!proto_check_icmp(b_icmp, l_icmp)) + luaL_error(L, "invalid icmp header"); + + size_t l_pl; + const uint8_t *b_pl = (const uint8_t*)lua_reqlstring(L, 3, &l_pl); + if (l_pl>0xFFFF) + luaL_error(L, "invalid payload length"); + + size_t l_tpl = l_icmp + l_pl; + uint8_t *tpl = malloc(l_tpl); + if (!tpl) luaL_error(L, "out of memory"); + + memcpy(tpl, b_icmp, l_icmp); + memcpy(tpl+l_icmp, b_pl, l_pl); + struct icmp46 *icmp = (struct icmp46*)tpl; + icmp_fix_checksum(icmp, l_tpl, ip6); + + lua_pushlstring(L,(char*)tpl,l_icmp); + free(tpl); + + LUA_STACK_GUARD_RETURN(L,1) +} static int luacall_ntop(lua_State *L) { @@ -2373,7 +2747,7 @@ static int luacall_ntop(lua_State *L) LUA_STACK_GUARD_ENTER(L) - p=luaL_checklstring(L,1,&l); + p=lua_reqlstring(L,1,&l); switch(l) { case sizeof(struct in_addr): @@ -2401,7 +2775,7 @@ static int luacall_pton(lua_State *L) LUA_STACK_GUARD_ENTER(L) - p=luaL_checkstring(L,1); + p=lua_reqstring(L,1); if (inet_pton(AF_INET,p,s)) lua_pushlstring(L,s,sizeof(struct in_addr)); else if (inet_pton(AF_INET6,p,s)) @@ -2462,7 +2836,7 @@ static int luacall_rawsend(lua_State *L) sockaddr_in46 sa; bool b; - data=(uint8_t*)luaL_checklstring(L,1,&len); + data=(uint8_t*)lua_reqlstring(L,1,&len); lua_rawsend_extract_options(L,2,&repeats,&fwmark,&ifout); if (!extract_dst(data, len, (struct sockaddr*)&sa)) @@ -2487,15 +2861,15 @@ static int luacall_rawsend_dissect(lua_State *L) int repeats; uint32_t fwmark; sockaddr_in46 sa; - bool b, badsum, ip6_preserve_next; + bool b, badsum, keepsum, ip6_preserve_next; uint8_t buf[RECONSTRUCT_MAX_SIZE] __attribute__((aligned(16))); len = sizeof(buf); luaL_checktype(L,1,LUA_TTABLE); lua_rawsend_extract_options(L,2, &repeats, &fwmark, &ifout); - lua_reconstruct_extract_options(L, 3, &badsum, &ip6_preserve_next, NULL); + lua_reconstruct_extract_options(L, 3, &keepsum, &badsum, &ip6_preserve_next, NULL); - if (!lua_reconstruct_dissect(L, 1, buf, &len, badsum, ip6_preserve_next)) + if (!lua_reconstruct_dissect(L, 1, buf, &len, keepsum, badsum, ip6_preserve_next)) luaL_error(L, "invalid dissect data"); if (!extract_dst(buf, len, (struct sockaddr*)&sa)) @@ -2507,6 +2881,211 @@ static int luacall_rawsend_dissect(lua_State *L) LUA_STACK_GUARD_RETURN(L,1) } +static int luacall_get_source_ip(lua_State *L) +{ + // get_source_ip(target_ip) + lua_check_argc(L,"get_source_ip",1); + + LUA_STACK_GUARD_ENTER(L) + + union + { + struct in_addr a4; + struct in6_addr a6; + } a; + size_t len; + const uint8_t *data = (uint8_t*)lua_reqlstring(L,1,&len); + + switch(len) + { + case sizeof(struct in_addr) : + if (get_source_ip4((struct in_addr*)data, &a.a4)) + lua_pushlstring(L,(char*)&a.a4,sizeof(a.a4)); + else + lua_pushnil(L); + break; + case sizeof(struct in6_addr) : + if (get_source_ip6((struct in6_addr*)data, &a.a6)) + lua_pushlstring(L,(char*)&a.a6,sizeof(a.a6)); + else + lua_pushnil(L); + break; + default: + luaL_error(L, "invalid IP length %u", (unsigned int)len); + } + + LUA_STACK_GUARD_RETURN(L,1) +} + +#ifdef __CYGWIN__ +#define GAA_FLAGS (GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_ANYCAST) +static int lua_get_ifaddrs(lua_State *L) +{ + LUA_STACK_GUARD_ENTER(L) + + ULONG Size=0; + if (GetAdaptersAddresses(AF_UNSPEC, GAA_FLAGS, NULL, NULL, &Size)==ERROR_BUFFER_OVERFLOW) + { + PIP_ADAPTER_ADDRESSES pip, pips = (PIP_ADAPTER_ADDRESSES)malloc(Size); + if (pips) + { + if (GetAdaptersAddresses(AF_UNSPEC, GAA_FLAGS, NULL, pips, &Size)==ERROR_SUCCESS) + { + lua_newtable(L); + for(pip=pips; pip ; pip=pip->Next) + { + if (!pip->FirstUnicastAddress || pip->OperStatus!=IfOperStatusUp) continue; // disconnected ? + + char ifname[16]; + snprintf(ifname,sizeof(ifname),"%u.0",pip->IfIndex); + lua_pushf_table(L,ifname); + lua_getfield(L,-1,ifname); + + lua_pushf_str(L, "guid", pip->AdapterName); + if (pip->PhysicalAddressLength) lua_pushf_lstr(L, "phys", pip->PhysicalAddress, pip->PhysicalAddressLength); + lua_pushf_int(L, "index", pip->IfIndex); + lua_pushf_int(L, "index6", pip->Ipv6IfIndex); + lua_pushf_int(L, "flags", pip->Flags); + lua_pushf_lint(L, "mtu", pip->Mtu); + lua_pushf_int(L, "iftype", pip->IfType); + lua_pushf_lint(L, "speed_xmit", pip->TransmitLinkSpeed); + lua_pushf_lint(L, "speed_recv", pip->ReceiveLinkSpeed); + lua_pushf_lint(L, "metric4", pip->Ipv4Metric); + lua_pushf_lint(L, "metric6", pip->Ipv6Metric); + lua_pushf_lint(L, "conntype", pip->ConnectionType); + lua_pushf_lint(L, "tunneltype", pip->TunnelType); + lua_pushf_table(L,"addr"); + lua_getfield(L,-1,"addr"); + + int n; + uint32_t a4,a44; + PIP_ADAPTER_UNICAST_ADDRESS_LH pa; + for(pa=pip->FirstUnicastAddress, n=1; pa ; pa=pa->Next, n++) + { + lua_pushi_table(L, n); + lua_rawgeti(L, -1, n); + lua_pushf_ipaddr(L, "addr", pa->Address.lpSockaddr); + switch(pa->Address.lpSockaddr->sa_family) + { + case AF_INET: + if (pa->OnLinkPrefixLength<=32) + { + a44 = mask_from_bitcount(pa->OnLinkPrefixLength); + a4 = ~a44; + lua_pushf_lstr(L, "netmask", (const char*)&a4, 4); + a4 &= ((struct sockaddr_in*)pa->Address.lpSockaddr)->sin_addr.s_addr; + a4 |= a44; + lua_pushf_lstr(L, "broadcast", (const char*)&a4, 4); + } + break; + case AF_INET6: + if (pa->OnLinkPrefixLength<=128) + { + lua_pushf_lstr(L, "netmask", (const char*)mask_from_bitcount6(128 - pa->OnLinkPrefixLength), 16); + } + break; + } + lua_pushf_ipaddr(L, "addr", pa->Address.lpSockaddr); + lua_pop(L,1); + } + lua_pop(L,2); + } + } + free (pips); + goto ok; + } + } + + lua_pushnil(L); + +ok: + LUA_STACK_GUARD_RETURN(L,1) +} +#else +// in cygwin this does not work with low intergity level because of cygwin objects in NT directory tree +static int lua_get_ifaddrs(lua_State *L) +{ + LUA_STACK_GUARD_ENTER(L) + + struct ifaddrs *addrs,*a; + unsigned int index; + lua_Integer li; + struct ifreq ifr; + const char *ifname; +#ifdef __CYGWIN__ + char ifname_buf[16]; +#endif + memset(&ifr,0,sizeof(ifr)); + + if (getifaddrs(&addrs)<0) + lua_pushnil(L); + else + { + int sock = socket(AF_INET,SOCK_DGRAM,0); + lua_newtable(L); + a = addrs; + for(a=addrs ; a ; a=a->ifa_next) + { + if (a->ifa_addr && (a->ifa_addr->sa_family==AF_INET || a->ifa_addr->sa_family==AF_INET6) && a->ifa_name && *a->ifa_name) + { +#ifdef __CYGWIN__ + // cygwin returns GUID interface names. windivert needs ifindex.subindex + if (index = if_nametoindex(a->ifa_name)) + { + snprintf(ifname_buf,sizeof(ifname_buf),"%u.0",index); + ifname = ifname_buf; + } +#else + ifname = a->ifa_name; +#endif + lua_getfield(L,-1,ifname); + if (lua_isnil(L,-1)) + { + lua_pop(L,1); + lua_pushf_table(L,ifname); + lua_getfield(L,-1,ifname); +#ifdef __CYGWIN__ + lua_pushf_str(L, "guid", a->ifa_name); +#else + index = if_nametoindex(ifname); +#endif + if (index) lua_pushf_int(L, "index", index); + lua_pushf_int(L, "flags", a->ifa_flags); +#ifdef HAS_FILTER_SSID + lua_pushf_str(L, "ssid", wlan_ssid_search_ifname(ifname)); +#endif + strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); + if (sock>=0 && !ioctl(sock, SIOCGIFMTU, &ifr)) + lua_pushf_int(L, "mtu", ifr.ifr_mtu); + + lua_pushf_table(L,"addr"); + } + lua_getfield(L,-1,"addr"); + li = lua_rawlen(L,-1)+1; + lua_pushi_table(L, li); + lua_rawgeti(L,-1,li); + lua_pushf_ipaddr(L, "addr", a->ifa_addr); + lua_pushf_ipaddr(L, "netmask", a->ifa_netmask); + lua_pushf_ipaddr(L, "broadcast", a->ifa_broadaddr); + lua_pushf_ipaddr(L, "dst", a->ifa_dstaddr); + lua_pop(L,3); + } + } + freeifaddrs(addrs); + if (sock>=0) close(sock); + } + + LUA_STACK_GUARD_RETURN(L,1) +} +#endif // CYGWIN + +static int luacall_get_ifaddrs(lua_State *L) +{ + lua_check_argc(L,"get_ifaddrs",0); + lua_get_ifaddrs(L); + return 1; +} + static int luacall_resolve_pos(lua_State *L) { // resolve_pos(blob,l7payload_type,marker[,zero_based_pos]) @@ -2516,9 +3095,9 @@ static int luacall_resolve_pos(lua_State *L) int argc=lua_gettop(L); size_t len; - const uint8_t *data = (uint8_t*)luaL_checklstring(L,1,&len); - const char *sl7payload = luaL_checkstring(L,2); - const char *smarker = luaL_checkstring(L,3); + const uint8_t *data = (uint8_t*)lua_reqlstring(L,1,&len); + const char *sl7payload = lua_reqstring(L,2); + const char *smarker = lua_reqstring(L,3); bool bZeroBased = argc>=4 && lua_toboolean(L,4); t_l7payload l7payload = l7payload_from_name(sl7payload); @@ -2546,9 +3125,9 @@ static int luacall_resolve_multi_pos(lua_State *L) int argc=lua_gettop(L); size_t len; - const uint8_t *data = (uint8_t*)luaL_checklstring(L,1,&len); - const char *sl7payload = luaL_checkstring(L,2); - const char *smarkers = luaL_checkstring(L,3); + const uint8_t *data = (uint8_t*)lua_reqlstring(L,1,&len); + const char *sl7payload = lua_reqstring(L,2); + const char *smarkers = lua_reqstring(L,3); bool bZeroBased = argc>=4 && lua_toboolean(L,4); t_l7payload l7payload = l7payload_from_name(sl7payload); @@ -2577,9 +3156,9 @@ static int luacall_resolve_range(lua_State *L) int argc=lua_gettop(L); size_t i,len; - const uint8_t *data = (uint8_t*)luaL_checklstring(L,1,&len); - const char *sl7payload = luaL_checkstring(L,2); - const char *smarkers = luaL_checkstring(L,3); + const uint8_t *data = (uint8_t*)lua_reqlstring(L,1,&len); + const char *sl7payload = lua_reqstring(L,2); + const char *smarkers = lua_reqstring(L,3); bool bStrict = argc>=4 && lua_toboolean(L,4); bool bZeroBased = argc>=5 && lua_toboolean(L,5); @@ -2627,13 +3206,13 @@ static int luacall_tls_mod(lua_State *L) size_t fake_tls_len; bool bRes; - const uint8_t *fake_tls = (uint8_t*)luaL_checklstring(L,1,&fake_tls_len); - const char *modlist = luaL_checkstring(L,2); + const uint8_t *fake_tls = (uint8_t*)lua_reqlstring(L,1,&fake_tls_len); + const char *modlist = lua_reqstring(L,2); size_t payload_len = 0; const uint8_t *payload = NULL; if (argc>=3 && lua_type(L,3)!=LUA_TNIL) - payload = (uint8_t*)luaL_checklstring(L,3,&payload_len); + payload = (uint8_t*)lua_reqlstring(L,3,&payload_len); struct fake_tls_mod mod; if (!TLSMod_parse_list(modlist, &mod)) @@ -3248,6 +3827,7 @@ static void lua_init_const(void) {"IP6_BASE_LEN",sizeof(struct ip6_hdr)}, {"TCP_BASE_LEN",sizeof(struct tcphdr)}, {"UDP_BASE_LEN",sizeof(struct udphdr)}, + {"ICMP_BASE_LEN",sizeof(struct icmp46)}, {"TCP_KIND_END",TCP_KIND_END}, {"TCP_KIND_NOOP",TCP_KIND_NOOP}, @@ -3300,7 +3880,62 @@ static void lua_init_const(void) {"IPPROTO_MH",IPPROTO_MH}, {"IPPROTO_HIP",IPPROTO_HIP}, {"IPPROTO_SHIM6",IPPROTO_SHIM6}, - {"IPPROTO_NONE",IPPROTO_NONE} + {"IPPROTO_NONE",IPPROTO_NONE}, + + // icmp types + {"ICMP_ECHOREPLY",ICMP_ECHOREPLY}, + {"ICMP_DEST_UNREACH",ICMP_DEST_UNREACH}, + {"ICMP_REDIRECT",ICMP_REDIRECT}, + {"ICMP_ECHO",ICMP_ECHO}, + {"ICMP_TIME_EXCEEDED",ICMP_TIME_EXCEEDED}, + {"ICMP_PARAMETERPROB",ICMP_PARAMETERPROB}, + {"ICMP_TIMESTAMP",ICMP_TIMESTAMP}, + {"ICMP_TIMESTAMPREPLY",ICMP_TIMESTAMPREPLY}, + {"ICMP_INFO_REQUEST",ICMP_INFO_REQUEST}, + {"ICMP_INFO_REPLY",ICMP_INFO_REPLY}, + + // icmp codes for UNREACH + {"ICMP_UNREACH_NET",ICMP_UNREACH_NET}, + {"ICMP_UNREACH_HOST",ICMP_UNREACH_HOST}, + {"ICMP_UNREACH_PROTOCOL",ICMP_UNREACH_PROTOCOL}, + {"ICMP_UNREACH_PORT",ICMP_UNREACH_PORT}, + {"ICMP_UNREACH_NEEDFRAG",ICMP_UNREACH_NEEDFRAG}, + {"ICMP_UNREACH_SRCFAIL",ICMP_UNREACH_SRCFAIL}, + {"ICMP_UNREACH_NET_UNKNOWN",ICMP_UNREACH_NET_UNKNOWN}, + {"ICMP_UNREACH_HOST_UNKNOWN",ICMP_UNREACH_HOST_UNKNOWN}, + {"ICMP_UNREACH_NET_PROHIB",ICMP_UNREACH_NET_PROHIB}, + {"ICMP_UNREACH_HOST_PROHIB",ICMP_UNREACH_HOST_PROHIB}, + {"ICMP_UNREACH_TOSNET",ICMP_UNREACH_TOSNET}, + {"ICMP_UNREACH_TOSHOST",ICMP_UNREACH_TOSHOST}, + {"ICMP_UNREACH_FILTER_PROHIB",ICMP_UNREACH_FILTER_PROHIB}, + {"ICMP_UNREACH_HOST_PRECEDENCE",ICMP_UNREACH_HOST_PRECEDENCE}, + {"ICMP_UNREACH_PRECEDENCE_CUTOFF",ICMP_UNREACH_PRECEDENCE_CUTOFF}, + + // icmp codes for REDIRECT + {"ICMP_REDIRECT_NET",ICMP_REDIRECT_NET}, + {"ICMP_REDIRECT_HOST",ICMP_REDIRECT_HOST}, + {"ICMP_REDIRECT_TOSNET",ICMP_REDIRECT_TOSNET}, + {"ICMP_REDIRECT_TOSHOST",ICMP_REDIRECT_TOSHOST}, + + // icmp codes for TIME_EXCEEDED + {"ICMP_TIMXCEED_INTRANS",ICMP_TIMXCEED_INTRANS}, + {"ICMP_TIMXCEED_REASS",ICMP_TIMXCEED_REASS}, + + // icmp6 types + {"ICMP6_ECHO_REQUEST",ICMP6_ECHO_REQUEST}, + {"ICMP6_ECHO_REPLY",ICMP6_ECHO_REPLY}, + {"ICMP6_DST_UNREACH",ICMP6_DST_UNREACH}, + {"ICMP6_PACKET_TOO_BIG",ICMP6_PACKET_TOO_BIG}, + {"ICMP6_TIME_EXCEEDED",ICMP6_TIME_EXCEEDED}, + {"ICMP6_PARAM_PROB",ICMP6_PARAM_PROB}, + {"MLD_LISTENER_QUERY",MLD_LISTENER_QUERY}, + {"MLD_LISTENER_REPORT",MLD_LISTENER_REPORT}, + {"MLD_LISTENER_REDUCTION",MLD_LISTENER_REDUCTION}, + {"ND_ROUTER_SOLICIT",ND_ROUTER_SOLICIT}, + {"ND_ROUTER_ADVERT",ND_ROUTER_ADVERT}, + {"ND_NEIGHBOR_SOLICIT",ND_NEIGHBOR_SOLICIT}, + {"ND_NEIGHBOR_ADVERT",ND_NEIGHBOR_ADVERT}, + {"ND_REDIRECT",ND_REDIRECT} }; DLOG("\nLUA NUMERIC:"); for (int i=0;i fd[1] ? fd[0] : fd[1]) + 1; + 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(); @@ -503,23 +528,25 @@ static int dvt_main(void) 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; - if (params.daemon) daemonize(); - - if (Fpid) + if (!params.intercept) { - if (fprintf(Fpid, "%d", getpid()) <= 0) - { - DLOG_PERROR("write pidfile"); - goto exiterr; - } - fclose(Fpid); - Fpid = NULL; + DLOG("no intercept quit\n"); + goto exitok; } - pre_desync(); + if (params.daemon) daemonize(); + if (!write_pidfile(&Fpid)) goto exiterr; for (;;) { @@ -637,6 +664,7 @@ static int win_main() int res=0; uint8_t packet[RECONSTRUCT_MAX_SIZE] __attribute__((aligned(16))); + // 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)) @@ -651,7 +679,7 @@ static int win_main() res=w_win32_error; goto ex; } - pre_desync(); + catch_signals(); for (;;) { @@ -675,6 +703,8 @@ static int win_main() 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; @@ -688,7 +718,11 @@ static int win_main() res=ERROR_INVALID_PARAMETER; goto ex; } - DLOG_CONDUP("windivert initialized. capture is started.\n"); + if (!params.intercept) + { + DLOG("no intercept quit\n"); + goto ex; + } for (id = 0;; id++) { @@ -765,8 +799,7 @@ ex: static void exit_clean(int code) { cleanup_params(¶ms); - - exit(code); + close_std_and_exit(code); } @@ -1042,6 +1075,52 @@ static bool parse_pf_list(char *opt, struct port_filters_head *pfl) return true; } +static bool parse_icf_list(char *opt, struct icmp_filters_head *icfl) +{ + char *e, *p, c; + icmp_filter icf; + bool b; + + for (p = opt; p; ) + { + if ((e = strchr(p, ','))) + { + c = *e; + *e = 0; + } + + b = icf_parse(p, &icf) && icmp_filter_add(icfl, &icf); + if (e) *e++ = c; + if (!b) return false; + + p = e; + } + return true; +} + +static bool parse_ipp_list(char *opt, struct ipp_filters_head *ippl) +{ + char *e, *p, c; + ipp_filter ipp; + bool b; + + for (p = opt; p; ) + { + if ((e = strchr(p, ','))) + { + c = *e; + *e = 0; + } + + b = ipp_parse(p, &ipp) && ipp_filter_add(ippl, &ipp); + if (e) *e++ = c; + if (!b) return false; + + p = e; + } + return true; +} + bool lua_call_param_add(char *opt, struct str2_list_head *args) { char c,*p; @@ -1260,6 +1339,82 @@ static bool wf_make_pf(char *opt, const char *l4, const char *portname, char *bu 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 < 127.0.0.1 or ip.DstAddr > 127.255.255.255) and " \ @@ -1306,6 +1461,8 @@ static bool wf_make_filter( 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 struct str_list_head *wf_raw_part, bool bFilterOutLAN, bool bFilterOutLoopback) { @@ -1354,6 +1511,11 @@ static bool wf_make_filter( 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) @@ -1403,6 +1565,7 @@ static void exithelp(void) " --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) @@ -1433,10 +1596,14 @@ static void exithelp(void) " --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-udp-in=[~]port1[-port2]\t\t\t\t; UDP 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 string or filename\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" @@ -1461,8 +1628,10 @@ static void exithelp(void) " --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 and not setting udp filter denies udp. comma separated list allowed.\n" - " --filter-udp=[~]port1[-port2]|*\t\t\t; UDP port filter. ~ means negation. setting udp and not setting tcp filter denies tcp. comma separated list 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" @@ -1504,7 +1673,7 @@ static void exithelp(void) HOSTLIST_AUTO_UDP_OUT, HOSTLIST_AUTO_UDP_IN, all_payloads ); - exit(1); + close_std_and_exit(1); } static void exithelp_clean(void) { @@ -1553,6 +1722,7 @@ static void ApplyDefaultBlobs(struct blob_collection_head *blobs) enum opt_indices { IDX_DEBUG, IDX_DRY_RUN, + IDX_INTERCEPT, IDX_VERSION, IDX_COMMENT, #ifdef __linux__ @@ -1610,6 +1780,8 @@ enum opt_indices { 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, @@ -1632,6 +1804,10 @@ enum opt_indices { 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_FILTER_LAN, @@ -1648,6 +1824,7 @@ enum opt_indices { 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_VERSION] = {"version", no_argument, 0, 0}, [IDX_COMMENT] = {"comment", optional_argument, 0, 0}, #ifdef __linux__ @@ -1702,6 +1879,8 @@ static const struct option long_options[] = { [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}, @@ -1724,6 +1903,10 @@ static const struct option long_options[] = { [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_FILTER_LAN] = {"wf-filter-lan", required_argument, 0, 0}, @@ -1738,6 +1921,29 @@ static const struct option long_options[] = { }; +#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) @@ -1754,6 +1960,7 @@ static const struct option long_options[] = { #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, GLOBAL_SSID_FILTER, GLOBAL_NLM_FILTER, WF_RAWF, WF_COUNT} t_wf_index; int main(int argc, char **argv) { #ifdef __CYGWIN__ @@ -1762,6 +1969,9 @@ int main(int argc, char **argv) // we were running as service. now exit. return 0; } + WinSetIcon(); + MAKE_VER(params.verstr, sizeof(params.verstr)); + printf("%s\n\n",params.verstr); #endif int result, v; int option_index = 0; @@ -1774,24 +1984,23 @@ int main(int argc, char **argv) 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_tcp_in = 0, hash_wf_udp_in = 0, hash_wf_tcp_out = 0, hash_wf_udp_out = 0, hash_wf_raw = 0, hash_wf_raw_part = 0, hash_ssid_filter = 0, hash_nlm_filter = 0; + unsigned int hash_wf[WF_COUNT]; #endif if (argc < 2) exithelp(); srandom(time(NULL)); aes_init_keygen_tables(); // required for aes + mask_from_bitcount6_prepare(); set_env_exedir(argv[0]); set_console_io_buffering(); #ifdef __CYGWIN__ + memset(hash_wf,0,sizeof(hash_wf)); prepare_low_appdata(); #endif init_params(¶ms); - MAKE_VER(params.verstr, sizeof(params.verstr)); - printf("%s\n\n",params.verstr); - ApplyDefaultBlobs(¶ms.blobs); struct desync_profile_list *dpl; @@ -1818,23 +2027,11 @@ int main(int argc, char **argv) #ifdef __CYGWIN__ params.windivert_filter = malloc(WINDIVERT_MAX); - if (!params.windivert_filter) + if (!params.windivert_filter || !alloc_windivert_portfilters(¶ms)) { DLOG_ERR("out of memory\n"); exit_clean(1); } - char **wdbufs[] = - {¶ms.wf_pf_tcp_src_in, ¶ms.wf_pf_tcp_dst_in, ¶ms.wf_pf_udp_src_in, ¶ms.wf_pf_udp_dst_in, - ¶ms.wf_pf_tcp_src_out, ¶ms.wf_pf_tcp_dst_out, ¶ms.wf_pf_udp_src_out, ¶ms.wf_pf_udp_dst_out}; - for (v=0 ; v<(sizeof(wdbufs)/sizeof(*wdbufs)) ; v++) - { - if (!(*wdbufs[v] = malloc(WINDIVERT_PORTFILTER_MAX))) - { - DLOG_ERR("out of memory\n"); - exit_clean(1); - } - **wdbufs[v] = 0; - } #endif while ((v = getopt_long_only(argc, argv, "", long_options, &option_index)) != -1) @@ -1898,6 +2095,9 @@ int main(int argc, char **argv) case IDX_DRY_RUN: bDry = true; break; + case IDX_INTERCEPT: + params.intercept = !optarg || atoi(optarg); + break; case IDX_VERSION: exit_clean(0); break; @@ -2289,8 +2489,10 @@ int main(int argc, char **argv) DLOG_ERR("Invalid port filter : %s\n", optarg); exit_clean(1); } - // deny udp if not set - if (!port_filters_deny_if_empty(&dp->pf_udp)) + // deny others if not set + if (!port_filters_deny_if_empty(&dp->pf_udp) || + !icmp_filters_deny_if_empty(&dp->icf) || + !ipp_filters_deny_if_empty(&dp->ipf)) exit_clean(1); break; case IDX_FILTER_UDP: @@ -2299,8 +2501,34 @@ int main(int argc, char **argv) DLOG_ERR("Invalid port filter : %s\n", optarg); exit_clean(1); } - // deny tcp if not set - if (!port_filters_deny_if_empty(&dp->pf_tcp)) + // deny others if not set + if (!port_filters_deny_if_empty(&dp->pf_tcp) || + !icmp_filters_deny_if_empty(&dp->icf) || + !ipp_filters_deny_if_empty(&dp->ipf)) + 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); + } + // deny others if not set + if (!port_filters_deny_if_empty(&dp->pf_tcp) || + !port_filters_deny_if_empty(&dp->pf_udp) || + !ipp_filters_deny_if_empty(&dp->ipf)) + 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); + } + // deny others if not set + if (!port_filters_deny_if_empty(&dp->pf_tcp) || + !port_filters_deny_if_empty(&dp->pf_udp) || + !icmp_filters_deny_if_empty(&dp->icf)) exit_clean(1); break; case IDX_FILTER_L7: @@ -2415,7 +2643,7 @@ int main(int argc, char **argv) } break; case IDX_WF_TCP_IN: - hash_wf_tcp_in = hash_jen(optarg, strlen(optarg)); + 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)) { @@ -2424,7 +2652,7 @@ int main(int argc, char **argv) } break; case IDX_WF_TCP_OUT: - hash_wf_tcp_out = hash_jen(optarg, strlen(optarg)); + 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)) { @@ -2433,7 +2661,7 @@ int main(int argc, char **argv) } break; case IDX_WF_UDP_IN: - hash_wf_udp_in = hash_jen(optarg, strlen(optarg)); + 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)) { @@ -2442,7 +2670,7 @@ int main(int argc, char **argv) } break; case IDX_WF_UDP_OUT: - hash_wf_udp_out = hash_jen(optarg, strlen(optarg)); + 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)) { @@ -2450,8 +2678,40 @@ int main(int argc, char **argv) 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: - hash_wf_raw = hash_jen(optarg, strlen(optarg)); + hash_wf[WF_RAWF] = hash_jen(optarg, strlen(optarg)); if (optarg[0] == '@') { size_t sz = WINDIVERT_MAX-1; @@ -2460,7 +2720,7 @@ int main(int argc, char **argv) } else { - strncpy(params.windivert_filter, optarg, WINDIVERT_MAX); + snprintf(params.windivert_filter, WINDIVERT_MAX, "%s", optarg); params.windivert_filter[WINDIVERT_MAX - 1] = '\0'; } break; @@ -2468,7 +2728,7 @@ int main(int argc, char **argv) wf_tcp_empty = !optarg || atoi(optarg); break; case IDX_WF_RAW_PART: - hash_wf_raw_part ^= hash_jen(optarg, strlen(optarg)); + hash_wf[WF_RAWF_PART] ^= hash_jen(optarg, strlen(optarg)); { char *wfpart = malloc(WINDIVERT_MAX); if (!wfpart) @@ -2484,7 +2744,7 @@ int main(int argc, char **argv) } else { - strncpy(wfpart, optarg, WINDIVERT_MAX); + snprintf(wfpart, WINDIVERT_MAX, "%s", optarg); wfpart[WINDIVERT_MAX - 1] = '\0'; } if (!strlist_add(¶ms.wf_raw_part, wfpart)) @@ -2510,7 +2770,7 @@ int main(int argc, char **argv) bDupCheck = !optarg || !!atoi(optarg); break; case IDX_SSID_FILTER: - hash_ssid_filter = hash_jen(optarg, strlen(optarg)); + hash_wf[GLOBAL_SSID_FILTER] = hash_jen(optarg, strlen(optarg)); if (!parse_strlist(optarg, ¶ms.ssid_filter)) { DLOG_ERR("strlist_add failed\n"); @@ -2518,7 +2778,7 @@ int main(int argc, char **argv) } break; case IDX_NLM_FILTER: - hash_nlm_filter = hash_jen(optarg, strlen(optarg)); + hash_wf[GLOBAL_NLM_FILTER] = hash_jen(optarg, strlen(optarg)); if (!parse_strlist(optarg, ¶ms.nlm_filter)) { DLOG_ERR("strlist_add failed\n"); @@ -2563,19 +2823,22 @@ int main(int argc, char **argv) #endif argv = NULL; argc = 0; + if (params.intercept) + { #ifdef __linux__ - if (params.qnum < 0) - { - DLOG_ERR("Need queue number (--qnum)\n"); - exit_clean(1); - } + 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); - } + 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 @@ -2659,37 +2922,50 @@ int main(int argc, char **argv) dp_list_destroy(¶ms.desync_templates); #ifdef __CYGWIN__ - if (!*params.windivert_filter) + if (params.intercept) { - 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 && LIST_EMPTY(¶ms.wf_raw_part)) + if (!*params.windivert_filter) { - DLOG_ERR("windivert filter : must specify port or/and partial raw filter\n"); - exit_clean(1); + 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, + ¶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, + ¶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; } - // 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, - ¶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, - ¶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) { @@ -2705,11 +2981,10 @@ int main(int argc, char **argv) } } HANDLE hMutexArg = NULL; - if (bDupCheck) + if (bDupCheck && params.intercept) { - char mutex_name[128]; - snprintf(mutex_name, sizeof(mutex_name), "Global\\winws2_arg_%u_%u_%u_%u_%u_%u_%u_%u_%u_%u_%u_%u", - hash_wf_tcp_in, hash_wf_udp_in, hash_wf_tcp_out, hash_wf_udp_out, hash_wf_raw, hash_wf_raw_part, hash_ssid_filter, hash_nlm_filter, IfIdx, SubIfIdx, wf_ipv4, wf_ipv6); + 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) @@ -2768,6 +3043,7 @@ ex: CloseHandle(hMutexArg); } #endif + close_std(); return result; exiterr: result = 1; diff --git a/nfq2/nfqws.h b/nfq2/nfqws.h index bcb0d65..29f435c 100644 --- a/nfq2/nfqws.h +++ b/nfq2/nfqws.h @@ -1,15 +1,15 @@ #pragma once -#include +#include #ifdef __linux__ #define HAS_FILTER_SSID 1 #endif #ifdef __CYGWIN__ -extern bool bQuit; +extern volatile sig_atomic_t bQuit; #endif int main(int argc, char *argv[]); // when something changes that can break LUA compatibility this version should be increased -#define LUA_COMPAT_VER 4 +#define LUA_COMPAT_VER 5 diff --git a/nfq2/params.c b/nfq2/params.c index 5f4f384..698efa0 100644 --- a/nfq2/params.c +++ b/nfq2/params.c @@ -337,6 +337,8 @@ void dp_init_dynamic(struct desync_profile *dp) LIST_INIT(&dp->ips_collection_exclude); LIST_INIT(&dp->pf_tcp); LIST_INIT(&dp->pf_udp); + LIST_INIT(&dp->icf); + LIST_INIT(&dp->ipf); LIST_INIT(&dp->lua_desync); #ifdef HAS_FILTER_SSID LIST_INIT(&dp->filter_ssid); @@ -368,6 +370,8 @@ static void dp_clear_dynamic(struct desync_profile *dp) ipset_collection_destroy(&dp->ips_collection_exclude); port_filters_destroy(&dp->pf_tcp); port_filters_destroy(&dp->pf_udp); + icmp_filters_destroy(&dp->icf); + ipp_filters_destroy(&dp->ipf); funclist_destroy(&dp->lua_desync); #ifdef HAS_FILTER_SSID strlist_destroy(&dp->filter_ssid); @@ -475,11 +479,30 @@ void cleanup_windivert_portfilters(struct params_s *params) { char **wdbufs[] = {¶ms->wf_pf_tcp_src_in, ¶ms->wf_pf_tcp_dst_in, ¶ms->wf_pf_udp_src_in, ¶ms->wf_pf_udp_dst_in, - ¶ms->wf_pf_tcp_src_out, ¶ms->wf_pf_tcp_dst_out, ¶ms->wf_pf_udp_src_out, ¶ms->wf_pf_udp_dst_out}; + ¶ms->wf_pf_tcp_src_out, ¶ms->wf_pf_tcp_dst_out, ¶ms->wf_pf_udp_src_out, ¶ms->wf_pf_udp_dst_out, + ¶ms->wf_icf_in, ¶ms->wf_icf_out, + ¶ms->wf_ipf_in, ¶ms->wf_ipf_out}; for (int i=0 ; i<(sizeof(wdbufs)/sizeof(*wdbufs)) ; i++) { - free(*wdbufs[i]); *wdbufs[i] = NULL; + free(*wdbufs[i]); + *wdbufs[i] = NULL; } + strlist_destroy(¶ms->wf_raw_part); +} +bool alloc_windivert_portfilters(struct params_s *params) +{ + char **wdbufs[] = + {¶ms->wf_pf_tcp_src_in, ¶ms->wf_pf_tcp_dst_in, ¶ms->wf_pf_udp_src_in, ¶ms->wf_pf_udp_dst_in, + ¶ms->wf_pf_tcp_src_out, ¶ms->wf_pf_tcp_dst_out, ¶ms->wf_pf_udp_src_out, ¶ms->wf_pf_udp_dst_out, + ¶ms->wf_icf_in, ¶ms->wf_icf_out, + ¶ms->wf_ipf_in, ¶ms->wf_ipf_out}; + for (int i=0 ; i<(sizeof(wdbufs)/sizeof(*wdbufs)) ; i++) + { + if (!(*wdbufs[i] = malloc(WINDIVERT_PORTFILTER_MAX))) + return false; + **wdbufs[i] = 0; + } + return true; } #endif void cleanup_params(struct params_s *params) @@ -514,6 +537,7 @@ void init_params(struct params_s *params) { memset(params, 0, sizeof(*params)); + params->intercept = true; #ifdef __linux__ params->qnum = -1; #elif defined(BSD) diff --git a/nfq2/params.h b/nfq2/params.h index 195cb12..579dd5b 100644 --- a/nfq2/params.h +++ b/nfq2/params.h @@ -63,6 +63,8 @@ struct desync_profile bool filter_ipv4,filter_ipv6; struct port_filters_head pf_tcp,pf_udp; + struct icmp_filters_head icf; + struct ipp_filters_head ipf; uint64_t filter_l7; // L7_PROTO_* bits #ifdef HAS_FILTER_SSID @@ -125,7 +127,7 @@ struct params_s char debug_logfile[PATH_MAX]; bool debug; - bool daemon; + bool daemon, intercept; #ifdef __linux__ int qnum; @@ -144,6 +146,7 @@ struct params_s char *windivert_filter; char *wf_pf_tcp_src_in, *wf_pf_tcp_dst_in, *wf_pf_udp_src_in, *wf_pf_udp_dst_in; char *wf_pf_tcp_src_out, *wf_pf_tcp_dst_out, *wf_pf_udp_src_out, *wf_pf_udp_dst_out; + char *wf_icf_in, *wf_icf_out, *wf_ipf_in, *wf_ipf_out; #else bool droproot; char *user; @@ -194,6 +197,7 @@ void init_params(struct params_s *params); void cleanup_args(struct params_s *params); #endif #ifdef __CYGWIN__ +bool alloc_windivert_portfilters(struct params_s *params); void cleanup_windivert_portfilters(struct params_s *params); #endif void cleanup_params(struct params_s *params); diff --git a/nfq2/pools.c b/nfq2/pools.c index d206cbf..4f9dac3 100644 --- a/nfq2/pools.c +++ b/nfq2/pools.c @@ -742,14 +742,14 @@ void port_filters_destroy(struct port_filters_head *head) free(entry); } } -bool port_filters_in_range(const struct port_filters_head *head, uint16_t port) +bool port_filters_match(const struct port_filters_head *head, uint16_t port) { const struct port_filter_item *item; if (LIST_EMPTY(head)) return true; LIST_FOREACH(item, head, next) { - if (pf_in_range(port, &item->pf)) + if (pf_match(port, &item->pf)) return true; } return false; @@ -762,6 +762,83 @@ bool port_filters_deny_if_empty(struct port_filters_head *head) } +bool icmp_filter_add(struct icmp_filters_head *head, const icmp_filter *icf) +{ + struct icmp_filter_item *entry = malloc(sizeof(struct icmp_filter_item)); + if (entry) + { + entry->icf = *icf; + LIST_INSERT_HEAD(head, entry, next); + } + return entry; +} +void icmp_filters_destroy(struct icmp_filters_head *head) +{ + struct icmp_filter_item *entry; + while ((entry = LIST_FIRST(head))) + { + LIST_REMOVE(entry, next); + free(entry); + } +} +bool icmp_filters_match(const struct icmp_filters_head *head, uint8_t type, uint8_t code) +{ + const struct icmp_filter_item *item; + + if (LIST_EMPTY(head)) return true; + LIST_FOREACH(item, head, next) + { + if (icf_match(type, code, &item->icf)) + return true; + } + return false; +} +bool icmp_filters_deny_if_empty(struct icmp_filters_head *head) +{ + icmp_filter icf; + if (!LIST_EMPTY(head)) return true; + return icf_parse("-",&icf) && icmp_filter_add(head,&icf); +} + + +bool ipp_filter_add(struct ipp_filters_head *head, const ipp_filter *ipp) +{ + struct ipp_filter_item *entry = malloc(sizeof(struct ipp_filter_item)); + if (entry) + { + entry->ipp = *ipp; + LIST_INSERT_HEAD(head, entry, next); + } + return entry; +} +void ipp_filters_destroy(struct ipp_filters_head *head) +{ + struct ipp_filter_item *entry; + while ((entry = LIST_FIRST(head))) + { + LIST_REMOVE(entry, next); + free(entry); + } +} +bool ipp_filters_match(const struct ipp_filters_head *head, uint8_t proto) +{ + const struct ipp_filter_item *item; + + if (LIST_EMPTY(head)) return true; + LIST_FOREACH(item, head, next) + { + if (ipp_match(proto, &item->ipp)) + return true; + } + return false; +} +bool ipp_filters_deny_if_empty(struct ipp_filters_head *head) +{ + ipp_filter ipp; + if (!LIST_EMPTY(head)) return true; + return ipp_parse("-",&ipp) && ipp_filter_add(head,&ipp); +} + struct blob_item *blob_collection_add(struct blob_collection_head *head) { diff --git a/nfq2/pools.h b/nfq2/pools.h index e3ecdd3..b91d36e 100644 --- a/nfq2/pools.h +++ b/nfq2/pools.h @@ -7,6 +7,7 @@ #include #include "helpers.h" +#include "filter.h" //#define HASH_BLOOM 20 #define HASH_NONFATAL_OOM 1 @@ -186,9 +187,28 @@ struct port_filter_item { LIST_HEAD(port_filters_head, port_filter_item); bool port_filter_add(struct port_filters_head *head, const port_filter *pf); void port_filters_destroy(struct port_filters_head *head); -bool port_filters_in_range(const struct port_filters_head *head, uint16_t port); +bool port_filters_match(const struct port_filters_head *head, uint16_t port); bool port_filters_deny_if_empty(struct port_filters_head *head); +struct icmp_filter_item { + icmp_filter icf; + LIST_ENTRY(icmp_filter_item) next; +}; +LIST_HEAD(icmp_filters_head, icmp_filter_item); +bool icmp_filter_add(struct icmp_filters_head *head, const icmp_filter *icf); +void icmp_filters_destroy(struct icmp_filters_head *head); +bool icmp_filters_match(const struct icmp_filters_head *head, uint8_t type, uint8_t code); +bool icmp_filters_deny_if_empty(struct icmp_filters_head *head); + +struct ipp_filter_item { + ipp_filter ipp; + LIST_ENTRY(ipp_filter_item) next; +}; +LIST_HEAD(ipp_filters_head, ipp_filter_item); +bool ipp_filter_add(struct ipp_filters_head *head, const ipp_filter *ipp); +void ipp_filters_destroy(struct ipp_filters_head *head); +bool ipp_filters_match(const struct ipp_filters_head *head, uint8_t proto); +bool ipp_filters_deny_if_empty(struct ipp_filters_head *head); struct blob_item { uint8_t *data; // main data blob diff --git a/nfq2/protocol.c b/nfq2/protocol.c index 40be021..6adc599 100644 --- a/nfq2/protocol.c +++ b/nfq2/protocol.c @@ -49,6 +49,7 @@ bool l7_proto_match(t_l7proto l7proto, uint64_t filter_l7) static const char *l7payload_name[] = { "all","unknown","empty","known", + "ipv4","ipv6", "http_req","http_reply", "tls_client_hello","tls_server_hello", "dtls_client_hello","dtls_server_hello", diff --git a/nfq2/protocol.h b/nfq2/protocol.h index cdcc655..fe10016 100644 --- a/nfq2/protocol.h +++ b/nfq2/protocol.h @@ -31,6 +31,8 @@ typedef enum { L7P_UNKNOWN, L7P_EMPTY, L7P_KNOWN, + L7P_IPV4, + L7P_IPV6, L7P_HTTP_REQ, L7P_HTTP_REPLY, L7P_TLS_CLIENT_HELLO, diff --git a/nfq2/windows/netinet/icmp6.h b/nfq2/windows/netinet/icmp6.h new file mode 100644 index 0000000..54ee8a7 --- /dev/null +++ b/nfq2/windows/netinet/icmp6.h @@ -0,0 +1,347 @@ +/* Copyright (C) 1991-2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#ifndef _NETINET_ICMP6_H +#define _NETINET_ICMP6_H 1 + +#include +#include +#include +#include + +#define ICMP6_FILTER 1 + +#define ICMP6_FILTER_BLOCK 1 +#define ICMP6_FILTER_PASS 2 +#define ICMP6_FILTER_BLOCKOTHERS 3 +#define ICMP6_FILTER_PASSONLY 4 + +struct icmp6_filter + { + uint32_t icmp6_filt[8]; + }; + +struct icmp6_hdr + { + uint8_t icmp6_type; /* type field */ + uint8_t icmp6_code; /* code field */ + uint16_t icmp6_cksum; /* checksum field */ + union + { + uint32_t icmp6_un_data32[1]; /* type-specific field */ + uint16_t icmp6_un_data16[2]; /* type-specific field */ + uint8_t icmp6_un_data8[4]; /* type-specific field */ + } icmp6_dataun; + }; + +#define icmp6_data32 icmp6_dataun.icmp6_un_data32 +#define icmp6_data16 icmp6_dataun.icmp6_un_data16 +#define icmp6_data8 icmp6_dataun.icmp6_un_data8 +#define icmp6_pptr icmp6_data32[0] /* parameter prob */ +#define icmp6_mtu icmp6_data32[0] /* packet too big */ +#define icmp6_id icmp6_data16[0] /* echo request/reply */ +#define icmp6_seq icmp6_data16[1] /* echo request/reply */ +#define icmp6_maxdelay icmp6_data16[0] /* mcast group membership */ + +#define ICMP6_DST_UNREACH 1 +#define ICMP6_PACKET_TOO_BIG 2 +#define ICMP6_TIME_EXCEEDED 3 +#define ICMP6_PARAM_PROB 4 + +#define ICMP6_INFOMSG_MASK 0x80 /* all informational messages */ + +#define ICMP6_ECHO_REQUEST 128 +#define ICMP6_ECHO_REPLY 129 +#define MLD_LISTENER_QUERY 130 +#define MLD_LISTENER_REPORT 131 +#define MLD_LISTENER_REDUCTION 132 +#define ICMPV6_EXT_ECHO_REQUEST 160 +#define ICMPV6_EXT_ECHO_REPLY 161 + +#define ICMP6_DST_UNREACH_NOROUTE 0 /* no route to destination */ +#define ICMP6_DST_UNREACH_ADMIN 1 /* communication with destination */ + /* administratively prohibited */ +#define ICMP6_DST_UNREACH_BEYONDSCOPE 2 /* beyond scope of source address */ +#define ICMP6_DST_UNREACH_ADDR 3 /* address unreachable */ +#define ICMP6_DST_UNREACH_NOPORT 4 /* bad port */ + +#define ICMP6_TIME_EXCEED_TRANSIT 0 /* Hop Limit == 0 in transit */ +#define ICMP6_TIME_EXCEED_REASSEMBLY 1 /* Reassembly time out */ + +#define ICMP6_PARAMPROB_HEADER 0 /* erroneous header field */ +#define ICMP6_PARAMPROB_NEXTHEADER 1 /* unrecognized Next Header */ +#define ICMP6_PARAMPROB_OPTION 2 /* unrecognized IPv6 option */ + +#define ICMP6_FILTER_WILLPASS(type, filterp) \ + ((((filterp)->icmp6_filt[(type) >> 5]) & (1U << ((type) & 31))) == 0) + +#define ICMP6_FILTER_WILLBLOCK(type, filterp) \ + ((((filterp)->icmp6_filt[(type) >> 5]) & (1U << ((type) & 31))) != 0) + +#define ICMP6_FILTER_SETPASS(type, filterp) \ + ((((filterp)->icmp6_filt[(type) >> 5]) &= ~(1U << ((type) & 31)))) + +#define ICMP6_FILTER_SETBLOCK(type, filterp) \ + ((((filterp)->icmp6_filt[(type) >> 5]) |= (1U << ((type) & 31)))) + +#define ICMP6_FILTER_SETPASSALL(filterp) \ + memset (filterp, 0, sizeof (struct icmp6_filter)); + +#define ICMP6_FILTER_SETBLOCKALL(filterp) \ + memset (filterp, 0xFF, sizeof (struct icmp6_filter)); + +#define ND_ROUTER_SOLICIT 133 +#define ND_ROUTER_ADVERT 134 +#define ND_NEIGHBOR_SOLICIT 135 +#define ND_NEIGHBOR_ADVERT 136 +#define ND_REDIRECT 137 + +struct nd_router_solicit /* router solicitation */ + { + struct icmp6_hdr nd_rs_hdr; + /* could be followed by options */ + }; + +#define nd_rs_type nd_rs_hdr.icmp6_type +#define nd_rs_code nd_rs_hdr.icmp6_code +#define nd_rs_cksum nd_rs_hdr.icmp6_cksum +#define nd_rs_reserved nd_rs_hdr.icmp6_data32[0] + +struct nd_router_advert /* router advertisement */ + { + struct icmp6_hdr nd_ra_hdr; + uint32_t nd_ra_reachable; /* reachable time */ + uint32_t nd_ra_retransmit; /* retransmit timer */ + /* could be followed by options */ + }; + +#define nd_ra_type nd_ra_hdr.icmp6_type +#define nd_ra_code nd_ra_hdr.icmp6_code +#define nd_ra_cksum nd_ra_hdr.icmp6_cksum +#define nd_ra_curhoplimit nd_ra_hdr.icmp6_data8[0] +#define nd_ra_flags_reserved nd_ra_hdr.icmp6_data8[1] +#define ND_RA_FLAG_MANAGED 0x80 +#define ND_RA_FLAG_OTHER 0x40 +#define ND_RA_FLAG_HOME_AGENT 0x20 +#define nd_ra_router_lifetime nd_ra_hdr.icmp6_data16[1] + +struct nd_neighbor_solicit /* neighbor solicitation */ + { + struct icmp6_hdr nd_ns_hdr; + struct in6_addr nd_ns_target; /* target address */ + /* could be followed by options */ + }; + +#define nd_ns_type nd_ns_hdr.icmp6_type +#define nd_ns_code nd_ns_hdr.icmp6_code +#define nd_ns_cksum nd_ns_hdr.icmp6_cksum +#define nd_ns_reserved nd_ns_hdr.icmp6_data32[0] + +struct nd_neighbor_advert /* neighbor advertisement */ + { + struct icmp6_hdr nd_na_hdr; + struct in6_addr nd_na_target; /* target address */ + /* could be followed by options */ + }; + +#define nd_na_type nd_na_hdr.icmp6_type +#define nd_na_code nd_na_hdr.icmp6_code +#define nd_na_cksum nd_na_hdr.icmp6_cksum +#define nd_na_flags_reserved nd_na_hdr.icmp6_data32[0] +#if __BYTE_ORDER == __BIG_ENDIAN +#define ND_NA_FLAG_ROUTER 0x80000000 +#define ND_NA_FLAG_SOLICITED 0x40000000 +#define ND_NA_FLAG_OVERRIDE 0x20000000 +#else /* __BYTE_ORDER == __LITTLE_ENDIAN */ +#define ND_NA_FLAG_ROUTER 0x00000080 +#define ND_NA_FLAG_SOLICITED 0x00000040 +#define ND_NA_FLAG_OVERRIDE 0x00000020 +#endif + +struct nd_redirect /* redirect */ + { + struct icmp6_hdr nd_rd_hdr; + struct in6_addr nd_rd_target; /* target address */ + struct in6_addr nd_rd_dst; /* destination address */ + /* could be followed by options */ + }; + +#define nd_rd_type nd_rd_hdr.icmp6_type +#define nd_rd_code nd_rd_hdr.icmp6_code +#define nd_rd_cksum nd_rd_hdr.icmp6_cksum +#define nd_rd_reserved nd_rd_hdr.icmp6_data32[0] + +struct nd_opt_hdr /* Neighbor discovery option header */ + { + uint8_t nd_opt_type; + uint8_t nd_opt_len; /* in units of 8 octets */ + /* followed by option specific data */ + }; + +#define ND_OPT_SOURCE_LINKADDR 1 +#define ND_OPT_TARGET_LINKADDR 2 +#define ND_OPT_PREFIX_INFORMATION 3 +#define ND_OPT_REDIRECTED_HEADER 4 +#define ND_OPT_MTU 5 +#define ND_OPT_RTR_ADV_INTERVAL 7 +#define ND_OPT_HOME_AGENT_INFO 8 + +struct nd_opt_prefix_info /* prefix information */ + { + uint8_t nd_opt_pi_type; + uint8_t nd_opt_pi_len; + uint8_t nd_opt_pi_prefix_len; + uint8_t nd_opt_pi_flags_reserved; + uint32_t nd_opt_pi_valid_time; + uint32_t nd_opt_pi_preferred_time; + uint32_t nd_opt_pi_reserved2; + struct in6_addr nd_opt_pi_prefix; + }; + +#define ND_OPT_PI_FLAG_ONLINK 0x80 +#define ND_OPT_PI_FLAG_AUTO 0x40 +#define ND_OPT_PI_FLAG_RADDR 0x20 + +struct nd_opt_rd_hdr /* redirected header */ + { + uint8_t nd_opt_rh_type; + uint8_t nd_opt_rh_len; + uint16_t nd_opt_rh_reserved1; + uint32_t nd_opt_rh_reserved2; + /* followed by IP header and data */ + }; + +struct nd_opt_mtu /* MTU option */ + { + uint8_t nd_opt_mtu_type; + uint8_t nd_opt_mtu_len; + uint16_t nd_opt_mtu_reserved; + uint32_t nd_opt_mtu_mtu; + }; + +struct mld_hdr + { + struct icmp6_hdr mld_icmp6_hdr; + struct in6_addr mld_addr; /* multicast address */ + }; + +#define mld_type mld_icmp6_hdr.icmp6_type +#define mld_code mld_icmp6_hdr.icmp6_code +#define mld_cksum mld_icmp6_hdr.icmp6_cksum +#define mld_maxdelay mld_icmp6_hdr.icmp6_data16[0] +#define mld_reserved mld_icmp6_hdr.icmp6_data16[1] + +#define ICMP6_ROUTER_RENUMBERING 138 + +struct icmp6_router_renum /* router renumbering header */ + { + struct icmp6_hdr rr_hdr; + uint8_t rr_segnum; + uint8_t rr_flags; + uint16_t rr_maxdelay; + uint32_t rr_reserved; + }; + +#define rr_type rr_hdr.icmp6_type +#define rr_code rr_hdr.icmp6_code +#define rr_cksum rr_hdr.icmp6_cksum +#define rr_seqnum rr_hdr.icmp6_data32[0] + +/* Router renumbering flags */ +#define ICMP6_RR_FLAGS_TEST 0x80 +#define ICMP6_RR_FLAGS_REQRESULT 0x40 +#define ICMP6_RR_FLAGS_FORCEAPPLY 0x20 +#define ICMP6_RR_FLAGS_SPECSITE 0x10 +#define ICMP6_RR_FLAGS_PREVDONE 0x08 + +struct rr_pco_match /* match prefix part */ + { + uint8_t rpm_code; + uint8_t rpm_len; + uint8_t rpm_ordinal; + uint8_t rpm_matchlen; + uint8_t rpm_minlen; + uint8_t rpm_maxlen; + uint16_t rpm_reserved; + struct in6_addr rpm_prefix; + }; + +/* PCO code values */ +#define RPM_PCO_ADD 1 +#define RPM_PCO_CHANGE 2 +#define RPM_PCO_SETGLOBAL 3 + +struct rr_pco_use /* use prefix part */ + { + uint8_t rpu_uselen; + uint8_t rpu_keeplen; + uint8_t rpu_ramask; + uint8_t rpu_raflags; + uint32_t rpu_vltime; + uint32_t rpu_pltime; + uint32_t rpu_flags; + struct in6_addr rpu_prefix; + }; + +#define ICMP6_RR_PCOUSE_RAFLAGS_ONLINK 0x20 +#define ICMP6_RR_PCOUSE_RAFLAGS_AUTO 0x10 + +#if __BYTE_ORDER == __BIG_ENDIAN +# define ICMP6_RR_PCOUSE_FLAGS_DECRVLTIME 0x80000000 +# define ICMP6_RR_PCOUSE_FLAGS_DECRPLTIME 0x40000000 +#elif __BYTE_ORDER == __LITTLE_ENDIAN +# define ICMP6_RR_PCOUSE_FLAGS_DECRVLTIME 0x80 +# define ICMP6_RR_PCOUSE_FLAGS_DECRPLTIME 0x40 +#endif + +struct rr_result /* router renumbering result message */ + { + uint16_t rrr_flags; + uint8_t rrr_ordinal; + uint8_t rrr_matchedlen; + uint32_t rrr_ifid; + struct in6_addr rrr_prefix; + }; + +#if __BYTE_ORDER == __BIG_ENDIAN +# define ICMP6_RR_RESULT_FLAGS_OOB 0x0002 +# define ICMP6_RR_RESULT_FLAGS_FORBIDDEN 0x0001 +#elif __BYTE_ORDER == __LITTLE_ENDIAN +# define ICMP6_RR_RESULT_FLAGS_OOB 0x0200 +# define ICMP6_RR_RESULT_FLAGS_FORBIDDEN 0x0100 +#endif + +/* Mobile IPv6 extension: Advertisement Interval. */ +struct nd_opt_adv_interval + { + uint8_t nd_opt_adv_interval_type; + uint8_t nd_opt_adv_interval_len; + uint16_t nd_opt_adv_interval_reserved; + uint32_t nd_opt_adv_interval_ival; + }; + +/* Mobile IPv6 extension: Home Agent Info. */ +struct nd_opt_home_agent_info + { + uint8_t nd_opt_home_agent_info_type; + uint8_t nd_opt_home_agent_info_len; + uint16_t nd_opt_home_agent_info_reserved; + uint16_t nd_opt_home_agent_info_preference; + uint16_t nd_opt_home_agent_info_lifetime; + }; + +#endif /* netinet/icmpv6.h */ diff --git a/nfq2/windows/netinet/ip_icmp.h b/nfq2/windows/netinet/ip_icmp.h new file mode 100644 index 0000000..d509714 --- /dev/null +++ b/nfq2/windows/netinet/ip_icmp.h @@ -0,0 +1,298 @@ +/* Copyright (C) 1991-2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#ifndef __NETINET_IP_ICMP_H +#define __NETINET_IP_ICMP_H 1 + +#include +#include + +__BEGIN_DECLS + +struct icmphdr +{ + uint8_t type; /* message type */ + uint8_t code; /* type sub-code */ + uint16_t checksum; + union + { + struct + { + uint16_t id; + uint16_t sequence; + } echo; /* echo datagram */ + uint32_t gateway; /* gateway address */ + struct + { + uint16_t __glibc_reserved; + uint16_t mtu; + } frag; /* path mtu discovery */ + } un; +}; + +#define ICMP_ECHOREPLY 0 /* Echo Reply */ +#define ICMP_DEST_UNREACH 3 /* Destination Unreachable */ +#define ICMP_SOURCE_QUENCH 4 /* Source Quench */ +#define ICMP_REDIRECT 5 /* Redirect (change route) */ +#define ICMP_ECHO 8 /* Echo Request */ +#define ICMP_TIME_EXCEEDED 11 /* Time Exceeded */ +#define ICMP_PARAMETERPROB 12 /* Parameter Problem */ +#define ICMP_TIMESTAMP 13 /* Timestamp Request */ +#define ICMP_TIMESTAMPREPLY 14 /* Timestamp Reply */ +#define ICMP_INFO_REQUEST 15 /* Information Request */ +#define ICMP_INFO_REPLY 16 /* Information Reply */ +#define ICMP_ADDRESS 17 /* Address Mask Request */ +#define ICMP_ADDRESSREPLY 18 /* Address Mask Reply */ +#define NR_ICMP_TYPES 18 + + +/* Codes for UNREACH. */ +#define ICMP_NET_UNREACH 0 /* Network Unreachable */ +#define ICMP_HOST_UNREACH 1 /* Host Unreachable */ +#define ICMP_PROT_UNREACH 2 /* Protocol Unreachable */ +#define ICMP_PORT_UNREACH 3 /* Port Unreachable */ +#define ICMP_FRAG_NEEDED 4 /* Fragmentation Needed/DF set */ +#define ICMP_SR_FAILED 5 /* Source Route failed */ +#define ICMP_NET_UNKNOWN 6 +#define ICMP_HOST_UNKNOWN 7 +#define ICMP_HOST_ISOLATED 8 +#define ICMP_NET_ANO 9 +#define ICMP_HOST_ANO 10 +#define ICMP_NET_UNR_TOS 11 +#define ICMP_HOST_UNR_TOS 12 +#define ICMP_PKT_FILTERED 13 /* Packet filtered */ +#define ICMP_PREC_VIOLATION 14 /* Precedence violation */ +#define ICMP_PREC_CUTOFF 15 /* Precedence cut off */ +#define NR_ICMP_UNREACH 15 /* instead of hardcoding immediate value */ + +/* Codes for REDIRECT. */ +#define ICMP_REDIR_NET 0 /* Redirect Net */ +#define ICMP_REDIR_HOST 1 /* Redirect Host */ +#define ICMP_REDIR_NETTOS 2 /* Redirect Net for TOS */ +#define ICMP_REDIR_HOSTTOS 3 /* Redirect Host for TOS */ + +/* Codes for TIME_EXCEEDED. */ +#define ICMP_EXC_TTL 0 /* TTL count exceeded */ +#define ICMP_EXC_FRAGTIME 1 /* Fragment Reass time exceeded */ + +/* Codes for ICMP_EXT_ECHO (PROBE) */ +#define ICMP_EXT_ECHO 42 +#define ICMP_EXT_ECHOREPLY 43 +#define ICMP_EXT_CODE_MAL_QUERY 1 /* Malformed Query */ +#define ICMP_EXT_CODE_NO_IF 2 /* No such Interface */ +#define ICMP_EXT_CODE_NO_TABLE_ENT 3 /* No table entry */ +#define ICMP_EXT_CODE_MULT_IFS 4 /* Multiple Interfaces Satisfy Query */ + +/* Constants for EXT_ECHO (PROBE) */ +#define ICMP_EXT_ECHOREPLY_ACTIVE (1 << 2)/* active bit in reply */ +#define ICMP_EXT_ECHOREPLY_IPV4 (1 << 1)/* ipv4 bit in reply */ +#define ICMP_EXT_ECHOREPLY_IPV6 1 /* ipv6 bit in reply */ +#define ICMP_EXT_ECHO_CTYPE_NAME 1 +#define ICMP_EXT_ECHO_CTYPE_INDEX 2 +#define ICMP_EXT_ECHO_CTYPE_ADDR 3 +#define ICMP_AFI_IP 1 /* Address Family Identifier for IPV4 */ +#define ICMP_AFI_IP6 2 /* Address Family Identifier for IPV6 */ + + +/* + * Copyright (c) 1982, 1986, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)ip_icmp.h 8.1 (Berkeley) 6/10/93 + */ + +#include +#include + +/* + * Internal of an ICMP Router Advertisement + */ +struct icmp_ra_addr +{ + uint32_t ira_addr; + uint32_t ira_preference; +}; + +struct icmp +{ + uint8_t icmp_type; /* type of message, see below */ + uint8_t icmp_code; /* type sub code */ + uint16_t icmp_cksum; /* ones complement checksum of struct */ + union + { + unsigned char ih_pptr; /* ICMP_PARAMPROB */ + struct in_addr ih_gwaddr; /* gateway address */ + struct ih_idseq /* echo datagram */ + { + uint16_t icd_id; + uint16_t icd_seq; + } ih_idseq; + uint32_t ih_void; + + /* ICMP_UNREACH_NEEDFRAG -- Path MTU Discovery (RFC1191) */ + struct ih_pmtu + { + uint16_t ipm_void; + uint16_t ipm_nextmtu; + } ih_pmtu; + + struct ih_rtradv + { + uint8_t irt_num_addrs; + uint8_t irt_wpa; + uint16_t irt_lifetime; + } ih_rtradv; + } icmp_hun; +#define icmp_pptr icmp_hun.ih_pptr +#define icmp_gwaddr icmp_hun.ih_gwaddr +#define icmp_id icmp_hun.ih_idseq.icd_id +#define icmp_seq icmp_hun.ih_idseq.icd_seq +#define icmp_void icmp_hun.ih_void +#define icmp_pmvoid icmp_hun.ih_pmtu.ipm_void +#define icmp_nextmtu icmp_hun.ih_pmtu.ipm_nextmtu +#define icmp_num_addrs icmp_hun.ih_rtradv.irt_num_addrs +#define icmp_wpa icmp_hun.ih_rtradv.irt_wpa +#define icmp_lifetime icmp_hun.ih_rtradv.irt_lifetime + union + { + struct + { + uint32_t its_otime; + uint32_t its_rtime; + uint32_t its_ttime; + } id_ts; + struct + { + struct ip idi_ip; + /* options and then 64 bits of data */ + } id_ip; + struct icmp_ra_addr id_radv; + uint32_t id_mask; + uint8_t id_data[1]; + } icmp_dun; +#define icmp_otime icmp_dun.id_ts.its_otime +#define icmp_rtime icmp_dun.id_ts.its_rtime +#define icmp_ttime icmp_dun.id_ts.its_ttime +#define icmp_ip icmp_dun.id_ip.idi_ip +#define icmp_radv icmp_dun.id_radv +#define icmp_mask icmp_dun.id_mask +#define icmp_data icmp_dun.id_data +}; + +/* + * Lower bounds on packet lengths for various types. + * For the error advice packets must first insure that the + * packet is large enough to contain the returned ip header. + * Only then can we do the check to see if 64 bits of packet + * data have been returned, since we need to check the returned + * ip header length. + */ +#define ICMP_MINLEN 8 /* abs minimum */ +#define ICMP_TSLEN (8 + 3 * sizeof (n_time)) /* timestamp */ +#define ICMP_MASKLEN 12 /* address mask */ +#define ICMP_ADVLENMIN (8 + sizeof (struct ip) + 8) /* min */ +#ifndef _IP_VHL +#define ICMP_ADVLEN(p) (8 + ((p)->icmp_ip.ip_hl << 2) + 8) + /* N.B.: must separately check that ip_hl >= 5 */ +#else +#define ICMP_ADVLEN(p) (8 + (IP_VHL_HL((p)->icmp_ip.ip_vhl) << 2) + 8) + /* N.B.: must separately check that header length >= 5 */ +#endif + +/* Definition of type and code fields. */ +/* defined above: ICMP_ECHOREPLY, ICMP_REDIRECT, ICMP_ECHO */ +#define ICMP_UNREACH 3 /* dest unreachable, codes: */ +#define ICMP_SOURCEQUENCH 4 /* packet lost, slow down */ +#define ICMP_ROUTERADVERT 9 /* router advertisement */ +#define ICMP_ROUTERSOLICIT 10 /* router solicitation */ +#define ICMP_TIMXCEED 11 /* time exceeded, code: */ +#define ICMP_PARAMPROB 12 /* ip header bad */ +#define ICMP_TSTAMP 13 /* timestamp request */ +#define ICMP_TSTAMPREPLY 14 /* timestamp reply */ +#define ICMP_IREQ 15 /* information request */ +#define ICMP_IREQREPLY 16 /* information reply */ +#define ICMP_MASKREQ 17 /* address mask request */ +#define ICMP_MASKREPLY 18 /* address mask reply */ + +#define ICMP_MAXTYPE 18 + +/* UNREACH codes */ +#define ICMP_UNREACH_NET 0 /* bad net */ +#define ICMP_UNREACH_HOST 1 /* bad host */ +#define ICMP_UNREACH_PROTOCOL 2 /* bad protocol */ +#define ICMP_UNREACH_PORT 3 /* bad port */ +#define ICMP_UNREACH_NEEDFRAG 4 /* IP_DF caused drop */ +#define ICMP_UNREACH_SRCFAIL 5 /* src route failed */ +#define ICMP_UNREACH_NET_UNKNOWN 6 /* unknown net */ +#define ICMP_UNREACH_HOST_UNKNOWN 7 /* unknown host */ +#define ICMP_UNREACH_ISOLATED 8 /* src host isolated */ +#define ICMP_UNREACH_NET_PROHIB 9 /* net denied */ +#define ICMP_UNREACH_HOST_PROHIB 10 /* host denied */ +#define ICMP_UNREACH_TOSNET 11 /* bad tos for net */ +#define ICMP_UNREACH_TOSHOST 12 /* bad tos for host */ +#define ICMP_UNREACH_FILTER_PROHIB 13 /* admin prohib */ +#define ICMP_UNREACH_HOST_PRECEDENCE 14 /* host prec vio. */ +#define ICMP_UNREACH_PRECEDENCE_CUTOFF 15 /* prec cutoff */ + +/* REDIRECT codes */ +#define ICMP_REDIRECT_NET 0 /* for network */ +#define ICMP_REDIRECT_HOST 1 /* for host */ +#define ICMP_REDIRECT_TOSNET 2 /* for tos and net */ +#define ICMP_REDIRECT_TOSHOST 3 /* for tos and host */ + +/* TIMEXCEED codes */ +#define ICMP_TIMXCEED_INTRANS 0 /* ttl==0 in transit */ +#define ICMP_TIMXCEED_REASS 1 /* ttl==0 in reass */ + +/* PARAMPROB code */ +#define ICMP_PARAMPROB_OPTABSENT 1 /* req. opt. absent */ + +#define ICMP_INFOTYPE(type) \ + ((type) == ICMP_ECHOREPLY || (type) == ICMP_ECHO \ + || (type) == ICMP_ROUTERADVERT || (type) == ICMP_ROUTERSOLICIT \ + || (type) == ICMP_TSTAMP || (type) == ICMP_TSTAMPREPLY \ + || (type) == ICMP_IREQ || (type) == ICMP_IREQREPLY \ + || (type) == ICMP_MASKREQ || (type) == ICMP_MASKREPLY) + + +__END_DECLS + +#endif /* netinet/ip_icmp.h */ diff --git a/nfq2/windows/nthacks.h b/nfq2/windows/nthacks.h new file mode 100644 index 0000000..cd482f6 --- /dev/null +++ b/nfq2/windows/nthacks.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +#define DIRECTORY_QUERY (0x0001) +#define DIRECTORY_TRAVERSE (0x0002) +#define DIRECTORY_CREATE_OBJECT (0x0004) +#define DIRECTORY_CREATE_SUBDIRECTORY (0x0008) +#define DIRECTORY_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | 0xF) + +typedef struct _PROCESS_SESSION_INFORMATION { + ULONG SessionId; +} PROCESS_SESSION_INFORMATION, *PPROCESS_SESSION_INFORMATION; + +typedef struct _OBJECT_DIRECTORY_INFORMATION { + UNICODE_STRING Name; + UNICODE_STRING TypeName; +} OBJECT_DIRECTORY_INFORMATION, *POBJECT_DIRECTORY_INFORMATION; + +#ifdef __cplusplus +extern "C" +{ +#endif + NTSTATUS NTAPI NtOpenDirectoryObject( + _Out_ PHANDLE DirectoryHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + NTSTATUS NTAPI NtQueryDirectoryObject( + _In_ HANDLE DirectoryHandle, + _Out_opt_ PVOID Buffer, + _In_ ULONG Length, + _In_ BOOLEAN ReturnSingleEntry, + _In_ BOOLEAN RestartScan, + _Inout_ PULONG Context, + _Out_opt_ PULONG ReturnLength + ); +#ifdef __cplusplus +}; +#endif diff --git a/nfq2/windows/res/howto.txt b/nfq2/windows/res/howto.txt new file mode 100644 index 0000000..b467934 --- /dev/null +++ b/nfq2/windows/res/howto.txt @@ -0,0 +1,4 @@ +# in 64 bit cygwin +windres winws.rc -O coff -o winws_res64.o +# in 32 bit cygwin +windres winws.rc -O coff -o winws_res32.o diff --git a/nfq2/windows/res/winws.ico b/nfq2/windows/res/winws.ico new file mode 100644 index 0000000000000000000000000000000000000000..62152beaea22edcd1a7564b42bafa1fd6326a24b GIT binary patch literal 29109 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 bkcmLt2c$r_2Z$RS7#I#bWMFvsnSlWS>Yg{b literal 0 HcmV?d00001 diff --git a/nfq2/windows/res/winws.manifest b/nfq2/windows/res/winws.manifest new file mode 100644 index 0000000..f702d38 --- /dev/null +++ b/nfq2/windows/res/winws.manifest @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nfq2/windows/res/winws.rc b/nfq2/windows/res/winws.rc new file mode 100644 index 0000000..ac104f4 --- /dev/null +++ b/nfq2/windows/res/winws.rc @@ -0,0 +1,3 @@ +LANGUAGE 0,0 +1 ICON "winws.ico" +1 24 "winws.manifest" diff --git a/nfq2/windows/res/winws_res32.o b/nfq2/windows/res/winws_res32.o new file mode 100644 index 0000000000000000000000000000000000000000..10703289c79bc3b0cb1dfebc76f643a84bebbac5 GIT binary patch literal 30830 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[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 literal 0 HcmV?d00001