/*
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 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;inetwork,
&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 */