From 9629ce5cb78fcc734776a3ed731d96aeb29f47a3 Mon Sep 17 00:00:00 2001 From: bol-van Date: Sun, 4 May 2025 10:42:47 +0300 Subject: [PATCH] tpws: ipcache --- nfq/pools.c | 2 +- tpws/params.h | 6 ++ tpws/pools.c | 275 ++++++++++++++++++++++++++++++++++++++++++++++++++ tpws/pools.h | 53 ++++++++++ tpws/tamper.c | 60 ++++++++++- tpws/tamper.h | 1 + tpws/tpws.c | 48 +++++++-- 7 files changed, 432 insertions(+), 13 deletions(-) diff --git a/nfq/pools.c b/nfq/pools.c index d07f994e..ed104074 100644 --- a/nfq/pools.c +++ b/nfq/pools.c @@ -550,7 +550,7 @@ struct blob_item *blob_collection_add_blob(struct blob_collection_head *head, co if (data) memcpy(entry->data,data,size); entry->size = size; entry->size_buf = size+size_reserve; - + // insert to the end struct blob_item *itemc,*iteml=LIST_FIRST(head); if (iteml) diff --git a/tpws/params.h b/tpws/params.h index 67a357b8..50345860 100644 --- a/tpws/params.h +++ b/tpws/params.h @@ -20,6 +20,8 @@ #define FIX_SEG_DEFAULT_MAX_WAIT 50 +#define IPCACHE_LIFETIME 7200 + enum bindll { unwanted=0, no, prefer, force }; #define MAX_BINDS 32 @@ -140,6 +142,10 @@ struct params_s bool tamper; // any tamper option is set bool tamper_lim; // tamper-start or tamper-cutoff set in any profile struct desync_profile_list_head desync_profiles; + + unsigned int ipcache_lifetime; + bool cache_hostname; + ip_cache ipcache; }; extern struct params_s params; diff --git a/tpws/pools.c b/tpws/pools.c index aca40245..4b19ac75 100644 --- a/tpws/pools.c +++ b/tpws/pools.c @@ -3,6 +3,7 @@ #include #include #include +#include #define DESTROY_STR_POOL(etype, ppool) \ etype *elem, *tmp; \ @@ -517,3 +518,277 @@ bool port_filters_deny_if_empty(struct port_filters_head *head) if (LIST_FIRST(head)) return true; return pf_parse("0",&pf) && port_filter_add(head,&pf); } + + + +struct blob_item *blob_collection_add(struct blob_collection_head *head) +{ + struct blob_item *entry = calloc(1,sizeof(struct blob_item)); + if (entry) + { + // insert to the end + struct blob_item *itemc,*iteml=LIST_FIRST(head); + if (iteml) + { + while ((itemc=LIST_NEXT(iteml,next))) iteml = itemc; + LIST_INSERT_AFTER(iteml, entry, next); + } + else + LIST_INSERT_HEAD(head, entry, next); + } + return entry; +} +struct blob_item *blob_collection_add_blob(struct blob_collection_head *head, const void *data, size_t size, size_t size_reserve) +{ + struct blob_item *entry = calloc(1,sizeof(struct blob_item)); + if (!entry) return NULL; + if (!(entry->data = malloc(size+size_reserve))) + { + free(entry); + return NULL; + } + if (data) memcpy(entry->data,data,size); + entry->size = size; + entry->size_buf = size+size_reserve; + + // insert to the end + struct blob_item *itemc,*iteml=LIST_FIRST(head); + if (iteml) + { + while ((itemc=LIST_NEXT(iteml,next))) iteml = itemc; + LIST_INSERT_AFTER(iteml, entry, next); + } + else + LIST_INSERT_HEAD(head, entry, next); + + return entry; +} + +void blob_collection_destroy(struct blob_collection_head *head) +{ + struct blob_item *entry; + while ((entry = LIST_FIRST(head))) + { + LIST_REMOVE(entry, next); + free(entry->extra); + free(entry->extra2); + free(entry->data); + free(entry); + } +} +bool blob_collection_empty(const struct blob_collection_head *head) +{ + return !LIST_FIRST(head); +} + + + +static void ipcache_item_touch(ip_cache_item *item) +{ + time(&item->last); +} +static void ipcache_item_init(ip_cache_item *item) +{ + ipcache_item_touch(item); + item->hostname = NULL; +} +static void ipcache_item_destroy(ip_cache_item *item) +{ + free(item->hostname); +} + +static void ipcache4Destroy(ip_cache4 **ipcache) +{ + ip_cache4 *elem, *tmp; + HASH_ITER(hh, *ipcache, elem, tmp) + { + HASH_DEL(*ipcache, elem); + ipcache_item_destroy(&elem->data); + free(elem); + } +} +static void ipcache4Key(ip4if *key, const struct in_addr *a) +{ + memset(key,0,sizeof(*key)); // make sure everything is zero + key->addr = *a; +} +static ip_cache4 *ipcache4Find(ip_cache4 *ipcache, const struct in_addr *a) +{ + ip_cache4 *entry; + struct ip4if key; + + ipcache4Key(&key,a); + HASH_FIND(hh, ipcache, &key, sizeof(key), entry); + return entry; +} +static ip_cache4 *ipcache4Add(ip_cache4 **ipcache, const struct in_addr *a) +{ + // avoid dups + ip_cache4 *entry = ipcache4Find(*ipcache,a); + if (entry) return entry; // already included + + entry = malloc(sizeof(ip_cache4)); + if (!entry) return NULL; + ipcache4Key(&entry->key,a); + + oom = false; + HASH_ADD(hh, *ipcache, key, sizeof(entry->key), entry); + if (oom) { free(entry); return NULL; } + + ipcache_item_init(&entry->data); + + return entry; +} +static void ipcache4Print(ip_cache4 *ipcache) +{ + char s_ip[16]; + time_t now; + ip_cache4 *ipc, *tmp; + + time(&now); + HASH_ITER(hh, ipcache , ipc, tmp) + { + *s_ip=0; + inet_ntop(AF_INET, &ipc->key.addr, s_ip, sizeof(s_ip)); + printf("%s : hostname=%s now=last+%llu\n", s_ip, ipc->data.hostname ? ipc->data.hostname : "", (unsigned long long)(now-ipc->data.last)); + } +} + +static void ipcache6Destroy(ip_cache6 **ipcache) +{ + ip_cache6 *elem, *tmp; + HASH_ITER(hh, *ipcache, elem, tmp) + { + HASH_DEL(*ipcache, elem); + ipcache_item_destroy(&elem->data); + free(elem); + } +} +static void ipcache6Key(ip6if *key, const struct in6_addr *a) +{ + memset(key,0,sizeof(*key)); // make sure everything is zero + key->addr = *a; +} +static ip_cache6 *ipcache6Find(ip_cache6 *ipcache, const struct in6_addr *a) +{ + ip_cache6 *entry; + ip6if key; + + ipcache6Key(&key,a); + HASH_FIND(hh, ipcache, &key, sizeof(key), entry); + return entry; +} +static ip_cache6 *ipcache6Add(ip_cache6 **ipcache, const struct in6_addr *a) +{ + // avoid dups + ip_cache6 *entry = ipcache6Find(*ipcache,a); + if (entry) return entry; // already included + + entry = malloc(sizeof(ip_cache6)); + if (!entry) return NULL; + ipcache6Key(&entry->key,a); + + oom = false; + HASH_ADD(hh, *ipcache, key, sizeof(entry->key), entry); + if (oom) { free(entry); return NULL; } + + ipcache_item_init(&entry->data); + + return entry; +} +static void ipcache6Print(ip_cache6 *ipcache) +{ + char s_ip[40]; + time_t now; + ip_cache6 *ipc, *tmp; + + time(&now); + HASH_ITER(hh, ipcache , ipc, tmp) + { + *s_ip=0; + inet_ntop(AF_INET6, &ipc->key.addr, s_ip, sizeof(s_ip)); + printf("%s : hostname=%s now=last+%llu\n", s_ip, ipc->data.hostname ? ipc->data.hostname : "", (unsigned long long)(now-ipc->data.last)); + } +} + +void ipcacheDestroy(ip_cache *ipcache) +{ + ipcache4Destroy(&ipcache->ipcache4); + ipcache6Destroy(&ipcache->ipcache6); +} +void ipcachePrint(ip_cache *ipcache) +{ + ipcache4Print(ipcache->ipcache4); + ipcache6Print(ipcache->ipcache6); +} + +ip_cache_item *ipcacheTouch(ip_cache *ipcache, const struct in_addr *a4, const struct in6_addr *a6) +{ + ip_cache4 *ipcache4; + ip_cache6 *ipcache6; + if (a4) + { + if ((ipcache4 = ipcache4Add(&ipcache->ipcache4,a4))) + { + ipcache_item_touch(&ipcache4->data); + return &ipcache4->data; + } + } + else if (a6) + { + if ((ipcache6 = ipcache6Add(&ipcache->ipcache6,a6))) + { + ipcache_item_touch(&ipcache6->data); + return &ipcache6->data; + } + } + return NULL; +} + +static void ipcache4_purge(ip_cache4 **ipcache, time_t lifetime) +{ + ip_cache4 *elem, *tmp; + time_t now = time(NULL); + HASH_ITER(hh, *ipcache, elem, tmp) + { + if (now >= (elem->data.last + lifetime)) + { + HASH_DEL(*ipcache, elem); + ipcache_item_destroy(&elem->data); + free(elem); + } + } +} +static void ipcache6_purge(ip_cache6 **ipcache, time_t lifetime) +{ + ip_cache6 *elem, *tmp; + time_t now = time(NULL); + HASH_ITER(hh, *ipcache, elem, tmp) + { + if (now >= (elem->data.last + lifetime)) + { + HASH_DEL(*ipcache, elem); + ipcache_item_destroy(&elem->data); + free(elem); + } + } +} +static void ipcache_purge(ip_cache *ipcache, time_t lifetime) +{ + if (lifetime) // 0 = no expire + { + ipcache4_purge(&ipcache->ipcache4, lifetime); + ipcache6_purge(&ipcache->ipcache6, lifetime); + } +} +static time_t ipcache_purge_prev=0; +void ipcachePurgeRateLimited(ip_cache *ipcache, time_t lifetime) +{ + time_t now = time(NULL); + // do not purge too often to save resources + if (ipcache_purge_prev != now) + { + ipcache_purge(ipcache, lifetime); + ipcache_purge_prev = now; + } +} diff --git a/tpws/pools.h b/tpws/pools.h index 32190c19..e127a048 100644 --- a/tpws/pools.h +++ b/tpws/pools.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "helpers.h" @@ -146,3 +147,55 @@ 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_deny_if_empty(struct port_filters_head *head); + + +struct blob_item { + uint8_t *data; // main data blob + size_t size; // main data blob size + size_t size_buf;// main data blob allocated size + void *extra; // any data without size + void *extra2; // any data without size + LIST_ENTRY(blob_item) next; +}; +LIST_HEAD(blob_collection_head, blob_item); +struct blob_item *blob_collection_add(struct blob_collection_head *head); +struct blob_item *blob_collection_add_blob(struct blob_collection_head *head, const void *data, size_t size, size_t size_reserve); +void blob_collection_destroy(struct blob_collection_head *head); +bool blob_collection_empty(const struct blob_collection_head *head); + + +typedef struct ip4if +{ + struct in_addr addr; +} ip4if; +typedef struct ip6if +{ + struct in6_addr addr; +} ip6if; +typedef struct ip_cache_item +{ + time_t last; + char *hostname; +} ip_cache_item; +typedef struct ip_cache4 +{ + ip4if key; + ip_cache_item data; + UT_hash_handle hh; /* makes this structure hashable */ +} ip_cache4; +typedef struct ip_cache6 +{ + ip6if key; + ip_cache_item data; + UT_hash_handle hh; /* makes this structure hashable */ +} ip_cache6; +typedef struct ip_cache +{ + ip_cache4 *ipcache4; + ip_cache6 *ipcache6; +} ip_cache; + +ip_cache_item *ipcacheTouch(ip_cache *ipcache, const struct in_addr *a4, const struct in6_addr *a6); +void ipcachePurgeRateLimited(ip_cache *ipcache, time_t lifetime); +void ipcacheDestroy(ip_cache *ipcache); +void ipcachePrint(ip_cache *ipcache); diff --git a/tpws/tamper.c b/tpws/tamper.c index 79e162d5..326445df 100644 --- a/tpws/tamper.c +++ b/tpws/tamper.c @@ -7,6 +7,7 @@ #include "ipset.h" #include "protocol.h" #include "helpers.h" +#include "pools.h" #define PKTDATA_MAXDUMP 32 @@ -90,6 +91,48 @@ static void TLSDebug(const uint8_t *tls,size_t sz) TLSDebugHandshake(tls+5,sz-5); } +static bool ipcache_put_hostname(const struct in_addr *a4, const struct in6_addr *a6, const char *hostname) +{ + if (!params.cache_hostname) return true; + + ip_cache_item *ipc = ipcacheTouch(¶ms.ipcache,a4,a6); + if (!ipc) + { + DLOG_ERR("ipcache_put_hostname: out of memory\n"); + return false; + } + free(ipc->hostname); + if (!(ipc->hostname = strdup(hostname))) + { + DLOG_ERR("ipcache_put_hostname: out of memory\n"); + return false; + } + VPRINT("hostname cached: %s\n", hostname); + return true; +} +static bool ipcache_get_hostname(const struct in_addr *a4, const struct in6_addr *a6, char *hostname, size_t hostname_buf_len) +{ + if (!params.cache_hostname) + { + *hostname = 0; + return true; + } + ip_cache_item *ipc = ipcacheTouch(¶ms.ipcache,a4,a6); + if (!ipc) + { + DLOG_ERR("ipcache_get_hostname: out of memory\n"); + return false; + } + if (ipc->hostname) + { + VPRINT("got cached hostname: %s\n", ipc->hostname); + snprintf(hostname,hostname_buf_len,"%s",ipc->hostname); + } + else + *hostname = 0; + return true; +} + static bool dp_match(struct desync_profile *dp, const struct sockaddr *dest, const char *hostname, t_l7proto l7proto) { bool bHostlistsEmpty; @@ -145,8 +188,17 @@ static struct desync_profile *dp_find(struct desync_profile_list_head *head, con VPRINT("desync profile not found\n"); return NULL; } + void apply_desync_profile(t_ctrack *ctrack, const struct sockaddr *dest) { + ipcachePurgeRateLimited(¶ms.ipcache, params.ipcache_lifetime); + if (!ctrack->hostname) + { + char host[256]; + if (ipcache_get_hostname(dest->sa_family==AF_INET ? &((struct sockaddr_in*)dest)->sin_addr : NULL, dest->sa_family==AF_INET6 ? &((struct sockaddr_in6*)dest)->sin6_addr : NULL , host, sizeof(host)) && *host) + if (!(ctrack->hostname=strdup(host))) + DLOG_ERR("hostname dup : out of memory"); + } ctrack->dp = dp_find(¶ms.desync_profiles, dest, ctrack->hostname, ctrack->l7proto); } @@ -215,7 +267,11 @@ void tamper_out(t_ctrack *ctrack, const struct sockaddr *dest, uint8_t *segment, } if (bHaveHost) + { VPRINT("request hostname: %s\n", Host); + if (!ipcache_put_hostname(dest->sa_family==AF_INET ? &((struct sockaddr_in*)dest)->sin_addr : NULL, dest->sa_family==AF_INET6 ? &((struct sockaddr_in6*)dest)->sin6_addr : NULL , Host)) + DLOG_ERR("ipcache_put_hostname: out of memory"); + } bool bDiscoveredL7 = ctrack->l7proto==UNKNOWN && l7proto!=UNKNOWN; if (bDiscoveredL7) @@ -224,15 +280,17 @@ void tamper_out(t_ctrack *ctrack, const struct sockaddr *dest, uint8_t *segment, ctrack->l7proto=l7proto; } - bool bDiscoveredHostname = bHaveHost && !ctrack->hostname; + bool bDiscoveredHostname = bHaveHost && !ctrack->hostname_discovered; if (bDiscoveredHostname) { VPRINT("discovered hostname\n"); + free(ctrack->hostname); if (!(ctrack->hostname=strdup(Host))) { DLOG_ERR("strdup hostname : out of memory\n"); return; } + ctrack->hostname_discovered = true; } if (bDiscoveredL7 || bDiscoveredHostname) diff --git a/tpws/tamper.h b/tpws/tamper.h index eb47522d..e0eb218d 100644 --- a/tpws/tamper.h +++ b/tpws/tamper.h @@ -15,6 +15,7 @@ typedef struct t_l7proto l7proto; bool bTamperInCutoff; bool b_host_checked,b_host_matches,b_ah_check; + bool hostname_discovered; char *hostname; struct desync_profile *dp; // desync profile cache } t_ctrack; diff --git a/tpws/tpws.c b/tpws/tpws.c index 1fb3cc70..0a353bce 100644 --- a/tpws/tpws.c +++ b/tpws/tpws.c @@ -88,6 +88,12 @@ static void onusr2(int sig) HostFailPoolDump(dpl->dp.hostlist_auto_fail_counters); } + if (params.cache_hostname) + { + printf("\nIPCACHE\n"); + ipcachePrint(¶ms.ipcache); + } + printf("\n"); } @@ -215,6 +221,8 @@ static void exithelp(void) #if defined(__linux__) " --fix-seg=\t\t\t; fix segmentation failures at the cost of possible slowdown. wait up to N msec (default %u)\n" #endif + " --ipcache-lifetime=\t\t; time in seconds to keep cached domain name (default %u). 0 = no expiration\n" + " --ipcache-hostname=[0|1]\t\t; 1 or no argument enables ip->hostname caching\n" " --debug=0|1|2|syslog|@\t; 1 and 2 means log to console and set debug level. for other targets use --debug-level.\n" " --debug-level=0|1|2\t\t\t; specify debug level\n" " --dry-run\t\t\t\t; verify parameters and exit with code 0 if successful\n" @@ -272,6 +280,7 @@ static void exithelp(void) #ifdef __linux__ FIX_SEG_DEFAULT_MAX_WAIT, #endif + IPCACHE_LIFETIME, HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT, HOSTLIST_AUTO_FAIL_TIME_DEFAULT ); exit(1); @@ -292,6 +301,7 @@ static void cleanup_params(void) hostlist_files_destroy(¶ms.hostlists); ipset_files_destroy(¶ms.ipsets); + ipcacheDestroy(¶ms.ipcache); } static void exithelp_clean(void) { @@ -628,6 +638,8 @@ enum opt_indices { IDX_MAXCONN, IDX_MAXFILES, IDX_MAX_ORPHAN_TIME, + IDX_IPCACHE_LIFETIME, + IDX_IPCACHE_HOSTNAME, IDX_HOSTCASE, IDX_HOSTSPELL, IDX_HOSTDOT, @@ -718,6 +730,8 @@ static const struct option long_options[] = { [IDX_UID] = {"uid", required_argument, 0, 0}, [IDX_MAXCONN] = {"maxconn", required_argument, 0, 0}, [IDX_MAXFILES] = {"maxfiles", required_argument, 0, 0}, + [IDX_IPCACHE_LIFETIME] = {"ipcache-lifetime", required_argument, 0, 0}, + [IDX_IPCACHE_HOSTNAME] = {"ipcache-hostname", optional_argument, 0, 0}, [IDX_MAX_ORPHAN_TIME] = {"max-orphan-time", required_argument, 0, 0}, [IDX_HOSTCASE] = {"hostcase", no_argument, 0, 0}, [IDX_HOSTSPELL] = {"hostspell", required_argument, 0, 0}, @@ -804,6 +818,7 @@ void parse_params(int argc, char *argv[]) params.maxconn = DEFAULT_MAX_CONN; params.max_orphan_time = DEFAULT_MAX_ORPHAN_TIME; params.binds_last = -1; + params.ipcache_lifetime = IPCACHE_LIFETIME; #if defined(__linux__) || defined(__APPLE__) params.tcp_user_timeout_local = DEFAULT_TCP_USER_TIMEOUT_LOCAL; params.tcp_user_timeout_remote = DEFAULT_TCP_USER_TIMEOUT_REMOTE; @@ -976,6 +991,16 @@ void parse_params(int argc, char *argv[]) exit_clean(1); } break; + case IDX_IPCACHE_LIFETIME: + if (sscanf(optarg, "%u", ¶ms.ipcache_lifetime)!=1) + { + DLOG_ERR("invalid ipcache-lifetime value\n"); + exit_clean(1); + } + break; + case IDX_IPCACHE_HOSTNAME: + params.cache_hostname = !optarg || !!atoi(optarg); + break; case IDX_HOSTCASE: dp->hostcase = true; params.tamper = true; @@ -1815,14 +1840,6 @@ int main(int argc, char *argv[]) parse_params(argc, argv); argv=NULL; argc=0; - if (params.daemon) daemonize(); - - if (*params.pidfile && !writepid(params.pidfile)) - { - DLOG_ERR("could not write pidfile\n"); - goto exiterr; - } - memset(&list, 0, sizeof(list)); for(i=0;i<=params.binds_last;i++) listen_fd[i]=-1; @@ -2054,6 +2071,18 @@ int main(int argc, char *argv[]) } } + if (params.cache_hostname) VPRINT("ipcache lifetime %us\n", params.ipcache_lifetime); + DLOG_CONDUP(params.proxy_type==CONN_TYPE_SOCKS ? "socks mode\n" : "transparent proxy mode\n"); + if (!params.tamper) DLOG_CONDUP("TCP proxy mode (no tampering)\n"); + + if (params.daemon) daemonize(); + + if (*params.pidfile && !writepid(params.pidfile)) + { + DLOG_ERR("could not write pidfile\n"); + goto exiterr; + } + set_ulimit(); sec_harden(); if (params.droproot && !droproot(params.uid,params.gid)) @@ -2074,9 +2103,6 @@ int main(int argc, char *argv[]) goto exiterr; } - DLOG_CONDUP(params.proxy_type==CONN_TYPE_SOCKS ? "socks mode\n" : "transparent proxy mode\n"); - if (!params.tamper) DLOG_CONDUP("TCP proxy mode (no tampering)\n"); - signal(SIGHUP, onhup); signal(SIGUSR2, onusr2);