From ceab392c600bb6c9d587ab6897121d1f55b54511 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Thu, 5 Feb 2026 12:54:31 +0000 Subject: [PATCH] Improve memory allocation for /etc/hosts etc. Clearing and reloading the DNS cache involved freeing and re-allocating many small memory blocks. Doing this frequently with a significant number of static DNS configuraions or DHCP leases could result in excessive heap fragmentation and memory use. This patch extends the pool memory allocation technique used else where to this memory. On cache clear, the memory is not freed, but added to a pool of useable but unused data structures which get re-used to store the new data. --- src/cache.c | 248 ++++++++++++++++++++++++++++++--------------------- src/config.h | 1 + 2 files changed, 147 insertions(+), 102 deletions(-) diff --git a/src/cache.c b/src/cache.c index cb80cde..abab9ed 100644 --- a/src/cache.c +++ b/src/cache.c @@ -17,13 +17,16 @@ #include "dnsmasq.h" static struct crec *cache_head = NULL, *cache_tail = NULL, **hash_table = NULL; -#ifdef HAVE_DHCP -static struct crec *dhcp_spare = NULL; -#endif +static struct crec *config_spare = NULL; static struct crec *new_chain = NULL; static int insert_error; static union bigname *big_free = NULL; static int bignames_left, hash_size; +struct nameblock { + struct nameblock *next; + unsigned int last, index; + char data[NAMEBLOCK_CHARS]; +} *hostblocks = NULL; static void make_non_terminals(struct crec *source); static struct crec *really_insert(char *name, union all_addr *addr, unsigned short class, @@ -192,6 +195,61 @@ void cache_init(void) rehash(daemon->cachesize); } +static struct crec *get_config_crec(void) +{ + struct crec *ret; + + if (config_spare) + { + ret = config_spare; + config_spare = config_spare->next; + } + else + ret = whine_malloc(SIZEOF_POINTER_CREC); + + return ret; +} + +static void free_config_crec(struct crec *p) +{ + p->next = config_spare; + config_spare = p; +} + +static char *store_name(unsigned int namelen, unsigned int index) +{ + struct nameblock *block; + char *ret = NULL; + + for (block = hostblocks; block; block = block->next) + if (block->index == index && NAMEBLOCK_CHARS - block->last >= namelen) + break; + + if (!block && ((block = whine_malloc(sizeof(struct nameblock))))) + { + block->next = hostblocks; + block->index = index; + hostblocks = block; + } + + if (block && NAMEBLOCK_CHARS - block->last >= namelen) + { + ret = &block->data[block->last]; + block->last += namelen; + } + + return ret; +} + +static void free_names(unsigned int index) +{ + struct nameblock *block; + + for (block = hostblocks; block; block = block->next) + if (index == UID_NONE || block->index == index) + block->last = 0; +} + /* In most cases, we create the hash table once here by calling this with (hash_table == NULL) but if the hosts file(s) are big (some people have 50000 ad-block entries), the table will be much too small, so the hosts reading code calls rehash every 1000 addresses, to @@ -441,12 +499,14 @@ unsigned int cache_remove_uid(const unsigned int uid) if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) && crecp->uid == uid) { *up = tmp; - free(crecp); + free_config_crec(crecp); removed++; } else up = &crecp->hash_next; } + + free_names(uid); return removed; } @@ -1261,7 +1321,7 @@ static void add_hosts_entry(struct crec *cache, union all_addr *addr, int addrle while ((lookup = cache_find_by_name(lookup, cache_get_name(cache), 0, cache->flags & (F_IPV4 | F_IPV6)))) if ((lookup->flags & F_HOSTS) && memcmp(&lookup->addr, addr, addrlen) == 0) { - free(cache); + free_config_crec(cache); return; } @@ -1384,13 +1444,13 @@ int read_hostsfile(char *filename, unsigned int index, int cache_size, struct cr { if (inet_pton(AF_INET, token, &addr) > 0) { - flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4; + flags = F_NAMEP | F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4; addrlen = INADDRSZ; domain_suffix = get_domain(addr.addr4); } else if (inet_pton(AF_INET6, token, &addr) > 0) { - flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV6; + flags = F_NAMEP | F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV6; addrlen = IN6ADDRSZ; domain_suffix = get_domain6(&addr.addr6); } @@ -1424,27 +1484,37 @@ int read_hostsfile(char *filename, unsigned int index, int cache_size, struct cr if ((canon = canonicalise(token, &nomem))) { /* If set, add a version of the name with a default domain appended */ - if (option_bool(OPT_EXPAND) && domain_suffix && !fqdn && - (cache = whine_malloc(SIZEOF_BARE_CREC + strlen(canon) + 2 + strlen(domain_suffix)))) + if (option_bool(OPT_EXPAND) && domain_suffix && !fqdn && (cache = get_config_crec())) { - strcpy(cache->name.sname, canon); - strcat(cache->name.sname, "."); - strcat(cache->name.sname, domain_suffix); - cache->flags = flags; - cache->ttd = daemon->local_ttl; - add_hosts_entry(cache, &addr, addrlen, index, rhash, hashsz); - name_count++; - names_done++; + if (!(cache->name.namep = store_name(strlen(canon) + 2 + strlen(domain_suffix), index))) + free_config_crec(cache); + else + { + strcpy(cache->name.namep, canon); + strcat(cache->name.namep, "."); + strcat(cache->name.namep, domain_suffix); + cache->flags = flags; + cache->ttd = daemon->local_ttl; + add_hosts_entry(cache, &addr, addrlen, index, rhash, hashsz); + name_count++; + names_done++; + } } - if ((cache = whine_malloc(SIZEOF_BARE_CREC + strlen(canon) + 1))) + if ((cache = get_config_crec())) { - strcpy(cache->name.sname, canon); - cache->flags = flags; - cache->ttd = daemon->local_ttl; - add_hosts_entry(cache, &addr, addrlen, index, rhash, hashsz); - name_count++; - names_done++; + if (!(cache->name.namep = store_name(strlen(canon) + 1, index))) + free_config_crec(cache); + else + { + strcpy(cache->name.namep, canon); + cache->flags = flags; + cache->ttd = daemon->local_ttl; + add_hosts_entry(cache, &addr, addrlen, index, rhash, hashsz); + name_count++; + names_done++; + } } + free(canon); } @@ -1495,7 +1565,7 @@ void cache_reload(void) if (cache->flags & (F_HOSTS | F_CONFIG)) { *up = cache->hash_next; - free(cache); + free_config_crec(cache); } else if (!(cache->flags & F_DHCP)) { @@ -1510,11 +1580,13 @@ void cache_reload(void) else up = &cache->hash_next; } + + free_names(UID_NONE); /* free everything */ /* Add locally-configured CNAMEs to the cache */ for (a = daemon->cnames; a; a = a->next) if (a->alias[1] != '*' && - ((cache = whine_malloc(SIZEOF_POINTER_CREC)))) + ((cache = get_config_crec()))) { cache->flags = F_FORWARD | F_NAMEP | F_CNAME | F_IMMORTAL | F_CONFIG; cache->ttd = a->ttl; @@ -1528,25 +1600,30 @@ void cache_reload(void) #ifdef HAVE_DNSSEC for (ds = daemon->ds; ds; ds = ds->next) - if ((cache = whine_malloc(SIZEOF_POINTER_CREC)) && - (cache->addr.ds.keydata = blockdata_alloc(ds->digest, ds->digestlen))) + if ((cache = get_config_crec())) { - cache->flags = F_FORWARD | F_IMMORTAL | F_DS | F_CONFIG | F_NAMEP; - cache->ttd = daemon->local_ttl; - cache->name.namep = ds->name; - cache->uid = ds->class; - if (ds->digestlen != 0) - { - cache->addr.ds.keylen = ds->digestlen; - cache->addr.ds.algo = ds->algo; - cache->addr.ds.keytag = ds->keytag; - cache->addr.ds.digest = ds->digest_type; - } - else - cache->flags |= F_NEG | F_DNSSECOK | F_NO_RR; - cache_hash(cache); - make_non_terminals(cache); + if (!(cache->addr.ds.keydata = blockdata_alloc(ds->digest, ds->digestlen))) + free_config_crec(cache); + else + { + cache->flags = F_FORWARD | F_IMMORTAL | F_DS | F_CONFIG | F_NAMEP; + cache->ttd = daemon->local_ttl; + cache->name.namep = ds->name; + cache->uid = ds->class; + if (ds->digestlen != 0) + { + cache->addr.ds.keylen = ds->digestlen; + cache->addr.ds.algo = ds->algo; + cache->addr.ds.keytag = ds->keytag; + cache->addr.ds.digest = ds->digest_type; + } + else + cache->flags |= F_NEG | F_DNSSECOK | F_NO_RR; + + cache_hash(cache); + make_non_terminals(cache); + } } #endif @@ -1561,7 +1638,7 @@ void cache_reload(void) for (nl = hr->names; nl; nl = nl->next) { if ((hr->flags & HR_4) && - (cache = whine_malloc(SIZEOF_POINTER_CREC))) + (cache = get_config_crec())) { cache->name.namep = nl->name; cache->ttd = hr->ttl; @@ -1570,7 +1647,7 @@ void cache_reload(void) } if ((hr->flags & HR_6) && - (cache = whine_malloc(SIZEOF_POINTER_CREC))) + (cache = get_config_crec())) { cache->name.namep = nl->name; cache->ttd = hr->ttl; @@ -1662,8 +1739,7 @@ void cache_unhash_dhcp(void) if (cache->flags & F_DHCP) { *up = cache->hash_next; - cache->next = dhcp_spare; - dhcp_spare = cache; + free_config_crec(cache); } else up = &cache->hash_next; @@ -1734,12 +1810,7 @@ void cache_add_dhcp_entry(char *host_name, int prot, else flags |= F_REVERSE; - if ((crec = dhcp_spare)) - dhcp_spare = dhcp_spare->next; - else /* need new one */ - crec = whine_malloc(SIZEOF_POINTER_CREC); - - if (crec) /* malloc may fail */ + if ((crec = get_config_crec())) { crec->flags = flags | F_NAMEP | F_DHCP | F_FORWARD; if (ttd == 0) @@ -1786,15 +1857,7 @@ static void make_non_terminals(struct crec *source) hostname_isequal(name, cache_get_name(crecp))) { *up = crecp->hash_next; -#ifdef HAVE_DHCP - if (type & F_DHCP) - { - crecp->next = dhcp_spare; - dhcp_spare = crecp; - } - else -#endif - free(crecp); + free_config_crec(crecp); break; } else @@ -1827,17 +1890,7 @@ static void make_non_terminals(struct crec *source) continue; } -#ifdef HAVE_DHCP - if ((source->flags & F_DHCP) && dhcp_spare) - { - crecp = dhcp_spare; - dhcp_spare = dhcp_spare->next; - } - else -#endif - crecp = whine_malloc(SIZEOF_POINTER_CREC); - - if (crecp) + if ((crecp = get_config_crec())) { crecp->flags = (source->flags | F_NAMEP) & ~(F_IPV4 | F_IPV6 | F_CNAME | F_RR | F_DNSKEY | F_DS | F_REVERSE); if (!(crecp->flags & F_IMMORTAL)) @@ -2166,9 +2219,9 @@ static char *querystr(char *desc, unsigned short type) unsigned int i; int len = 10; /* strlen("type=xxxxx") */ const char *types = NULL; - static char *buff = NULL; - static int bufflen = 0; - + static struct iovec buff = { NULL, 0 }; + char *buffp; + for (i = 0; i < (sizeof(typestr)/sizeof(typestr[0])); i++) if (typestr[i].type == type) { @@ -2183,37 +2236,28 @@ static char *querystr(char *desc, unsigned short type) len += strlen(desc); } len++; /* terminator */ - - if (!buff || bufflen < len) - { - if (buff) - free(buff); - else if (len < 20) - len = 20; - - buff = whine_malloc(len); - bufflen = len; - } - if (buff) + if (!expand_buf(&buff, len)) + return ""; + + buffp = buff.iov_base; + + if (desc) { - if (desc) - { - if (types) - sprintf(buff, "%s[%s]", desc, types); - else - sprintf(buff, "%s[type=%d]", desc, type); - } + if (types) + sprintf(buffp, "%s[%s]", desc, types); else - { - if (types) - sprintf(buff, "<%s>", types); - else - sprintf(buff, "", type); - } + sprintf(buffp, "%s[type=%d]", desc, type); + } + else + { + if (types) + sprintf(buffp, "<%s>", types); + else + sprintf(buffp, "", type); } - return buff ? buff : ""; + return buffp; } static char *edestr(int ede) diff --git a/src/config.h b/src/config.h index b18754d..a73c134 100644 --- a/src/config.h +++ b/src/config.h @@ -22,6 +22,7 @@ #define TCP_BACKLOG 32 /* kernel backlog limit for TCP connections */ #define EDNS_PKTSZ 1232 /* default max EDNS.0 UDP packet from from /dnsflagday.net/2020 */ #define KEYBLOCK_LEN 40 /* choose to minimise fragmentation when storing DNSSEC keys */ +#define NAMEBLOCK_CHARS 1500 /* quantum of memory allocation for names from /etc/hosts */ #define DNSSEC_LIMIT_WORK 40 /* Max number of queries to validate one question */ #define DNSSEC_LIMIT_SIG_FAIL 20 /* Number of signature that can fail to validate in one answer */ #define DNSSEC_LIMIT_CRYPTO 200 /* max no. of crypto operations to validate one query. */