From 4a1f21163c041e673604813a8c48c14bf9f20222 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 25 Jun 2018 19:52:15 +0200 Subject: move dns ops again, 2nd try --- src/util/Makefile.am | 25 +- src/util/dnsparser.c | 1334 +++++++++++++++++++++++++++++++++++++++++++++++++ src/util/dnsstub.c | 749 +++++++++++++++++++++++++++ src/util/regex.c | 834 +++++++++++++++++++++++++++++++ src/util/test_regex.c | 179 +++++++ src/util/test_tun.c | 72 +++ src/util/tun.c | 309 ++++++++++++ 7 files changed, 3500 insertions(+), 2 deletions(-) create mode 100644 src/util/dnsparser.c create mode 100644 src/util/dnsstub.c create mode 100644 src/util/regex.c create mode 100644 src/util/test_regex.c create mode 100644 src/util/test_tun.c create mode 100644 src/util/tun.c (limited to 'src/util') diff --git a/src/util/Makefile.am b/src/util/Makefile.am index 4296199db..ec7bcb016 100644 --- a/src/util/Makefile.am +++ b/src/util/Makefile.am @@ -89,6 +89,8 @@ libgnunetutil_la_SOURCES = \ crypto_rsa.c \ disk.c \ disk.h \ + dnsparser.c \ + dnsstub.c \ getopt.c \ getopt_helpers.c \ helper.c \ @@ -104,12 +106,14 @@ libgnunetutil_la_SOURCES = \ peer.c \ plugin.c \ program.c \ + regex.c \ resolver_api.c resolver.h \ scheduler.c \ service.c \ signal.c \ strings.c \ time.c \ + tun.c \ speedup.c speedup.h libgnunetutil_la_LIBADD = \ @@ -117,7 +121,7 @@ libgnunetutil_la_LIBADD = \ $(LIBGCRYPT_LIBS) \ $(LTLIBICONV) \ $(LTLIBINTL) \ - -lltdl $(Z_LIBS) -lunistring $(XLIB) + -lltdl -lidn $(Z_LIBS) -lunistring $(XLIB) libgnunetutil_la_LDFLAGS = \ $(GN_LIB_LDFLAGS) \ @@ -291,19 +295,22 @@ check_PROGRAMS = \ test_crypto_rsa \ test_disk \ test_getopt \ + test_hexcoder \ test_mq \ test_os_network \ test_peer \ test_plugin \ test_program \ + test_regex \ test_resolver_api.nc \ test_scheduler \ test_scheduler_delay \ test_service \ test_strings \ test_strings_to_data \ - test_time \ test_speedup \ + test_time \ + test_tun \ $(BENCHMARKS) \ test_os_start_process \ test_common_logging_runtime_loglevels @@ -319,6 +326,20 @@ test_bio_SOURCES = \ test_bio_LDADD = \ libgnunetutil.la +test_hexcoder_SOURCES = \ + test_hexcoder.c +test_hexcoder_LDADD = \ + libgnunetutil.la + +test_tun_SOURCES = \ + test_tun.c +test_tun_LDADD = \ + libgnunetutil.la + +test_regex_SOURCES = \ + test_regex.c +test_regex_LDADD = \ + libgnunetutil.la test_os_start_process_SOURCES = \ test_os_start_process.c diff --git a/src/util/dnsparser.c b/src/util/dnsparser.c new file mode 100644 index 000000000..32ad7c0c2 --- /dev/null +++ b/src/util/dnsparser.c @@ -0,0 +1,1334 @@ +/* + This file is part of GNUnet + Copyright (C) 2010-2014 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 . + */ + +/** + * @file dns/dnsparser.c + * @brief helper library to parse DNS packets. + * @author Philipp Toelke + * @author Christian Grothoff + */ +#include "platform.h" +#include +#if WINDOWS +#include +#endif +#include "gnunet_util_lib.h" +#include "gnunet_dnsparser_lib.h" +#include "gnunet_tun_lib.h" + + +/** + * Check if a label in UTF-8 format can be coded into valid IDNA. + * This can fail if the ASCII-conversion becomes longer than 63 characters. + * + * @param label label to check (UTF-8 string) + * @return #GNUNET_OK if the label can be converted to IDNA, + * #GNUNET_SYSERR if the label is not valid for DNS names + */ +int +GNUNET_DNSPARSER_check_label (const char *label) +{ + char *output; + size_t slen; + + if (NULL != strchr (label, '.')) + return GNUNET_SYSERR; /* not a label! Did you mean GNUNET_DNSPARSER_check_name? */ + if (IDNA_SUCCESS != + idna_to_ascii_8z (label, &output, IDNA_ALLOW_UNASSIGNED)) + return GNUNET_SYSERR; + slen = strlen (output); +#if WINDOWS + idn_free (output); +#else + free (output); +#endif + return (slen > 63) ? GNUNET_SYSERR : GNUNET_OK; +} + + +/** + * Check if a label in UTF-8 format can be coded into valid IDNA. + * This can fail if the ASCII-conversion becomes longer than 253 characters. + * + * @param name name to check (UTF-8 string) + * @return #GNUNET_OK if the label can be converted to IDNA, + * #GNUNET_SYSERR if the label is not valid for DNS names + */ +int +GNUNET_DNSPARSER_check_name (const char *name) +{ + char *ldup; + char *output; + size_t slen; + char *tok; + + ldup = GNUNET_strdup (name); + for (tok = strtok (ldup, "."); NULL != tok; tok = strtok (NULL, ".")) + if (GNUNET_OK != + GNUNET_DNSPARSER_check_label (tok)) + { + GNUNET_free (ldup); + return GNUNET_SYSERR; + } + GNUNET_free (ldup); + if (IDNA_SUCCESS != + idna_to_ascii_8z (name, &output, IDNA_ALLOW_UNASSIGNED)) + return GNUNET_SYSERR; + slen = strlen (output); +#if WINDOWS + idn_free (output); +#else + free (output); +#endif + return (slen > 253) ? GNUNET_SYSERR : GNUNET_OK; +} + + +/** + * Free SOA information record. + * + * @param soa record to free + */ +void +GNUNET_DNSPARSER_free_soa (struct GNUNET_DNSPARSER_SoaRecord *soa) +{ + if (NULL == soa) + return; + GNUNET_free_non_null (soa->mname); + GNUNET_free_non_null (soa->rname); + GNUNET_free (soa); +} + + +/** + * Free CERT information record. + * + * @param cert record to free + */ +void +GNUNET_DNSPARSER_free_cert (struct GNUNET_DNSPARSER_CertRecord *cert) +{ + if (NULL == cert) + return; + GNUNET_free_non_null (cert->certificate_data); + GNUNET_free (cert); +} + + +/** + * Free SRV information record. + * + * @param srv record to free + */ +void +GNUNET_DNSPARSER_free_srv (struct GNUNET_DNSPARSER_SrvRecord *srv) +{ + if (NULL == srv) + return; + GNUNET_free_non_null (srv->target); + GNUNET_free (srv); +} + + +/** + * Free MX information record. + * + * @param mx record to free + */ +void +GNUNET_DNSPARSER_free_mx (struct GNUNET_DNSPARSER_MxRecord *mx) +{ + if (NULL == mx) + return; + GNUNET_free_non_null (mx->mxhost); + GNUNET_free (mx); +} + + +/** + * Free the given DNS record. + * + * @param r record to free + */ +void +GNUNET_DNSPARSER_free_record (struct GNUNET_DNSPARSER_Record *r) +{ + GNUNET_free_non_null (r->name); + switch (r->type) + { + case GNUNET_DNSPARSER_TYPE_MX: + GNUNET_DNSPARSER_free_mx (r->data.mx); + break; + case GNUNET_DNSPARSER_TYPE_SOA: + GNUNET_DNSPARSER_free_soa (r->data.soa); + break; + case GNUNET_DNSPARSER_TYPE_SRV: + GNUNET_DNSPARSER_free_srv (r->data.srv); + break; + case GNUNET_DNSPARSER_TYPE_CERT: + GNUNET_DNSPARSER_free_cert (r->data.cert); + break; + case GNUNET_DNSPARSER_TYPE_NS: + case GNUNET_DNSPARSER_TYPE_CNAME: + case GNUNET_DNSPARSER_TYPE_PTR: + GNUNET_free_non_null (r->data.hostname); + break; + default: + GNUNET_free_non_null (r->data.raw.data); + break; + } +} + + +/** + * Parse name inside of a DNS query or record. + * + * @param udp_payload entire UDP payload + * @param udp_payload_length length of @a udp_payload + * @param off pointer to the offset of the name to parse in the udp_payload (to be + * incremented by the size of the name) + * @param depth current depth of our recursion (to prevent stack overflow) + * @return name as 0-terminated C string on success, NULL if the payload is malformed + */ +static char * +parse_name (const char *udp_payload, + size_t udp_payload_length, + size_t *off, + unsigned int depth) +{ + const uint8_t *input = (const uint8_t *) udp_payload; + char *ret; + char *tmp; + char *xstr; + uint8_t len; + size_t xoff; + char *utf8; + Idna_rc rc; + + ret = GNUNET_strdup (""); + while (1) + { + if (*off >= udp_payload_length) + { + GNUNET_break_op (0); + goto error; + } + len = input[*off]; + if (0 == len) + { + (*off)++; + break; + } + if (len < 64) + { + if (*off + 1 + len > udp_payload_length) + { + GNUNET_break_op (0); + goto error; + } + GNUNET_asprintf (&tmp, + "%.*s", + (int) len, + &udp_payload[*off + 1]); + if (IDNA_SUCCESS != + (rc = idna_to_unicode_8z8z (tmp, &utf8, IDNA_ALLOW_UNASSIGNED))) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + _("Failed to convert DNS IDNA name `%s' to UTF-8: %s\n"), + tmp, + idna_strerror (rc)); + GNUNET_free (tmp); + GNUNET_asprintf (&tmp, + "%s%.*s.", + ret, + (int) len, + &udp_payload[*off + 1]); + } + else + { + GNUNET_free (tmp); + GNUNET_asprintf (&tmp, + "%s%s.", + ret, + utf8); +#if WINDOWS + idn_free (utf8); +#else + free (utf8); +#endif + } + GNUNET_free (ret); + ret = tmp; + *off += 1 + len; + } + else if ((64 | 128) == (len & (64 | 128)) ) + { + if (depth > 32) + { + GNUNET_break_op (0); + goto error; /* hard bound on stack to prevent "infinite" recursion, disallow! */ + } + /* pointer to string */ + if (*off + 1 > udp_payload_length) + { + GNUNET_break_op (0); + goto error; + } + xoff = ((len - (64 | 128)) << 8) + input[*off+1]; + xstr = parse_name (udp_payload, + udp_payload_length, + &xoff, + depth + 1); + if (NULL == xstr) + { + GNUNET_break_op (0); + goto error; + } + GNUNET_asprintf (&tmp, + "%s%s.", + ret, + xstr); + GNUNET_free (ret); + GNUNET_free (xstr); + ret = tmp; + if (strlen (ret) > udp_payload_length) + { + GNUNET_break_op (0); + goto error; /* we are looping (building an infinite string) */ + } + *off += 2; + /* pointers always terminate names */ + break; + } + else + { + /* neither pointer nor inline string, not supported... */ + GNUNET_break_op (0); + goto error; + } + } + if (0 < strlen(ret)) + ret[strlen(ret)-1] = '\0'; /* eat tailing '.' */ + return ret; + error: + GNUNET_break_op (0); + GNUNET_free (ret); + return NULL; +} + + +/** + * Parse name inside of a DNS query or record. + * + * @param udp_payload entire UDP payload + * @param udp_payload_length length of @a udp_payload + * @param off pointer to the offset of the name to parse in the udp_payload (to be + * incremented by the size of the name) + * @return name as 0-terminated C string on success, NULL if the payload is malformed + */ +char * +GNUNET_DNSPARSER_parse_name (const char *udp_payload, + size_t udp_payload_length, + size_t *off) +{ + return parse_name (udp_payload, udp_payload_length, off, 0); +} + + +/** + * Parse a DNS query entry. + * + * @param udp_payload entire UDP payload + * @param udp_payload_length length of @a udp_payload + * @param off pointer to the offset of the query to parse in the udp_payload (to be + * incremented by the size of the query) + * @param q where to write the query information + * @return #GNUNET_OK on success, #GNUNET_SYSERR if the query is malformed + */ +int +GNUNET_DNSPARSER_parse_query (const char *udp_payload, + size_t udp_payload_length, + size_t *off, + struct GNUNET_DNSPARSER_Query *q) +{ + char *name; + struct GNUNET_TUN_DnsQueryLine ql; + + name = GNUNET_DNSPARSER_parse_name (udp_payload, + udp_payload_length, + off); + if (NULL == name) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + q->name = name; + if (*off + sizeof (struct GNUNET_TUN_DnsQueryLine) > udp_payload_length) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + GNUNET_memcpy (&ql, &udp_payload[*off], sizeof (ql)); + *off += sizeof (ql); + q->type = ntohs (ql.type); + q->dns_traffic_class = ntohs (ql.dns_traffic_class); + return GNUNET_OK; +} + + +/** + * Parse a DNS SOA record. + * + * @param udp_payload reference to UDP packet + * @param udp_payload_length length of @a udp_payload + * @param off pointer to the offset of the query to parse in the SOA record (to be + * incremented by the size of the record), unchanged on error + * @return the parsed SOA record, NULL on error + */ +struct GNUNET_DNSPARSER_SoaRecord * +GNUNET_DNSPARSER_parse_soa (const char *udp_payload, + size_t udp_payload_length, + size_t *off) +{ + struct GNUNET_DNSPARSER_SoaRecord *soa; + struct GNUNET_TUN_DnsSoaRecord soa_bin; + size_t old_off; + + old_off = *off; + soa = GNUNET_new (struct GNUNET_DNSPARSER_SoaRecord); + soa->mname = GNUNET_DNSPARSER_parse_name (udp_payload, + udp_payload_length, + off); + soa->rname = GNUNET_DNSPARSER_parse_name (udp_payload, + udp_payload_length, + off); + if ( (NULL == soa->mname) || + (NULL == soa->rname) || + (*off + sizeof (struct GNUNET_TUN_DnsSoaRecord) > udp_payload_length) ) + { + GNUNET_break_op (0); + GNUNET_DNSPARSER_free_soa (soa); + *off = old_off; + return NULL; + } + GNUNET_memcpy (&soa_bin, + &udp_payload[*off], + sizeof (struct GNUNET_TUN_DnsSoaRecord)); + soa->serial = ntohl (soa_bin.serial); + soa->refresh = ntohl (soa_bin.refresh); + soa->retry = ntohl (soa_bin.retry); + soa->expire = ntohl (soa_bin.expire); + soa->minimum_ttl = ntohl (soa_bin.minimum); + (*off) += sizeof (struct GNUNET_TUN_DnsSoaRecord); + return soa; +} + + +/** + * Parse a DNS MX record. + * + * @param udp_payload reference to UDP packet + * @param udp_payload_length length of @a udp_payload + * @param off pointer to the offset of the query to parse in the MX record (to be + * incremented by the size of the record), unchanged on error + * @return the parsed MX record, NULL on error + */ +struct GNUNET_DNSPARSER_MxRecord * +GNUNET_DNSPARSER_parse_mx (const char *udp_payload, + size_t udp_payload_length, + size_t *off) +{ + struct GNUNET_DNSPARSER_MxRecord *mx; + uint16_t mxpref; + size_t old_off; + + old_off = *off; + if (*off + sizeof (uint16_t) > udp_payload_length) + { + GNUNET_break_op (0); + return NULL; + } + GNUNET_memcpy (&mxpref, &udp_payload[*off], sizeof (uint16_t)); + (*off) += sizeof (uint16_t); + mx = GNUNET_new (struct GNUNET_DNSPARSER_MxRecord); + mx->preference = ntohs (mxpref); + mx->mxhost = GNUNET_DNSPARSER_parse_name (udp_payload, + udp_payload_length, + off); + if (NULL == mx->mxhost) + { + GNUNET_break_op (0); + GNUNET_DNSPARSER_free_mx (mx); + *off = old_off; + return NULL; + } + return mx; +} + + +/** + * Parse a DNS SRV record. + * + * @param udp_payload reference to UDP packet + * @param udp_payload_length length of @a udp_payload + * @param off pointer to the offset of the query to parse in the SRV record (to be + * incremented by the size of the record), unchanged on error + * @return the parsed SRV record, NULL on error + */ +struct GNUNET_DNSPARSER_SrvRecord * +GNUNET_DNSPARSER_parse_srv (const char *udp_payload, + size_t udp_payload_length, + size_t *off) +{ + struct GNUNET_DNSPARSER_SrvRecord *srv; + struct GNUNET_TUN_DnsSrvRecord srv_bin; + size_t old_off; + + old_off = *off; + if (*off + sizeof (struct GNUNET_TUN_DnsSrvRecord) > udp_payload_length) + return NULL; + GNUNET_memcpy (&srv_bin, + &udp_payload[*off], + sizeof (struct GNUNET_TUN_DnsSrvRecord)); + (*off) += sizeof (struct GNUNET_TUN_DnsSrvRecord); + srv = GNUNET_new (struct GNUNET_DNSPARSER_SrvRecord); + srv->priority = ntohs (srv_bin.prio); + srv->weight = ntohs (srv_bin.weight); + srv->port = ntohs (srv_bin.port); + srv->target = GNUNET_DNSPARSER_parse_name (udp_payload, + udp_payload_length, + off); + if (NULL == srv->target) + { + GNUNET_DNSPARSER_free_srv (srv); + *off = old_off; + return NULL; + } + return srv; +} + + +/** + * Parse a DNS CERT record. + * + * @param udp_payload reference to UDP packet + * @param udp_payload_length length of @a udp_payload + * @param off pointer to the offset of the query to parse in the CERT record (to be + * incremented by the size of the record), unchanged on error + * @return the parsed CERT record, NULL on error + */ +struct GNUNET_DNSPARSER_CertRecord * +GNUNET_DNSPARSER_parse_cert (const char *udp_payload, + size_t udp_payload_length, + size_t *off) +{ + struct GNUNET_DNSPARSER_CertRecord *cert; + struct GNUNET_TUN_DnsCertRecord dcert; + + if (*off + sizeof (struct GNUNET_TUN_DnsCertRecord) >= udp_payload_length) + { + GNUNET_break_op (0); + return NULL; + } + GNUNET_memcpy (&dcert, &udp_payload[*off], sizeof (struct GNUNET_TUN_DnsCertRecord)); + (*off) += sizeof (struct GNUNET_TUN_DnsCertRecord); + cert = GNUNET_new (struct GNUNET_DNSPARSER_CertRecord); + cert->cert_type = ntohs (dcert.cert_type); + cert->cert_tag = ntohs (dcert.cert_tag); + cert->algorithm = dcert.algorithm; + cert->certificate_size = udp_payload_length - (*off); + cert->certificate_data = GNUNET_malloc (cert->certificate_size); + GNUNET_memcpy (cert->certificate_data, + &udp_payload[*off], + cert->certificate_size); + (*off) += cert->certificate_size; + return cert; +} + + +/** + * Parse a DNS record entry. + * + * @param udp_payload entire UDP payload + * @param udp_payload_length length of @a udp_payload + * @param off pointer to the offset of the record to parse in the udp_payload (to be + * incremented by the size of the record) + * @param r where to write the record information + * @return #GNUNET_OK on success, #GNUNET_SYSERR if the record is malformed + */ +int +GNUNET_DNSPARSER_parse_record (const char *udp_payload, + size_t udp_payload_length, + size_t *off, + struct GNUNET_DNSPARSER_Record *r) +{ + char *name; + struct GNUNET_TUN_DnsRecordLine rl; + size_t old_off; + uint16_t data_len; + + name = GNUNET_DNSPARSER_parse_name (udp_payload, + udp_payload_length, + off); + if (NULL == name) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + r->name = name; + if (*off + sizeof (struct GNUNET_TUN_DnsRecordLine) > udp_payload_length) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + GNUNET_memcpy (&rl, &udp_payload[*off], sizeof (rl)); + (*off) += sizeof (rl); + r->type = ntohs (rl.type); + r->dns_traffic_class = ntohs (rl.dns_traffic_class); + r->expiration_time = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, + ntohl (rl.ttl))); + data_len = ntohs (rl.data_len); + if (*off + data_len > udp_payload_length) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + old_off = *off; + switch (r->type) + { + case GNUNET_DNSPARSER_TYPE_NS: + case GNUNET_DNSPARSER_TYPE_CNAME: + case GNUNET_DNSPARSER_TYPE_DNAME: + case GNUNET_DNSPARSER_TYPE_PTR: + r->data.hostname = GNUNET_DNSPARSER_parse_name (udp_payload, + udp_payload_length, + off); + if ( (NULL == r->data.hostname) || + (old_off + data_len != *off) ) + return GNUNET_SYSERR; + return GNUNET_OK; + case GNUNET_DNSPARSER_TYPE_SOA: + r->data.soa = GNUNET_DNSPARSER_parse_soa (udp_payload, + udp_payload_length, + off); + if ( (NULL == r->data.soa) || + (old_off + data_len != *off) ) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; + case GNUNET_DNSPARSER_TYPE_MX: + r->data.mx = GNUNET_DNSPARSER_parse_mx (udp_payload, + udp_payload_length, + off); + if ( (NULL == r->data.mx) || + (old_off + data_len != *off) ) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; + case GNUNET_DNSPARSER_TYPE_SRV: + r->data.srv = GNUNET_DNSPARSER_parse_srv (udp_payload, + udp_payload_length, + off); + if ( (NULL == r->data.srv) || + (old_off + data_len != *off) ) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; + default: + r->data.raw.data = GNUNET_malloc (data_len); + r->data.raw.data_len = data_len; + GNUNET_memcpy (r->data.raw.data, + &udp_payload[*off], + data_len); + break; + } + (*off) += data_len; + return GNUNET_OK; +} + + +/** + * Parse a UDP payload of a DNS packet in to a nice struct for further + * processing and manipulation. + * + * @param udp_payload wire-format of the DNS packet + * @param udp_payload_length number of bytes in @a udp_payload + * @return NULL on error, otherwise the parsed packet + */ +struct GNUNET_DNSPARSER_Packet * +GNUNET_DNSPARSER_parse (const char *udp_payload, + size_t udp_payload_length) +{ + struct GNUNET_DNSPARSER_Packet *p; + const struct GNUNET_TUN_DnsHeader *dns; + size_t off; + unsigned int n; + unsigned int i; + + if (udp_payload_length < sizeof (struct GNUNET_TUN_DnsHeader)) + return NULL; + dns = (const struct GNUNET_TUN_DnsHeader *) udp_payload; + off = sizeof (struct GNUNET_TUN_DnsHeader); + p = GNUNET_new (struct GNUNET_DNSPARSER_Packet); + p->flags = dns->flags; + p->id = dns->id; + n = ntohs (dns->query_count); + if (n > 0) + { + p->queries = GNUNET_malloc (n * sizeof (struct GNUNET_DNSPARSER_Query)); + p->num_queries = n; + for (i=0;iqueries[i])) + goto error; + } + n = ntohs (dns->answer_rcount); + if (n > 0) + { + p->answers = GNUNET_malloc (n * sizeof (struct GNUNET_DNSPARSER_Record)); + p->num_answers = n; + for (i=0;ianswers[i])) + goto error; + } + n = ntohs (dns->authority_rcount); + if (n > 0) + { + p->authority_records = GNUNET_malloc (n * sizeof (struct GNUNET_DNSPARSER_Record)); + p->num_authority_records = n; + for (i=0;iauthority_records[i])) + goto error; + } + n = ntohs (dns->additional_rcount); + if (n > 0) + { + p->additional_records = GNUNET_malloc (n * sizeof (struct GNUNET_DNSPARSER_Record)); + p->num_additional_records = n; + for (i=0;iadditional_records[i])) + goto error; + } + return p; + error: + GNUNET_break_op (0); + GNUNET_DNSPARSER_free_packet (p); + return NULL; +} + + +/** + * Free memory taken by a packet. + * + * @param p packet to free + */ +void +GNUNET_DNSPARSER_free_packet (struct GNUNET_DNSPARSER_Packet *p) +{ + unsigned int i; + + for (i=0;inum_queries;i++) + GNUNET_free_non_null (p->queries[i].name); + GNUNET_free_non_null (p->queries); + for (i=0;inum_answers;i++) + GNUNET_DNSPARSER_free_record (&p->answers[i]); + GNUNET_free_non_null (p->answers); + for (i=0;inum_authority_records;i++) + GNUNET_DNSPARSER_free_record (&p->authority_records[i]); + GNUNET_free_non_null (p->authority_records); + for (i=0;inum_additional_records;i++) + GNUNET_DNSPARSER_free_record (&p->additional_records[i]); + GNUNET_free_non_null (p->additional_records); + GNUNET_free (p); +} + + +/* ********************** DNS packet assembly code **************** */ + + +/** + * Add a DNS name to the UDP packet at the given location, converting + * the name to IDNA notation as necessary. + * + * @param dst where to write the name (UDP packet) + * @param dst_len number of bytes in @a dst + * @param off pointer to offset where to write the name (increment by bytes used) + * must not be changed if there is an error + * @param name name to write + * @return #GNUNET_SYSERR if @a name is invalid + * #GNUNET_NO if @a name did not fit + * #GNUNET_OK if @a name was added to @a dst + */ +int +GNUNET_DNSPARSER_builder_add_name (char *dst, + size_t dst_len, + size_t *off, + const char *name) +{ + const char *dot; + const char *idna_name; + char *idna_start; + size_t start; + size_t pos; + size_t len; + Idna_rc rc; + + if (NULL == name) + return GNUNET_SYSERR; + + if (IDNA_SUCCESS != + (rc = idna_to_ascii_8z (name, + &idna_start, + IDNA_ALLOW_UNASSIGNED))) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + _("Failed to convert UTF-8 name `%s' to DNS IDNA format: %s\n"), + name, + idna_strerror (rc)); + return GNUNET_NO; + } + idna_name = idna_start; + start = *off; + if (start + strlen (idna_name) + 2 > dst_len) + goto fail; + pos = start; + do + { + dot = strchr (idna_name, '.'); + if (NULL == dot) + len = strlen (idna_name); + else + len = dot - idna_name; + if ( (len >= 64) || (0 == len) ) + { + GNUNET_break (0); + goto fail; /* segment too long or empty */ + } + dst[pos++] = (char) (uint8_t) len; + GNUNET_memcpy (&dst[pos], + idna_name, + len); + pos += len; + idna_name += len + 1; /* also skip dot */ + } + while (NULL != dot); + dst[pos++] = '\0'; /* terminator */ + *off = pos; +#if WINDOWS + idn_free (idna_start); +#else + free (idna_start); +#endif + return GNUNET_OK; + fail: +#if WINDOWS + idn_free (idna_start); +#else + free (idna_start); +#endif + return GNUNET_NO; +} + + +/** + * Add a DNS query to the UDP packet at the given location. + * + * @param dst where to write the query + * @param dst_len number of bytes in @a dst + * @param off pointer to offset where to write the query (increment by bytes used) + * must not be changed if there is an error + * @param query query to write + * @return #GNUNET_SYSERR if @a query is invalid + * #GNUNET_NO if @a query did not fit + * #GNUNET_OK if @a query was added to @a dst + */ +int +GNUNET_DNSPARSER_builder_add_query (char *dst, + size_t dst_len, + size_t *off, + const struct GNUNET_DNSPARSER_Query *query) +{ + int ret; + struct GNUNET_TUN_DnsQueryLine ql; + + ret = GNUNET_DNSPARSER_builder_add_name (dst, dst_len - sizeof (struct GNUNET_TUN_DnsQueryLine), off, query->name); + if (ret != GNUNET_OK) + return ret; + ql.type = htons (query->type); + ql.dns_traffic_class = htons (query->dns_traffic_class); + GNUNET_memcpy (&dst[*off], &ql, sizeof (ql)); + (*off) += sizeof (ql); + return GNUNET_OK; +} + + +/** + * Add an MX record to the UDP packet at the given location. + * + * @param dst where to write the mx record + * @param dst_len number of bytes in @a dst + * @param off pointer to offset where to write the mx information (increment by bytes used); + * can also change if there was an error + * @param mx mx information to write + * @return #GNUNET_SYSERR if @a mx is invalid + * #GNUNET_NO if @a mx did not fit + * #GNUNET_OK if @a mx was added to @a dst + */ +int +GNUNET_DNSPARSER_builder_add_mx (char *dst, + size_t dst_len, + size_t *off, + const struct GNUNET_DNSPARSER_MxRecord *mx) +{ + uint16_t mxpref; + + if (*off + sizeof (uint16_t) > dst_len) + return GNUNET_NO; + mxpref = htons (mx->preference); + GNUNET_memcpy (&dst[*off], + &mxpref, + sizeof (mxpref)); + (*off) += sizeof (mxpref); + return GNUNET_DNSPARSER_builder_add_name (dst, + dst_len, + off, + mx->mxhost); +} + + +/** + * Add a CERT record to the UDP packet at the given location. + * + * @param dst where to write the CERT record + * @param dst_len number of bytes in @a dst + * @param off pointer to offset where to write the CERT information (increment by bytes used); + * can also change if there was an error + * @param cert CERT information to write + * @return #GNUNET_SYSERR if @a cert is invalid + * #GNUNET_NO if @a cert did not fit + * #GNUNET_OK if @a cert was added to @a dst + */ +int +GNUNET_DNSPARSER_builder_add_cert (char *dst, + size_t dst_len, + size_t *off, + const struct GNUNET_DNSPARSER_CertRecord *cert) +{ + struct GNUNET_TUN_DnsCertRecord dcert; + + if ( (cert->cert_type > UINT16_MAX) || + (cert->cert_tag > UINT16_MAX) || + (cert->algorithm > UINT8_MAX) ) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (*off + sizeof (struct GNUNET_TUN_DnsCertRecord) + cert->certificate_size > dst_len) + return GNUNET_NO; + dcert.cert_type = htons ((uint16_t) cert->cert_type); + dcert.cert_tag = htons ((uint16_t) cert->cert_tag); + dcert.algorithm = (uint8_t) cert->algorithm; + GNUNET_memcpy (&dst[*off], &dcert, sizeof (dcert)); + (*off) += sizeof (dcert); + GNUNET_memcpy (&dst[*off], cert->certificate_data, cert->certificate_size); + (*off) += cert->certificate_size; + return GNUNET_OK; +} + + +/** + * Add an SOA record to the UDP packet at the given location. + * + * @param dst where to write the SOA record + * @param dst_len number of bytes in @a dst + * @param off pointer to offset where to write the SOA information (increment by bytes used) + * can also change if there was an error + * @param soa SOA information to write + * @return #GNUNET_SYSERR if @a soa is invalid + * #GNUNET_NO if @a soa did not fit + * #GNUNET_OK if @a soa was added to @a dst + */ +int +GNUNET_DNSPARSER_builder_add_soa (char *dst, + size_t dst_len, + size_t *off, + const struct GNUNET_DNSPARSER_SoaRecord *soa) +{ + struct GNUNET_TUN_DnsSoaRecord sd; + int ret; + + if ( (GNUNET_OK != (ret = GNUNET_DNSPARSER_builder_add_name (dst, + dst_len, + off, + soa->mname))) || + (GNUNET_OK != (ret = GNUNET_DNSPARSER_builder_add_name (dst, + dst_len, + off, + soa->rname)) ) ) + return ret; + if (*off + sizeof (struct GNUNET_TUN_DnsSoaRecord) > dst_len) + return GNUNET_NO; + sd.serial = htonl (soa->serial); + sd.refresh = htonl (soa->refresh); + sd.retry = htonl (soa->retry); + sd.expire = htonl (soa->expire); + sd.minimum = htonl (soa->minimum_ttl); + GNUNET_memcpy (&dst[*off], &sd, sizeof (sd)); + (*off) += sizeof (sd); + return GNUNET_OK; +} + + +/** + * Add an SRV record to the UDP packet at the given location. + * + * @param dst where to write the SRV record + * @param dst_len number of bytes in @a dst + * @param off pointer to offset where to write the SRV information (increment by bytes used) + * can also change if there was an error + * @param srv SRV information to write + * @return #GNUNET_SYSERR if @a srv is invalid + * #GNUNET_NO if @a srv did not fit + * #GNUNET_OK if @a srv was added to @a dst + */ +int +GNUNET_DNSPARSER_builder_add_srv (char *dst, + size_t dst_len, + size_t *off, + const struct GNUNET_DNSPARSER_SrvRecord *srv) +{ + struct GNUNET_TUN_DnsSrvRecord sd; + int ret; + + if (*off + sizeof (struct GNUNET_TUN_DnsSrvRecord) > dst_len) + return GNUNET_NO; + sd.prio = htons (srv->priority); + sd.weight = htons (srv->weight); + sd.port = htons (srv->port); + GNUNET_memcpy (&dst[*off], &sd, sizeof (sd)); + (*off) += sizeof (sd); + if (GNUNET_OK != (ret = GNUNET_DNSPARSER_builder_add_name (dst, + dst_len, + off, + srv->target))) + return ret; + return GNUNET_OK; +} + + +/** + * Add a DNS record to the UDP packet at the given location. + * + * @param dst where to write the query + * @param dst_len number of bytes in @a dst + * @param off pointer to offset where to write the query (increment by bytes used) + * must not be changed if there is an error + * @param record record to write + * @return #GNUNET_SYSERR if @a record is invalid + * #GNUNET_NO if @a record did not fit + * #GNUNET_OK if @a record was added to @a dst + */ +static int +add_record (char *dst, + size_t dst_len, + size_t *off, + const struct GNUNET_DNSPARSER_Record *record) +{ + int ret; + size_t start; + size_t pos; + struct GNUNET_TUN_DnsRecordLine rl; + + start = *off; + ret = GNUNET_DNSPARSER_builder_add_name (dst, + dst_len - sizeof (struct GNUNET_TUN_DnsRecordLine), + off, + record->name); + if (GNUNET_OK != ret) + return ret; + /* '*off' is now the position where we will need to write the record line */ + + pos = *off + sizeof (struct GNUNET_TUN_DnsRecordLine); + switch (record->type) + { + case GNUNET_DNSPARSER_TYPE_MX: + ret = GNUNET_DNSPARSER_builder_add_mx (dst, + dst_len, + &pos, + record->data.mx); + break; + case GNUNET_DNSPARSER_TYPE_CERT: + ret = GNUNET_DNSPARSER_builder_add_cert (dst, + dst_len, + &pos, + record->data.cert); + break; + case GNUNET_DNSPARSER_TYPE_SOA: + ret = GNUNET_DNSPARSER_builder_add_soa (dst, + dst_len, + &pos, + record->data.soa); + break; + case GNUNET_DNSPARSER_TYPE_NS: + case GNUNET_DNSPARSER_TYPE_CNAME: + case GNUNET_DNSPARSER_TYPE_PTR: + ret = GNUNET_DNSPARSER_builder_add_name (dst, + dst_len, + &pos, + record->data.hostname); + break; + case GNUNET_DNSPARSER_TYPE_SRV: + ret = GNUNET_DNSPARSER_builder_add_srv (dst, + dst_len, + &pos, + record->data.srv); + break; + default: + if (pos + record->data.raw.data_len > dst_len) + { + ret = GNUNET_NO; + break; + } + GNUNET_memcpy (&dst[pos], + record->data.raw.data, + record->data.raw.data_len); + pos += record->data.raw.data_len; + ret = GNUNET_OK; + break; + } + if (GNUNET_OK != ret) + { + *off = start; + return GNUNET_NO; + } + + if (pos - (*off + sizeof (struct GNUNET_TUN_DnsRecordLine)) > UINT16_MAX) + { + /* record data too long */ + *off = start; + return GNUNET_NO; + } + rl.type = htons (record->type); + rl.dns_traffic_class = htons (record->dns_traffic_class); + rl.ttl = htonl (GNUNET_TIME_absolute_get_remaining (record->expiration_time).rel_value_us / 1000LL / 1000LL); /* in seconds */ + rl.data_len = htons ((uint16_t) (pos - (*off + sizeof (struct GNUNET_TUN_DnsRecordLine)))); + GNUNET_memcpy (&dst[*off], &rl, sizeof (struct GNUNET_TUN_DnsRecordLine)); + *off = pos; + return GNUNET_OK; +} + + +/** + * Given a DNS packet @a p, generate the corresponding UDP payload. + * Note that we do not attempt to pack the strings with pointers + * as this would complicate the code and this is about being + * simple and secure, not fast, fancy and broken like bind. + * + * @param p packet to pack + * @param max maximum allowed size for the resulting UDP payload + * @param buf set to a buffer with the packed message + * @param buf_length set to the length of @a buf + * @return #GNUNET_SYSERR if @a p is invalid + * #GNUNET_NO if @a p was truncated (but there is still a result in @a buf) + * #GNUNET_OK if @a p was packed completely into @a buf + */ +int +GNUNET_DNSPARSER_pack (const struct GNUNET_DNSPARSER_Packet *p, + uint16_t max, + char **buf, + size_t *buf_length) +{ + struct GNUNET_TUN_DnsHeader dns; + size_t off; + char tmp[max]; + unsigned int i; + int ret; + int trc; + + if ( (p->num_queries > UINT16_MAX) || + (p->num_answers > UINT16_MAX) || + (p->num_authority_records > UINT16_MAX) || + (p->num_additional_records > UINT16_MAX) ) + return GNUNET_SYSERR; + dns.id = p->id; + dns.flags = p->flags; + dns.query_count = htons (p->num_queries); + dns.answer_rcount = htons (p->num_answers); + dns.authority_rcount = htons (p->num_authority_records); + dns.additional_rcount = htons (p->num_additional_records); + + off = sizeof (struct GNUNET_TUN_DnsHeader); + trc = GNUNET_NO; + for (i=0;inum_queries;i++) + { + ret = GNUNET_DNSPARSER_builder_add_query (tmp, + sizeof (tmp), + &off, + &p->queries[i]); + if (GNUNET_SYSERR == ret) + return GNUNET_SYSERR; + if (GNUNET_NO == ret) + { + dns.query_count = htons ((uint16_t) (i-1)); + trc = GNUNET_YES; + break; + } + } + for (i=0;inum_answers;i++) + { + ret = add_record (tmp, + sizeof (tmp), + &off, + &p->answers[i]); + if (GNUNET_SYSERR == ret) + return GNUNET_SYSERR; + if (GNUNET_NO == ret) + { + dns.answer_rcount = htons ((uint16_t) (i-1)); + trc = GNUNET_YES; + break; + } + } + for (i=0;inum_authority_records;i++) + { + ret = add_record (tmp, + sizeof (tmp), + &off, + &p->authority_records[i]); + if (GNUNET_SYSERR == ret) + return GNUNET_SYSERR; + if (GNUNET_NO == ret) + { + dns.authority_rcount = htons ((uint16_t) (i-1)); + trc = GNUNET_YES; + break; + } + } + for (i=0;inum_additional_records;i++) + { + ret = add_record (tmp, + sizeof (tmp), + &off, + &p->additional_records[i]); + if (GNUNET_SYSERR == ret) + return GNUNET_SYSERR; + if (GNUNET_NO == ret) + { + dns.additional_rcount = htons (i-1); + trc = GNUNET_YES; + break; + } + } + + if (GNUNET_YES == trc) + dns.flags.message_truncated = 1; + GNUNET_memcpy (tmp, + &dns, + sizeof (struct GNUNET_TUN_DnsHeader)); + + *buf = GNUNET_malloc (off); + *buf_length = off; + GNUNET_memcpy (*buf, + tmp, + off); + if (GNUNET_YES == trc) + return GNUNET_NO; + return GNUNET_OK; +} + + +/** + * Convert a block of binary data to HEX. + * + * @param data binary data to convert + * @param data_size number of bytes in @a data + * @return HEX string (lower case) + */ +char * +GNUNET_DNSPARSER_bin_to_hex (const void *data, + size_t data_size) +{ + char *ret; + size_t off; + const uint8_t *idata; + + idata = data; + ret = GNUNET_malloc (data_size * 2 + 1); + for (off = 0; off < data_size; off++) + sprintf (&ret[off * 2], + "%02x", + idata[off]); + return ret; +} + + +/** + * Convert a HEX string to block of binary data. + * + * @param hex HEX string to convert (may contain mixed case) + * @param data where to write result, must be + * at least `strlen(hex)/2` bytes long + * @return number of bytes written to data + */ +size_t +GNUNET_DNSPARSER_hex_to_bin (const char *hex, + void *data) +{ + size_t data_size; + size_t off; + uint8_t *idata; + unsigned int h; + char in[3]; + + data_size = strlen (hex) / 2; + idata = data; + in[2] = '\0'; + for (off = 0; off < data_size; off++) + { + in[0] = tolower ((unsigned char) hex[off * 2]); + in[1] = tolower ((unsigned char) hex[off * 2 + 1]); + if (1 != sscanf (in, "%x", &h)) + return off; + idata[off] = (uint8_t) h; + } + return off; +} + + +/* end of dnsparser.c */ diff --git a/src/util/dnsstub.c b/src/util/dnsstub.c new file mode 100644 index 000000000..969ff7beb --- /dev/null +++ b/src/util/dnsstub.c @@ -0,0 +1,749 @@ +/* + This file is part of GNUnet. + Copyright (C) 2012, 2018 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 . +*/ +/** + * @file dns/dnsstub.c + * @brief DNS stub resolver which sends DNS requests to an actual resolver + * @author Christian Grothoff + */ +#include "platform.h" +#include "gnunet_util_lib.h" +#include "gnunet_tun_lib.h" +#include "gnunet_dnsstub_lib.h" + +/** + * Timeout for retrying DNS queries. + */ +#define DNS_RETRANSMIT_DELAY GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 250) + + +/** + * DNS Server used for resolution. + */ +struct DnsServer; + + +/** + * UDP socket we are using for sending DNS requests to the Internet. + */ +struct GNUNET_DNSSTUB_RequestSocket +{ + + /** + * UDP socket we use for this request for IPv4 + */ + struct GNUNET_NETWORK_Handle *dnsout4; + + /** + * UDP socket we use for this request for IPv6 + */ + struct GNUNET_NETWORK_Handle *dnsout6; + + /** + * Function to call with result. + */ + GNUNET_DNSSTUB_ResultCallback rc; + + /** + * Closure for @e rc. + */ + void *rc_cls; + + /** + * Task for reading from dnsout4 and dnsout6. + */ + struct GNUNET_SCHEDULER_Task *read_task; + + /** + * Task for retrying transmission of the query. + */ + struct GNUNET_SCHEDULER_Task *retry_task; + + /** + * Next address we sent the DNS request to. + */ + struct DnsServer *ds_pos; + + /** + * Context this request executes in. + */ + struct GNUNET_DNSSTUB_Context *ctx; + + /** + * Query we sent to @e addr. + */ + void *request; + + /** + * Number of bytes in @a request. + */ + size_t request_len; + +}; + + +/** + * DNS Server used for resolution. + */ +struct DnsServer +{ + + /** + * Kept in a DLL. + */ + struct DnsServer *next; + + /** + * Kept in a DLL. + */ + struct DnsServer *prev; + + /** + * IP address of the DNS resolver. + */ + struct sockaddr_storage ss; +}; + + +/** + * Handle to the stub resolver. + */ +struct GNUNET_DNSSTUB_Context +{ + + /** + * Array of all open sockets for DNS requests. + */ + struct GNUNET_DNSSTUB_RequestSocket *sockets; + + /** + * DLL of DNS resolvers we use. + */ + struct DnsServer *dns_head; + + /** + * DLL of DNS resolvers we use. + */ + struct DnsServer *dns_tail; + + /** + * How frequently do we retry requests? + */ + struct GNUNET_TIME_Relative retry_freq; + + /** + * Length of @e sockets array. + */ + unsigned int num_sockets; + +}; + + +/** + * We're done with a `struct GNUNET_DNSSTUB_RequestSocket`, close it for now. + * + * @param rs request socket to clean up + */ +static void +cleanup_rs (struct GNUNET_DNSSTUB_RequestSocket *rs) +{ + if (NULL != rs->dnsout4) + { + GNUNET_NETWORK_socket_close (rs->dnsout4); + rs->dnsout4 = NULL; + } + if (NULL != rs->dnsout6) + { + GNUNET_NETWORK_socket_close (rs->dnsout6); + rs->dnsout6 = NULL; + } + if (NULL != rs->read_task) + { + GNUNET_SCHEDULER_cancel (rs->read_task); + rs->read_task = NULL; + } + if (NULL != rs->retry_task) + { + GNUNET_SCHEDULER_cancel (rs->retry_task); + rs->retry_task = NULL; + } + if (NULL != rs->request) + { + GNUNET_free (rs->request); + rs->request = NULL; + } +} + + +/** + * Open source port for sending DNS requests + * + * @param af AF_INET or AF_INET6 + * @return #GNUNET_OK on success + */ +static struct GNUNET_NETWORK_Handle * +open_socket (int af) +{ + struct sockaddr_in a4; + struct sockaddr_in6 a6; + struct sockaddr *sa; + socklen_t alen; + struct GNUNET_NETWORK_Handle *ret; + + ret = GNUNET_NETWORK_socket_create (af, SOCK_DGRAM, 0); + if (NULL == ret) + return NULL; + switch (af) + { + case AF_INET: + memset (&a4, 0, alen = sizeof (struct sockaddr_in)); + sa = (struct sockaddr *) &a4; + break; + case AF_INET6: + memset (&a6, 0, alen = sizeof (struct sockaddr_in6)); + sa = (struct sockaddr *) &a6; + break; + default: + GNUNET_break (0); + GNUNET_NETWORK_socket_close (ret); + return NULL; + } + sa->sa_family = af; + if (GNUNET_OK != GNUNET_NETWORK_socket_bind (ret, + sa, + alen)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Could not bind to any port: %s\n"), + STRERROR (errno)); + GNUNET_NETWORK_socket_close (ret); + return NULL; + } + return ret; +} + + +/** + * Get a socket of the specified address family to send out a + * UDP DNS request to the Internet. + * + * @param ctx the DNSSTUB context + * @return NULL on error + */ +static struct GNUNET_DNSSTUB_RequestSocket * +get_request_socket (struct GNUNET_DNSSTUB_Context *ctx) +{ + struct GNUNET_DNSSTUB_RequestSocket *rs; + + for (unsigned int i=0;i<256;i++) + { + rs = &ctx->sockets[GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE, + ctx->num_sockets)]; + if (NULL == rs->rc) + break; + } + if (NULL != rs->rc) + { + /* signal "failure" */ + rs->rc (rs->rc_cls, + NULL, + 0); + rs->rc = NULL; + } + if (NULL != rs->read_task) + { + GNUNET_SCHEDULER_cancel (rs->read_task); + rs->read_task = NULL; + } + if (NULL != rs->retry_task) + { + GNUNET_SCHEDULER_cancel (rs->retry_task); + rs->retry_task = NULL; + } + if (NULL != rs->request) + { + GNUNET_free (rs->request); + rs->request = NULL; + } + rs->ctx = ctx; + return rs; +} + + +/** + * Actually do the reading of a DNS packet from our UDP socket and see + * if we have a valid, matching, pending request. + * + * @param rs request socket with callback details + * @param dnsout socket to read from + * @return #GNUNET_OK on success, #GNUNET_NO on drop, #GNUNET_SYSERR on IO-errors (closed socket) + */ +static int +do_dns_read (struct GNUNET_DNSSTUB_RequestSocket *rs, + struct GNUNET_NETWORK_Handle *dnsout) +{ + struct GNUNET_DNSSTUB_Context *ctx = rs->ctx; + ssize_t r; + int len; + +#ifndef MINGW + if (0 != ioctl (GNUNET_NETWORK_get_fd (dnsout), + FIONREAD, + &len)) + { + /* conservative choice: */ + len = UINT16_MAX; + } +#else + /* port the code above? */ + len = UINT16_MAX; +#endif + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Receiving %d byte DNS reply\n", + len); + { + unsigned char buf[len] GNUNET_ALIGN; + int found; + struct sockaddr_storage addr; + socklen_t addrlen; + struct GNUNET_TUN_DnsHeader *dns; + + addrlen = sizeof (addr); + memset (&addr, + 0, + sizeof (addr)); + r = GNUNET_NETWORK_socket_recvfrom (dnsout, + buf, + sizeof (buf), + (struct sockaddr*) &addr, + &addrlen); + if (-1 == r) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "recvfrom"); + GNUNET_NETWORK_socket_close (dnsout); + return GNUNET_SYSERR; + } + found = GNUNET_NO; + for (struct DnsServer *ds = ctx->dns_head; NULL != ds; ds = ds->next) + { + if (0 == memcmp (&addr, + &ds->ss, + GNUNET_MIN (sizeof (struct sockaddr_storage), + addrlen))) + { + found = GNUNET_YES; + break; + } + } + if (GNUNET_NO == found) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received DNS response from server we never asked (ignored)"); + return GNUNET_NO; + } + if (sizeof (struct GNUNET_TUN_DnsHeader) > r) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Received DNS response that is too small (%u bytes)"), + (unsigned int) r); + return GNUNET_NO; + } + dns = (struct GNUNET_TUN_DnsHeader *) buf; + if (NULL == rs->rc) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Request timeout or cancelled; ignoring reply\n"); + return GNUNET_NO; + } + rs->rc (rs->rc_cls, + dns, + r); + } + return GNUNET_OK; +} + + +/** + * Read a DNS response from the (unhindered) UDP-Socket + * + * @param cls socket to read from + */ +static void +read_response (void *cls); + + +/** + * Schedule #read_response() task for @a rs. + * + * @param rs request to schedule read operation for + */ +static void +schedule_read (struct GNUNET_DNSSTUB_RequestSocket *rs) +{ + struct GNUNET_NETWORK_FDSet *rset; + + if (NULL != rs->read_task) + GNUNET_SCHEDULER_cancel (rs->read_task); + rset = GNUNET_NETWORK_fdset_create (); + if (NULL != rs->dnsout4) + GNUNET_NETWORK_fdset_set (rset, + rs->dnsout4); + if (NULL != rs->dnsout6) + GNUNET_NETWORK_fdset_set (rset, + rs->dnsout6); + rs->read_task = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT, + GNUNET_TIME_UNIT_FOREVER_REL, + rset, + NULL, + &read_response, + rs); + GNUNET_NETWORK_fdset_destroy (rset); +} + + +/** + * Read a DNS response from the (unhindered) UDP-Socket + * + * @param cls `struct GNUNET_DNSSTUB_RequestSocket` to read from + */ +static void +read_response (void *cls) +{ + struct GNUNET_DNSSTUB_RequestSocket *rs = cls; + const struct GNUNET_SCHEDULER_TaskContext *tc; + + rs->read_task = NULL; + tc = GNUNET_SCHEDULER_get_task_context (); + /* read and process ready sockets */ + if ( (NULL != rs->dnsout4) && + (GNUNET_NETWORK_fdset_isset (tc->read_ready, + rs->dnsout4)) && + (GNUNET_SYSERR == + do_dns_read (rs, + rs->dnsout4)) ) + rs->dnsout4 = NULL; + if ( (NULL != rs->dnsout6) && + (GNUNET_NETWORK_fdset_isset (tc->read_ready, + rs->dnsout6)) && + (GNUNET_SYSERR == + do_dns_read (rs, + rs->dnsout6)) ) + rs->dnsout6 = NULL; + /* re-schedule read task */ + schedule_read (rs); +} + + +/** + * Task to (re)transmit the DNS query, possibly repeatedly until + * we succeed. + * + * @param cls our `struct GNUNET_DNSSTUB_RequestSocket *` + */ +static void +transmit_query (void *cls) +{ + struct GNUNET_DNSSTUB_RequestSocket *rs = cls; + struct GNUNET_DNSSTUB_Context *ctx = rs->ctx; + const struct sockaddr *sa; + socklen_t salen; + struct DnsServer *ds; + struct GNUNET_NETWORK_Handle *dnsout; + + rs->retry_task = GNUNET_SCHEDULER_add_delayed (ctx->retry_freq, + &transmit_query, + rs); + ds = rs->ds_pos; + rs->ds_pos = ds->next; + if (NULL == rs->ds_pos) + rs->ds_pos = ctx->dns_head; + GNUNET_assert (NULL != ds); + dnsout = NULL; + switch (ds->ss.ss_family) + { + case AF_INET: + if (NULL == rs->dnsout4) + rs->dnsout4 = open_socket (AF_INET); + dnsout = rs->dnsout4; + sa = (const struct sockaddr *) &ds->ss; + salen = sizeof (struct sockaddr_in); + break; + case AF_INET6: + if (NULL == rs->dnsout6) + rs->dnsout6 = open_socket (AF_INET6); + dnsout = rs->dnsout6; + sa = (const struct sockaddr *) &ds->ss; + salen = sizeof (struct sockaddr_in6); + break; + default: + return; + } + if (NULL == dnsout) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unable to use configure DNS server, skipping\n"); + return; + } + if (GNUNET_SYSERR == + GNUNET_NETWORK_socket_sendto (dnsout, + rs->request, + rs->request_len, + sa, + salen)) + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + _("Failed to send DNS request to %s: %s\n"), + GNUNET_a2s (sa, + salen), + STRERROR (errno)); + else + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + _("Sent DNS request to %s\n"), + GNUNET_a2s (sa, + salen)); + schedule_read (rs); +} + + +/** + * Perform DNS resolution using our default IP from init. + * + * @param ctx stub resolver to use + * @param request DNS request to transmit + * @param request_len number of bytes in msg + * @param rc function to call with result + * @param rc_cls closure for 'rc' + * @return socket used for the request, NULL on error + */ +struct GNUNET_DNSSTUB_RequestSocket * +GNUNET_DNSSTUB_resolve (struct GNUNET_DNSSTUB_Context *ctx, + const void *request, + size_t request_len, + GNUNET_DNSSTUB_ResultCallback rc, + void *rc_cls) +{ + struct GNUNET_DNSSTUB_RequestSocket *rs; + + if (NULL == ctx->dns_head) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "No DNS server configured for resolution\n"); + return NULL; + } + if (NULL == (rs = get_request_socket (ctx))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "No request socket available for DNS resolution\n"); + return NULL; + } + rs->ds_pos = ctx->dns_head; + rs->rc = rc; + rs->rc_cls = rc_cls; + rs->request = GNUNET_memdup (request, + request_len); + rs->request_len = request_len; + rs->retry_task = GNUNET_SCHEDULER_add_now (&transmit_query, + rs); + return rs; +} + + +/** + * Cancel DNS resolution. + * + * @param rs resolution to cancel + */ +void +GNUNET_DNSSTUB_resolve_cancel (struct GNUNET_DNSSTUB_RequestSocket *rs) +{ + rs->rc = NULL; + if (NULL != rs->retry_task) + { + GNUNET_SCHEDULER_cancel (rs->retry_task); + rs->retry_task = NULL; + } + if (NULL != rs->read_task) + { + GNUNET_SCHEDULER_cancel (rs->read_task); + rs->read_task = NULL; + } +} + + +/** + * Start a DNS stub resolver. + * + * @param num_sockets how many sockets should we open + * in parallel for DNS queries for this stub? + * @return NULL on error + */ +struct GNUNET_DNSSTUB_Context * +GNUNET_DNSSTUB_start (unsigned int num_sockets) +{ + struct GNUNET_DNSSTUB_Context *ctx; + + if (0 == num_sockets) + { + GNUNET_break (0); + return NULL; + } + ctx = GNUNET_new (struct GNUNET_DNSSTUB_Context); + ctx->num_sockets = num_sockets; + ctx->sockets = GNUNET_new_array (num_sockets, + struct GNUNET_DNSSTUB_RequestSocket); + ctx->retry_freq = DNS_RETRANSMIT_DELAY; + return ctx; +} + + +/** + * Add nameserver for use by the DNSSTUB. We will use + * all provided nameservers for resolution (round-robin). + * + * @param ctx resolver context to modify + * @param dns_ip target IP address to use (as string) + * @return #GNUNET_OK on success + */ +int +GNUNET_DNSSTUB_add_dns_ip (struct GNUNET_DNSSTUB_Context *ctx, + const char *dns_ip) +{ + struct DnsServer *ds; + struct in_addr i4; + struct in6_addr i6; + + ds = GNUNET_new (struct DnsServer); + if (1 == inet_pton (AF_INET, + dns_ip, + &i4)) + { + struct sockaddr_in *s4 = (struct sockaddr_in *) &ds->ss; + + s4->sin_family = AF_INET; + s4->sin_port = htons (53); + s4->sin_addr = i4; +#if HAVE_SOCKADDR_IN_SIN_LEN + s4->sin_len = (u_char) sizeof (struct sockaddr_in); +#endif + } + else if (1 == inet_pton (AF_INET6, + dns_ip, + &i6)) + { + struct sockaddr_in6 *s6 = (struct sockaddr_in6 *) &ds->ss; + + s6->sin6_family = AF_INET6; + s6->sin6_port = htons (53); + s6->sin6_addr = i6; +#if HAVE_SOCKADDR_IN_SIN_LEN + s6->sin6_len = (u_char) sizeof (struct sockaddr_in6); +#endif + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Malformed IP address `%s' for DNS server\n", + dns_ip); + GNUNET_free (ds); + return GNUNET_SYSERR; + } + GNUNET_CONTAINER_DLL_insert (ctx->dns_head, + ctx->dns_tail, + ds); + return GNUNET_OK; +} + + +/** + * Add nameserver for use by the DNSSTUB. We will use + * all provided nameservers for resolution (round-robin). + * + * @param ctx resolver context to modify + * @param sa socket address of DNS resolver to use + * @return #GNUNET_OK on success + */ +int +GNUNET_DNSSTUB_add_dns_sa (struct GNUNET_DNSSTUB_Context *ctx, + const struct sockaddr *sa) +{ + struct DnsServer *ds; + + ds = GNUNET_new (struct DnsServer); + switch (sa->sa_family) + { + case AF_INET: + GNUNET_memcpy (&ds->ss, + sa, + sizeof (struct sockaddr_in)); + break; + case AF_INET6: + GNUNET_memcpy (&ds->ss, + sa, + sizeof (struct sockaddr_in6)); + break; + default: + GNUNET_break (0); + GNUNET_free (ds); + return GNUNET_SYSERR; + } + GNUNET_CONTAINER_DLL_insert (ctx->dns_head, + ctx->dns_tail, + ds); + return GNUNET_OK; +} + + +/** + * How long should we try requests before timing out? + * Only effective for requests issued after this call. + * + * @param ctx resolver context to modify + * @param retry_freq how long to wait between retries + */ +void +GNUNET_DNSSTUB_set_retry (struct GNUNET_DNSSTUB_Context *ctx, + struct GNUNET_TIME_Relative retry_freq) +{ + ctx->retry_freq = retry_freq; +} + + +/** + * Cleanup DNSSTUB resolver. + * + * @param ctx stub resolver to clean up + */ +void +GNUNET_DNSSTUB_stop (struct GNUNET_DNSSTUB_Context *ctx) +{ + struct DnsServer *ds; + + while (NULL != (ds = ctx->dns_head)) + { + GNUNET_CONTAINER_DLL_remove (ctx->dns_head, + ctx->dns_tail, + ds); + GNUNET_free (ds); + } + for (unsigned int i=0;inum_sockets;i++) + cleanup_rs (&ctx->sockets[i]); + GNUNET_free (ctx->sockets); + GNUNET_free (ctx); +} + + +/* end of dnsstub.c */ diff --git a/src/util/regex.c b/src/util/regex.c new file mode 100644 index 000000000..7565a9eac --- /dev/null +++ b/src/util/regex.c @@ -0,0 +1,834 @@ +/* + 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 . +*/ +/** + * @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 */ diff --git a/src/util/test_regex.c b/src/util/test_regex.c new file mode 100644 index 000000000..2e7d52828 --- /dev/null +++ b/src/util/test_regex.c @@ -0,0 +1,179 @@ +/* + This file is part of GNUnet + Copyright (C) 2012, 2013 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 . +*/ +/** + * @file tun/test_regex.c + * @brief simple test for regex.c iptoregex functions + * @author Maximilian Szengel + */ +#include "platform.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)" + + +static int +test_iptoregex (const char *ipv4, + uint16_t port, + const char *expectedv4, + const char *ipv6, + uint16_t port6, + const char *expectedv6) +{ + int error = 0; + + struct in_addr a; + struct in6_addr b; + char rxv4[GNUNET_TUN_IPV4_REGEXLEN]; + char rxv6[GNUNET_TUN_IPV6_REGEXLEN]; + + GNUNET_assert (1 == inet_pton (AF_INET, ipv4, &a)); + GNUNET_TUN_ipv4toregexsearch (&a, port, rxv4); + + if (0 != strcmp (rxv4, expectedv4)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Expected: %s but got: %s\n", + expectedv4, + rxv4); + error++; + } + + GNUNET_assert (1 == inet_pton (AF_INET6, ipv6, &b)); + GNUNET_TUN_ipv6toregexsearch (&b, port6, rxv6); + if (0 != strcmp (rxv6, expectedv6)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Expected: %s but got: %s\n", + expectedv6, rxv6); + error++; + } + return error; +} + + +static int +test_policy4toregex (const char *policy, + const char *regex) +{ + char *r; + int ret; + + ret = 0; + r = GNUNET_TUN_ipv4policy2regex (policy); + if (NULL == r) + { + GNUNET_break (0); + return 1; + } + if (0 != strcmp (regex, r)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Expected: `%s' but got: `%s'\n", + regex, r); + ret = 2; + } + GNUNET_free (r); + return ret; +} + + +static int +test_policy6toregex (const char *policy, + const char *regex) +{ + char *r; + int ret; + + ret = 0; + r = GNUNET_TUN_ipv6policy2regex (policy); + if (NULL == r) + { + GNUNET_break (0); + return 1; + } + if (0 != strcmp (regex, r)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Expected: `%s' but got: `%s'\n", + regex, r); + ret = 2; + } + GNUNET_free (r); + return ret; +} + + +int +main (int argc, char *argv[]) +{ + int error; + char *r; + + GNUNET_log_setup ("test-regex", "WARNING", NULL); + error = 0; + + /* this is just a performance test ... */ + r = GNUNET_TUN_ipv4policy2regex ("1.2.3.4/16:!25;"); + GNUNET_break (NULL != r); + GNUNET_free (r); + + error += + test_iptoregex ("192.1.2.3", 2086, + "4-0826-C0010203", + "FFFF::1", 8080, + "6-1F90-FFFF0000000000000000000000000001"); + error += + test_iptoregex ("187.238.255.0", 80, + "4-0050-BBEEFF00", + "E1E1:73F9:51BE::0", 49, + "6-0031-E1E173F951BE00000000000000000000"); + error += + test_policy4toregex ("192.1.2.0/24:80;", + "4-0050-C00102" DOT DOT); + error += + test_policy4toregex ("192.1.0.0/16;", + "4-" DOT DOT DOT DOT "-C001" DOT DOT DOT DOT); + error += + test_policy4toregex ("192.1.0.0/16:80-81;", + "4-(0050|0051)-C001" DOT DOT DOT DOT); + error += + test_policy4toregex ("192.1.0.0/8:!3-65535;", + "4-000(0|1|2)-C0" DOT DOT DOT DOT DOT DOT); + error += + test_policy4toregex ("192.1.0.0/8:!25-56;", + "4-(0(0(0"DOT"|1(0|1|2|3|4|5|6|7|8)|3(9|A|B|C|D|E|F)|(4|5|6|7|8|9|A|B|C|D|E|F)"DOT")|(1|2|3|4|5|6|7|8|9|A|B|C|D|E|F)"DOT DOT")|(1|2|3|4|5|6|7|8|9|A|B|C|D|E|F)"DOT DOT DOT")-C0"DOT DOT DOT DOT DOT DOT); + error += + test_policy6toregex ("E1E1::1;", + "6-"DOT DOT DOT DOT"-E1E10000000000000000000000000001"); + error += + test_policy6toregex ("E1E1:ABCD::1/120;", + "6-"DOT DOT DOT DOT"-E1E1ABCD0000000000000000000000" DOT DOT); + error += + test_policy6toregex ("E1E1:ABCD::ABCD/126;", + "6-"DOT DOT DOT DOT"-E1E1ABCD00000000000000000000ABC(C|D|E|F)"); + error += + test_policy6toregex ("E1E1:ABCD::ABCD/127;", + "6-"DOT DOT DOT DOT"-E1E1ABCD00000000000000000000ABC(C|D)"); + error += + test_policy6toregex ("E1E1:ABCD::ABCD/128:80;", + "6-0050-E1E1ABCD00000000000000000000ABCD"); + return error; +} diff --git a/src/util/test_tun.c b/src/util/test_tun.c new file mode 100644 index 000000000..edbd4c05d --- /dev/null +++ b/src/util/test_tun.c @@ -0,0 +1,72 @@ +/* + This file is part of GNUnet. + Copyright (C) 2010, 2011, 2012 Christian Grothoff + + 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 . +*/ + +/** + * @file tun/test_tun.c + * @brief test for tun.c + * @author Christian Grothoff + */ +#include "platform.h" +#include "gnunet_tun_lib.h" + +static int ret; + +static void +test_udp (size_t pll, + int pl_fill, + uint16_t crc) +{ + struct GNUNET_TUN_IPv4Header ip; + struct GNUNET_TUN_UdpHeader udp; + char payload[pll]; + struct in_addr src; + struct in_addr dst; + + GNUNET_assert (1 == inet_pton (AF_INET, "1.2.3.4", &src)); + GNUNET_assert (1 == inet_pton (AF_INET, "122.2.3.5", &dst)); + memset (payload, pl_fill, sizeof (payload)); + GNUNET_TUN_initialize_ipv4_header (&ip, + IPPROTO_UDP, + pll + sizeof (udp), + &src, + &dst); + udp.source_port = htons (4242); + udp.destination_port = htons (4242); + udp.len = htons (pll); + GNUNET_TUN_calculate_udp4_checksum (&ip, + &udp, + payload, + pll); + if (crc != ntohs (udp.crc)) + { + fprintf (stderr, "Got CRC: %u, wanted: %u\n", + ntohs (udp.crc), + crc); + ret = 1; + } +} + +int main (int argc, + char **argv) +{ + test_udp (4, 3, 22439); + test_udp (4, 1, 23467); + test_udp (7, 17, 6516); + test_udp (12451, 251, 42771); + return ret; +} diff --git a/src/util/tun.c b/src/util/tun.c new file mode 100644 index 000000000..f85f72209 --- /dev/null +++ b/src/util/tun.c @@ -0,0 +1,309 @@ +/* + This file is part of GNUnet. + Copyright (C) 2010, 2011, 2012 Christian Grothoff + + 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 . +*/ + +/** + * @file tun/tun.c + * @brief standard IP calculations for TUN interaction + * @author Philipp Toelke + * @author Christian Grothoff + */ +#include "platform.h" +#include "gnunet_tun_lib.h" + +/** + * IP TTL we use for packets that we assemble (8 bit unsigned integer) + */ +#define FRESH_TTL 64 + + +/** + * Initialize an IPv4 header. + * + * @param ip header to initialize + * @param protocol protocol to use (i.e. IPPROTO_UDP) + * @param payload_length number of bytes of payload that follow (excluding IPv4 header) + * @param src source IP address to use + * @param dst destination IP address to use + */ +void +GNUNET_TUN_initialize_ipv4_header (struct GNUNET_TUN_IPv4Header *ip, + uint8_t protocol, + uint16_t payload_length, + const struct in_addr *src, + const struct in_addr *dst) +{ + GNUNET_assert (20 == sizeof (struct GNUNET_TUN_IPv4Header)); + GNUNET_assert (payload_length <= UINT16_MAX - sizeof (struct GNUNET_TUN_IPv4Header)); + memset (ip, 0, sizeof (struct GNUNET_TUN_IPv4Header)); + ip->header_length = sizeof (struct GNUNET_TUN_IPv4Header) / 4; + ip->version = 4; + ip->total_length = htons (sizeof (struct GNUNET_TUN_IPv4Header) + payload_length); + ip->identification = (uint16_t) GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, + 65536); + ip->ttl = FRESH_TTL; + ip->protocol = protocol; + ip->source_address = *src; + ip->destination_address = *dst; + ip->checksum = GNUNET_CRYPTO_crc16_n (ip, sizeof (struct GNUNET_TUN_IPv4Header)); +} + + +/** + * Initialize an IPv6 header. + * + * @param ip header to initialize + * @param protocol protocol to use (i.e. IPPROTO_UDP), technically "next_header" for IPv6 + * @param payload_length number of bytes of payload that follow (excluding IPv6 header) + * @param src source IP address to use + * @param dst destination IP address to use + */ +void +GNUNET_TUN_initialize_ipv6_header (struct GNUNET_TUN_IPv6Header *ip, + uint8_t protocol, + uint16_t payload_length, + const struct in6_addr *src, + const struct in6_addr *dst) +{ + GNUNET_assert (40 == sizeof (struct GNUNET_TUN_IPv6Header)); + GNUNET_assert (payload_length <= UINT16_MAX - sizeof (struct GNUNET_TUN_IPv6Header)); + memset (ip, 0, sizeof (struct GNUNET_TUN_IPv6Header)); + ip->version = 6; + ip->next_header = protocol; + ip->payload_length = htons ((uint16_t) payload_length); + ip->hop_limit = FRESH_TTL; + ip->destination_address = *dst; + ip->source_address = *src; +} + + +/** + * Calculate IPv4 TCP checksum. + * + * @param ip ipv4 header fully initialized + * @param tcp TCP header (initialized except for CRC) + * @param payload the TCP payload + * @param payload_length number of bytes of TCP payload + */ +void +GNUNET_TUN_calculate_tcp4_checksum (const struct GNUNET_TUN_IPv4Header *ip, + struct GNUNET_TUN_TcpHeader *tcp, + const void *payload, + uint16_t payload_length) +{ + uint32_t sum; + uint16_t tmp; + + GNUNET_assert (20 == sizeof (struct GNUNET_TUN_TcpHeader)); + GNUNET_assert (payload_length + sizeof (struct GNUNET_TUN_IPv4Header) + sizeof (struct GNUNET_TUN_TcpHeader) == + ntohs (ip->total_length)); + GNUNET_assert (IPPROTO_TCP == ip->protocol); + + tcp->crc = 0; + sum = GNUNET_CRYPTO_crc16_step (0, + &ip->source_address, + sizeof (struct in_addr) * 2); + tmp = htons (IPPROTO_TCP); + sum = GNUNET_CRYPTO_crc16_step (sum, &tmp, sizeof (uint16_t)); + tmp = htons (payload_length + sizeof (struct GNUNET_TUN_TcpHeader)); + sum = GNUNET_CRYPTO_crc16_step (sum, &tmp, sizeof (uint16_t)); + sum = GNUNET_CRYPTO_crc16_step (sum, tcp, sizeof (struct GNUNET_TUN_TcpHeader)); + sum = GNUNET_CRYPTO_crc16_step (sum, payload, payload_length); + tcp->crc = GNUNET_CRYPTO_crc16_finish (sum); +} + + +/** + * Calculate IPv6 TCP checksum. + * + * @param ip ipv6 header fully initialized + * @param tcp header (initialized except for CRC) + * @param payload the TCP payload + * @param payload_length number of bytes of TCP payload + */ +void +GNUNET_TUN_calculate_tcp6_checksum (const struct GNUNET_TUN_IPv6Header *ip, + struct GNUNET_TUN_TcpHeader *tcp, + const void *payload, + uint16_t payload_length) +{ + uint32_t sum; + uint32_t tmp; + + GNUNET_assert (20 == sizeof (struct GNUNET_TUN_TcpHeader)); + GNUNET_assert (payload_length + sizeof (struct GNUNET_TUN_TcpHeader) == + ntohs (ip->payload_length)); + GNUNET_assert (IPPROTO_TCP == ip->next_header); + tcp->crc = 0; + sum = GNUNET_CRYPTO_crc16_step (0, &ip->source_address, 2 * sizeof (struct in6_addr)); + tmp = htonl (sizeof (struct GNUNET_TUN_TcpHeader) + payload_length); + sum = GNUNET_CRYPTO_crc16_step (sum, &tmp, sizeof (uint32_t)); + tmp = htonl (IPPROTO_TCP); + sum = GNUNET_CRYPTO_crc16_step (sum, &tmp, sizeof (uint32_t)); + sum = GNUNET_CRYPTO_crc16_step (sum, tcp, + sizeof (struct GNUNET_TUN_TcpHeader)); + sum = GNUNET_CRYPTO_crc16_step (sum, payload, payload_length); + tcp->crc = GNUNET_CRYPTO_crc16_finish (sum); +} + + +/** + * Calculate IPv4 UDP checksum. + * + * @param ip ipv4 header fully initialized + * @param udp UDP header (initialized except for CRC) + * @param payload the UDP payload + * @param payload_length number of bytes of UDP payload + */ +void +GNUNET_TUN_calculate_udp4_checksum (const struct GNUNET_TUN_IPv4Header *ip, + struct GNUNET_TUN_UdpHeader *udp, + const void *payload, + uint16_t payload_length) +{ + uint32_t sum; + uint16_t tmp; + + GNUNET_assert (8 == sizeof (struct GNUNET_TUN_UdpHeader)); + GNUNET_assert (payload_length + sizeof (struct GNUNET_TUN_IPv4Header) + sizeof (struct GNUNET_TUN_UdpHeader) == + ntohs (ip->total_length)); + GNUNET_assert (IPPROTO_UDP == ip->protocol); + + udp->crc = 0; /* technically optional, but we calculate it anyway, just to be sure */ + sum = GNUNET_CRYPTO_crc16_step (0, + &ip->source_address, + sizeof (struct in_addr) * 2); + tmp = htons (IPPROTO_UDP); + sum = GNUNET_CRYPTO_crc16_step (sum, + &tmp, + sizeof (uint16_t)); + tmp = htons (sizeof (struct GNUNET_TUN_UdpHeader) + payload_length); + sum = GNUNET_CRYPTO_crc16_step (sum, + &tmp, + sizeof (uint16_t)); + sum = GNUNET_CRYPTO_crc16_step (sum, + udp, + sizeof (struct GNUNET_TUN_UdpHeader)); + sum = GNUNET_CRYPTO_crc16_step (sum, + payload, + payload_length); + udp->crc = GNUNET_CRYPTO_crc16_finish (sum); +} + + +/** + * Calculate IPv6 UDP checksum. + * + * @param ip ipv6 header fully initialized + * @param udp UDP header (initialized except for CRC) + * @param payload the UDP payload + * @param payload_length number of bytes of UDP payload + */ +void +GNUNET_TUN_calculate_udp6_checksum (const struct GNUNET_TUN_IPv6Header *ip, + struct GNUNET_TUN_UdpHeader *udp, + const void *payload, + uint16_t payload_length) +{ + uint32_t sum; + uint32_t tmp; + + GNUNET_assert (payload_length + sizeof (struct GNUNET_TUN_UdpHeader) == + ntohs (ip->payload_length)); + GNUNET_assert (payload_length + sizeof (struct GNUNET_TUN_UdpHeader) == + ntohs (udp->len)); + GNUNET_assert (IPPROTO_UDP == ip->next_header); + + udp->crc = 0; + sum = GNUNET_CRYPTO_crc16_step (0, + &ip->source_address, + sizeof (struct in6_addr) * 2); + tmp = htons (sizeof (struct GNUNET_TUN_UdpHeader) + payload_length); /* aka udp->len */ + sum = GNUNET_CRYPTO_crc16_step (sum, &tmp, sizeof (uint32_t)); + tmp = htons (ip->next_header); + sum = GNUNET_CRYPTO_crc16_step (sum, &tmp, sizeof (uint32_t)); + sum = GNUNET_CRYPTO_crc16_step (sum, udp, sizeof (struct GNUNET_TUN_UdpHeader)); + sum = GNUNET_CRYPTO_crc16_step (sum, payload, payload_length); + udp->crc = GNUNET_CRYPTO_crc16_finish (sum); +} + + +/** + * Calculate ICMP checksum. + * + * @param icmp IMCP header (initialized except for CRC) + * @param payload the ICMP payload + * @param payload_length number of bytes of ICMP payload + */ +void +GNUNET_TUN_calculate_icmp_checksum (struct GNUNET_TUN_IcmpHeader *icmp, + const void *payload, + uint16_t payload_length) +{ + uint32_t sum; + + GNUNET_assert (8 == sizeof (struct GNUNET_TUN_IcmpHeader)); + icmp->crc = 0; + sum = GNUNET_CRYPTO_crc16_step (0, + icmp, + sizeof (struct GNUNET_TUN_IcmpHeader)); + sum = GNUNET_CRYPTO_crc16_step (sum, payload, payload_length); + icmp->crc = GNUNET_CRYPTO_crc16_finish (sum); +} + + +/** + * Check if two sockaddrs are equal. + * + * @param sa one address + * @param sb another address + * @param include_port also check ports + * @return #GNUNET_YES if they are equal + */ +int +GNUNET_TUN_sockaddr_cmp (const struct sockaddr *sa, + const struct sockaddr *sb, + int include_port) +{ + if (sa->sa_family != sb->sa_family) + return GNUNET_NO; + + switch (sa->sa_family) + { + case AF_INET: + { + const struct sockaddr_in *sa4 = (const struct sockaddr_in *) sa; + const struct sockaddr_in *sb4 = (const struct sockaddr_in *) sb; + return (sa4->sin_addr.s_addr == sb4->sin_addr.s_addr); + } + case AF_INET6: + { + const struct sockaddr_in6 *sa6 = (const struct sockaddr_in6 *) sa; + const struct sockaddr_in6 *sb6 = (const struct sockaddr_in6 *) sb; + + return (0 == memcmp(&sa6->sin6_addr, + &sb6->sin6_addr, + sizeof (struct in6_addr))); + } + default: + GNUNET_break (0); + return GNUNET_SYSERR; + } +} + + +/* end of tun.c */ -- cgit v1.2.3