Browse Source

rules: reduce memory use.

Nick Peng 1 tháng trước cách đây
mục cha
commit
aac8682578

+ 9 - 4
src/dns_conf/dns_conf_group.c

@@ -191,14 +191,19 @@ static int _config_domain_rule_iter_copy(void *data, const unsigned char *key, u
 	struct dns_domain_rule *old_domain_rule = NULL;
 	struct dns_domain_rule *new_domain_rule = NULL;
 
-	new_domain_rule = malloc(sizeof(struct dns_domain_rule));
+	old_domain_rule = (struct dns_domain_rule *)value;
+	
+	/* Allocate new domain rule with same capacity as original */
+	size_t size = sizeof(struct dns_domain_rule) + old_domain_rule->capacity * sizeof(struct dns_rule *);
+	new_domain_rule = malloc(size);
 	if (new_domain_rule == NULL) {
 		return -1;
 	}
-	memset(new_domain_rule, 0, sizeof(struct dns_domain_rule));
+	memset(new_domain_rule, 0, size);
+	new_domain_rule->capacity = old_domain_rule->capacity;
 	
-	old_domain_rule = (struct dns_domain_rule *)value;
-	for (int i = 0; i < DOMAIN_RULE_MAX; i++) {
+	/* Copy rules using actual capacity, not DOMAIN_RULE_MAX */
+	for (int i = 0; i < old_domain_rule->capacity; i++) {
 		if (old_domain_rule->rules[i]) {
 			_dns_rule_get(old_domain_rule->rules[i]);
 			new_domain_rule->rules[i] = old_domain_rule->rules[i];

+ 120 - 25
src/dns_conf/domain_rule.c

@@ -32,6 +32,86 @@
 
 #include <getopt.h>
 
+static inline uint8_t _get_required_capacity(enum domain_rule type, uint8_t current_capacity)
+{
+	uint8_t required = type + 1;
+
+	/* Ensure type is within valid range */
+	if (type >= DOMAIN_RULE_MAX || type < 0) {
+		return 0;
+	}
+
+	if (current_capacity == 0) {
+		return required;
+	}
+
+	/* Expand by 2 slots at a time, but cap at DOMAIN_RULE_MAX */
+	uint8_t new_capacity = ((required - current_capacity + 1) / 2) * 2 + current_capacity;
+
+	if (new_capacity > DOMAIN_RULE_MAX) {
+		new_capacity = DOMAIN_RULE_MAX;
+	}
+
+	return new_capacity;
+}
+
+static struct dns_domain_rule *_alloc_domain_rule(uint8_t capacity)
+{
+	size_t size = sizeof(struct dns_domain_rule) + capacity * sizeof(struct dns_rule *);
+	struct dns_domain_rule *rule = malloc(size);
+
+	if (rule == NULL) {
+		return NULL;
+	}
+
+	memset(rule, 0, size);
+	rule->capacity = capacity;
+
+	return rule;
+}
+
+/*
+ * Ensure the domain rule has enough capacity for the given rule type
+ * Reallocates if necessary, preserving existing rules
+ */
+static struct dns_domain_rule *_ensure_domain_rule_capacity(struct dns_domain_rule *domain_rule, enum domain_rule type)
+{
+	if (type >= DOMAIN_RULE_MAX || type < 0) {
+		tlog(TLOG_ERROR, "Invalid domain rule type %d", type);
+		return NULL;
+	}
+
+	if (domain_rule == NULL) {
+		uint8_t capacity = _get_required_capacity(type, 0);
+		return _alloc_domain_rule(capacity);
+	}
+
+	if (type < domain_rule->capacity) {
+		return domain_rule;
+	}
+
+	uint8_t new_capacity = _get_required_capacity(type, domain_rule->capacity);
+	if (new_capacity == 0) {
+		return NULL;
+	}
+
+	if (new_capacity <= domain_rule->capacity) {
+		return domain_rule;
+	}
+
+	size_t new_size = sizeof(struct dns_domain_rule) + new_capacity * sizeof(struct dns_rule *);
+	struct dns_domain_rule *new_rule = realloc(domain_rule, new_size);
+	if (new_rule == NULL) {
+		return NULL;
+	}
+
+	uint8_t old_capacity = new_rule->capacity;
+	memset(new_rule->rules + old_capacity, 0, (new_capacity - old_capacity) * sizeof(struct dns_rule *));
+	new_rule->capacity = new_capacity;
+
+	return new_rule;
+}
+
 void *_new_dns_rule_ext(enum domain_rule domain_rule, int ext_size)
 {
 	struct dns_rule *rule;
@@ -174,8 +254,7 @@ static int _config_setup_domain_key(const char *domain, char *domain_key, int do
 
 	int len = strlen(domain);
 	domain_len = len;
-	if (!domain_key || !domain_key_len || domain_key_max_len <= 0 || 
-		len + 3 > domain_key_max_len) {
+	if (!domain_key || !domain_key_len || domain_key_max_len <= 0 || len + 3 > domain_key_max_len) {
 		tlog(TLOG_ERROR, "invalid parameters or domain too long: %s (max %d)", domain, domain_key_max_len - 3);
 		return -1;
 	}
@@ -226,7 +305,7 @@ static int _config_setup_domain_key(const char *domain, char *domain_key, int do
 
 static __attribute__((unused)) struct dns_domain_rule *_config_domain_rule_get(const char *domain)
 {
-	char domain_key[DNS_MAX_CONF_CNAME_LEN];
+	char domain_key[DNS_MAX_CONF_CNAME_LEN] = {0};
 	int len = 0;
 
 	if (_config_setup_domain_key(domain, domain_key, sizeof(domain_key), &len, NULL, NULL) != 0) {
@@ -244,7 +323,8 @@ int _config_domain_rule_free(struct dns_domain_rule *domain_rule)
 		return 0;
 	}
 
-	for (i = 0; i < DOMAIN_RULE_MAX; i++) {
+	/* Iterate only through allocated capacity, not DOMAIN_RULE_MAX */
+	for (i = 0; i < domain_rule->capacity; i++) {
 		if (domain_rule->rules[i] == NULL) {
 			continue;
 		}
@@ -270,7 +350,7 @@ static int _config_domain_rule_delete_callback(const char *domain, void *priv)
 
 int _config_domain_rule_delete(const char *domain)
 {
-	char domain_key[DNS_MAX_CONF_CNAME_LEN];
+	char domain_key[DNS_MAX_CONF_CNAME_LEN] = {0};
 	int len = 0;
 
 	if (strncmp(domain, "domain-set:", sizeof("domain-set:") - 1) == 0) {
@@ -308,7 +388,7 @@ int _config_domain_rule_flag_set(const char *domain, unsigned int flag, unsigned
 	struct dns_domain_rule *add_domain_rule = NULL;
 	struct dns_rule_flags *rule_flags = NULL;
 
-	char domain_key[DNS_MAX_CONF_CNAME_LEN];
+	char domain_key[DNS_MAX_CONF_CNAME_LEN] = {0};
 	int len = 0;
 	int sub_rule_only = 0;
 	int root_rule_only = 0;
@@ -325,15 +405,15 @@ int _config_domain_rule_flag_set(const char *domain, unsigned int flag, unsigned
 		goto errout;
 	}
 
-	/* Get existing or create domain rule */
+	/* Get existing domain rule */
 	domain_rule = art_search(&_config_current_rule_group()->domain_rule.tree, (unsigned char *)domain_key, len);
 	if (domain_rule == NULL) {
-		add_domain_rule = malloc(sizeof(*add_domain_rule));
-		if (add_domain_rule == NULL) {
+		/* Allocate new domain rule with minimum capacity for flags */
+		domain_rule = _alloc_domain_rule(_get_required_capacity(DOMAIN_RULE_FLAGS, 0));
+		if (domain_rule == NULL) {
 			goto errout;
 		}
-		memset(add_domain_rule, 0, sizeof(*add_domain_rule));
-		domain_rule = add_domain_rule;
+		add_domain_rule = domain_rule;
 	}
 
 	/* add new rule to domain */
@@ -375,7 +455,7 @@ errout:
 
 int _config_domain_rule_remove(const char *domain, enum domain_rule type)
 {
-	char domain_key[DNS_MAX_CONF_CNAME_LEN];
+	char domain_key[DNS_MAX_CONF_CNAME_LEN] = {0};
 	int len = 0;
 	int sub_rule_only = 0;
 	int root_rule_only = 0;
@@ -395,11 +475,11 @@ int _config_domain_rule_remove(const char *domain, enum domain_rule type)
 		return -1;
 	}
 
-	struct dns_domain_rule *domain_rule = art_search(&_config_current_rule_group()->domain_rule.tree,
-													   (unsigned char *)domain_key, len);
+	struct dns_domain_rule *domain_rule =
+		art_search(&_config_current_rule_group()->domain_rule.tree, (unsigned char *)domain_key, len);
 	if (domain_rule == NULL) {
 		tlog(TLOG_ERROR, "domain %s not found", domain);
-		return -1;		
+		return -1;
 	}
 
 	if (domain_rule->rules[type] == NULL) {
@@ -408,7 +488,7 @@ int _config_domain_rule_remove(const char *domain, enum domain_rule type)
 
 	_dns_rule_put(domain_rule->rules[type]);
 	domain_rule->rules[type] = NULL;
-	
+
 	return 0;
 }
 
@@ -418,7 +498,7 @@ int _config_domain_rule_add(const char *domain, enum domain_rule type, void *rul
 	struct dns_domain_rule *old_domain_rule = NULL;
 	struct dns_domain_rule *add_domain_rule = NULL;
 
-	char domain_key[DNS_MAX_CONF_CNAME_LEN];
+	char domain_key[DNS_MAX_CONF_CNAME_LEN] = {0};
 	int len = 0;
 	int sub_rule_only = 0;
 	int root_rule_only = 0;
@@ -440,15 +520,30 @@ int _config_domain_rule_add(const char *domain, enum domain_rule type, void *rul
 		goto errout;
 	}
 
-	/* Get existing or create domain rule */
+	/* Get existing domain rule */
 	domain_rule = art_search(&_config_current_rule_group()->domain_rule.tree, (unsigned char *)domain_key, len);
+
+	/* Track if this is a new allocation (before capacity expansion) */
+	int was_new_allocation = (domain_rule == NULL);
+	struct dns_domain_rule *old_ptr = domain_rule;
+
+	/* Ensure capacity for the new rule type */
+	domain_rule = _ensure_domain_rule_capacity(domain_rule, type);
 	if (domain_rule == NULL) {
-		add_domain_rule = malloc(sizeof(*add_domain_rule));
-		if (add_domain_rule == NULL) {
-			goto errout;
-		}
-		memset(add_domain_rule, 0, sizeof(*add_domain_rule));
-		domain_rule = add_domain_rule;
+		tlog(TLOG_ERROR, "failed to allocate capacity for domain %s rule type %d", domain, type);
+		goto errout;
+	}
+
+	/* Set add_domain_rule if this was a new allocation or if realloc moved the memory */
+	if (was_new_allocation) {
+		add_domain_rule = domain_rule;
+	} else if (domain_rule != old_ptr) {
+		/* Memory was moved by realloc, need to update ART tree
+		 * Note: old_ptr is already freed by realloc, so we don't free it again */
+		old_domain_rule =
+			art_insert(&_config_current_rule_group()->domain_rule.tree, (unsigned char *)domain_key, len, domain_rule);
+		/* old_domain_rule == old_ptr, already freed by realloc, don't free again */
+		add_domain_rule = NULL;
 	}
 
 	/* add new rule to domain */
@@ -462,7 +557,7 @@ int _config_domain_rule_add(const char *domain, enum domain_rule type, void *rul
 	domain_rule->root_rule_only = root_rule_only;
 	_dns_rule_get(rule);
 
-	/* update domain rule */
+	/* update domain rule - only for new allocations */
 	if (add_domain_rule) {
 		old_domain_rule = art_insert(&_config_current_rule_group()->domain_rule.tree, (unsigned char *)domain_key, len,
 									 add_domain_rule);

+ 7 - 7
src/dns_server/rules.c

@@ -45,7 +45,7 @@ static int _dns_server_is_dns_rule_extract_match_ext(struct dns_request_domain_r
 static void _dns_server_log_rule(const char *domain, enum domain_rule rule_type, unsigned char *rule_key,
 								 int rule_key_len)
 {
-	char rule_name[DNS_MAX_CNAME_LEN];
+	char rule_name[DNS_MAX_CNAME_LEN] = {0};
 	if (rule_key_len <= 0) {
 		return;
 	}
@@ -135,7 +135,7 @@ static int _dns_server_get_rules(unsigned char *key, uint32_t key_len, int is_su
 		i = 0;
 	}
 
-	for (; i < DOMAIN_RULE_MAX; i++) {
+	for (; i < domain_rule->capacity; i++) {
 		if (domain_rule->rules[i] == NULL) {
 			if (walk_args->rule_index >= 0) {
 				break;
@@ -163,10 +163,10 @@ void _dns_server_get_domain_rule_by_domain_ext(struct dns_conf_group *conf,
 											   const char *domain, int out_log)
 {
 	int domain_len = 0;
-	char domain_key[DNS_MAX_CNAME_LEN];
+	char domain_key[DNS_MAX_CNAME_LEN] = {0};
 	struct rule_walk_args walk_args;
 	int matched_key_len = DNS_MAX_CNAME_LEN;
-	unsigned char matched_key[DNS_MAX_CNAME_LEN];
+	unsigned char matched_key[DNS_MAX_CNAME_LEN] = {0};
 	int i = 0;
 
 	memset(&walk_args, 0, sizeof(walk_args));
@@ -238,7 +238,7 @@ int _dns_server_passthrough_rule_check(struct dns_request *request, const char *
 {
 	int ttl = 0;
 	char name[DNS_MAX_CNAME_LEN] = {0};
-	char cname[DNS_MAX_CNAME_LEN];
+	char cname[DNS_MAX_CNAME_LEN] = {0};
 	int rr_count = 0;
 	int i = 0;
 	int j = 0;
@@ -350,8 +350,8 @@ int _dns_server_passthrough_rule_check(struct dns_request *request, const char *
 			default:
 				if (ttl == 0) {
 					/* Get TTL */
-					char tmpname[DNS_MAX_CNAME_LEN];
-					char tmpbuf[DNS_MAX_CNAME_LEN];
+					char tmpname[DNS_MAX_CNAME_LEN] = {0};
+					char tmpbuf[DNS_MAX_CNAME_LEN] = {0};
 					dns_get_CNAME(rrs, tmpname, DNS_MAX_CNAME_LEN, &ttl, tmpbuf, DNS_MAX_CNAME_LEN);
 					if (request->ip_ttl == 0) {
 						request->ip_ttl = _dns_server_get_conf_ttl(request, ttl);

+ 2 - 2
src/http_parse/http3_parse.c

@@ -178,7 +178,7 @@ static int _http3_build_headers_payload(struct http_head *http_head, uint8_t *bu
 		offset += offset_ret;
 	} else if (http_head->head_type == HTTP_HEAD_RESPONSE) {
 		char status_str[12];
-		sprintf(status_str, "%d", http_head->code);
+		snprintf(status_str, sizeof(status_str), "%d", http_head->code);
 		offset_ret = _qpack_build_header(":status", status_str, buffer + offset, buffer_len - offset);
 		if (offset_ret < 0) {
 			return -1;
@@ -197,7 +197,7 @@ static int _http3_build_headers_payload(struct http_head *http_head, uint8_t *bu
 
 	if (http_head->data_len > 0 && http_head->data) {
 		char len_str[12];
-		sprintf(len_str, "%d", http_head->data_len);
+		snprintf(len_str, sizeof(len_str), "%d", http_head->data_len);
 		offset_ret = _qpack_build_header("content-length", len_str, buffer + offset, buffer_len - offset);
 		if (offset_ret < 0) {
 			return -1;

+ 25 - 17
src/include/smartdns/dns_conf.h

@@ -51,7 +51,7 @@ extern "C" {
 #define DNS_PROXY_MAX_LEN 128
 #define DNS_CONF_USERNAME_LEN 32
 #define DNS_MAX_SPKI_LEN 64
-#define DNS_MAX_URL_LEN 256
+#define DNS_MAX_URL_LEN 1024
 #define DNS_MAX_PATH 1024
 #define DEFAULT_DNS_PORT 53
 #define DEFAULT_DNS_TLS_PORT 853
@@ -76,22 +76,29 @@ extern "C" {
 
 #define DNS64_IPV4ONLY_APRA_DOMAIN "ipv4only.arpa"
 
+/* Domain rule types, ordered by usage frequency for memory optimization */
 enum domain_rule {
-	DOMAIN_RULE_FLAGS = 0,
-	DOMAIN_RULE_ADDRESS_IPV4,
-	DOMAIN_RULE_ADDRESS_IPV6,
-	DOMAIN_RULE_IPSET,
-	DOMAIN_RULE_IPSET_IPV4,
-	DOMAIN_RULE_IPSET_IPV6,
-	DOMAIN_RULE_NFTSET_IP,
-	DOMAIN_RULE_NFTSET_IP6,
-	DOMAIN_RULE_NAMESERVER,
-	DOMAIN_RULE_GROUP,
-	DOMAIN_RULE_CHECKSPEED,
-	DOMAIN_RULE_RESPONSE_MODE,
-	DOMAIN_RULE_CNAME,
-	DOMAIN_RULE_HTTPS,
-	DOMAIN_RULE_TTL,
+	DOMAIN_RULE_FLAGS = 0,               /* Flags (block, ignore, cache, etc.) */
+	
+	DOMAIN_RULE_ADDRESS_IPV4,            /* IPv4 address rule (ad-block, custom DNS) */
+	DOMAIN_RULE_ADDRESS_IPV6,            /* IPv6 address rule */
+	DOMAIN_RULE_NAMESERVER,              /* Nameserver group (domain routing) */
+	
+	DOMAIN_RULE_CHECKSPEED,              /* Speed check mode */
+	DOMAIN_RULE_IPSET,                   /* IPSet rule for traffic routing */
+	DOMAIN_RULE_NFTSET_IP,               /* NFTSet IPv4 */
+	DOMAIN_RULE_IPSET_IPV4,              /* IPv4 IPSet */
+	
+	DOMAIN_RULE_GROUP,                   /* Group rule */
+
+	DOMAIN_RULE_NFTSET_IP6,              /* NFTSet IPv6 */
+	DOMAIN_RULE_IPSET_IPV6,              /* IPv6 IPSet */
+	
+	DOMAIN_RULE_HTTPS,                   /* HTTPS record */
+	DOMAIN_RULE_RESPONSE_MODE,           /* Response mode */
+	DOMAIN_RULE_CNAME,                   /* CNAME rule */
+	DOMAIN_RULE_TTL,                     /* TTL control */
+	
 	DOMAIN_RULE_MAX,
 };
 
@@ -261,7 +268,8 @@ extern struct dns_nftset_names dns_conf_nftset;
 struct dns_domain_rule {
 	unsigned char sub_rule_only : 1;
 	unsigned char root_rule_only : 1;
-	struct dns_rule *rules[DOMAIN_RULE_MAX];
+	unsigned char capacity : 6;          /* Current allocated capacity (max 63) */
+	struct dns_rule *rules[];            /* Flexible array member */
 };
 
 struct dns_nameserver_rule {

+ 13 - 3
src/include/smartdns/lib/art.h

@@ -34,10 +34,11 @@ extern "C" {
 
 #define NODE4   1
 #define NODE16  2
-#define NODE48  3
-#define NODE256 4
+#define NODE32  3
+#define NODE48  4
+#define NODE256 5
 
-#define MAX_PREFIX_LEN 10
+#define MAX_PREFIX_LEN 6
 
 #if defined(__GNUC__) && !defined(__clang__)
 # if __STDC_VERSION__ >= 199901L && 402 == (__GNUC__ * 100 + __GNUC_MINOR__)
@@ -81,6 +82,15 @@ typedef struct {
     art_node *children[16];
 } art_node16;
 
+/**
+ * Node with 32 children
+ */
+typedef struct {
+    art_node n;
+    unsigned char keys[32];
+    art_node *children[32];
+} art_node32;
+
 /**
  * Node with 48 children, but
  * a full 256 byte field.

+ 1 - 3
src/include/smartdns/util.h

@@ -42,7 +42,7 @@ extern "C" {
 #define TCP_THIN_DUPACK 17
 #endif
 
-#define PORT_NOT_DEFINED -1
+#define PORT_NOT_DEFINED (-1)
 #define MAX_IP_LEN 64
 
 #define IPV6_ADDR_LEN 16
@@ -114,8 +114,6 @@ char *reverse_string(char *output, const char *input, int len, int to_lower_case
 
 char *to_lower_case(char *output, const char *input, int len);
 
-void print_stack(void);
-
 int ipset_add(const char *ipset_name, const unsigned char addr[], int addr_len, unsigned long timeout);
 
 int ipset_del(const char *ipset_name, const unsigned char addr[], int addr_len);

+ 115 - 24
src/lib/art.c

@@ -46,6 +46,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #define SET_LEAF(x) ((void*)((uintptr_t)x | 1))
 #define LEAF_RAW(x) ((art_leaf*)((void*)((uintptr_t)x & ~1)))
 
+/**
+ * Forward declarations
+ */
+static void add_child32(art_node32 *n, art_node **ref, unsigned char c, void *child);
+static void add_child48(art_node48 *n, art_node **ref, unsigned char c, void *child);
+
 /**
  * Allocates a node of the given type,
  * initializes to zero and sets the type.
@@ -60,6 +66,9 @@ static art_node* alloc_node(uint8_t type) {
         case NODE16:
             mem = (art_node*)calloc(1, sizeof(art_node16));
             break;
+        case NODE32:
+            mem = (art_node*)calloc(1, sizeof(art_node32));
+            break;
         case NODE48:
             mem = (art_node*)calloc(1, sizeof(art_node48));
             break;
@@ -103,8 +112,9 @@ static void destroy_node(art_node *n) {
     union {
         art_node4 *p1;
         art_node16 *p2;
-        art_node48 *p3;
-        art_node256 *p4;
+        art_node32 *p3;
+        art_node48 *p4;
+        art_node256 *p5;
     } p;
     switch (n->type) {
         case NODE4:
@@ -121,20 +131,27 @@ static void destroy_node(art_node *n) {
             }
             break;
 
+        case NODE32:
+            p.p3 = (art_node32*)n;
+            for (i=0;i<n->num_children;i++) {
+                destroy_node(p.p3->children[i]);
+            }
+            break;
+
         case NODE48:
-            p.p3 = (art_node48*)n;
+            p.p4 = (art_node48*)n;
             for (i=0;i<256;i++) {
                 idx = ((art_node48*)n)->keys[i]; 
                 if (!idx) continue; 
-                destroy_node(p.p3->children[idx-1]);
+                destroy_node(p.p4->children[idx-1]);
             }
             break;
 
         case NODE256:
-            p.p4 = (art_node256*)n;
+            p.p5 = (art_node256*)n;
             for (i=0;i<256;i++) {
-                if (p.p4->children[i])
-                    destroy_node(p.p4->children[i]);
+                if (p.p5->children[i])
+                    destroy_node(p.p5->children[i]);
             }
             break;
 
@@ -168,8 +185,9 @@ static art_node** find_child(art_node *n, unsigned char c) {
     union {
         art_node4 *p1;
         art_node16 *p2;
-        art_node48 *p3;
-        art_node256 *p4;
+        art_node32 *p3;
+        art_node48 *p4;
+        art_node256 *p5;
     } p;
     switch (n->type) {
         case NODE4:
@@ -232,17 +250,26 @@ static art_node** find_child(art_node *n, unsigned char c) {
             break;
         }
 
+        case NODE32:
+            p.p3 = (art_node32*)n;
+            // Linear search for NODE32
+            for (i = 0; i < n->num_children; i++) {
+                if (p.p3->keys[i] == c)
+                    return &p.p3->children[i];
+            }
+            break;
+
         case NODE48:
-            p.p3 = (art_node48*)n;
-            i = p.p3->keys[c];
+            p.p4 = (art_node48*)n;
+            i = p.p4->keys[c];
             if (i)
-                return &p.p3->children[i-1];
+                return &p.p4->children[i-1];
             break;
 
         case NODE256:
-            p.p4 = (art_node256*)n;
-            if (p.p4->children[c])
-                return &p.p4->children[c];
+            p.p5 = (art_node256*)n;
+            if (p.p5->children[c])
+                return &p.p5->children[c];
             break;
 
         default:
@@ -334,6 +361,8 @@ static art_leaf* minimum(const art_node *n) {
             return minimum(((const art_node4*)n)->children[0]);
         case NODE16:
             return minimum(((const art_node16*)n)->children[0]);
+        case NODE32:
+            return minimum(((const art_node32*)n)->children[0]);
         case NODE48:
             idx=0;
             while (!((const art_node48*)n)->keys[idx]) idx++;
@@ -360,6 +389,8 @@ static art_leaf* maximum(const art_node *n) {
             return maximum(((const art_node4*)n)->children[n->num_children-1]);
         case NODE16:
             return maximum(((const art_node16*)n)->children[n->num_children-1]);
+        case NODE32:
+            return maximum(((const art_node32*)n)->children[n->num_children-1]);
         case NODE48:
             idx=255;
             while (!((const art_node48*)n)->keys[idx]) idx--;
@@ -444,6 +475,41 @@ static void add_child48(art_node48 *n, art_node **ref, unsigned char c, void *ch
     }
 }
 
+static void add_child32(art_node32 *n, art_node **ref, unsigned char c, void *child) {
+    if (n->n.num_children < 32) {
+        unsigned idx;
+        // Find insertion point
+        for (idx = 0; idx < n->n.num_children; idx++) {
+            if (c < n->keys[idx]) break;
+        }
+
+        // Shift to make room
+        memmove(n->keys+idx+1, n->keys+idx, n->n.num_children - idx);
+        memmove(n->children+idx+1, n->children+idx,
+                (n->n.num_children - idx)*sizeof(void*));
+
+        // Insert element
+        n->keys[idx] = c;
+        n->children[idx] = (art_node*)child;
+        n->n.num_children++;
+
+    } else {
+        art_node48 *new_node = (art_node48*)alloc_node(NODE48);
+        int i;
+
+        // Copy the child pointers and populate the key map
+        memcpy(new_node->children, n->children,
+                sizeof(void*)*n->n.num_children);
+        for (i=0;i<n->n.num_children;i++) {
+            new_node->keys[n->keys[i]] = i + 1;
+        }
+        copy_header((art_node*)new_node, (art_node*)n);
+        *ref = (art_node*)new_node;
+        free(n);
+        add_child48(new_node, ref, c, child);
+    }
+}
+
 static void add_child16(art_node16 *n, art_node **ref, unsigned char c, void *child) {
     if (n->n.num_children < 16) {
         unsigned mask = (1 << n->n.num_children) - 1;
@@ -498,19 +564,17 @@ static void add_child16(art_node16 *n, art_node **ref, unsigned char c, void *ch
         n->n.num_children++;
 
     } else {
-        art_node48 *new_node = (art_node48*)alloc_node(NODE48);
-        int i;
+        art_node32 *new_node = (art_node32*)alloc_node(NODE32);
 
-        // Copy the child pointers and populate the key map
+        // Copy the child pointers and keys
         memcpy(new_node->children, n->children,
                 sizeof(void*)*n->n.num_children);
-        for (i=0;i<n->n.num_children;i++) {
-            new_node->keys[n->keys[i]] = i + 1;
-        }
+        memcpy(new_node->keys, n->keys,
+                sizeof(unsigned char)*n->n.num_children);
         copy_header((art_node*)new_node, (art_node*)n);
         *ref = (art_node*)new_node;
         free(n);
-        add_child48(new_node, ref, c, child);
+        add_child32(new_node, ref, c, child);
     }
 }
 
@@ -552,6 +616,8 @@ static void add_child(art_node *n, art_node **ref, unsigned char c, void *child)
             return add_child4((art_node4*)n, ref, c, child);
         case NODE16:
             return add_child16((art_node16*)n, ref, c, child);
+        case NODE32:
+            return add_child32((art_node32*)n, ref, c, child);
         case NODE48:
             return add_child48((art_node48*)n, ref, c, child);
         case NODE256:
@@ -716,8 +782,8 @@ static void remove_child48(art_node48 *n, art_node **ref, unsigned char c) {
     n->children[pos-1] = NULL;
     n->n.num_children--;
 
-    if (n->n.num_children == 12) {
-        art_node16 *new_node = (art_node16*)alloc_node(NODE16);
+    if (n->n.num_children == 31) {
+        art_node32 *new_node = (art_node32*)alloc_node(NODE32);
         *ref = (art_node*)new_node;
         copy_header((art_node*)new_node, (art_node*)n);
 
@@ -735,6 +801,22 @@ static void remove_child48(art_node48 *n, art_node **ref, unsigned char c) {
     }
 }
 
+static void remove_child32(art_node32 *n, art_node **ref, art_node **l) {
+    int pos = l - n->children;
+    memmove(n->keys+pos, n->keys+pos+1, n->n.num_children - 1 - pos);
+    memmove(n->children+pos, n->children+pos+1, (n->n.num_children - 1 - pos)*sizeof(void*));
+    n->n.num_children--;
+
+    if (n->n.num_children == 15) {
+        art_node16 *new_node = (art_node16*)alloc_node(NODE16);
+        *ref = (art_node*)new_node;
+        copy_header((art_node*)new_node, (art_node*)n);
+        memcpy(new_node->keys, n->keys, 16);
+        memcpy(new_node->children, n->children, 16*sizeof(void*));
+        free(n);
+    }
+}
+
 static void remove_child16(art_node16 *n, art_node **ref, art_node **l) {
     int pos = l - n->children;
     memmove(n->keys+pos, n->keys+pos+1, n->n.num_children - 1 - pos);
@@ -788,6 +870,8 @@ static void remove_child(art_node *n, art_node **ref, unsigned char c, art_node
             return remove_child4((art_node4*)n, ref, l);
         case NODE16:
             return remove_child16((art_node16*)n, ref, l);
+        case NODE32:
+            return remove_child32((art_node32*)n, ref, l);
         case NODE48:
             return remove_child48((art_node48*)n, ref, c);
         case NODE256:
@@ -884,6 +968,13 @@ static int recursive_iter(art_node *n, art_callback cb, void *data) {
             }
             break;
 
+        case NODE32:
+            for (i=0; i < n->num_children; i++) {
+                res = recursive_iter(((art_node32*)n)->children[i], cb, data);
+                if (res) return res;
+            }
+            break;
+
         case NODE48:
             for (i=0; i < 256; i++) {
                 idx = ((art_node48*)n)->keys[i];