/* This file is part of GNUnet Copyright (C) 2012, 2013, 2015 GNUnet e.V. GNUnet is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. GNUnet 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . SPDX-License-Identifier: AGPL3.0-or-later */ /** * @file src/tun/regex.c * @brief functions to convert IP networks to regexes * @author Maximilian Szengel * @author Christian Grothoff */ #include "platform.h" #include "gnunet_util_lib.h" #include "gnunet_tun_lib.h" /** * 'wildcard', matches all possible values (for HEX encoding). */ #define DOT "(0|1|2|3|4|5|6|7|8|9|A|B|C|D|E|F)" /** * Create a regex in @a rxstr from the given @a ip and @a netmask. * * @param ip IPv4 representation. * @param port destination port * @param rxstr generated regex, must be at least #GNUNET_TUN_IPV4_REGEXLEN * bytes long. */ void GNUNET_TUN_ipv4toregexsearch (const struct in_addr *ip, uint16_t port, char *rxstr) { GNUNET_snprintf (rxstr, GNUNET_TUN_IPV4_REGEXLEN, "4-%04X-%08X", (unsigned int) port, ntohl (ip->s_addr)); } /** * Create a regex in @a rxstr from the given @a ipv6 and @a prefixlen. * * @param ipv6 IPv6 representation. * @param port destination port * @param rxstr generated regex, must be at least #GNUNET_TUN_IPV6_REGEXLEN * bytes long. */ void GNUNET_TUN_ipv6toregexsearch (const struct in6_addr *ipv6, uint16_t port, char *rxstr) { const uint32_t *addr; addr = (const uint32_t *) ipv6; GNUNET_snprintf (rxstr, GNUNET_TUN_IPV6_REGEXLEN, "6-%04X-%08X%08X%08X%08X", (unsigned int) port, ntohl (addr[0]), ntohl (addr[1]), ntohl (addr[2]), ntohl (addr[3])); } /** * Convert the given 4-bit (!) number to a regex. * * @param value the value, only the lowest 4 bits will be looked at * @param mask which bits in value are wildcards (any value)? */ static char * nibble_to_regex (uint8_t value, uint8_t mask) { char *ret; value &= mask; switch (mask) { case 0: return GNUNET_strdup (DOT); case 8: GNUNET_asprintf (&ret, "(%X|%X|%X|%X|%X|%X|%X|%X)", value, value + 1, value + 2, value + 3, value + 4, value + 5, value + 6, value + 7); return ret; case 12: GNUNET_asprintf (&ret, "(%X|%X|%X|%X)", value, value + 1, value + 2, value + 3); return ret; case 14: GNUNET_asprintf (&ret, "(%X|%X)", value, value + 1); return ret; case 15: GNUNET_asprintf (&ret, "%X", value); return ret; default: GNUNET_log (GNUNET_ERROR_TYPE_WARNING, _ ("Bad mask: %d\n"), mask); GNUNET_break (0); return NULL; } } /** * Convert the given 16-bit number to a regex. * * @param value the value * @param mask which bits in value are wildcards (any value)? */ static char * num_to_regex (uint16_t value, uint16_t mask) { const uint8_t *v = (const uint8_t *) &value; const uint8_t *m = (const uint8_t *) &mask; char *a; char *b; char *c; char *d; char *ret; a = nibble_to_regex (v[0] >> 4, m[0] >> 4); b = nibble_to_regex (v[0] & 15, m[0] & 15); c = nibble_to_regex (v[1] >> 4, m[1] >> 4); d = nibble_to_regex (v[1] & 15, m[1] & 15); ret = NULL; if ((NULL != a) && (NULL != b) && (NULL != c) && (NULL != d)) GNUNET_asprintf (&ret, "%s%s%s%s", a, b, c, d); GNUNET_free_non_null (a); GNUNET_free_non_null (b); GNUNET_free_non_null (c); GNUNET_free_non_null (d); return ret; } /** * Do we need to put parents around the given argument? * * @param arg part of a regular expression * @return #GNUNET_YES if we should parens, * #GNUNET_NO if not */ static int needs_parens (const char *arg) { size_t off; size_t len; unsigned int op; op = 0; len = strlen (arg); for (off = 0; off < len; off++) { switch (arg[off]) { case '(': op++; break; case ')': GNUNET_assert (op > 0); op--; break; case '|': if (0 == op) return GNUNET_YES; break; default: break; } } return GNUNET_NO; } /** * Compute port policy for the given range of * port numbers. * * @param start starting offset * @param end end offset * @param step increment level (power of 16) * @param pp port policy to convert * @return corresponding regex */ static char * compute_policy (unsigned int start, unsigned int end, unsigned int step, const struct GNUNET_STRINGS_PortPolicy *pp) { unsigned int i; char before[36]; /* 16 * 2 + 3 dots + 0-terminator */ char middlel[33]; /* 16 * 2 + 0-terminator */ char middleh[33]; /* 16 * 2 + 0-terminator */ char after[36]; /* 16 * 2 + 3 dots + 0-terminator */ char beforep[36 + 2]; /* 16 * 2 + 3 dots + 0-terminator + ()*/ char middlehp[33 + 2]; /* 16 * 2 + 0-terminator + () */ char middlelp[33 + 2]; /* 16 * 2 + 0-terminator + () */ char afterp[36 + 2]; /* 16 * 2 + 3 dots + 0-terminator + () */ char dots[5 * strlen (DOT)]; char buf[3]; char *middle; char *ret; unsigned int xstep; char *recl; char *rech; char *reclp; char *rechp; unsigned int start_port; unsigned int end_port; GNUNET_assert (GNUNET_YES == pp->negate_portrange); start_port = pp->start_port; if (1 == start_port) start_port = 0; end_port = pp->end_port; GNUNET_assert ((end - start) / step <= 0xF); before[0] = '\0'; middlel[0] = '\0'; middleh[0] = '\0'; after[0] = '\0'; for (i = start; i <= end; i += step) { GNUNET_snprintf (buf, sizeof(buf), "%X|", (i - start) / step); if (i / step < start_port / step) strcat (before, buf); else if (i / step > end_port / step) strcat (after, buf); else if (i / step == start_port / step) strcat (middlel, buf); else if (i / step == end_port / step) strcat (middleh, buf); } if (strlen (before) > 0) before[strlen (before) - 1] = '\0'; if (strlen (middlel) > 0) middlel[strlen (middlel) - 1] = '\0'; if (strlen (middleh) > 0) middleh[strlen (middleh) - 1] = '\0'; if (strlen (after) > 0) after[strlen (after) - 1] = '\0'; if (needs_parens (before)) GNUNET_snprintf (beforep, sizeof(beforep), "(%s)", before); else strcpy (beforep, before); if (needs_parens (middlel)) GNUNET_snprintf (middlelp, sizeof(middlelp), "(%s)", middlel); else strcpy (middlelp, middlel); if (needs_parens (middleh)) GNUNET_snprintf (middlehp, sizeof(middlehp), "(%s)", middleh); else strcpy (middlehp, middleh); if (needs_parens (after)) GNUNET_snprintf (afterp, sizeof(afterp), "(%s)", after); else strcpy (afterp, after); dots[0] = '\0'; for (xstep = step / 16; xstep > 0; xstep /= 16) strcat (dots, DOT); if (step >= 16) { if (strlen (middlel) > 0) recl = compute_policy ((start_port / step) * step, (start_port / step) * step + step - 1, step / 16, pp); else recl = GNUNET_strdup (""); if (strlen (middleh) > 0) rech = compute_policy ((end_port / step) * step, (end_port / step) * step + step - 1, step / 16, pp); else rech = GNUNET_strdup (""); } else { recl = GNUNET_strdup (""); rech = GNUNET_strdup (""); middlel[0] = '\0'; middlelp[0] = '\0'; middleh[0] = '\0'; middlehp[0] = '\0'; } if (needs_parens (recl)) GNUNET_asprintf (&reclp, "(%s)", recl); else reclp = GNUNET_strdup (recl); if (needs_parens (rech)) GNUNET_asprintf (&rechp, "(%s)", rech); else rechp = GNUNET_strdup (rech); if ((strlen (middleh) > 0) && (strlen (rech) > 0) && (strlen (middlel) > 0) && (strlen (recl) > 0)) { GNUNET_asprintf (&middle, "%s%s|%s%s", middlel, reclp, middleh, rechp); } else if ((strlen (middleh) > 0) && (strlen (rech) > 0)) { GNUNET_asprintf (&middle, "%s%s", middleh, rechp); } else if ((strlen (middlel) > 0) && (strlen (recl) > 0)) { GNUNET_asprintf (&middle, "%s%s", middlel, reclp); } else { middle = GNUNET_strdup (""); } if ((strlen (before) > 0) && (strlen (after) > 0)) { if (strlen (dots) > 0) { if (strlen (middle) > 0) GNUNET_asprintf (&ret, "(%s%s|%s|%s%s)", beforep, dots, middle, afterp, dots); else GNUNET_asprintf (&ret, "(%s|%s)%s", beforep, afterp, dots); } else { if (strlen (middle) > 0) GNUNET_asprintf (&ret, "(%s|%s|%s)", before, middle, after); else if (1 == step) GNUNET_asprintf (&ret, "%s|%s", before, after); else GNUNET_asprintf (&ret, "(%s|%s)", before, after); } } else if (strlen (before) > 0) { if (strlen (dots) > 0) { if (strlen (middle) > 0) GNUNET_asprintf (&ret, "(%s%s|%s)", beforep, dots, middle); else GNUNET_asprintf (&ret, "%s%s", beforep, dots); } else { if (strlen (middle) > 0) GNUNET_asprintf (&ret, "(%s|%s)", before, middle); else GNUNET_asprintf (&ret, "%s", before); } } else if (strlen (after) > 0) { if (strlen (dots) > 0) { if (strlen (middle) > 0) GNUNET_asprintf (&ret, "(%s|%s%s)", middle, afterp, dots); else GNUNET_asprintf (&ret, "%s%s", afterp, dots); } else { if (strlen (middle) > 0) GNUNET_asprintf (&ret, "%s|%s", middle, after); else GNUNET_asprintf (&ret, "%s", after); } } else if (strlen (middle) > 0) { GNUNET_asprintf (&ret, "%s", middle); } else { ret = GNUNET_strdup (""); } GNUNET_free (middle); GNUNET_free (reclp); GNUNET_free (rechp); GNUNET_free (recl); GNUNET_free (rech); return ret; } /** * Convert a port policy to a regular expression. Note: this is a * very simplistic implementation, we might want to consider doing * something more sophisiticated (resulting in smaller regular * expressions) at a later time. * * @param pp port policy to convert * @return NULL on error */ static char * port_to_regex (const struct GNUNET_STRINGS_PortPolicy *pp) { char *reg; char *ret; char *pos; unsigned int i; unsigned int cnt; if ((0 == pp->start_port) || ((1 == pp->start_port) && (0xFFFF == pp->end_port) && (GNUNET_NO == pp->negate_portrange))) return GNUNET_strdup (DOT DOT DOT DOT); if ((pp->start_port == pp->end_port) && (GNUNET_NO == pp->negate_portrange)) { GNUNET_asprintf (&ret, "%04X", pp->start_port); return ret; } if (pp->end_port < pp->start_port) return NULL; if (GNUNET_YES == pp->negate_portrange) { ret = compute_policy (0, 0xFFFF, 0x1000, pp); } else { cnt = pp->end_port - pp->start_port + 1; reg = GNUNET_malloc (cnt * 5 + 1); pos = reg; for (i = 1; i <= 0xFFFF; i++) { if ((i >= pp->start_port) && (i <= pp->end_port)) { if (pos == reg) { GNUNET_snprintf (pos, 5, "%04X", i); } else { GNUNET_snprintf (pos, 6, "|%04X", i); } pos += strlen (pos); } } GNUNET_asprintf (&ret, "(%s)", reg); GNUNET_free (reg); } return ret; } /** * Convert an address (IPv4 or IPv6) to a regex. * * @param addr address * @param mask network mask * @param len number of bytes in @a addr and @a mask * @return NULL on error, otherwise regex for the address */ static char * address_to_regex (const void *addr, const void *mask, size_t len) { const uint16_t *a = addr; const uint16_t *m = mask; char *ret; char *tmp; char *reg; unsigned int i; ret = NULL; GNUNET_assert (1 != (len % 2)); for (i = 0; i < len / 2; i++) { reg = num_to_regex (a[i], m[i]); if (NULL == reg) { GNUNET_free_non_null (ret); return NULL; } if (NULL == ret) { ret = reg; } else { GNUNET_asprintf (&tmp, "%s%s", ret, reg); GNUNET_free (ret); GNUNET_free (reg); ret = tmp; } } return ret; } /** * Convert a single line of an IPv4 policy to a regular expression. * * @param v4 line to convert * @return NULL on error */ static char * ipv4_to_regex (const struct GNUNET_STRINGS_IPv4NetworkPolicy *v4) { char *reg; char *pp; char *ret; reg = address_to_regex (&v4->network, &v4->netmask, sizeof(struct in_addr)); if (NULL == reg) return NULL; pp = port_to_regex (&v4->pp); if (NULL == pp) { GNUNET_free (reg); return NULL; } GNUNET_asprintf (&ret, "4-%s-%s", pp, reg); GNUNET_free (pp); GNUNET_free (reg); return ret; } /** * Convert a single line of an IPv4 policy to a regular expression. * * @param v6 line to convert * @return NULL on error */ static char * ipv6_to_regex (const struct GNUNET_STRINGS_IPv6NetworkPolicy *v6) { char *reg; char *pp; char *ret; reg = address_to_regex (&v6->network, &v6->netmask, sizeof(struct in6_addr)); if (NULL == reg) return NULL; pp = port_to_regex (&v6->pp); if (NULL == pp) { GNUNET_free (reg); return NULL; } GNUNET_asprintf (&ret, "6-%s-%s", pp, reg); GNUNET_free (pp); GNUNET_free (reg); return ret; } /** * Convert an exit policy to a regular expression. The exit policy * specifies a set of subnets this peer is willing to serve as an * exit for; the resulting regular expression will match the * IPv4 address strings as returned by #GNUNET_TUN_ipv4toregexsearch(). * * @param policy exit policy specification * @return regular expression, NULL on error */ char * GNUNET_TUN_ipv4policy2regex (const char *policy) { struct GNUNET_STRINGS_IPv4NetworkPolicy *np; char *reg; char *tmp; char *line; unsigned int i; np = GNUNET_STRINGS_parse_ipv4_policy (policy); if (NULL == np) return NULL; reg = NULL; for (i = 0; (0 == i) || (0 != np[i].network.s_addr); i++) { line = ipv4_to_regex (&np[i]); if (NULL == line) { GNUNET_free_non_null (reg); GNUNET_free (np); return NULL; } if (NULL == reg) { reg = line; } else { GNUNET_asprintf (&tmp, "%s|(%s)", reg, line); GNUNET_free (reg); GNUNET_free (line); reg = tmp; } if (0 == np[i].network.s_addr) break; } GNUNET_free (np); return reg; } /** * Convert an exit policy to a regular expression. The exit policy * specifies a set of subnets this peer is willing to serve as an * exit for; the resulting regular expression will match the * IPv6 address strings as returned by #GNUNET_TUN_ipv6toregexsearch(). * * @param policy exit policy specification * @return regular expression, NULL on error */ char * GNUNET_TUN_ipv6policy2regex (const char *policy) { struct in6_addr zero; struct GNUNET_STRINGS_IPv6NetworkPolicy *np; char *reg; char *tmp; char *line; unsigned int i; np = GNUNET_STRINGS_parse_ipv6_policy (policy); if (NULL == np) return NULL; reg = NULL; memset (&zero, 0, sizeof(struct in6_addr)); for (i = 0; (0 == i) || (0 != memcmp (&zero, &np[i].network, sizeof(struct in6_addr))); i++) { line = ipv6_to_regex (&np[i]); if (NULL == line) { GNUNET_free_non_null (reg); GNUNET_free (np); return NULL; } if (NULL == reg) { reg = line; } else { GNUNET_asprintf (&tmp, "%s|(%s)", reg, line); GNUNET_free (reg); GNUNET_free (line); reg = tmp; } if (0 == memcmp (&zero, &np[i].network, sizeof(struct in6_addr))) break; } GNUNET_free (np); return reg; } /** * Hash the service name of a hosted service to the * hash code that is used to identify the service on * the network. * * @param service_name a string * @param hc corresponding hash */ void GNUNET_TUN_service_name_to_hash (const char *service_name, struct GNUNET_HashCode *hc) { GNUNET_CRYPTO_hash (service_name, strlen (service_name), hc); } /** * Compute the CADET port given a service descriptor * (returned from #GNUNET_TUN_service_name_to_hash) and * a TCP/UDP port @a ip_port. * * @param desc service shared secret * @param ip_port TCP/UDP port, use 0 for ICMP * @param[out] cadet_port CADET port to use */ void GNUNET_TUN_compute_service_cadet_port (const struct GNUNET_HashCode *desc, uint16_t ip_port, struct GNUNET_HashCode *cadet_port) { uint16_t be_port = htons (ip_port); *cadet_port = *desc; GNUNET_memcpy (cadet_port, &be_port, sizeof(uint16_t)); } /* end of regex.c */