summaryrefslogtreecommitdiffstats
path: root/src/libutil/radix.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libutil/radix.c')
-rw-r--r--src/libutil/radix.c434
1 files changed, 434 insertions, 0 deletions
diff --git a/src/libutil/radix.c b/src/libutil/radix.c
new file mode 100644
index 0000000..93c728c
--- /dev/null
+++ b/src/libutil/radix.c
@@ -0,0 +1,434 @@
+/*-
+ * Copyright 2016 Vsevolod Stakhov
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "config.h"
+#include "radix.h"
+#include "rspamd.h"
+#include "mem_pool.h"
+#include "btrie.h"
+
+#define msg_err_radix(...) rspamd_default_log_function(G_LOG_LEVEL_CRITICAL, \
+ "radix", tree->pool->tag.uid, \
+ G_STRFUNC, \
+ __VA_ARGS__)
+#define msg_warn_radix(...) rspamd_default_log_function(G_LOG_LEVEL_WARNING, \
+ "radix", tree->pool->tag.uid, \
+ G_STRFUNC, \
+ __VA_ARGS__)
+#define msg_info_radix(...) rspamd_default_log_function(G_LOG_LEVEL_INFO, \
+ "radix", tree->pool->tag.uid, \
+ G_STRFUNC, \
+ __VA_ARGS__)
+#define msg_debug_radix(...) rspamd_conditional_debug_fast(NULL, NULL, \
+ rspamd_radix_log_id, "radix", tree->pool->tag.uid, \
+ G_STRFUNC, \
+ __VA_ARGS__)
+
+INIT_LOG_MODULE(radix)
+
+struct radix_tree_compressed {
+ rspamd_mempool_t *pool;
+ struct btrie *tree;
+ const gchar *name;
+ size_t size;
+ guint duplicates;
+ gboolean own_pool;
+};
+
+uintptr_t
+radix_find_compressed(radix_compressed_t *tree, const guint8 *key, gsize keylen)
+{
+ gconstpointer ret;
+
+ g_assert(tree != NULL);
+
+ ret = btrie_lookup(tree->tree, key, keylen * NBBY);
+
+ if (ret == NULL) {
+ return RADIX_NO_VALUE;
+ }
+
+ return (uintptr_t) ret;
+}
+
+
+uintptr_t
+radix_insert_compressed(radix_compressed_t *tree,
+ guint8 *key, gsize keylen,
+ gsize masklen,
+ uintptr_t value)
+{
+ static const guint max_duplicates = 32;
+ guint keybits = keylen * NBBY;
+ uintptr_t old;
+ gchar ip_str[INET6_ADDRSTRLEN + 1];
+ int ret;
+
+ g_assert(tree != NULL);
+ g_assert(keybits >= masklen);
+
+ msg_debug_radix("%s: want insert value %p with mask %z, key: %*xs",
+ tree->name, (gpointer) value, keybits - masklen, (int) keylen, key);
+
+ old = radix_find_compressed(tree, key, keylen);
+
+ ret = btrie_add_prefix(tree->tree, key, keybits - masklen,
+ (gconstpointer) value);
+
+ if (ret != BTRIE_OKAY) {
+ tree->duplicates++;
+
+ if (tree->duplicates == max_duplicates) {
+ msg_err_radix("%s: maximum duplicates limit reached: %d, "
+ "suppress further errors",
+ tree->name, max_duplicates);
+ }
+ else if (tree->duplicates < max_duplicates) {
+ memset(ip_str, 0, sizeof(ip_str));
+
+ if (keybits == 32) {
+ msg_err_radix("%s: cannot insert %p, key: %s/%d, duplicate value",
+ tree->name,
+ (gpointer) value,
+ inet_ntop(AF_INET, key, ip_str, sizeof(ip_str) - 1),
+ (gint) (keybits - masklen));
+ }
+ else if (keybits == 128) {
+ msg_err_radix("%s: cannot insert %p, key: [%s]/%d, duplicate value",
+ tree->name,
+ (gpointer) value,
+ inet_ntop(AF_INET6, key, ip_str, sizeof(ip_str) - 1),
+ (gint) (keybits - masklen));
+ }
+ else {
+ msg_err_radix("%s: cannot insert %p with mask %z, key: %*xs, duplicate value",
+ tree->name,
+ (gpointer) value,
+ keybits - masklen,
+ (int) keylen, key);
+ }
+ }
+ }
+ else {
+ tree->size++;
+ }
+
+ return old;
+}
+
+
+radix_compressed_t *
+radix_create_compressed(const gchar *tree_name)
+{
+ radix_compressed_t *tree;
+
+ tree = g_malloc(sizeof(*tree));
+ if (tree == NULL) {
+ return NULL;
+ }
+
+ tree->pool = rspamd_mempool_new(rspamd_mempool_suggest_size(), NULL, 0);
+ tree->size = 0;
+ tree->duplicates = 0;
+ tree->tree = btrie_init(tree->pool);
+ tree->own_pool = TRUE;
+ tree->name = tree_name;
+
+ return tree;
+}
+
+radix_compressed_t *
+radix_create_compressed_with_pool(rspamd_mempool_t *pool, const gchar *tree_name)
+{
+ radix_compressed_t *tree;
+
+ tree = rspamd_mempool_alloc(pool, sizeof(*tree));
+ tree->pool = pool;
+ tree->size = 0;
+ tree->duplicates = 0;
+ tree->tree = btrie_init(tree->pool);
+ tree->own_pool = FALSE;
+ tree->name = tree_name;
+
+ return tree;
+}
+
+void radix_destroy_compressed(radix_compressed_t *tree)
+{
+ if (tree) {
+ if (tree->own_pool) {
+ rspamd_mempool_delete(tree->pool);
+ g_free(tree);
+ }
+ }
+}
+
+uintptr_t
+radix_find_compressed_addr(radix_compressed_t *tree,
+ const rspamd_inet_addr_t *addr)
+{
+ const guchar *key;
+ guint klen = 0;
+ guchar buf[16];
+
+ if (addr == NULL) {
+ return RADIX_NO_VALUE;
+ }
+
+ key = rspamd_inet_address_get_hash_key(addr, &klen);
+
+ if (key && klen) {
+ if (klen == 4) {
+ /* Map to ipv6 */
+ memset(buf, 0, 10);
+ buf[10] = 0xffu;
+ buf[11] = 0xffu;
+ memcpy(buf + 12, key, klen);
+
+ key = buf;
+ klen = sizeof(buf);
+ }
+
+ return radix_find_compressed(tree, key, klen);
+ }
+
+ return RADIX_NO_VALUE;
+}
+
+gint rspamd_radix_add_iplist(const gchar *list, const gchar *separators,
+ radix_compressed_t *tree, gconstpointer value,
+ gboolean resolve, const gchar *tree_name)
+{
+ gchar *token, *ipnet, *err_str, **strv, **cur, *brace;
+ union {
+ struct in_addr ina;
+ struct in6_addr ina6;
+ guchar buf[16];
+ } addr_buf;
+ guint k = G_MAXINT;
+ gint af;
+ gint res = 0, r;
+ struct addrinfo hints, *ai_res, *cur_ai;
+
+ /* Split string if there are multiple items inside a single string */
+ strv = g_strsplit_set(list, separators, 0);
+ cur = strv;
+ while (*cur) {
+ af = AF_UNSPEC;
+ if (**cur == '\0') {
+ cur++;
+ continue;
+ }
+
+ /* Extract ipnet */
+ ipnet = g_strstrip(*cur);
+ token = strsep(&ipnet, "/");
+
+ if (ipnet != NULL) {
+ errno = 0;
+ /* Get mask */
+ k = strtoul(ipnet, &err_str, 10);
+ if (errno != 0) {
+ msg_warn_radix(
+ "%s: invalid netmask, error detected on symbol: %s, error: %s",
+ tree_name,
+ err_str,
+ strerror(errno));
+ k = G_MAXINT;
+ }
+ }
+
+ /* Check IP */
+ if (token[0] == '[') {
+ /* Braced IPv6 */
+ brace = strrchr(token, ']');
+
+ if (brace != NULL) {
+ token++;
+ *brace = '\0';
+
+ if (inet_pton(AF_INET6, token, &addr_buf.ina6) == 1) {
+ af = AF_INET6;
+ }
+ else {
+ msg_warn_radix("invalid IP address: %s", token);
+
+ cur++;
+ continue;
+ }
+ }
+ else {
+ msg_warn_radix("invalid IP address: %s", token);
+
+ cur++;
+ continue;
+ }
+ }
+ else {
+ if (inet_pton(AF_INET, token, &addr_buf.ina) == 1) {
+ af = AF_INET;
+ }
+ else if (inet_pton(AF_INET6, token, &addr_buf.ina6) == 1) {
+ af = AF_INET6;
+ }
+ else {
+
+ if (resolve) {
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_socktype = SOCK_STREAM; /* Type of the socket */
+ hints.ai_flags = AI_NUMERICSERV;
+ hints.ai_family = AF_UNSPEC;
+
+ if ((r = getaddrinfo(token, NULL, &hints, &ai_res)) == 0) {
+ for (cur_ai = ai_res; cur_ai != NULL;
+ cur_ai = cur_ai->ai_next) {
+
+ if (cur_ai->ai_family == AF_INET) {
+ struct sockaddr_in *sin;
+
+ sin = (struct sockaddr_in *) cur_ai->ai_addr;
+ if (k > 32) {
+ k = 32;
+ }
+
+ /* Convert to IPv4 mapped IPv6 */
+ memset(addr_buf.buf, 0, 10);
+ addr_buf.buf[10] = 0xffu;
+ addr_buf.buf[11] = 0xffu;
+ memcpy(addr_buf.buf + 12,
+ &sin->sin_addr, 4);
+
+ k += 96;
+
+ radix_insert_compressed(tree,
+ addr_buf.buf,
+ sizeof(addr_buf.buf),
+ 128 - k, (uintptr_t) value);
+ res++;
+ }
+ else if (cur_ai->ai_family == AF_INET6) {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *) cur_ai->ai_addr;
+ if (k > 128) {
+ k = 128;
+ }
+
+ memcpy(addr_buf.buf, &sin6->sin6_addr,
+ sizeof(sin6->sin6_addr));
+ radix_insert_compressed(tree,
+ addr_buf.buf,
+ sizeof(addr_buf.buf),
+ 128 - k, (uintptr_t) value);
+ res++;
+ }
+ }
+
+ freeaddrinfo(ai_res);
+ }
+ else {
+ msg_warn_radix("getaddrinfo failed for %s: %s", token,
+ gai_strerror(r));
+ }
+
+ cur++;
+ continue;
+ }
+ else {
+ msg_warn_radix("invalid IP address: %s", token);
+
+ cur++;
+ continue;
+ }
+ }
+ }
+
+ if (af == AF_INET) {
+ if (k > 32) {
+ k = 32;
+ }
+
+ /* Move to the last part of the address */
+ memmove(addr_buf.buf + 12, &addr_buf.ina, 4);
+ memset(addr_buf.buf, 0, 10);
+ addr_buf.buf[10] = 0xffu;
+ addr_buf.buf[11] = 0xffu;
+ k += 96;
+ radix_insert_compressed(tree, addr_buf.buf, sizeof(addr_buf.buf),
+ 128 - k, (uintptr_t) value);
+ res++;
+ }
+ else if (af == AF_INET6) {
+ if (k > 128) {
+ k = 128;
+ }
+
+ radix_insert_compressed(tree, addr_buf.buf, sizeof(addr_buf),
+ 128 - k, (uintptr_t) value);
+ res++;
+ }
+ cur++;
+ }
+
+ g_strfreev(strv);
+
+ return res;
+}
+
+gboolean
+radix_add_generic_iplist(const gchar *ip_list, radix_compressed_t **tree,
+ gboolean resolve, const gchar *tree_name)
+{
+ static const char fill_ptr[] = "1";
+
+ if (*tree == NULL) {
+ *tree = radix_create_compressed(tree_name);
+ }
+
+ return (rspamd_radix_add_iplist(ip_list, ",; ", *tree,
+ fill_ptr, resolve, tree_name) > 0);
+}
+
+
+gsize radix_get_size(radix_compressed_t *tree)
+{
+ if (tree != NULL) {
+ return tree->size;
+ }
+
+ return 0;
+}
+
+
+rspamd_mempool_t *
+radix_get_pool(radix_compressed_t *tree)
+{
+
+ if (tree != NULL) {
+ return tree->pool;
+ }
+
+ return NULL;
+}
+
+const gchar *
+radix_get_info(radix_compressed_t *tree)
+{
+ if (tree == NULL) {
+ return NULL;
+ }
+
+ return btrie_stats(tree->tree, tree->duplicates);
+}