Rebase Cradlepoint diff over v2.90
Run these commands only if remote upstream is not added:
1. git remote add upstream http://thekelleys.org.uk/git/dnsmasq.git
2. git remote -v show
3. git fetch upstream
Following are the commands used for generating the diff
between upstream version 2.85 and origin/cp-main:
1. git diff v2.85 origin/cp-main > ../dnsmasq_v2.85-to-cp_main.gitdiff
2. patch -p1 < ../dnsmasq_v2.85-to-cp_main.gitdiff
3. git add . && git commit -m "Rebase Cradlepoint diff over v2.90"
And then fix all the rejects generated while applying ‘patch -p1 < ../dnsmasq_v2.85-to-cp_main.gitdiff’
EDNS_PKTSZ and SAFE_PKTSZ were already present due to backport of CVE
patch:
Fix up ./man/dnsmasq.8
Fix up ./src/config.h
Fix up ./CHANGELOG
As domain is no longer used in upstream, we removed the check for OPT_EDNS_RESTRICT:
Fix up ./src/forward.c
Fix up ./src/dhcp.c
Fix up ./src/dhcp6.c
Fix up ./src/dns-protocol.h
Fix up ./src/dnsmasq.c
Fix up ./src/dnsmasq.h
Fix up ./src/edns0.c
Fix up ./src/option.c
Fix up ./src/radv.c
Fix up ./src/rfc1035.c
Fix up ./src/util.c
Fix up ./Makefile
Compilation fixes for cache.c
Change-Id: Ibb8d162526168e5963aedb98b6333ded5f054a82
diff --git a/src/cache.c b/src/cache.c
index 0eacec9..06ead08 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -1972,6 +1972,198 @@
}
}
+/* This function will return the app_ids, cat_ids along with their respective
+counts in the ipset for the host enquired */
+static void extract_appid_catid_fm_ipset(struct ipsets *ipset, char *host,
+ long *app_ids, long *cat_ids, int *app_id_count,
+ int *cat_id_count)
+{
+ char *setptr = NULL;
+ char *tempptr = NULL, *endptr = NULL;
+ int index = 0;
+ while (ipset)
+ {
+ if (strcmp(ipset->domain, host) == 0) {
+ index = 0;
+ setptr = ipset->sets[index];
+ while (setptr)
+ {
+ if (strncmp(setptr, "ip4-a", 5) == 0) {
+ tempptr = &setptr[5];
+ if (tempptr!= NULL && *app_id_count < MAX_APPID_PER_HOST) {
+ app_ids[(*app_id_count)] = strtoul(tempptr, &endptr, 16);
+ *app_id_count = *app_id_count + 1;
+ }
+ }
+ if (strncmp(setptr, "ip4-c", 5) == 0) {
+ tempptr = &setptr[5];
+ if (tempptr!= NULL && *cat_id_count < MAX_CATID_PER_HOST) {
+ cat_ids[(*cat_id_count)] = strtoul(tempptr, &endptr, 16);
+ *cat_id_count = *cat_id_count + 1;
+ }
+ }
+ index++;
+ setptr = ipset->sets[index];
+ }
+ }
+ ipset = ipset->next;
+ }
+}
+
+/* CRADLEPOINT */
+void dump_cache_json(time_t now)
+{
+ struct server *serv, *serv1;
+ int add_comma;
+ char *t = "";
+
+ // assume signaler has created this named pipe already with mkfifo
+ // If not it will end up being a simple file
+ FILE *f = fopen("/tmp/dns_cache.pipe", "w");
+
+ if (!f)
+ {
+ my_syslog(LOG_ERR, _("failed to open dns cache dump pipe"));
+ return;
+ }
+
+ // rather than pull in complex json libs, brute force it
+ fprintf(f, "{");
+ fprintf(f, "\"time\": %lu,", (unsigned long)now);
+ fprintf(f, "\"size\": %d,", daemon->cachesize);
+ fprintf(f, "\"freed\": %d,", daemon->metrics[METRIC_DNS_CACHE_LIVE_FREED]);
+ fprintf(f, "\"inserted\": %d,", daemon->metrics[METRIC_DNS_CACHE_INSERTED]);
+ fprintf(f, "\"forwarded\": %d,", daemon->metrics[METRIC_DNS_QUERIES_FORWARDED]);
+ fprintf(f, "\"local\": %d,", daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]);
+#ifdef HAVE_AUTH
+ fprintf(f, "\"authoritative\": %u,", daemon->metrics[METRIC_DNS_AUTH_ANSWERED]);
+#endif
+
+ /* sum counts from different records for same server */
+ for (serv = daemon->servers; serv; serv = serv->next)
+ serv->flags &= ~SERV_MARK;
+
+ fprintf(f, "\"servers\": [");
+ add_comma = 0;
+ for (serv = daemon->servers; serv; serv = serv->next)
+ if (!(serv->flags & (SERV_LITERAL_ADDRESS | SERV_MARK |
+ SERV_USE_RESOLV)))
+ {
+ int port;
+ unsigned int queries = 0, failed_queries = 0;
+ for (serv1 = serv; serv1; serv1 = serv1->next)
+ if (!(serv1->flags &
+ (SERV_LITERAL_ADDRESS | SERV_MARK |
+ SERV_USE_RESOLV)) &&
+ sockaddr_isequal(&serv->addr, &serv1->addr))
+ {
+ serv1->flags |= SERV_MARK;
+ queries += serv1->queries;
+ failed_queries += serv1->failed_queries;
+ }
+ port = prettyprint_addr(&serv->addr, daemon->addrbuff);
+ if (add_comma)
+ fprintf(f, ",");
+ else
+ add_comma = 1;
+ fprintf(f, "{\"addr\": \"%s\", \"port\": %d, \"queries\": %u, \
+ \"failed\": %u}", daemon->addrbuff, port, queries, failed_queries);
+ }
+ fprintf(f, "], \"entries\": [");
+
+ struct crec *cache ;
+ int i;
+
+ add_comma = 0;
+ for (i=0; i<hash_size; i++)
+ {
+ for (cache = hash_table[i]; cache; cache = cache->hash_next)
+ {
+ char *a = daemon->addrbuff, *n = cache_get_name(cache);
+ *a = 0;
+
+ if (add_comma)
+ fprintf(f, ",");
+ else
+ add_comma = 1;
+
+ if (strlen(n) == 0 && !(cache->flags & F_REVERSE))
+ n = "<Root>";
+ fprintf(f, "{\"host\": \"%s\",", sanitise(n));
+
+ if ((cache->flags & F_CNAME) && !is_outdated_cname_pointer(cache))
+ a = sanitise(cache_get_cname_target(cache));
+#ifdef HAVE_DNSSEC
+ else if (cache->flags & F_DS)
+ {
+ if (!(cache->flags & F_NEG))
+ sprintf(a, "%5u %3u %3u", cache->addr.ds.keytag,
+ cache->addr.ds.algo, cache->addr.ds.digest);
+ }
+ else if (cache->flags & F_DNSKEY)
+ sprintf(a, "%5u %3u %3u", cache->addr.key.keytag,
+ cache->addr.key.algo, cache->addr.key.flags);
+#endif
+ else if (!(cache->flags & F_NEG) || !(cache->flags & F_FORWARD))
+ {
+ a = daemon->addrbuff;
+ if (cache->flags & F_IPV4)
+ inet_ntop(AF_INET, &cache->addr.addr4, a, ADDRSTRLEN);
+ else if (cache->flags & F_IPV6)
+ inet_ntop(AF_INET6, &cache->addr.addr6, a, ADDRSTRLEN);
+ }
+ fprintf(f, "\"addr\": \"%s\",", a);
+
+ if (cache->flags & F_IPV4)
+ t = "4";
+ else if (cache->flags & F_IPV6)
+ t = "6";
+ else if (cache->flags & F_CNAME)
+ t = "C";
+#ifdef HAVE_DNSSEC
+ else if (cache->flags & F_DS)
+ t = "S";
+ else if (cache->flags & F_DNSKEY)
+ t = "K";
+#endif
+ fprintf(f, "\"type\": \"%s\",", t);
+ fprintf(f, "\"flags\": \"%s%s%s%s%s%s%s%s\",",
+ cache->flags & F_FORWARD ? "F" : "",
+ cache->flags & F_REVERSE ? "R" : "",
+ cache->flags & F_IMMORTAL ? "I" : "",
+ cache->flags & F_DHCP ? "D" : "",
+ cache->flags & F_NEG ? "N" : "",
+ cache->flags & F_NXDOMAIN ? "X" : "",
+ cache->flags & F_HOSTS ? "H" : "",
+ cache->flags & F_DNSSECOK ? "V" : "");
+ fprintf(f, "\"expires\": %lu", (unsigned long)(cache->ttd - now));
+ long app_id[MAX_APPID_PER_HOST], cat_id[MAX_CATID_PER_HOST];
+ int app_id_count = 0, cat_id_count = 0;
+ int index = 0;
+ extract_appid_catid_fm_ipset(daemon->ipsets, n, app_id, cat_id,
+ &app_id_count, &cat_id_count);
+ if (app_id_count) {
+ fprintf(f, ",\"appid\": [");
+ for (index = 0; index < app_id_count - 1; index++) {
+ fprintf(f, "%ld,", app_id[index]);
+ }
+ fprintf(f, "%ld]", app_id[index]);
+ }
+ if (cat_id_count) {
+ fprintf(f, ",\"catid\": [");
+ for (index = 0; index < cat_id_count - 1; index++) {
+ fprintf(f, "%ld,", cat_id[index]);
+ }
+ fprintf(f, "%ld]", cat_id[index]);
+ }
+ fprintf(f, "}");
+ }
+ }
+ fprintf(f, "]}\n");
+ fclose(f);
+}
+/* CRADLEPOINT */
+
char *record_source(unsigned int index)
{
struct hostsfile *ah;
diff --git a/src/cache.c.orig b/src/cache.c.orig
new file mode 100644
index 0000000..0eacec9
--- /dev/null
+++ b/src/cache.c.orig
@@ -0,0 +1,2254 @@
+/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 dated June, 1991, or
+ (at your option) version 3 dated 29 June, 2007.
+
+ This program 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "dnsmasq.h"
+
+static struct crec *cache_head = NULL, *cache_tail = NULL, **hash_table = NULL;
+#ifdef HAVE_DHCP
+static struct crec *dhcp_spare = NULL;
+#endif
+static struct crec *new_chain = NULL;
+static int insert_error;
+static union bigname *big_free = NULL;
+static int bignames_left, hash_size;
+
+static void make_non_terminals(struct crec *source);
+static struct crec *really_insert(char *name, union all_addr *addr, unsigned short class,
+ time_t now, unsigned long ttl, unsigned int flags);
+static void dump_cache_entry(struct crec *cache, time_t now);
+static char *querystr(char *desc, unsigned short type);
+
+/* type->string mapping: this is also used by the name-hash function as a mixing table. */
+/* taken from https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml */
+static const struct {
+ unsigned int type;
+ const char * const name;
+} typestr[] = {
+ { 1, "A" }, /* a host address [RFC1035] */
+ { 2, "NS" }, /* an authoritative name server [RFC1035] */
+ { 3, "MD" }, /* a mail destination (OBSOLETE - use MX) [RFC1035] */
+ { 4, "MF" }, /* a mail forwarder (OBSOLETE - use MX) [RFC1035] */
+ { 5, "CNAME" }, /* the canonical name for an alias [RFC1035] */
+ { 6, "SOA" }, /* marks the start of a zone of authority [RFC1035] */
+ { 7, "MB" }, /* a mailbox domain name (EXPERIMENTAL) [RFC1035] */
+ { 8, "MG" }, /* a mail group member (EXPERIMENTAL) [RFC1035] */
+ { 9, "MR" }, /* a mail rename domain name (EXPERIMENTAL) [RFC1035] */
+ { 10, "NULL" }, /* a null RR (EXPERIMENTAL) [RFC1035] */
+ { 11, "WKS" }, /* a well known service description [RFC1035] */
+ { 12, "PTR" }, /* a domain name pointer [RFC1035] */
+ { 13, "HINFO" }, /* host information [RFC1035] */
+ { 14, "MINFO" }, /* mailbox or mail list information [RFC1035] */
+ { 15, "MX" }, /* mail exchange [RFC1035] */
+ { 16, "TXT" }, /* text strings [RFC1035] */
+ { 17, "RP" }, /* for Responsible Person [RFC1183] */
+ { 18, "AFSDB" }, /* for AFS Data Base location [RFC1183][RFC5864] */
+ { 19, "X25" }, /* for X.25 PSDN address [RFC1183] */
+ { 20, "ISDN" }, /* for ISDN address [RFC1183] */
+ { 21, "RT" }, /* for Route Through [RFC1183] */
+ { 22, "NSAP" }, /* for NSAP address, NSAP style A record [RFC1706] */
+ { 23, "NSAP_PTR" }, /* for domain name pointer, NSAP style [RFC1348][RFC1637][RFC1706] */
+ { 24, "SIG" }, /* for security signature [RFC2535][RFC2536][RFC2537][RFC2931][RFC3008][RFC3110][RFC3755][RFC4034] */
+ { 25, "KEY" }, /* for security key [RFC2535][RFC2536][RFC2537][RFC2539][RFC3008][RFC3110][RFC3755][RFC4034] */
+ { 26, "PX" }, /* X.400 mail mapping information [RFC2163] */
+ { 27, "GPOS" }, /* Geographical Position [RFC1712] */
+ { 28, "AAAA" }, /* IP6 Address [RFC3596] */
+ { 29, "LOC" }, /* Location Information [RFC1876] */
+ { 30, "NXT" }, /* Next Domain (OBSOLETE) [RFC2535][RFC3755] */
+ { 31, "EID" }, /* Endpoint Identifier [Michael_Patton][http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt] 1995-06*/
+ { 32, "NIMLOC" }, /* Nimrod Locator [1][Michael_Patton][http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt] 1995-06*/
+ { 33, "SRV" }, /* Server Selection [1][RFC2782] */
+ { 34, "ATMA" }, /* ATM Address [ ATM Forum Technical Committee, "ATM Name System, V2.0", Doc ID: AF-DANS-0152.000, July 2000. Available from and held in escrow by IANA.] */
+ { 35, "NAPTR" }, /* Naming Authority Pointer [RFC2168][RFC2915][RFC3403] */
+ { 36, "KX" }, /* Key Exchanger [RFC2230] */
+ { 37, "CERT" }, /* CERT [RFC4398] */
+ { 38, "A6" }, /* A6 (OBSOLETE - use AAAA) [RFC2874][RFC3226][RFC6563] */
+ { 39, "DNAME" }, /* DNAME [RFC6672] */
+ { 40, "SINK" }, /* SINK [Donald_E_Eastlake][http://tools.ietf.org/html/draft-eastlake-kitchen-sink] 1997-11*/
+ { 41, "OPT" }, /* OPT [RFC3225][RFC6891] */
+ { 42, "APL" }, /* APL [RFC3123] */
+ { 43, "DS" }, /* Delegation Signer [RFC3658][RFC4034] */
+ { 44, "SSHFP" }, /* SSH Key Fingerprint [RFC4255] */
+ { 45, "IPSECKEY" }, /* IPSECKEY [RFC4025] */
+ { 46, "RRSIG" }, /* RRSIG [RFC3755][RFC4034] */
+ { 47, "NSEC" }, /* NSEC [RFC3755][RFC4034][RFC9077] */
+ { 48, "DNSKEY" }, /* DNSKEY [RFC3755][RFC4034] */
+ { 49, "DHCID" }, /* DHCID [RFC4701] */
+ { 50, "NSEC3" }, /* NSEC3 [RFC5155][RFC9077] */
+ { 51, "NSEC3PARAM" }, /* NSEC3PARAM [RFC5155] */
+ { 52, "TLSA" }, /* TLSA [RFC6698] */
+ { 53, "SMIMEA" }, /* S/MIME cert association [RFC8162] SMIMEA/smimea-completed-template 2015-12-01*/
+ { 55, "HIP" }, /* Host Identity Protocol [RFC8005] */
+ { 56, "NINFO" }, /* NINFO [Jim_Reid] NINFO/ninfo-completed-template 2008-01-21*/
+ { 57, "RKEY" }, /* RKEY [Jim_Reid] RKEY/rkey-completed-template 2008-01-21*/
+ { 58, "TALINK" }, /* Trust Anchor LINK [Wouter_Wijngaards] TALINK/talink-completed-template 2010-02-17*/
+ { 59, "CDS" }, /* Child DS [RFC7344] CDS/cds-completed-template 2011-06-06*/
+ { 60, "CDNSKEY" }, /* DNSKEY(s) the Child wants reflected in DS [RFC7344] 2014-06-16*/
+ { 61, "OPENPGPKEY" }, /* OpenPGP Key [RFC7929] OPENPGPKEY/openpgpkey-completed-template 2014-08-12*/
+ { 62, "CSYNC" }, /* Child-To-Parent Synchronization [RFC7477] 2015-01-27*/
+ { 63, "ZONEMD" }, /* Message Digest Over Zone Data [RFC8976] ZONEMD/zonemd-completed-template 2018-12-12*/
+ { 64, "SVCB" }, /* Service Binding [draft-ietf-dnsop-svcb-https-00] SVCB/svcb-completed-template 2020-06-30*/
+ { 65, "HTTPS" }, /* HTTPS Binding [draft-ietf-dnsop-svcb-https-00] HTTPS/https-completed-template 2020-06-30*/
+ { 99, "SPF" }, /* [RFC7208] */
+ { 100, "UINFO" }, /* [IANA-Reserved] */
+ { 101, "UID" }, /* [IANA-Reserved] */
+ { 102, "GID" }, /* [IANA-Reserved] */
+ { 103, "UNSPEC" }, /* [IANA-Reserved] */
+ { 104, "NID" }, /* [RFC6742] ILNP/nid-completed-template */
+ { 105, "L32" }, /* [RFC6742] ILNP/l32-completed-template */
+ { 106, "L64" }, /* [RFC6742] ILNP/l64-completed-template */
+ { 107, "LP" }, /* [RFC6742] ILNP/lp-completed-template */
+ { 108, "EUI48" }, /* an EUI-48 address [RFC7043] EUI48/eui48-completed-template 2013-03-27*/
+ { 109, "EUI64" }, /* an EUI-64 address [RFC7043] EUI64/eui64-completed-template 2013-03-27*/
+ { 249, "TKEY" }, /* Transaction Key [RFC2930] */
+ { 250, "TSIG" }, /* Transaction Signature [RFC8945] */
+ { 251, "IXFR" }, /* incremental transfer [RFC1995] */
+ { 252, "AXFR" }, /* transfer of an entire zone [RFC1035][RFC5936] */
+ { 253, "MAILB" }, /* mailbox-related RRs (MB, MG or MR) [RFC1035] */
+ { 254, "MAILA" }, /* mail agent RRs (OBSOLETE - see MX) [RFC1035] */
+ { 255, "ANY" }, /* A request for some or all records the server has available [RFC1035][RFC6895][RFC8482] */
+ { 256, "URI" }, /* URI [RFC7553] URI/uri-completed-template 2011-02-22*/
+ { 257, "CAA" }, /* Certification Authority Restriction [RFC8659] CAA/caa-completed-template 2011-04-07*/
+ { 258, "AVC" }, /* Application Visibility and Control [Wolfgang_Riedel] AVC/avc-completed-template 2016-02-26*/
+ { 259, "DOA" }, /* Digital Object Architecture [draft-durand-doa-over-dns] DOA/doa-completed-template 2017-08-30*/
+ { 260, "AMTRELAY" }, /* Automatic Multicast Tunneling Relay [RFC8777] AMTRELAY/amtrelay-completed-template 2019-02-06*/
+ { 261, "RESINFO" }, /* Resolver Information as Key/Value Pairs https://datatracker.ietf.org/doc/draft-ietf-add-resolver-info/06/ */
+ { 32768, "TA" }, /* DNSSEC Trust Authorities [Sam_Weiler][http://cameo.library.cmu.edu/][ Deploying DNSSEC Without a Signed Root. Technical Report 1999-19, Information Networking Institute, Carnegie Mellon University, April 2004.] 2005-12-13*/
+ { 32769, "DLV" }, /* DNSSEC Lookaside Validation (OBSOLETE) [RFC8749][RFC4431] */
+};
+
+static void cache_free(struct crec *crecp);
+static void cache_unlink(struct crec *crecp);
+static void cache_link(struct crec *crecp);
+static void rehash(int size);
+static void cache_hash(struct crec *crecp);
+
+unsigned short rrtype(char *in)
+{
+ unsigned int i;
+
+ for (i = 0; i < (sizeof(typestr)/sizeof(typestr[0])); i++)
+ if (strcasecmp(in, typestr[i].name) == 0)
+ return typestr[i].type;
+
+ return 0;
+}
+
+void next_uid(struct crec *crecp)
+{
+ static unsigned int uid = 0;
+
+ if (crecp->uid == UID_NONE)
+ {
+ uid++;
+
+ /* uid == 0 used to indicate CNAME to interface name. */
+ if (uid == UID_NONE)
+ uid++;
+
+ crecp->uid = uid;
+ }
+}
+
+void cache_init(void)
+{
+ struct crec *crecp;
+ int i;
+
+ bignames_left = daemon->cachesize/10;
+
+ if (daemon->cachesize > 0)
+ {
+ crecp = safe_malloc(daemon->cachesize*sizeof(struct crec));
+
+ for (i=0; i < daemon->cachesize; i++, crecp++)
+ {
+ cache_link(crecp);
+ crecp->flags = 0;
+ crecp->uid = UID_NONE;
+ }
+ }
+
+ /* create initial hash table*/
+ rehash(daemon->cachesize);
+}
+
+/* In most cases, we create the hash table once here by calling this with (hash_table == NULL)
+ but if the hosts file(s) are big (some people have 50000 ad-block entries), the table
+ will be much too small, so the hosts reading code calls rehash every 1000 addresses, to
+ expand the table. */
+static void rehash(int size)
+{
+ struct crec **new, **old, *p, *tmp;
+ int i, new_size, old_size;
+
+ /* hash_size is a power of two. */
+ for (new_size = 64; new_size < size/10; new_size = new_size << 1);
+
+ /* must succeed in getting first instance, failure later is non-fatal */
+ if (!hash_table)
+ new = safe_malloc(new_size * sizeof(struct crec *));
+ else if (new_size <= hash_size || !(new = whine_malloc(new_size * sizeof(struct crec *))))
+ return;
+
+ for (i = 0; i < new_size; i++)
+ new[i] = NULL;
+
+ old = hash_table;
+ old_size = hash_size;
+ hash_table = new;
+ hash_size = new_size;
+
+ if (old)
+ {
+ for (i = 0; i < old_size; i++)
+ for (p = old[i]; p ; p = tmp)
+ {
+ tmp = p->hash_next;
+ cache_hash(p);
+ }
+ free(old);
+ }
+}
+
+static struct crec **hash_bucket(char *name)
+{
+ unsigned int c, val = 017465; /* Barker code - minimum self-correlation in cyclic shift */
+ const unsigned char *mix_tab = (const unsigned char*)typestr;
+
+ while((c = (unsigned char) *name++))
+ {
+ /* don't use tolower and friends here - they may be messed up by LOCALE */
+ if (c >= 'A' && c <= 'Z')
+ c += 'a' - 'A';
+ val = ((val << 7) | (val >> (32 - 7))) + (mix_tab[(val + c) & 0x3F] ^ c);
+ }
+
+ /* hash_size is a power of two */
+ return hash_table + ((val ^ (val >> 16)) & (hash_size - 1));
+}
+
+static void cache_hash(struct crec *crecp)
+{
+ /* maintain an invariant that all entries with F_REVERSE set
+ are at the start of the hash-chain and all non-reverse
+ immortal entries are at the end of the hash-chain.
+ This allows reverse searches and garbage collection to be optimised */
+
+ char *name = cache_get_name(crecp);
+ struct crec **up = hash_bucket(name);
+ unsigned int flags = crecp->flags & (F_IMMORTAL | F_REVERSE);
+
+ if (!(flags & F_REVERSE))
+ {
+ while (*up && ((*up)->flags & F_REVERSE))
+ up = &((*up)->hash_next);
+
+ if (flags & F_IMMORTAL)
+ while (*up && !((*up)->flags & F_IMMORTAL))
+ up = &((*up)->hash_next);
+ }
+
+ /* Preserve order when inserting the same name multiple times.
+ Do not mess up the flag invariants. */
+ while (*up &&
+ hostname_isequal(cache_get_name(*up), name) &&
+ flags == ((*up)->flags & (F_IMMORTAL | F_REVERSE)))
+ up = &((*up)->hash_next);
+
+ crecp->hash_next = *up;
+ *up = crecp;
+}
+
+static void cache_blockdata_free(struct crec *crecp)
+{
+ if (!(crecp->flags & F_NEG))
+ {
+ if ((crecp->flags & F_RR) && (crecp->flags & F_KEYTAG))
+ blockdata_free(crecp->addr.rrblock.rrdata);
+#ifdef HAVE_DNSSEC
+ else if (crecp->flags & F_DNSKEY)
+ blockdata_free(crecp->addr.key.keydata);
+ else if (crecp->flags & F_DS)
+ blockdata_free(crecp->addr.ds.keydata);
+#endif
+ }
+}
+
+static void cache_free(struct crec *crecp)
+{
+ crecp->flags &= ~F_FORWARD;
+ crecp->flags &= ~F_REVERSE;
+ crecp->uid = UID_NONE; /* invalidate CNAMES pointing to this. */
+
+ if (cache_tail)
+ cache_tail->next = crecp;
+ else
+ cache_head = crecp;
+ crecp->prev = cache_tail;
+ crecp->next = NULL;
+ cache_tail = crecp;
+
+ /* retrieve big name for further use. */
+ if (crecp->flags & F_BIGNAME)
+ {
+ crecp->name.bname->next = big_free;
+ big_free = crecp->name.bname;
+ crecp->flags &= ~F_BIGNAME;
+ }
+
+ cache_blockdata_free(crecp);
+}
+
+/* insert a new cache entry at the head of the list (youngest entry) */
+static void cache_link(struct crec *crecp)
+{
+ if (cache_head) /* check needed for init code */
+ cache_head->prev = crecp;
+ crecp->next = cache_head;
+ crecp->prev = NULL;
+ cache_head = crecp;
+ if (!cache_tail)
+ cache_tail = crecp;
+}
+
+/* remove an arbitrary cache entry for promotion */
+static void cache_unlink (struct crec *crecp)
+{
+ if (crecp->prev)
+ crecp->prev->next = crecp->next;
+ else
+ cache_head = crecp->next;
+
+ if (crecp->next)
+ crecp->next->prev = crecp->prev;
+ else
+ cache_tail = crecp->prev;
+}
+
+char *cache_get_name(struct crec *crecp)
+{
+ if (crecp->flags & F_BIGNAME)
+ return crecp->name.bname->name;
+ else if (crecp->flags & F_NAMEP)
+ return crecp->name.namep;
+
+ return crecp->name.sname;
+}
+
+char *cache_get_cname_target(struct crec *crecp)
+{
+ if (crecp->addr.cname.is_name_ptr)
+ return crecp->addr.cname.target.name;
+ else
+ return cache_get_name(crecp->addr.cname.target.cache);
+}
+
+
+
+struct crec *cache_enumerate(int init)
+{
+ static int bucket;
+ static struct crec *cache;
+
+ if (init)
+ {
+ bucket = 0;
+ cache = NULL;
+ }
+ else if (cache && cache->hash_next)
+ cache = cache->hash_next;
+ else
+ {
+ cache = NULL;
+ while (bucket < hash_size)
+ if ((cache = hash_table[bucket++]))
+ break;
+ }
+
+ return cache;
+}
+
+static int is_outdated_cname_pointer(struct crec *crecp)
+{
+ if (!(crecp->flags & F_CNAME) || crecp->addr.cname.is_name_ptr)
+ return 0;
+
+ /* NB. record may be reused as DS or DNSKEY, where uid is
+ overloaded for something completely different */
+ if (crecp->addr.cname.target.cache &&
+ !(crecp->addr.cname.target.cache->flags & (F_DNSKEY | F_DS)) &&
+ crecp->addr.cname.uid == crecp->addr.cname.target.cache->uid)
+ return 0;
+
+ return 1;
+}
+
+static int is_expired(time_t now, struct crec *crecp)
+{
+ /* Don't dump expired entries if they are within the accepted timeout range.
+ The cache becomes approx. LRU. Never use expired DS or DNSKEY entries.
+ Possible values for daemon->cache_max_expiry:
+ -1 == serve cached content regardless how long ago it expired
+ 0 == the option is disabled, expired content isn't served
+ <n> == serve cached content only if it expire less than <n> seconds
+ ago (where n is a positive integer) */
+ if (daemon->cache_max_expiry != 0 &&
+ (daemon->cache_max_expiry == -1 ||
+ difftime(now, crecp->ttd) < daemon->cache_max_expiry) &&
+ !(crecp->flags & (F_DS | F_DNSKEY)))
+ return 0;
+
+ if (crecp->flags & F_IMMORTAL)
+ return 0;
+
+ if (difftime(now, crecp->ttd) < 0)
+ return 0;
+
+ return 1;
+}
+
+/* Remove entries with a given UID from the cache */
+unsigned int cache_remove_uid(const unsigned int uid)
+{
+ int i;
+ unsigned int removed = 0;
+ struct crec *crecp, *tmp, **up;
+
+ for (i = 0; i < hash_size; i++)
+ for (crecp = hash_table[i], up = &hash_table[i]; crecp; crecp = tmp)
+ {
+ tmp = crecp->hash_next;
+ if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) && crecp->uid == uid)
+ {
+ *up = tmp;
+ free(crecp);
+ removed++;
+ }
+ else
+ up = &crecp->hash_next;
+ }
+
+ return removed;
+}
+
+static struct crec *cache_scan_free(char *name, union all_addr *addr, unsigned short class, time_t now,
+ unsigned int flags, struct crec **target_crec, unsigned int *target_uid)
+{
+ /* Scan and remove old entries.
+ If (flags & F_FORWARD) then remove any forward entries for name and any expired
+ entries but only in the same hash bucket as name.
+ If (flags & F_REVERSE) then remove any reverse entries for addr and any expired
+ entries in the whole cache.
+ If (flags == 0) remove any expired entries in the whole cache.
+
+ In the flags & F_FORWARD case, the return code is valid, and returns a non-NULL pointer
+ to a cache entry if the name exists in the cache as a HOSTS or DHCP entry (these are never deleted)
+
+ We take advantage of the fact that hash chains have stuff in the order <reverse>,<other>,<immortal>
+ so that when we hit an entry which isn't reverse and is immortal, we're done.
+
+ If we free a crec which is a CNAME target, return the entry and uid in target_crec and target_uid.
+ This entry will get re-used with the same name, to preserve CNAMEs. */
+
+ struct crec *crecp, **up;
+
+ (void)class;
+
+ if (flags & F_FORWARD)
+ {
+ for (up = hash_bucket(name), crecp = *up; crecp; crecp = crecp->hash_next)
+ {
+ if ((crecp->flags & F_FORWARD) && hostname_isequal(cache_get_name(crecp), name))
+ {
+ int rrmatch = 0;
+ if (crecp->flags & flags & F_RR)
+ {
+ unsigned short rrc = (crecp->flags & F_KEYTAG) ? crecp->addr.rrblock.rrtype : crecp->addr.rrdata.rrtype;
+ unsigned short rra = (flags & F_KEYTAG) ? addr->rrblock.rrtype : addr->rrdata.rrtype;
+
+ if (rrc == rra)
+ rrmatch = 1;
+ }
+
+ /* Don't delete DNSSEC in favour of a CNAME, they can co-exist */
+ if ((flags & crecp->flags & (F_IPV4 | F_IPV6 | F_NXDOMAIN)) ||
+ (((crecp->flags | flags) & F_CNAME) && !(crecp->flags & (F_DNSKEY | F_DS))) ||
+ rrmatch)
+ {
+ if (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))
+ return crecp;
+ *up = crecp->hash_next;
+ /* If this record is for the name we're inserting and is the target
+ of a CNAME record. Make the new record for the same name, in the same
+ crec, with the same uid to avoid breaking the existing CNAME. */
+ if (crecp->uid != UID_NONE)
+ {
+ if (target_crec)
+ *target_crec = crecp;
+ if (target_uid)
+ *target_uid = crecp->uid;
+ }
+ cache_unlink(crecp);
+ cache_free(crecp);
+ continue;
+ }
+
+#ifdef HAVE_DNSSEC
+ /* Deletion has to be class-sensitive for DS and DNSKEY */
+ if ((flags & crecp->flags & (F_DNSKEY | F_DS)) && crecp->uid == class)
+ {
+ if (crecp->flags & F_CONFIG)
+ return crecp;
+ *up = crecp->hash_next;
+ cache_unlink(crecp);
+ cache_free(crecp);
+ continue;
+ }
+#endif
+ }
+
+ if (is_expired(now, crecp) || is_outdated_cname_pointer(crecp))
+ {
+ *up = crecp->hash_next;
+ if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
+ {
+ cache_unlink(crecp);
+ cache_free(crecp);
+ }
+ continue;
+ }
+
+ up = &crecp->hash_next;
+ }
+ }
+ else
+ {
+ int i;
+ int addrlen = (flags & F_IPV6) ? IN6ADDRSZ : INADDRSZ;
+
+ for (i = 0; i < hash_size; i++)
+ for (crecp = hash_table[i], up = &hash_table[i];
+ crecp && ((crecp->flags & F_REVERSE) || !(crecp->flags & F_IMMORTAL));
+ crecp = crecp->hash_next)
+ if (is_expired(now, crecp))
+ {
+ *up = crecp->hash_next;
+ if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
+ {
+ cache_unlink(crecp);
+ cache_free(crecp);
+ }
+ }
+ else if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) &&
+ (flags & crecp->flags & F_REVERSE) &&
+ (flags & crecp->flags & (F_IPV4 | F_IPV6)) &&
+ addr && memcmp(&crecp->addr, addr, addrlen) == 0)
+ {
+ *up = crecp->hash_next;
+ cache_unlink(crecp);
+ cache_free(crecp);
+ }
+ else
+ up = &crecp->hash_next;
+ }
+
+ return NULL;
+}
+
+/* Note: The normal calling sequence is
+ cache_start_insert
+ cache_insert * n
+ cache_end_insert
+
+ but an abort can cause the cache_end_insert to be missed
+ in which can the next cache_start_insert cleans things up. */
+
+void cache_start_insert(void)
+{
+ /* Free any entries which didn't get committed during the last
+ insert due to error.
+ */
+ while (new_chain)
+ {
+ struct crec *tmp = new_chain->next;
+ cache_free(new_chain);
+ new_chain = tmp;
+ }
+ new_chain = NULL;
+ insert_error = 0;
+}
+
+struct crec *cache_insert(char *name, union all_addr *addr, unsigned short class,
+ time_t now, unsigned long ttl, unsigned int flags)
+{
+#ifdef HAVE_DNSSEC
+ if (flags & (F_DNSKEY | F_DS))
+ {
+ /* The DNSSEC validation process works by getting needed records into the
+ cache, then retrying the validation until they are all in place.
+ This can be messed up by very short TTLs, and _really_ messed up by
+ zero TTLs, so we force the TTL to be at least long enough to do a validation.
+ Ideally, we should use some kind of reference counting so that records are
+ locked until the validation that asked for them is complete, but this
+ is much easier, and just as effective. */
+ if (ttl < DNSSEC_MIN_TTL)
+ ttl = DNSSEC_MIN_TTL;
+ }
+ else
+#endif
+ {
+ if (daemon->max_cache_ttl != 0 && daemon->max_cache_ttl < ttl)
+ ttl = daemon->max_cache_ttl;
+ if (daemon->min_cache_ttl != 0 && daemon->min_cache_ttl > ttl)
+ ttl = daemon->min_cache_ttl;
+ }
+
+ return really_insert(name, addr, class, now, ttl, flags);
+}
+
+
+static struct crec *really_insert(char *name, union all_addr *addr, unsigned short class,
+ time_t now, unsigned long ttl, unsigned int flags)
+{
+ struct crec *new, *target_crec = NULL;
+ union bigname *big_name = NULL;
+ int freed_all = (flags & F_REVERSE);
+ struct crec *free_avail = NULL;
+ unsigned int target_uid;
+
+ /* if previous insertion failed give up now. */
+ if (insert_error)
+ return NULL;
+
+ /* we don't cache zero-TTL records unless we're doing stale-caching. */
+ if (daemon->cache_max_expiry == 0 && ttl == 0)
+ {
+ insert_error = 1;
+ return NULL;
+ }
+
+ /* First remove any expired entries and entries for the name/address we
+ are currently inserting. */
+ if ((new = cache_scan_free(name, addr, class, now, flags, &target_crec, &target_uid)))
+ {
+ /* We're trying to insert a record over one from
+ /etc/hosts or DHCP, or other config. If the
+ existing record is for an A or AAAA or CNAME and
+ the record we're trying to insert is the same,
+ just drop the insert, but don't error the whole process. */
+ if ((flags & (F_IPV4 | F_IPV6)) && (flags & F_FORWARD) && addr)
+ {
+ if ((flags & F_IPV4) && (new->flags & F_IPV4) &&
+ new->addr.addr4.s_addr == addr->addr4.s_addr)
+ return new;
+ else if ((flags & F_IPV6) && (new->flags & F_IPV6) &&
+ IN6_ARE_ADDR_EQUAL(&new->addr.addr6, &addr->addr6))
+ return new;
+ }
+
+ insert_error = 1;
+ return NULL;
+ }
+
+ /* Now get a cache entry from the end of the LRU list */
+ if (!target_crec)
+ while (1) {
+ if (!(new = cache_tail)) /* no entries left - cache is too small, bail */
+ {
+ insert_error = 1;
+ return NULL;
+ }
+
+ /* Free entry at end of LRU list, use it. */
+ if (!(new->flags & (F_FORWARD | F_REVERSE)))
+ break;
+
+ /* End of LRU list is still in use: if we didn't scan all the hash
+ chains for expired entries do that now. If we already tried that
+ then it's time to start spilling things. */
+
+ /* If free_avail set, we believe that an entry has been freed.
+ Bugs have been known to make this not true, resulting in
+ a tight loop here. If that happens, abandon the
+ insert. Once in this state, all inserts will probably fail. */
+ if (free_avail)
+ {
+ my_syslog(LOG_ERR, _("Internal error in cache."));
+ /* Log the entry we tried to delete. */
+ dump_cache_entry(free_avail, now);
+ insert_error = 1;
+ return NULL;
+ }
+
+ if (freed_all)
+ {
+ /* For DNSSEC records, uid holds class. */
+ free_avail = new; /* Must be free space now. */
+
+ /* condition valid when stale-caching */
+ if (difftime(now, new->ttd) < 0)
+ daemon->metrics[METRIC_DNS_CACHE_LIVE_FREED]++;
+
+ cache_scan_free(cache_get_name(new), &new->addr, new->uid, now, new->flags, NULL, NULL);
+ }
+ else
+ {
+ cache_scan_free(NULL, NULL, class, now, 0, NULL, NULL);
+ freed_all = 1;
+ }
+ }
+
+ /* Check if we need to and can allocate extra memory for a long name.
+ If that fails, give up now, always succeed for DNSSEC records. */
+ if (name && (strlen(name) > SMALLDNAME-1))
+ {
+ if (big_free)
+ {
+ big_name = big_free;
+ big_free = big_free->next;
+ }
+ else if ((bignames_left == 0 && !(flags & (F_DS | F_DNSKEY))) ||
+ !(big_name = (union bigname *)whine_malloc(sizeof(union bigname))))
+ {
+ insert_error = 1;
+ return NULL;
+ }
+ else if (bignames_left != 0)
+ bignames_left--;
+
+ }
+
+ /* If we freed a cache entry for our name which was a CNAME target, use that.
+ and preserve the uid, so that existing CNAMES are not broken. */
+ if (target_crec)
+ {
+ new = target_crec;
+ new->uid = target_uid;
+ }
+
+ /* Got the rest: finally grab entry. */
+ cache_unlink(new);
+
+ new->flags = flags;
+ if (big_name)
+ {
+ new->name.bname = big_name;
+ new->flags |= F_BIGNAME;
+ }
+
+ if (name)
+ strcpy(cache_get_name(new), name);
+ else
+ *cache_get_name(new) = 0;
+
+#ifdef HAVE_DNSSEC
+ if (flags & (F_DS | F_DNSKEY))
+ new->uid = class;
+#endif
+
+ if (addr)
+ new->addr = *addr;
+
+ new->ttd = now + (time_t)ttl;
+ new->next = new_chain;
+ new_chain = new;
+
+ return new;
+}
+
+/* after end of insertion, commit the new entries */
+void cache_end_insert(void)
+{
+ if (insert_error)
+ return;
+
+ while (new_chain)
+ {
+ struct crec *tmp = new_chain->next;
+ /* drop CNAMEs which didn't find a target. */
+ if (is_outdated_cname_pointer(new_chain))
+ cache_free(new_chain);
+ else
+ {
+ cache_hash(new_chain);
+ cache_link(new_chain);
+ daemon->metrics[METRIC_DNS_CACHE_INSERTED]++;
+
+ /* If we're a child process, send this cache entry up the pipe to the master.
+ The marshalling process is rather nasty. */
+ if (daemon->pipe_to_parent != -1)
+ {
+ char *name = cache_get_name(new_chain);
+ ssize_t m = strlen(name);
+ unsigned int flags = new_chain->flags;
+#ifdef HAVE_DNSSEC
+ u16 class = new_chain->uid;
+#endif
+
+ read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0);
+ read_write(daemon->pipe_to_parent, (unsigned char *)name, m, 0);
+ read_write(daemon->pipe_to_parent, (unsigned char *)&new_chain->ttd, sizeof(new_chain->ttd), 0);
+ read_write(daemon->pipe_to_parent, (unsigned char *)&flags, sizeof(flags), 0);
+ read_write(daemon->pipe_to_parent, (unsigned char *)&new_chain->addr, sizeof(new_chain->addr), 0);
+
+ if (flags & F_RR)
+ {
+ /* A negative RR entry is possible and has no data, obviously. */
+ if (!(flags & F_NEG) && (flags & F_KEYTAG))
+ blockdata_write(new_chain->addr.rrblock.rrdata, new_chain->addr.rrblock.datalen, daemon->pipe_to_parent);
+ }
+#ifdef HAVE_DNSSEC
+ if (flags & F_DNSKEY)
+ {
+ read_write(daemon->pipe_to_parent, (unsigned char *)&class, sizeof(class), 0);
+ blockdata_write(new_chain->addr.key.keydata, new_chain->addr.key.keylen, daemon->pipe_to_parent);
+ }
+ else if (flags & F_DS)
+ {
+ read_write(daemon->pipe_to_parent, (unsigned char *)&class, sizeof(class), 0);
+ /* A negative DS entry is possible and has no data, obviously. */
+ if (!(flags & F_NEG))
+ blockdata_write(new_chain->addr.ds.keydata, new_chain->addr.ds.keylen, daemon->pipe_to_parent);
+ }
+#endif
+ }
+ }
+
+ new_chain = tmp;
+ }
+
+ /* signal end of cache insert in master process */
+ if (daemon->pipe_to_parent != -1)
+ {
+ ssize_t m = -1;
+
+ read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0);
+
+#ifdef HAVE_DNSSEC
+ /* Sneak out possibly updated crypto HWM values. */
+ m = daemon->metrics[METRIC_CRYPTO_HWM];
+ read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0);
+ m = daemon->metrics[METRIC_SIG_FAIL_HWM];
+ read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0);
+ m = daemon->metrics[METRIC_WORK_HWM];
+ read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0);
+#endif
+ }
+
+ new_chain = NULL;
+}
+
+
+/* A marshalled cache entry arrives on fd, read, unmarshall and insert into cache of master process. */
+int cache_recv_insert(time_t now, int fd)
+{
+ ssize_t m;
+ union all_addr addr;
+ unsigned long ttl;
+ time_t ttd;
+ unsigned int flags;
+ struct crec *crecp = NULL;
+
+ cache_start_insert();
+
+ while (1)
+ {
+
+ if (!read_write(fd, (unsigned char *)&m, sizeof(m), 1))
+ return 0;
+
+ if (m == -1)
+ {
+#ifdef HAVE_DNSSEC
+ /* Sneak in possibly updated crypto HWM. */
+ if (!read_write(fd, (unsigned char *)&m, sizeof(m), 1))
+ return 0;
+ if (m > daemon->metrics[METRIC_CRYPTO_HWM])
+ daemon->metrics[METRIC_CRYPTO_HWM] = m;
+ if (!read_write(fd, (unsigned char *)&m, sizeof(m), 1))
+ return 0;
+ if (m > daemon->metrics[METRIC_SIG_FAIL_HWM])
+ daemon->metrics[METRIC_SIG_FAIL_HWM] = m;
+ if (!read_write(fd, (unsigned char *)&m, sizeof(m), 1))
+ return 0;
+ if (m > daemon->metrics[METRIC_WORK_HWM])
+ daemon->metrics[METRIC_WORK_HWM] = m;
+#endif
+ cache_end_insert();
+ return 1;
+ }
+
+ if (!read_write(fd, (unsigned char *)daemon->namebuff, m, 1) ||
+ !read_write(fd, (unsigned char *)&ttd, sizeof(ttd), 1) ||
+ !read_write(fd, (unsigned char *)&flags, sizeof(flags), 1) ||
+ !read_write(fd, (unsigned char *)&addr, sizeof(addr), 1))
+ return 0;
+
+ daemon->namebuff[m] = 0;
+
+ ttl = difftime(ttd, now);
+
+ if (flags & F_CNAME)
+ {
+ struct crec *newc = really_insert(daemon->namebuff, NULL, C_IN, now, ttl, flags);
+ /* This relies on the fact that the target of a CNAME immediately precedes
+ it because of the order of extraction in extract_addresses, and
+ the order reversal on the new_chain. */
+ if (newc)
+ {
+ newc->addr.cname.is_name_ptr = 0;
+
+ if (!crecp)
+ newc->addr.cname.target.cache = NULL;
+ else
+ {
+ next_uid(crecp);
+ newc->addr.cname.target.cache = crecp;
+ newc->addr.cname.uid = crecp->uid;
+ }
+ }
+ }
+ else
+ {
+ unsigned short class = C_IN;
+
+ if ((flags & F_RR) && !(flags & F_NEG) && (flags & F_KEYTAG)
+ && !(addr.rrblock.rrdata = blockdata_read(fd, addr.rrblock.datalen)))
+ return 0;
+#ifdef HAVE_DNSSEC
+ if (flags & F_DNSKEY)
+ {
+ if (!read_write(fd, (unsigned char *)&class, sizeof(class), 1) ||
+ !(addr.key.keydata = blockdata_read(fd, addr.key.keylen)))
+ return 0;
+ }
+ else if (flags & F_DS)
+ {
+ if (!read_write(fd, (unsigned char *)&class, sizeof(class), 1) ||
+ (!(flags & F_NEG) && !(addr.key.keydata = blockdata_read(fd, addr.key.keylen))))
+ return 0;
+ }
+#endif
+ crecp = really_insert(daemon->namebuff, &addr, class, now, ttl, flags);
+ }
+ }
+}
+
+int cache_find_non_terminal(char *name, time_t now)
+{
+ struct crec *crecp;
+
+ for (crecp = *hash_bucket(name); crecp; crecp = crecp->hash_next)
+ if (!is_outdated_cname_pointer(crecp) &&
+ !is_expired(now, crecp) &&
+ (crecp->flags & F_FORWARD) &&
+ !(crecp->flags & F_NXDOMAIN) &&
+ hostname_isequal(name, cache_get_name(crecp)))
+ return 1;
+
+ return 0;
+}
+
+struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsigned int prot)
+{
+ struct crec *ans;
+ int no_rr = (prot & F_NO_RR) || option_bool(OPT_NORR);
+
+ prot &= ~F_NO_RR;
+
+ if (crecp) /* iterating */
+ ans = crecp->next;
+ else
+ {
+ /* first search, look for relevant entries and push to top of list
+ also free anything which has expired */
+ struct crec *next, **up, **insert = NULL, **chainp = &ans;
+ unsigned int ins_flags = 0;
+
+ for (up = hash_bucket(name), crecp = *up; crecp; crecp = next)
+ {
+ next = crecp->hash_next;
+
+ if (!is_expired(now, crecp) && !is_outdated_cname_pointer(crecp))
+ {
+ if ((crecp->flags & F_FORWARD) &&
+ (crecp->flags & prot) &&
+ hostname_isequal(cache_get_name(crecp), name))
+ {
+ if (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))
+ {
+ *chainp = crecp;
+ chainp = &crecp->next;
+ }
+ else
+ {
+ cache_unlink(crecp);
+ cache_link(crecp);
+ }
+
+ /* Move all but the first entry up the hash chain
+ this implements round-robin.
+ Make sure that re-ordering doesn't break the hash-chain
+ order invariants.
+ */
+ if (insert && (crecp->flags & (F_REVERSE | F_IMMORTAL)) == ins_flags)
+ {
+ *up = crecp->hash_next;
+ crecp->hash_next = *insert;
+ *insert = crecp;
+ insert = &crecp->hash_next;
+ }
+ else
+ {
+ if (!insert && !no_rr)
+ {
+ insert = up;
+ ins_flags = crecp->flags & (F_REVERSE | F_IMMORTAL);
+ }
+ up = &crecp->hash_next;
+ }
+ }
+ else
+ /* case : not expired, incorrect entry. */
+ up = &crecp->hash_next;
+ }
+ else
+ {
+ /* expired entry, free it */
+ *up = crecp->hash_next;
+ if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
+ {
+ cache_unlink(crecp);
+ cache_free(crecp);
+ }
+ }
+ }
+
+ *chainp = cache_head;
+ }
+
+ if (ans &&
+ (ans->flags & F_FORWARD) &&
+ (ans->flags & prot) &&
+ hostname_isequal(cache_get_name(ans), name))
+ return ans;
+
+ return NULL;
+}
+
+struct crec *cache_find_by_addr(struct crec *crecp, union all_addr *addr,
+ time_t now, unsigned int prot)
+{
+ struct crec *ans;
+ int addrlen = (prot == F_IPV6) ? IN6ADDRSZ : INADDRSZ;
+
+ if (crecp) /* iterating */
+ ans = crecp->next;
+ else
+ {
+ /* first search, look for relevant entries and push to top of list
+ also free anything which has expired. All the reverse entries are at the
+ start of the hash chain, so we can give up when we find the first
+ non-REVERSE one. */
+ int i;
+ struct crec **up, **chainp = &ans;
+
+ for (i=0; i<hash_size; i++)
+ for (crecp = hash_table[i], up = &hash_table[i];
+ crecp && (crecp->flags & F_REVERSE);
+ crecp = crecp->hash_next)
+ if (!is_expired(now, crecp))
+ {
+ if ((crecp->flags & prot) &&
+ memcmp(&crecp->addr, addr, addrlen) == 0)
+ {
+ if (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))
+ {
+ *chainp = crecp;
+ chainp = &crecp->next;
+ }
+ else
+ {
+ cache_unlink(crecp);
+ cache_link(crecp);
+ }
+ }
+ up = &crecp->hash_next;
+ }
+ else
+ {
+ *up = crecp->hash_next;
+ if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
+ {
+ cache_unlink(crecp);
+ cache_free(crecp);
+ }
+ }
+
+ *chainp = cache_head;
+ }
+
+ if (ans &&
+ (ans->flags & F_REVERSE) &&
+ (ans->flags & prot) &&
+ memcmp(&ans->addr, addr, addrlen) == 0)
+ return ans;
+
+ return NULL;
+}
+
+static void add_hosts_entry(struct crec *cache, union all_addr *addr, int addrlen,
+ unsigned int index, struct crec **rhash, int hashsz)
+{
+ int i;
+ unsigned int j;
+ struct crec *lookup = NULL;
+
+ /* Remove duplicates in hosts files. */
+ while ((lookup = cache_find_by_name(lookup, cache_get_name(cache), 0, cache->flags & (F_IPV4 | F_IPV6))))
+ if ((lookup->flags & F_HOSTS) && memcmp(&lookup->addr, addr, addrlen) == 0)
+ {
+ free(cache);
+ return;
+ }
+
+ /* Ensure there is only one address -> name mapping (first one trumps)
+ We do this by steam here, The entries are kept in hash chains, linked
+ by ->next (which is unused at this point) held in hash buckets in
+ the array rhash, hashed on address. Note that rhash and the values
+ in ->next are only valid whilst reading hosts files: the buckets are
+ then freed, and the ->next pointer used for other things.
+ Only insert each unique address once into this hashing structure.
+
+ This complexity avoids O(n^2) divergent CPU use whilst reading
+ large (10000 entry) hosts files.
+
+ Note that we only do this process when bulk-reading hosts files,
+ for incremental reads, rhash is NULL, and we use cache lookups
+ instead.
+ */
+
+ if (rhash)
+ {
+ /* hash address */
+ for (j = 0, i = 0; i < addrlen; i++)
+ j = (j*2 +((unsigned char *)addr)[i]) % hashsz;
+
+ for (lookup = rhash[j]; lookup; lookup = lookup->next)
+ if ((lookup->flags & cache->flags & (F_IPV4 | F_IPV6)) &&
+ memcmp(&lookup->addr, addr, addrlen) == 0)
+ {
+ cache->flags &= ~F_REVERSE;
+ break;
+ }
+
+ /* maintain address hash chain, insert new unique address */
+ if (!lookup)
+ {
+ cache->next = rhash[j];
+ rhash[j] = cache;
+ }
+ }
+ else
+ {
+ /* incremental read, lookup in cache */
+ lookup = cache_find_by_addr(NULL, addr, 0, cache->flags & (F_IPV4 | F_IPV6));
+ if (lookup && lookup->flags & F_HOSTS)
+ cache->flags &= ~F_REVERSE;
+ }
+
+ cache->uid = index;
+ memcpy(&cache->addr, addr, addrlen);
+ cache_hash(cache);
+ make_non_terminals(cache);
+}
+
+static int eatspace(FILE *f)
+{
+ int c, nl = 0;
+
+ while (1)
+ {
+ if ((c = getc(f)) == '#')
+ while (c != '\n' && c != EOF)
+ c = getc(f);
+
+ if (c == EOF)
+ return 1;
+
+ if (!isspace(c))
+ {
+ ungetc(c, f);
+ return nl;
+ }
+
+ if (c == '\n')
+ nl++;
+ }
+}
+
+static int gettok(FILE *f, char *token)
+{
+ int c, count = 0;
+
+ while (1)
+ {
+ if ((c = getc(f)) == EOF)
+ return (count == 0) ? -1 : 1;
+
+ if (isspace(c) || c == '#')
+ {
+ ungetc(c, f);
+ return eatspace(f);
+ }
+
+ if (count < (MAXDNAME - 1))
+ {
+ token[count++] = c;
+ token[count] = 0;
+ }
+ }
+}
+
+int read_hostsfile(char *filename, unsigned int index, int cache_size, struct crec **rhash, int hashsz)
+{
+ FILE *f = fopen(filename, "r");
+ char *token = daemon->namebuff, *domain_suffix = NULL;
+ int names_done = 0, name_count = cache_size, lineno = 1;
+ unsigned int flags = 0;
+ union all_addr addr;
+ int atnl, addrlen = 0;
+
+ if (!f)
+ {
+ my_syslog(LOG_ERR, _("failed to load names from %s: %s"), filename, strerror(errno));
+ return cache_size;
+ }
+
+ lineno += eatspace(f);
+
+ while ((atnl = gettok(f, token)) != -1)
+ {
+ if (inet_pton(AF_INET, token, &addr) > 0)
+ {
+ flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4;
+ addrlen = INADDRSZ;
+ domain_suffix = get_domain(addr.addr4);
+ }
+ else if (inet_pton(AF_INET6, token, &addr) > 0)
+ {
+ flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV6;
+ addrlen = IN6ADDRSZ;
+ domain_suffix = get_domain6(&addr.addr6);
+ }
+ else
+ {
+ my_syslog(LOG_ERR, _("bad address at %s line %d"), filename, lineno);
+ while (atnl == 0)
+ atnl = gettok(f, token);
+ lineno += atnl;
+ continue;
+ }
+
+ /* rehash every 1000 names. */
+ if (rhash && ((name_count - cache_size) > 1000))
+ {
+ rehash(name_count);
+ cache_size = name_count;
+ }
+
+ while (atnl == 0)
+ {
+ struct crec *cache;
+ int fqdn, nomem;
+ char *canon;
+
+ if ((atnl = gettok(f, token)) == -1)
+ break;
+
+ fqdn = !!strchr(token, '.');
+
+ if ((canon = canonicalise(token, &nomem)))
+ {
+ /* If set, add a version of the name with a default domain appended */
+ if (option_bool(OPT_EXPAND) && domain_suffix && !fqdn &&
+ (cache = whine_malloc(SIZEOF_BARE_CREC + strlen(canon) + 2 + strlen(domain_suffix))))
+ {
+ strcpy(cache->name.sname, canon);
+ strcat(cache->name.sname, ".");
+ strcat(cache->name.sname, domain_suffix);
+ cache->flags = flags;
+ cache->ttd = daemon->local_ttl;
+ add_hosts_entry(cache, &addr, addrlen, index, rhash, hashsz);
+ name_count++;
+ names_done++;
+ }
+ if ((cache = whine_malloc(SIZEOF_BARE_CREC + strlen(canon) + 1)))
+ {
+ strcpy(cache->name.sname, canon);
+ cache->flags = flags;
+ cache->ttd = daemon->local_ttl;
+ add_hosts_entry(cache, &addr, addrlen, index, rhash, hashsz);
+ name_count++;
+ names_done++;
+ }
+ free(canon);
+
+ }
+ else if (!nomem)
+ my_syslog(LOG_ERR, _("bad name at %s line %d"), filename, lineno);
+ }
+
+ lineno += atnl;
+ }
+
+ fclose(f);
+
+ if (rhash)
+ rehash(name_count);
+
+ my_syslog(LOG_INFO, _("read %s - %d names"), filename, names_done);
+
+ return name_count;
+}
+
+void cache_reload(void)
+{
+ struct crec *cache, **up, *tmp;
+ int revhashsz, i, total_size = daemon->cachesize;
+ struct hostsfile *ah;
+ struct host_record *hr;
+ struct name_list *nl;
+ struct cname *a;
+ struct crec lrec;
+ struct mx_srv_record *mx;
+ struct txt_record *txt;
+ struct interface_name *intr;
+ struct ptr_record *ptr;
+ struct naptr *naptr;
+#ifdef HAVE_DNSSEC
+ struct ds_config *ds;
+#endif
+
+ daemon->metrics[METRIC_DNS_CACHE_INSERTED] = 0;
+ daemon->metrics[METRIC_DNS_CACHE_LIVE_FREED] = 0;
+
+ for (i=0; i<hash_size; i++)
+ for (cache = hash_table[i], up = &hash_table[i]; cache; cache = tmp)
+ {
+ cache_blockdata_free(cache);
+
+ tmp = cache->hash_next;
+ if (cache->flags & (F_HOSTS | F_CONFIG))
+ {
+ *up = cache->hash_next;
+ free(cache);
+ }
+ else if (!(cache->flags & F_DHCP))
+ {
+ *up = cache->hash_next;
+ if (cache->flags & F_BIGNAME)
+ {
+ cache->name.bname->next = big_free;
+ big_free = cache->name.bname;
+ }
+ cache->flags = 0;
+ }
+ else
+ up = &cache->hash_next;
+ }
+
+ /* Add locally-configured CNAMEs to the cache */
+ for (a = daemon->cnames; a; a = a->next)
+ if (a->alias[1] != '*' &&
+ ((cache = whine_malloc(SIZEOF_POINTER_CREC))))
+ {
+ cache->flags = F_FORWARD | F_NAMEP | F_CNAME | F_IMMORTAL | F_CONFIG;
+ cache->ttd = a->ttl;
+ cache->name.namep = a->alias;
+ cache->addr.cname.target.name = a->target;
+ cache->addr.cname.is_name_ptr = 1;
+ cache->uid = UID_NONE;
+ cache_hash(cache);
+ make_non_terminals(cache);
+ }
+
+#ifdef HAVE_DNSSEC
+ for (ds = daemon->ds; ds; ds = ds->next)
+ if ((cache = whine_malloc(SIZEOF_POINTER_CREC)) &&
+ (cache->addr.ds.keydata = blockdata_alloc(ds->digest, ds->digestlen)))
+ {
+ cache->flags = F_FORWARD | F_IMMORTAL | F_DS | F_CONFIG | F_NAMEP;
+ cache->ttd = daemon->local_ttl;
+ cache->name.namep = ds->name;
+ cache->addr.ds.keylen = ds->digestlen;
+ cache->addr.ds.algo = ds->algo;
+ cache->addr.ds.keytag = ds->keytag;
+ cache->addr.ds.digest = ds->digest_type;
+ cache->uid = ds->class;
+ cache_hash(cache);
+ make_non_terminals(cache);
+ }
+#endif
+
+ /* borrow the packet buffer for a temporary by-address hash */
+ memset(daemon->packet, 0, daemon->packet_buff_sz);
+ revhashsz = daemon->packet_buff_sz / sizeof(struct crec *);
+ /* we overwrote the buffer... */
+ daemon->srv_save = NULL;
+
+ /* Do host_records in config. */
+ for (hr = daemon->host_records; hr; hr = hr->next)
+ for (nl = hr->names; nl; nl = nl->next)
+ {
+ if ((hr->flags & HR_4) &&
+ (cache = whine_malloc(SIZEOF_POINTER_CREC)))
+ {
+ cache->name.namep = nl->name;
+ cache->ttd = hr->ttl;
+ cache->flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4 | F_NAMEP | F_CONFIG;
+ add_hosts_entry(cache, (union all_addr *)&hr->addr, INADDRSZ, SRC_CONFIG, (struct crec **)daemon->packet, revhashsz);
+ }
+
+ if ((hr->flags & HR_6) &&
+ (cache = whine_malloc(SIZEOF_POINTER_CREC)))
+ {
+ cache->name.namep = nl->name;
+ cache->ttd = hr->ttl;
+ cache->flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV6 | F_NAMEP | F_CONFIG;
+ add_hosts_entry(cache, (union all_addr *)&hr->addr6, IN6ADDRSZ, SRC_CONFIG, (struct crec **)daemon->packet, revhashsz);
+ }
+ }
+
+ if (option_bool(OPT_NO_HOSTS) && !daemon->addn_hosts)
+ {
+ if (daemon->cachesize > 0)
+ my_syslog(LOG_INFO, _("cleared cache"));
+ }
+ else
+ {
+ if (!option_bool(OPT_NO_HOSTS))
+ total_size = read_hostsfile(HOSTSFILE, SRC_HOSTS, total_size, (struct crec **)daemon->packet, revhashsz);
+
+ daemon->addn_hosts = expand_filelist(daemon->addn_hosts);
+ for (ah = daemon->addn_hosts; ah; ah = ah->next)
+ if (!(ah->flags & AH_INACTIVE))
+ total_size = read_hostsfile(ah->fname, ah->index, total_size, (struct crec **)daemon->packet, revhashsz);
+ }
+
+ /* Make non-terminal records for all locally-define RRs */
+ lrec.flags = F_FORWARD | F_CONFIG | F_NAMEP | F_IMMORTAL;
+
+ for (txt = daemon->txt; txt; txt = txt->next)
+ {
+ lrec.name.namep = txt->name;
+ make_non_terminals(&lrec);
+ }
+
+ for (naptr = daemon->naptr; naptr; naptr = naptr->next)
+ {
+ lrec.name.namep = naptr->name;
+ make_non_terminals(&lrec);
+ }
+
+ for (mx = daemon->mxnames; mx; mx = mx->next)
+ {
+ lrec.name.namep = mx->name;
+ make_non_terminals(&lrec);
+ }
+
+ for (intr = daemon->int_names; intr; intr = intr->next)
+ {
+ lrec.name.namep = intr->name;
+ make_non_terminals(&lrec);
+ }
+
+ for (ptr = daemon->ptr; ptr; ptr = ptr->next)
+ {
+ lrec.name.namep = ptr->name;
+ make_non_terminals(&lrec);
+ }
+
+#ifdef HAVE_INOTIFY
+ set_dynamic_inotify(AH_HOSTS, total_size, (struct crec **)daemon->packet, revhashsz);
+#endif
+
+}
+
+#ifdef HAVE_DHCP
+struct in_addr a_record_from_hosts(char *name, time_t now)
+{
+ struct crec *crecp = NULL;
+ struct in_addr ret;
+
+ /* If no DNS service, cache not initialised. */
+ if (daemon->port != 0)
+ while ((crecp = cache_find_by_name(crecp, name, now, F_IPV4)))
+ if (crecp->flags & F_HOSTS)
+ return crecp->addr.addr4;
+
+ my_syslog(MS_DHCP | LOG_WARNING, _("No IPv4 address found for %s"), name);
+
+ ret.s_addr = 0;
+ return ret;
+}
+
+void cache_unhash_dhcp(void)
+{
+ struct crec *cache, **up;
+ int i;
+
+ for (i=0; i<hash_size; i++)
+ for (cache = hash_table[i], up = &hash_table[i]; cache; cache = cache->hash_next)
+ if (cache->flags & F_DHCP)
+ {
+ *up = cache->hash_next;
+ cache->next = dhcp_spare;
+ dhcp_spare = cache;
+ }
+ else
+ up = &cache->hash_next;
+}
+
+void cache_add_dhcp_entry(char *host_name, int prot,
+ union all_addr *host_address, time_t ttd)
+{
+ struct crec *crec = NULL, *fail_crec = NULL;
+ unsigned int flags = F_IPV4;
+ int in_hosts = 0;
+ size_t addrlen = sizeof(struct in_addr);
+
+ if (prot == AF_INET6)
+ {
+ flags = F_IPV6;
+ addrlen = sizeof(struct in6_addr);
+ }
+
+ inet_ntop(prot, host_address, daemon->addrbuff, ADDRSTRLEN);
+
+ while ((crec = cache_find_by_name(crec, host_name, 0, flags | F_CNAME)))
+ {
+ /* check all addresses associated with name */
+ if (crec->flags & (F_HOSTS | F_CONFIG))
+ {
+ if (crec->flags & F_CNAME)
+ my_syslog(MS_DHCP | LOG_WARNING,
+ _("%s is a CNAME, not giving it to the DHCP lease of %s"),
+ host_name, daemon->addrbuff);
+ else if (memcmp(&crec->addr, host_address, addrlen) == 0)
+ in_hosts = 1;
+ else
+ fail_crec = crec;
+ }
+ else if (!(crec->flags & F_DHCP))
+ {
+ cache_scan_free(host_name, NULL, C_IN, 0, crec->flags & (flags | F_CNAME | F_FORWARD), NULL, NULL);
+ /* scan_free deletes all addresses associated with name */
+ break;
+ }
+ }
+
+ /* if in hosts, don't need DHCP record */
+ if (in_hosts)
+ return;
+
+ /* Name in hosts, address doesn't match */
+ if (fail_crec)
+ {
+ inet_ntop(prot, &fail_crec->addr, daemon->namebuff, MAXDNAME);
+ my_syslog(MS_DHCP | LOG_WARNING,
+ _("not giving name %s to the DHCP lease of %s because "
+ "the name exists in %s with address %s"),
+ host_name, daemon->addrbuff,
+ record_source(fail_crec->uid), daemon->namebuff);
+ return;
+ }
+
+ if ((crec = cache_find_by_addr(NULL, (union all_addr *)host_address, 0, flags)))
+ {
+ if (crec->flags & F_NEG)
+ {
+ flags |= F_REVERSE;
+ cache_scan_free(NULL, (union all_addr *)host_address, C_IN, 0, flags, NULL, NULL);
+ }
+ }
+ else
+ flags |= F_REVERSE;
+
+ if ((crec = dhcp_spare))
+ dhcp_spare = dhcp_spare->next;
+ else /* need new one */
+ crec = whine_malloc(SIZEOF_POINTER_CREC);
+
+ if (crec) /* malloc may fail */
+ {
+ crec->flags = flags | F_NAMEP | F_DHCP | F_FORWARD;
+ if (ttd == 0)
+ crec->flags |= F_IMMORTAL;
+ else
+ crec->ttd = ttd;
+ crec->addr = *host_address;
+ crec->name.namep = host_name;
+ crec->uid = UID_NONE;
+ cache_hash(crec);
+ make_non_terminals(crec);
+ }
+}
+#endif
+
+/* Called when we put a local or DHCP name into the cache.
+ Creates empty cache entries for subnames (ie,
+ for three.two.one, for two.one and one), without
+ F_IPV4 or F_IPV6 or F_CNAME set. These convert
+ NXDOMAIN answers to NoData ones. */
+static void make_non_terminals(struct crec *source)
+{
+ char *name = cache_get_name(source);
+ struct crec *crecp, *tmp, **up;
+ int type = F_HOSTS | F_CONFIG;
+#ifdef HAVE_DHCP
+ if (source->flags & F_DHCP)
+ type = F_DHCP;
+#endif
+
+ /* First delete any empty entries for our new real name. Note that
+ we only delete empty entries deriving from DHCP for a new DHCP-derived
+ entry and vice-versa for HOSTS and CONFIG. This ensures that
+ non-terminals from DHCP go when we reload DHCP and
+ for HOSTS/CONFIG when we re-read. */
+ for (up = hash_bucket(name), crecp = *up; crecp; crecp = tmp)
+ {
+ tmp = crecp->hash_next;
+
+ if (!is_outdated_cname_pointer(crecp) &&
+ (crecp->flags & F_FORWARD) &&
+ (crecp->flags & type) &&
+ !(crecp->flags & (F_IPV4 | F_IPV6 | F_CNAME | F_DNSKEY | F_DS | F_RR)) &&
+ hostname_isequal(name, cache_get_name(crecp)))
+ {
+ *up = crecp->hash_next;
+#ifdef HAVE_DHCP
+ if (type & F_DHCP)
+ {
+ crecp->next = dhcp_spare;
+ dhcp_spare = crecp;
+ }
+ else
+#endif
+ free(crecp);
+ break;
+ }
+ else
+ up = &crecp->hash_next;
+ }
+
+ while ((name = strchr(name, '.')))
+ {
+ name++;
+
+ /* Look for one existing, don't need another */
+ for (crecp = *hash_bucket(name); crecp; crecp = crecp->hash_next)
+ if (!is_outdated_cname_pointer(crecp) &&
+ (crecp->flags & F_FORWARD) &&
+ (crecp->flags & type) &&
+ hostname_isequal(name, cache_get_name(crecp)))
+ break;
+
+ if (crecp)
+ {
+ /* If the new name expires later, transfer that time to
+ empty non-terminal entry. */
+ if (!(crecp->flags & F_IMMORTAL))
+ {
+ if (source->flags & F_IMMORTAL)
+ crecp->flags |= F_IMMORTAL;
+ else if (difftime(crecp->ttd, source->ttd) < 0)
+ crecp->ttd = source->ttd;
+ }
+ continue;
+ }
+
+#ifdef HAVE_DHCP
+ if ((source->flags & F_DHCP) && dhcp_spare)
+ {
+ crecp = dhcp_spare;
+ dhcp_spare = dhcp_spare->next;
+ }
+ else
+#endif
+ crecp = whine_malloc(SIZEOF_POINTER_CREC);
+
+ if (crecp)
+ {
+ crecp->flags = (source->flags | F_NAMEP) & ~(F_IPV4 | F_IPV6 | F_CNAME | F_RR | F_DNSKEY | F_DS | F_REVERSE);
+ if (!(crecp->flags & F_IMMORTAL))
+ crecp->ttd = source->ttd;
+ crecp->name.namep = name;
+
+ cache_hash(crecp);
+ }
+ }
+}
+
+#ifndef NO_ID
+int cache_make_stat(struct txt_record *t)
+{
+ static char *buff = NULL;
+ static int bufflen = 60;
+ int len;
+ struct server *serv, *serv1;
+ char *p;
+
+ if (!buff && !(buff = whine_malloc(60)))
+ return 0;
+
+ p = buff;
+
+ switch (t->stat)
+ {
+ case TXT_STAT_CACHESIZE:
+ sprintf(buff+1, "%d", daemon->cachesize);
+ break;
+
+ case TXT_STAT_INSERTS:
+ sprintf(buff+1, "%d", daemon->metrics[METRIC_DNS_CACHE_INSERTED]);
+ break;
+
+ case TXT_STAT_EVICTIONS:
+ sprintf(buff+1, "%d", daemon->metrics[METRIC_DNS_CACHE_LIVE_FREED]);
+ break;
+
+ case TXT_STAT_MISSES:
+ sprintf(buff+1, "%u", daemon->metrics[METRIC_DNS_QUERIES_FORWARDED]);
+ break;
+
+ case TXT_STAT_HITS:
+ sprintf(buff+1, "%u", daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]);
+ break;
+
+#ifdef HAVE_AUTH
+ case TXT_STAT_AUTH:
+ sprintf(buff+1, "%u", daemon->metrics[METRIC_DNS_AUTH_ANSWERED]);
+ break;
+#endif
+
+ case TXT_STAT_SERVERS:
+ /* sum counts from different records for same server */
+ for (serv = daemon->servers; serv; serv = serv->next)
+ serv->flags &= ~SERV_MARK;
+
+ for (serv = daemon->servers; serv; serv = serv->next)
+ if (!(serv->flags & SERV_MARK))
+ {
+ char *new, *lenp;
+ int port, newlen, bytes_avail, bytes_needed;
+ unsigned int queries = 0, failed_queries = 0;
+ for (serv1 = serv; serv1; serv1 = serv1->next)
+ if (!(serv1->flags & SERV_MARK) && sockaddr_isequal(&serv->addr, &serv1->addr))
+ {
+ serv1->flags |= SERV_MARK;
+ queries += serv1->queries;
+ failed_queries += serv1->failed_queries;
+ }
+ port = prettyprint_addr(&serv->addr, daemon->addrbuff);
+ lenp = p++; /* length */
+ bytes_avail = bufflen - (p - buff );
+ bytes_needed = snprintf(p, bytes_avail, "%s#%d %u %u", daemon->addrbuff, port, queries, failed_queries);
+ if (bytes_needed >= bytes_avail)
+ {
+ /* expand buffer if necessary */
+ newlen = bytes_needed + 1 + bufflen - bytes_avail;
+ if (!(new = whine_realloc(buff, newlen)))
+ return 0;
+ p = new + (p - buff);
+ lenp = p - 1;
+ buff = new;
+ bufflen = newlen;
+ bytes_avail = bufflen - (p - buff );
+ bytes_needed = snprintf(p, bytes_avail, "%s#%d %u %u", daemon->addrbuff, port, queries, failed_queries);
+ }
+ *lenp = bytes_needed;
+ p += bytes_needed;
+ }
+ t->txt = (unsigned char *)buff;
+ t->len = p - buff;
+
+ return 1;
+ }
+
+ len = strlen(buff+1);
+ t->txt = (unsigned char *)buff;
+ t->len = len + 1;
+ *buff = len;
+ return 1;
+}
+#endif
+
+/* There can be names in the cache containing control chars, don't
+ mess up logging or open security holes. */
+static char *sanitise(char *name)
+{
+ unsigned char *r;
+ if (name)
+ for (r = (unsigned char *)name; *r; r++)
+ if (!isprint((int)*r))
+ return "<name unprintable>";
+
+ return name;
+}
+
+static void dump_cache_entry(struct crec *cache, time_t now)
+{
+ (void)now;
+ static char *buff = NULL;
+
+ char *p, *t = " ";
+ char *a = daemon->addrbuff, *n = cache_get_name(cache);
+
+ /* String length is limited below */
+ if (!buff && !(buff = whine_malloc(150)))
+ return;
+
+ p = buff;
+
+ *a = 0;
+
+ if (cache->flags & F_REVERSE)
+ {
+ if ((cache->flags & F_NEG))
+ n = "";
+ }
+ else
+ {
+ if (strlen(n) == 0)
+ n = "<Root>";
+ }
+
+ p += sprintf(p, "%-30.30s ", sanitise(n));
+ if ((cache->flags & F_CNAME) && !is_outdated_cname_pointer(cache))
+ a = sanitise(cache_get_cname_target(cache));
+ else if (cache->flags & F_RR)
+ {
+ if (cache->flags & F_KEYTAG)
+ sprintf(a, "%s", querystr(NULL, cache->addr.rrblock.rrtype));
+ else
+ sprintf(a, "%s", querystr(NULL, cache->addr.rrdata.rrtype));
+ }
+#ifdef HAVE_DNSSEC
+ else if (cache->flags & F_DS)
+ {
+ if (!(cache->flags & F_NEG))
+ sprintf(a, "%5u %3u %3u", cache->addr.ds.keytag,
+ cache->addr.ds.algo, cache->addr.ds.digest);
+ }
+ else if (cache->flags & F_DNSKEY)
+ sprintf(a, "%5u %3u %3u", cache->addr.key.keytag,
+ cache->addr.key.algo, cache->addr.key.flags);
+#endif
+ else if (!(cache->flags & F_NEG) || !(cache->flags & F_FORWARD))
+ {
+ a = daemon->addrbuff;
+ if (cache->flags & F_IPV4)
+ inet_ntop(AF_INET, &cache->addr, a, ADDRSTRLEN);
+ else if (cache->flags & F_IPV6)
+ inet_ntop(AF_INET6, &cache->addr, a, ADDRSTRLEN);
+ }
+
+ if (cache->flags & F_IPV4)
+ t = "4";
+ else if (cache->flags & F_IPV6)
+ t = "6";
+ else if (cache->flags & F_CNAME)
+ t = "C";
+ else if (cache->flags & F_RR)
+ t = "T";
+#ifdef HAVE_DNSSEC
+ else if (cache->flags & F_DS)
+ t = "S";
+ else if (cache->flags & F_DNSKEY)
+ t = "K";
+#endif
+ else if (!(cache->flags & F_NXDOMAIN)) /* non-terminal */
+ t = "!";
+
+ p += sprintf(p, "%-40.40s %s%s%s%s%s%s%s%s%s%s ", a, t,
+ cache->flags & F_FORWARD ? "F" : " ",
+ cache->flags & F_REVERSE ? "R" : " ",
+ cache->flags & F_IMMORTAL ? "I" : " ",
+ cache->flags & F_DHCP ? "D" : " ",
+ cache->flags & F_NEG ? "N" : " ",
+ cache->flags & F_NXDOMAIN ? "X" : " ",
+ cache->flags & F_HOSTS ? "H" : " ",
+ cache->flags & F_CONFIG ? "C" : " ",
+ cache->flags & F_DNSSECOK ? "V" : " ");
+#ifdef HAVE_BROKEN_RTC
+ p += sprintf(p, "%-24lu", cache->flags & F_IMMORTAL ? 0: (unsigned long)(cache->ttd - now));
+#else
+ p += sprintf(p, "%-24.24s", cache->flags & F_IMMORTAL ? "" : ctime(&(cache->ttd)));
+#endif
+ if(cache->flags & (F_HOSTS | F_CONFIG) && cache->uid > 0)
+ p += sprintf(p, " %-40.40s", record_source(cache->uid));
+
+ my_syslog(LOG_INFO, "%s", buff);
+}
+
+void dump_cache(time_t now)
+{
+ struct server *serv, *serv1;
+
+ my_syslog(LOG_INFO, _("time %lu"), (unsigned long)now);
+ my_syslog(LOG_INFO, _("cache size %d, %d/%d cache insertions re-used unexpired cache entries."),
+ daemon->cachesize, daemon->metrics[METRIC_DNS_CACHE_LIVE_FREED], daemon->metrics[METRIC_DNS_CACHE_INSERTED]);
+ my_syslog(LOG_INFO, _("queries forwarded %u, queries answered locally %u"),
+ daemon->metrics[METRIC_DNS_QUERIES_FORWARDED], daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]);
+ if (daemon->cache_max_expiry != 0)
+ my_syslog(LOG_INFO, _("queries answered from stale cache %u"), daemon->metrics[METRIC_DNS_STALE_ANSWERED]);
+#ifdef HAVE_AUTH
+ my_syslog(LOG_INFO, _("queries for authoritative zones %u"), daemon->metrics[METRIC_DNS_AUTH_ANSWERED]);
+#endif
+#ifdef HAVE_DNSSEC
+ my_syslog(LOG_INFO, _("DNSSEC per-query subqueries HWM %u"), daemon->metrics[METRIC_WORK_HWM]);
+ my_syslog(LOG_INFO, _("DNSSEC per-query crypto work HWM %u"), daemon->metrics[METRIC_CRYPTO_HWM]);
+ my_syslog(LOG_INFO, _("DNSSEC per-RRSet signature fails HWM %u"), daemon->metrics[METRIC_SIG_FAIL_HWM]);
+#endif
+
+ blockdata_report();
+ my_syslog(LOG_INFO, _("child processes for TCP requests: in use %zu, highest since last SIGUSR1 %zu, max allowed %zu."),
+ daemon->metrics[METRIC_TCP_CONNECTIONS],
+ daemon->max_procs_used,
+ daemon->max_procs);
+ daemon->max_procs_used = daemon->metrics[METRIC_TCP_CONNECTIONS];
+
+ /* sum counts from different records for same server */
+ for (serv = daemon->servers; serv; serv = serv->next)
+ serv->flags &= ~SERV_MARK;
+
+ for (serv = daemon->servers; serv; serv = serv->next)
+ if (!(serv->flags & SERV_MARK))
+ {
+ int port;
+ unsigned int queries = 0, failed_queries = 0, nxdomain_replies = 0, retrys = 0;
+ unsigned int sigma_latency = 0, count_latency = 0;
+
+ for (serv1 = serv; serv1; serv1 = serv1->next)
+ if (!(serv1->flags & SERV_MARK) && sockaddr_isequal(&serv->addr, &serv1->addr))
+ {
+ serv1->flags |= SERV_MARK;
+ queries += serv1->queries;
+ failed_queries += serv1->failed_queries;
+ nxdomain_replies += serv1->nxdomain_replies;
+ retrys += serv1->retrys;
+ sigma_latency += serv1->query_latency;
+ count_latency++;
+ }
+ port = prettyprint_addr(&serv->addr, daemon->addrbuff);
+ my_syslog(LOG_INFO, _("server %s#%d: queries sent %u, retried %u, failed %u, nxdomain replies %u, avg. latency %ums"),
+ daemon->addrbuff, port, queries, retrys, failed_queries, nxdomain_replies, sigma_latency/count_latency);
+ }
+
+ if (option_bool(OPT_DEBUG) || option_bool(OPT_LOG))
+ {
+ struct crec *cache;
+ int i;
+ my_syslog(LOG_INFO, "Host Address Flags Expires Source");
+ my_syslog(LOG_INFO, "------------------------------ ---------------------------------------- ---------- ------------------------ ------------");
+
+ for (i=0; i<hash_size; i++)
+ for (cache = hash_table[i]; cache; cache = cache->hash_next)
+ dump_cache_entry(cache, now);
+ }
+}
+
+char *record_source(unsigned int index)
+{
+ struct hostsfile *ah;
+#ifdef HAVE_INOTIFY
+ struct dyndir *dd;
+#endif
+
+ if (index == SRC_CONFIG)
+ return "config";
+ else if (index == SRC_HOSTS)
+ return HOSTSFILE;
+
+ for (ah = daemon->addn_hosts; ah; ah = ah->next)
+ if (ah->index == index)
+ return ah->fname;
+
+#ifdef HAVE_INOTIFY
+ /* Dynamic directories contain multiple files */
+ for (dd = daemon->dynamic_dirs; dd; dd = dd->next)
+ for (ah = dd->files; ah; ah = ah->next)
+ if (ah->index == index)
+ return ah->fname;
+#endif
+
+ return "<unknown>";
+}
+
+static char *querystr(char *desc, unsigned short type)
+{
+ unsigned int i;
+ int len = 10; /* strlen("type=xxxxx") */
+ const char *types = NULL;
+ static char *buff = NULL;
+ static int bufflen = 0;
+
+ for (i = 0; i < (sizeof(typestr)/sizeof(typestr[0])); i++)
+ if (typestr[i].type == type)
+ {
+ types = typestr[i].name;
+ len = strlen(types);
+ break;
+ }
+
+ if (desc)
+ {
+ len += 2; /* braces */
+ len += strlen(desc);
+ }
+ len++; /* terminator */
+
+ if (!buff || bufflen < len)
+ {
+ if (buff)
+ free(buff);
+ else if (len < 20)
+ len = 20;
+
+ buff = whine_malloc(len);
+ bufflen = len;
+ }
+
+ if (buff)
+ {
+ if (desc)
+ {
+ if (types)
+ sprintf(buff, "%s[%s]", desc, types);
+ else
+ sprintf(buff, "%s[type=%d]", desc, type);
+ }
+ else
+ {
+ if (types)
+ sprintf(buff, "<%s>", types);
+ else
+ sprintf(buff, "<type=%d>", type);
+ }
+ }
+
+ return buff ? buff : "";
+}
+
+static char *edestr(int ede)
+{
+ switch (ede)
+ {
+ case EDE_OTHER: return "other";
+ case EDE_USUPDNSKEY: return "unsupported DNSKEY algorithm";
+ case EDE_USUPDS: return "unsupported DS digest";
+ case EDE_STALE: return "stale answer";
+ case EDE_FORGED: return "forged";
+ case EDE_DNSSEC_IND: return "DNSSEC indeterminate";
+ case EDE_DNSSEC_BOGUS: return "DNSSEC bogus";
+ case EDE_SIG_EXP: return "DNSSEC signature expired";
+ case EDE_SIG_NYV: return "DNSSEC sig not yet valid";
+ case EDE_NO_DNSKEY: return "DNSKEY missing";
+ case EDE_NO_RRSIG: return "RRSIG missing";
+ case EDE_NO_ZONEKEY: return "no zone key bit set";
+ case EDE_NO_NSEC: return "NSEC(3) missing";
+ case EDE_CACHED_ERR: return "cached error";
+ case EDE_NOT_READY: return "not ready";
+ case EDE_BLOCKED: return "blocked";
+ case EDE_CENSORED: return "censored";
+ case EDE_FILTERED: return "filtered";
+ case EDE_PROHIBITED: return "prohibited";
+ case EDE_STALE_NXD: return "stale NXDOMAIN";
+ case EDE_NOT_AUTH: return "not authoritative";
+ case EDE_NOT_SUP: return "not supported";
+ case EDE_NO_AUTH: return "no reachable authority";
+ case EDE_NETERR: return "network error";
+ case EDE_INVALID_DATA: return "invalid data";
+ case EDE_SIG_E_B_V: return "signature expired before valid";
+ case EDE_TOO_EARLY: return "too early";
+ case EDE_UNS_NS3_ITER: return "unsupported NSEC3 iterations value";
+ case EDE_UNABLE_POLICY: return "uanble to conform to policy";
+ case EDE_SYNTHESIZED: return "synthesized";
+ default: return "unknown";
+ }
+}
+
+void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg, unsigned short type)
+{
+ char *source, *dest;
+ char *verb = "is";
+ char *extra = "";
+ char *gap = " ";
+ char portstring[7]; /* space for #<portnum> */
+
+ if (!option_bool(OPT_LOG))
+ return;
+
+ /* build query type string if requested */
+ if (!(flags & (F_SERVER | F_IPSET)) && type > 0)
+ arg = querystr(arg, type);
+
+ dest = arg;
+
+#ifdef HAVE_DNSSEC
+ if ((flags & F_DNSSECOK) && option_bool(OPT_EXTRALOG))
+ extra = " (DNSSEC signed)";
+#endif
+
+ name = sanitise(name);
+
+ if (addr)
+ {
+ dest = daemon->addrbuff;
+
+ if (flags & F_RR)
+ {
+ if (flags & F_KEYTAG)
+ dest = querystr(NULL, addr->rrblock.rrtype);
+ else
+ dest = querystr(NULL, addr->rrdata.rrtype);
+ }
+ else if (flags & F_KEYTAG)
+ sprintf(daemon->addrbuff, arg, addr->log.keytag, addr->log.algo, addr->log.digest);
+ else if (flags & F_RCODE)
+ {
+ unsigned int rcode = addr->log.rcode;
+
+ if (rcode == SERVFAIL)
+ dest = "SERVFAIL";
+ else if (rcode == REFUSED)
+ dest = "REFUSED";
+ else if (rcode == NOTIMP)
+ dest = "not implemented";
+ else
+ sprintf(daemon->addrbuff, "%u", rcode);
+
+ if (addr->log.ede != EDE_UNSET)
+ {
+ extra = daemon->addrbuff;
+ sprintf(extra, " (EDE: %s)", edestr(addr->log.ede));
+ }
+ }
+ else if (flags & (F_IPV4 | F_IPV6))
+ {
+ inet_ntop(flags & F_IPV4 ? AF_INET : AF_INET6,
+ addr, daemon->addrbuff, ADDRSTRLEN);
+ if ((flags & F_SERVER) && type != NAMESERVER_PORT)
+ {
+ extra = portstring;
+ sprintf(portstring, "#%u", type);
+ }
+ }
+ else
+ dest = arg;
+ }
+
+ if (flags & F_REVERSE)
+ {
+ dest = name;
+ name = daemon->addrbuff;
+ }
+
+ if (flags & F_NEG)
+ {
+ if (flags & F_NXDOMAIN)
+ dest = "NXDOMAIN";
+ else
+ {
+ if (flags & F_IPV4)
+ dest = "NODATA-IPv4";
+ else if (flags & F_IPV6)
+ dest = "NODATA-IPv6";
+ else
+ dest = "NODATA";
+ }
+ }
+ else if (flags & F_CNAME)
+ dest = "<CNAME>";
+ else if (flags & F_RRNAME)
+ dest = arg;
+
+ if (flags & F_CONFIG)
+ source = "config";
+ else if (flags & F_DHCP)
+ source = "DHCP";
+ else if (flags & F_HOSTS)
+ source = arg;
+ else if (flags & F_UPSTREAM)
+ source = "reply";
+ else if (flags & F_SECSTAT)
+ {
+ if (addr && addr->log.ede != EDE_UNSET && option_bool(OPT_EXTRALOG))
+ {
+ extra = daemon->addrbuff;
+ sprintf(extra, " (EDE: %s)", edestr(addr->log.ede));
+ }
+ source = "validation";
+ dest = arg;
+ }
+ else if (flags & F_AUTH)
+ source = "auth";
+ else if (flags & F_DNSSEC)
+ {
+ source = arg;
+ verb = "to";
+ }
+ else if (flags & F_SERVER)
+ {
+ source = "forwarded";
+ verb = "to";
+ }
+ else if (flags & F_QUERY)
+ {
+ source = arg;
+ verb = "from";
+ }
+ else if (flags & F_IPSET)
+ {
+ source = type ? "ipset add" : "nftset add";
+ dest = name;
+ name = arg;
+ verb = daemon->addrbuff;
+ }
+ else if (flags & F_STALE)
+ source = "cached-stale";
+ else
+ source = "cached";
+
+ if (!name)
+ gap = name = "";
+ else if (!name[0])
+ name = ".";
+
+ if (option_bool(OPT_EXTRALOG))
+ {
+ if (flags & F_NOEXTRA)
+ my_syslog(LOG_INFO, "%u %s %s%s%s %s%s", daemon->log_display_id, source, name, gap, verb, dest, extra);
+ else
+ {
+ int port = prettyprint_addr(daemon->log_source_addr, daemon->addrbuff2);
+ my_syslog(LOG_INFO, "%u %s/%u %s %s%s%s %s%s", daemon->log_display_id, daemon->addrbuff2, port, source, name, gap, verb, dest, extra);
+ }
+ }
+ else
+ my_syslog(LOG_INFO, "%s %s%s%s %s%s", source, name, gap, verb, dest, extra);
+}
diff --git a/src/config.h b/src/config.h
index e722e98..41d9f66 100644
--- a/src/config.h
+++ b/src/config.h
@@ -179,14 +179,18 @@
/* The default set of options to build. Built with these options, dnsmasq
has no library dependencies other than libc */
+/* CRADLEPOINT */
+#define NO_LARGEFILE
+#define NO_CONNTRACK
+#define NO_TFTP
+#define NO_AUTH
+#define NO_INOTIFY
#define HAVE_DHCP
#define HAVE_DHCP6
-#define HAVE_TFTP
#define HAVE_SCRIPT
-#define HAVE_AUTH
#define HAVE_IPSET
#define HAVE_LOOP
-#define HAVE_DUMPFILE
+/* CRADLEPOINT */
/* Build options which require external libraries.
diff --git a/src/dhcp.c b/src/dhcp.c
index b65facd..3bbc7e8 100644
--- a/src/dhcp.c
+++ b/src/dhcp.c
@@ -283,6 +283,9 @@
#ifdef HAVE_LINUX_NETWORK
safe_strncpy(arp_req.arp_dev, ifr.ifr_name, sizeof(arp_req.arp_dev));
#endif
+/* CRADLEPOINT */
+ parm.current = NULL;
+/* CRADLEPOINT */
}
else
{
@@ -395,17 +398,21 @@
else
{
/* fill cmsg for outbound interface (both broadcast & unicast) */
- struct in_pktinfo *pkt;
- msg.msg_control = control_u.control;
- msg.msg_controllen = sizeof(control_u);
- cmptr = CMSG_FIRSTHDR(&msg);
- pkt = (struct in_pktinfo *)CMSG_DATA(cmptr);
- pkt->ipi_ifindex = rcvd_iface_index;
- pkt->ipi_spec_dst.s_addr = 0;
- msg.msg_controllen = CMSG_SPACE(sizeof(struct in_pktinfo));
- cmptr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
- cmptr->cmsg_level = IPPROTO_IP;
- cmptr->cmsg_type = IP_PKTINFO;
+/* CRADLEPOINT */
+ if (!parm.current || parm.current->forcedaddress.s_addr == 0)
+ {
+ struct in_pktinfo *pkt;
+ msg.msg_control = control_u.control;
+ msg.msg_controllen = sizeof(control_u);
+ cmptr = CMSG_FIRSTHDR(&msg);
+ pkt = (struct in_pktinfo *)CMSG_DATA(cmptr);
+ pkt->ipi_ifindex = rcvd_iface_index;
+ pkt->ipi_spec_dst.s_addr = 0;
+ msg.msg_controllen = cmptr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
+ cmptr->cmsg_level = IPPROTO_IP;
+ cmptr->cmsg_type = IP_PKTINFO;
+ }
+/* CRADLEPOINT */
if ((ntohs(mess->flags) & 0x8000) || mess->hlen == 0 ||
mess->hlen > sizeof(ifr.ifr_addr.sa_data) || mess->htype == 0)
@@ -476,6 +483,30 @@
setsockopt(fd, IPPROTO_IP, IP_BOUND_IF, &iface_index, sizeof(iface_index));
#endif
+/* CRADLEPOINT */
+#ifdef HAVE_LINUX_NETWORK
+ /* if we're forcing the address, use IP_PKTINFO and IP_TRANSPARENT to lie about
+ * our source address and force it out the same iface we got it on. */
+ if (parm.current && parm.current->forcedaddress.s_addr != 0)
+ {
+ struct in_pktinfo *pkt;
+ int transparent = 1;
+
+ msg.msg_control = control_u.control;
+ msg.msg_controllen = sizeof(control_u);
+ cmptr = CMSG_FIRSTHDR(&msg);
+ pkt = (struct in_pktinfo *)CMSG_DATA(cmptr);
+ pkt->ipi_ifindex = rcvd_iface_index;
+ pkt->ipi_spec_dst = parm.current->forcedaddress;
+ msg.msg_controllen = cmptr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
+ cmptr->cmsg_level = IPPROTO_IP;
+ cmptr->cmsg_type = IP_PKTINFO;
+ #define IP_TRANSPARENT 19 /* XXX: This isn't in uclibc headers, hardcode for now */
+ setsockopt(fd, SOL_IP, IP_TRANSPARENT, &transparent, sizeof(transparent));
+ }
+#endif
+/* CRADLEPOINT */
+
#ifdef HAVE_DUMPFILE
dump_packet_udp(DUMP_DHCP, (void *)iov.iov_base, iov.iov_len, NULL,
(union mysockaddr *)&dest, fd);
@@ -558,7 +589,10 @@
struct dhcp_relay *relay;
struct iface_param *param = vparam;
struct shared_network *share;
-
+/* CRADLEPOINT */
+ char namebuff[IF_NAMESIZE];
+/* CRADLEPOINT */
+
(void)label;
for (share = daemon->shared_networks; share; share = share->next)
@@ -606,8 +640,40 @@
for (context = daemon->dhcp; context; context = context->next)
{
- if (context->netmask.s_addr != 0 &&
- is_same_net(local, context->start, context->netmask) &&
+/* CRADLEPOINT */
+ if (context->forcedinterface &&
+ indextoname(daemon->dhcpfd, if_index, namebuff) &&
+ strcmp(context->forcedinterface, namebuff) == 0)
+ {
+ /* link it onto the current chain if we've not seen it before */
+ if (if_index == param->ind && context->current == context)
+ {
+ if (context->forcedaddress.s_addr != 0)
+ {
+ context->router = context->forcedaddress;
+ context->local = context->forcedaddress;
+ }
+ else
+ {
+ context->router = local;
+ context->local = local;
+ }
+
+ context->current = param->current;
+ param->current = context;
+ }
+
+ if (!(context->flags & CONTEXT_BRDCAST))
+ {
+ if (is_same_net(broadcast, context->start, context->netmask))
+ context->broadcast = broadcast;
+ else
+ context->broadcast.s_addr = context->start.s_addr | ~context->netmask.s_addr;
+ }
+ }
+ else if (context->netmask.s_addr != 0 &&
+/* CRADLEPOINT */
+ is_same_net(local, context->start, context->netmask) &&
is_same_net(local, context->end, context->netmask))
{
/* link it onto the current chain if we've not seen it before */
diff --git a/src/dhcp6.c b/src/dhcp6.c
index c9d54dc..2d65313 100644
--- a/src/dhcp6.c
+++ b/src/dhcp6.c
@@ -348,6 +348,8 @@
struct iname *tmp;
int match = !daemon->if_addrs;
+ char ifr_namebuff[IF_NAMESIZE];
+ int force_ra = 0;
(void)scope; /* warning */
if (if_index != param->ind)
@@ -359,7 +361,7 @@
param->ula_addr = *local;
if (IN6_IS_ADDR_LOOPBACK(local) ||
- IN6_IS_ADDR_LINKLOCAL(local) ||
+ (IN6_IS_ADDR_LINKLOCAL(local) && !option_bool(OPT_RA)) ||
IN6_IS_ADDR_MULTICAST(local))
return 1;
@@ -382,8 +384,39 @@
prefix <= context->prefix &&
context->current == context)
{
- if (is_same_net6(local, &context->start6, context->prefix) &&
- is_same_net6(local, &context->end6, context->prefix))
+ memset(ifr_namebuff, 0, IF_NAMESIZE);
+ force_ra = context->forcedinterface6 &&
+ indextoname(daemon->dhcpfd, if_index, ifr_namebuff) &&
+ (strncmp(context->forcedinterface6, ifr_namebuff, IF_NAMESIZE) == 0) &&
+ option_bool(OPT_FORCE_RA);
+ if (force_ra)
+ {
+ struct dhcp_context *tmp, **up;
+
+ /* use interface values only for constructed contexts */
+ if (!(context->flags & CONTEXT_CONSTRUCTED))
+ preferred = valid = 0xffffffff;
+ else if (flags & IFACE_DEPRECATED)
+ preferred = 0;
+
+ if (context->flags & CONTEXT_DEPRECATE)
+ preferred = 0;
+
+ /* order chain, longest preferred time first */
+ for (up = ¶m->current, tmp = param->current; tmp; tmp = tmp->current)
+ if (tmp->preferred <= preferred)
+ break;
+ else
+ up = &tmp->current;
+
+ context->current = *up;
+ *up = context;
+ context->local6 = context->forcedaddress6;
+ context->preferred = preferred;
+ context->valid = valid;
+ }
+ else if (is_same_net6(local, &context->start6, context->prefix) &&
+ is_same_net6(local, &context->end6, context->prefix))
{
struct dhcp_context *tmp, **up;
diff --git a/src/dns-protocol.h b/src/dns-protocol.h
index 2777be9..f1e7602 100644
--- a/src/dns-protocol.h
+++ b/src/dns-protocol.h
@@ -83,6 +83,10 @@
#define EDNS0_OPTION_EDE 15 /* IANA - RFC 8914 */
#define EDNS0_OPTION_NOMDEVICEID 65073 /* Nominum temporary assignment */
#define EDNS0_OPTION_NOMCPEID 65074 /* Nominum temporary assignment */
+/* CRADLEPOINT */
+#define EDNS0_OPTION_OWNER 4 /* Owner Option (Used by OpenDNS) */
+#define EDNS0_OPTION_APPID 65001
+/* CRADLEPOINT */
#define EDNS0_OPTION_UMBRELLA 20292 /* Cisco Umbrella temporary assignment */
/* RFC-8914 extended errors, negative values are our definitions */
diff --git a/src/dnsmasq.c b/src/dnsmasq.c
index 30fb419..1fc8e41 100644
--- a/src/dnsmasq.c
+++ b/src/dnsmasq.c
@@ -30,7 +30,9 @@
static volatile int pipewrite;
static void set_dns_listeners(void);
+#ifdef HAVE_TFTP
static void set_tftp_listeners(void);
+#endif
static void check_dns_listeners(time_t now);
static void sig_handler(int sig);
static void async_event(int pipe, time_t now);
@@ -336,13 +338,10 @@
#endif
#ifdef HAVE_IPSET
- if (daemon->ipsets)
- {
ipset_init();
# ifdef HAVE_LINUX_NETWORK
need_cap_net_admin = 1;
# endif
- }
#endif
#ifdef HAVE_NFTSET
@@ -1506,7 +1505,10 @@
case EVENT_DUMP:
if (daemon->port != 0)
- dump_cache(now);
+/* CRADLEPOINT */
+ dump_cache_json(now);
+/* CRADLEPOINT */
+
break;
case EVENT_ALARM:
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index e455c3f..f6a03a3 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -65,6 +65,8 @@
#include "ip6addr.h"
#include "metrics.h"
+#include <stdbool.h>
+
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
@@ -282,7 +284,9 @@
#define OPT_NO_IDENT 70
#define OPT_CACHE_RR 71
#define OPT_LOCALHOST_SERVICE 72
-#define OPT_LAST 73
+#define OPT_EDNS_RESTRICT 73
+#define OPT_FORCE_RA 74
+#define OPT_LAST 75
#define OPTION_BITS (sizeof(unsigned int)*8)
#define OPTION_SIZE ( (OPT_LAST/OPTION_BITS)+((OPT_LAST%OPTION_BITS)!=0) )
@@ -667,6 +671,10 @@
char *name;
union mysockaddr addr;
int flags;
+ bool is_ra_adv_dis;
+ bool first_ra_delayed;
+ unsigned int final_ra_sent_count;
+ time_t last_ra_for_rtr_sol;
struct iname *next;
};
@@ -945,6 +953,13 @@
struct dhcp_opt *next;
};
+/* CRADLEPOINT */
+struct ssid_device_id_map {
+ unsigned char *map;
+ size_t map_size;
+};
+/* CRADLEPOINT */
+
#define DHOPT_ADDR 1
#define DHOPT_STRING 2
#define DHOPT_ENCAPSULATE 4
@@ -1032,6 +1047,8 @@
char *name;
char *mtu_name;
int interval, lifetime, prio, mtu;
+ unsigned int reachable_time, retrans_time, min_ra_adv_interval, max_ra_adv_interval, ra_sent_count;
+ unsigned char hop_limit;
struct ra_interface *next;
};
@@ -1044,11 +1061,17 @@
struct in6_addr start6, end6; /* range of available addresses */
struct in6_addr local6;
int prefix, if_index;
- unsigned int valid, preferred, saved_valid;
+ unsigned int valid, preferred, saved_valid, valid_lifetime;
time_t ra_time, ra_short_period_start, address_lost_time;
char *template_interface;
+ char *forcedinterface6;
+ struct in6_addr forcedaddress6;
#endif
int flags;
+ /* CRADLEPOINT */
+ char *forcedinterface;
+ struct in_addr forcedaddress;
+ /* CRADLEPOINT */
struct dhcp_netid netid, *filter;
struct dhcp_context *next, *current;
};
@@ -1083,6 +1106,8 @@
#define CONTEXT_V6 (1u<<17)
#define CONTEXT_RA_OFF_LINK (1u<<18)
#define CONTEXT_SETLEASE (1u<<19)
+#define CONTEXT_SETVALID (1u<<20)
+#define CONTEXT_RA_AUTO_OFF (1u<<21)
struct ping_result {
struct in_addr addr;
@@ -1172,7 +1197,7 @@
struct cond_domain *cond_domain, *synth_domains;
char *runfile;
char *lease_change_command;
- struct iname *if_names, *if_addrs, *if_except, *dhcp_except, *auth_peers, *tftp_interfaces;
+ struct iname *if_names, *if_addrs, *if_except, *dhcp_except, *auth_peers, *tftp_interfaces, *ra_adv_if_names;
struct bogus_addr *bogus_addr, *ignore_addr;
struct server *servers, *servers_tail, *local_domains, **serverarray;
struct rebind_domain *no_rebind;
@@ -1317,6 +1342,14 @@
char *addrbuff;
char *addrbuff2; /* only allocated when OPT_EXTRALOG */
+/* CRADLEPOINT */
+ /* EDNS options */
+ unsigned char *edns_options;
+ int edns_options_len;
+
+ /* ssid => network id map */
+ struct ssid_device_id_map ssid_device_id_map;
+/* CRADLEPOINT */
#ifdef HAVE_DUMPFILE
/* file for packet dumps. */
int dumpfd;
@@ -1356,6 +1389,9 @@
struct in_addr a_record_from_hosts(char *name, time_t now);
void cache_unhash_dhcp(void);
void dump_cache(time_t now);
+/*CRADLEPOINT*/
+void dump_cache_json(time_t now);
+/*CRADLEPOINT*/
#ifndef NO_ID
int cache_make_stat(struct txt_record *t);
#endif
@@ -1686,7 +1722,7 @@
/* ipset.c */
#ifdef HAVE_IPSET
void ipset_init(void);
-int add_to_ipset(const char *setname, const union all_addr *ipaddr, int flags, int remove);
+int add_to_ipset(const char *setname, const union all_addr *ipaddr, int flags, int remove, unsigned long attl);
#endif
/* nftset.c */
@@ -1817,6 +1853,22 @@
/* radv.c */
#ifdef HAVE_DHCP6
+// All RTR_ADV_INTERVALs in seconds
+#define DEFAULT_RTR_ADV_INTERVAL 600
+#define MAX_RTR_ADV_INTERVAL_UPPER_BOUND 1800
+#define MAX_RTR_ADV_INTERVAL_LOWER_BOUND 4
+#define MIN_RTR_ADV_INTERVAL 3
+// MAX_RA_DELAY_TIME in milliseconds
+#define MAX_RA_DELAY_TIME 500
+#define random_num_in_range(range) (rand() % (range))
+// MAX_INITIAL_RTR_ADVERT_INTERVAL in seconds
+#define MAX_INITIAL_RTR_ADVERT_INTERVAL 16
+// MAX_INITIAL_RTR_ADVERTISEMENTS in number of transmissions
+#define MAX_INITIAL_RTR_ADVERTISEMENTS 3
+// MAX_FINAL_RTR_ADVERTISEMENTS in number of transmissions
+#define MAX_FINAL_RTR_ADVERTISEMENTS 3
+// MIN_DELAY_BETWEEN_RAS in seconds
+#define MIN_DELAY_BETWEEN_RAS 3
void ra_init(time_t now);
void icmp6_packet(time_t now);
time_t periodic_ra(time_t now);
@@ -1869,6 +1921,9 @@
size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *limit,
union mysockaddr *source, time_t now, int *cacheable);
int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer);
+int process_appid_catid(struct dns_header *dns_buffer, size_t dns_buffer_length);
+#define MAX_APPID_PER_HOST 1
+#define MAX_CATID_PER_HOST 8
/* arp.c */
int find_mac(union mysockaddr *addr, unsigned char *mac, int lazy, time_t now);
diff --git a/src/edns0.c b/src/edns0.c
index 598478f..ce10a8d 100644
--- a/src/edns0.c
+++ b/src/edns0.c
@@ -15,6 +15,16 @@
*/
#include "dnsmasq.h"
+/* CRADLEPOINT */
+#include "opendns.h"
+/* CRADLEPOINT */
+
+unsigned char *process_ar(unsigned char *ansp, int arcount, struct dns_header *header, size_t plen);
+static void insert_appid_catid_to_ipset(unsigned char *ansp, size_t plen);
+static void add_appid_catid_to_ipset(struct ipsets **ipset, char *domain, char *set);
+static struct ipsets *search_domain_in_ipset(struct ipsets *ipset, char *domain);
+static struct ipsets *add_domain_to_ipset(struct ipsets *ipset, char *domain);
+static void add_set_to_ipset(struct ipsets *ipset, char *set);
unsigned char *find_pseudoheader(struct dns_header *header, size_t plen, size_t *len, unsigned char **p, int *is_sign, int *is_last)
{
@@ -524,6 +534,12 @@
size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *limit,
union mysockaddr *source, time_t now, int *cacheable)
{
+/* CRADLEPOINT */
+ unsigned char *edns_options = daemon->edns_options;
+ size_t edns_options_len = daemon->edns_options_len;
+ int i, len, code;
+/* CRADLEPOINT */
+
*cacheable = 1;
plen = add_mac(header, plen, limit, source, now, cacheable);
@@ -538,5 +554,218 @@
plen = add_source_addr(header, plen, limit, source, cacheable);
+
+/* CRADLEPOINT */
+ if (daemon->ssid_device_id_map.map) {
+ opendns_pop_tag_from_query((unsigned char *) header, &plen,
+ &edns_options, &edns_options_len);
+ }
+
+ if (edns_options) {
+ for (i = edns_options_len; i > 0; i -= len + 4)
+ {
+ GETSHORT(code, edns_options);
+ GETSHORT(len, edns_options);
+ plen = add_pseudoheader(header, plen, limit, PACKETSZ, code,
+ (unsigned char *)edns_options, len, 0, 1);
+ edns_options += len;
+
+ /* my_syslog(LOG_DEBUG, _("EDNS options added")); */
+ }
+ }
+/* CRADLEPOINT */
+
return plen;
}
+
+int process_appid_catid(struct dns_header *dns_buffer, size_t dns_buffer_length)
+{
+ struct dns_header *header = dns_buffer;
+ unsigned char *ansp;
+
+ /* Skip DNS header and all questions records
+ if packet is malformed, return as-is. */
+ if (!(ansp = skip_questions(dns_buffer, dns_buffer_length)))
+ return dns_buffer_length;
+
+ /* Skip all answers records and auth records
+ if packet is malformed, return as-is. */
+ if (!(ansp = skip_section(ansp, ntohs(dns_buffer->ancount) + ntohs(dns_buffer->nscount),
+ dns_buffer, dns_buffer_length)))
+ return dns_buffer_length;
+
+ /* Now processing Additional records */
+ if (!(ansp = process_ar(ansp, ntohs(header->arcount), dns_buffer, dns_buffer_length)))
+ return dns_buffer_length;
+ return dns_buffer_length;
+}
+
+unsigned char *process_ar(unsigned char *ansp, int arcount, struct dns_header *header, size_t plen)
+{
+ unsigned short rdlen, type, index, opcode, opdlen, oplen;
+
+ for (index = 0; index < arcount; index++)
+ {
+ if (!(ansp = skip_name(ansp, header, plen, 10)))
+ return NULL;
+ GETSHORT(type, ansp);
+ ansp += 6; /* To skip type, UDP payload size, extended RCODE and flags. Ref: RFC6891 */
+ GETSHORT(rdlen, ansp);
+ if (type == T_OPT)
+ {
+ while (rdlen > 0)
+ {
+ GETSHORT(opcode, ansp);
+ GETSHORT(opdlen, ansp);
+ /* 2 byte option code + 2 byte option length + option data */
+ oplen = 2 + 2 + opdlen;
+ if (opcode == EDNS0_OPTION_APPID)
+ {
+ insert_appid_catid_to_ipset(ansp, opdlen);
+ memmove(ansp, ansp + oplen, plen - oplen);
+ header->arcount = htons(arcount-1);
+ }
+ else
+ {
+ ansp += oplen;
+ }
+ rdlen -= oplen;
+ }
+ }
+ else
+ {
+ ansp += rdlen - 8;
+ }
+ }
+ return ansp;
+}
+
+static void insert_appid_catid_to_ipset(unsigned char *ansp, size_t plen)
+{
+ unsigned char *end = ansp + plen;
+ char *set_ipv4, *set_ipv6;
+ ansp++;
+ while (ansp < end)
+ {
+ int entry_len = strnlen((char *)ansp, plen) + 1;
+ if (ansp + entry_len > end)
+ {
+ // Malformed data
+ return;
+ }
+
+ if ((set_ipv4 = whine_malloc(5 + entry_len)))
+ {
+ memcpy(set_ipv4, "ip4-", 4);
+ strncpy(set_ipv4 + 4, (char *)ansp, entry_len);
+ add_appid_catid_to_ipset(&daemon->ipsets, daemon->namebuff, set_ipv4);
+ free(set_ipv4);
+ }
+
+ if ((set_ipv6 = whine_malloc(5 + entry_len)))
+ {
+ memcpy(set_ipv6, "ip6-", 4);
+ strncpy(set_ipv6 + 4, (char *)ansp, entry_len);
+ add_appid_catid_to_ipset(&daemon->ipsets, daemon->namebuff, set_ipv6);
+ free(set_ipv6);
+ }
+
+ ansp += entry_len;
+ }
+}
+
+static void add_appid_catid_to_ipset(struct ipsets **ipset, char *domain, char *set)
+{
+ struct ipsets *ipsetptr;
+ if (!(*ipset))
+ {
+ if ((*ipset = whine_malloc(sizeof(struct ipsets))))
+ {
+ memset(*ipset, 0, sizeof(struct ipsets));
+ ipsetptr = *ipset;
+ if ((ipsetptr->domain = whine_malloc(strlen(domain) + 1)))
+ {
+ strcpy(ipsetptr->domain, domain);
+ }
+ }
+ }
+
+ ipsetptr = search_domain_in_ipset(*ipset, domain);
+
+ if (!(ipsetptr))
+ ipsetptr = add_domain_to_ipset(*ipset, domain);
+ if (ipsetptr)
+ add_set_to_ipset(ipsetptr, set);
+}
+
+static struct ipsets *search_domain_in_ipset(struct ipsets *ipset, char *domain)
+{
+ while (ipset)
+ {
+ if (strcmp(ipset->domain, domain) == 0)
+ {
+ return ipset;
+ }
+ ipset = ipset->next;
+ }
+ return NULL;
+}
+
+static struct ipsets *add_domain_to_ipset(struct ipsets *ipset, char *domain)
+{
+ while (ipset)
+ {
+ if (ipset->next == NULL)
+ break;
+ ipset = ipset->next;
+ }
+
+ if ((ipset->next = whine_malloc(sizeof(struct ipsets))))
+ {
+ memset(ipset->next, 0, sizeof(struct ipsets));
+ if ((ipset->next->domain = whine_malloc(strlen(domain) + 1)))
+ {
+ strcpy(ipset->next->domain, domain);
+ return ipset->next;
+ }
+ }
+ return NULL;
+}
+
+static void add_set_to_ipset(struct ipsets *ipset, char *set)
+{
+ int count = 0;
+ if (!(ipset->sets))
+ {
+ if ((ipset->sets = realloc(ipset->sets, 2 * sizeof(char *))) == NULL)
+ {
+ my_syslog(LOG_ERR, _("failed to allocate %d bytes"), (int)(2 * sizeof(char *)));
+ return;
+ }
+ if ((ipset->sets[0] = whine_malloc(strlen(set) + 1)))
+ {
+ strcpy(ipset->sets[0], set);
+ ipset->sets[1] = NULL;
+ return;
+ }
+ }
+
+ while (ipset->sets[count])
+ {
+ if (strcmp(ipset->sets[count], set) == 0)
+ return;
+ else
+ count++;
+ }
+ if ((ipset->sets = realloc(ipset->sets, sizeof(char *) * (count + 2))) == NULL)
+ {
+ my_syslog(LOG_ERR, _("failed to allocate %d bytes"), (int)((count + 2) * sizeof(char *)));
+ return;
+ }
+ if ((ipset->sets[count] = whine_malloc(strlen(set) + 1)))
+ {
+ strcpy(ipset->sets[count], set);
+ ipset->sets[count + 1] = NULL;
+ }
+ return;
+}
diff --git a/src/forward.c b/src/forward.c
index 32f37e4..44501bf 100644
--- a/src/forward.c
+++ b/src/forward.c
@@ -521,9 +521,11 @@
}
#endif
- if (retry_send(sendto(fd, (char *)header, plen, 0,
- &srv->addr.sa,
- sa_len(&srv->addr))))
+ if (udpaddr &&
+ (srv->addr.in.sin_addr.s_addr != udpaddr->in.sin_addr.s_addr) &&
+ (retry_send(sendto(fd, (char *)header, plen, 0,
+ &srv->addr.sa,
+ sa_len(&srv->addr)))))
continue;
if (errno == 0)
@@ -666,7 +668,8 @@
struct ipsets *ipset_pos, *ret = NULL;
unsigned int namelen = strlen(domain);
unsigned int matchlen = 0;
- for (ipset_pos = setlist; ipset_pos; ipset_pos = ipset_pos->next)
+ if (daemon->ipsets)
+ for (ipset_pos = setlist; ipset_pos; ipset_pos = ipset_pos->next)
{
unsigned int domainlen = strlen(ipset_pos->domain);
const char *matchstart = domain + namelen - domainlen;
@@ -696,8 +699,12 @@
(void)do_bit;
#ifdef HAVE_IPSET
- if (daemon->ipsets && extract_request(header, n, daemon->namebuff, NULL))
+ if (extract_request(header, n, daemon->namebuff, NULL))
+ {
+ if (header && (ntohs(header->arcount)))
+ n = process_appid_catid(header, n);
ipsets = domain_find_sets(daemon->ipsets, daemon->namebuff);
+ }
#endif
#ifdef HAVE_NFTSET
diff --git a/src/ipset.c b/src/ipset.c
index 0c014cb..76972ea 100644
--- a/src/ipset.c
+++ b/src/ipset.c
@@ -24,63 +24,27 @@
#include <sys/socket.h>
#include <arpa/inet.h>
#include <linux/netlink.h>
-
-/* We want to be able to compile against old header files
- Kernel version is handled at run-time. */
-
-#define NFNL_SUBSYS_IPSET 6
-
-#define IPSET_ATTR_DATA 7
-#define IPSET_ATTR_IP 1
-#define IPSET_ATTR_IPADDR_IPV4 1
-#define IPSET_ATTR_IPADDR_IPV6 2
-#define IPSET_ATTR_PROTOCOL 1
-#define IPSET_ATTR_SETNAME 2
-#define IPSET_CMD_ADD 9
-#define IPSET_CMD_DEL 10
-#define IPSET_MAXNAMELEN 32
-#define IPSET_PROTOCOL 6
-
-#ifndef NFNETLINK_V0
-#define NFNETLINK_V0 0
-#endif
-
-#ifndef NLA_F_NESTED
-#define NLA_F_NESTED (1 << 15)
-#endif
-
-#ifndef NLA_F_NET_BYTEORDER
-#define NLA_F_NET_BYTEORDER (1 << 14)
-#endif
-
-struct my_nlattr {
- __u16 nla_len;
- __u16 nla_type;
-};
-
-struct my_nfgenmsg {
- __u8 nfgen_family; /* AF_xxx */
- __u8 version; /* nfnetlink version */
- __be16 res_id; /* resource id */
-};
-
+#include <linux/netfilter/ipset/ip_set.h>
+#include <linux/netfilter/nfnetlink.h>
/* data structure size in here is fixed */
#define BUFF_SZ 256
-#define NL_ALIGN(len) (((len)+3) & ~(3))
static const struct sockaddr_nl snl = { .nl_family = AF_NETLINK };
static int ipset_sock, old_kernel;
static char *buffer;
-static inline void add_attr(struct nlmsghdr *nlh, uint16_t type, size_t len, const void *data)
+static inline struct nlattr *add_attr(struct nlmsghdr *nlh, uint16_t type, size_t len, const void *data)
{
- struct my_nlattr *attr = (void *)nlh + NL_ALIGN(nlh->nlmsg_len);
- uint16_t payload_len = NL_ALIGN(sizeof(struct my_nlattr)) + len;
+ struct nlattr *attr = (void *)nlh + NLA_ALIGN(nlh->nlmsg_len);
+ uint16_t payload_len = NLA_ALIGN(sizeof(struct nlattr)) + len;
attr->nla_type = type;
attr->nla_len = payload_len;
- memcpy((void *)attr + NL_ALIGN(sizeof(struct my_nlattr)), data, len);
- nlh->nlmsg_len += NL_ALIGN(payload_len);
+ if (len > 0) {
+ memcpy((void *)attr + NLA_ALIGN(sizeof(struct nlattr)), data, len);
+ }
+ nlh->nlmsg_len += NLA_ALIGN(payload_len);
+ return attr;
}
void ipset_init(void)
@@ -99,13 +63,39 @@
die (_("failed to create IPset control socket: %s"), NULL, EC_MISC);
}
-static int new_add_to_ipset(const char *setname, const union all_addr *ipaddr, int af, int remove)
+/*
+ * <------- NLA_HDRLEN ------> <-- NLA_ALIGN(payload)-->
+ * +---------------------+- - -+- - - - - - - - - -+- - -+
+ * | Header | Pad | Payload | Pad |
+ * | (struct nlattr) | ing | | ing |
+ * +---------------------+- - -+- - - - - - - - - -+- - -+
+ * <-------------- nlattr->nla_len -------------->
+ *
+ * For nested attributes, nla_len of IPSET_ATTR_DATA nlattr
+ * need to be updated appropriately after adding each attribute
+ *
+ * nla_type (16 bits)
+ * +---+---+-------------------------------+
+ * | N | O | Attribute Type |
+ * +---+---+-------------------------------+
+ * N := Carries nested attributes
+ * O := Payload stored in network byte order
+ *
+ * Note: The N and O flag are mutually exclusive.
+ */
+
+static int new_add_to_ipset(const char *setname, const union all_addr *ipaddr,
+ int af, int remove, unsigned long attl)
{
struct nlmsghdr *nlh;
- struct my_nfgenmsg *nfg;
- struct my_nlattr *nested[2];
+ struct nfgenmsg *nfg;
uint8_t proto;
int addrsz = (af == AF_INET6) ? IN6ADDRSZ : INADDRSZ;
+ struct nlattr *ipset_attr_data_ptr = NULL;
+ struct nlattr *ipset_attr_ip_ptr = NULL;
+ struct nlattr *ipset_attr_ip_addr_ptr = NULL;
+ struct nlattr *ipset_attr_timeout_ptr = NULL;
+ unsigned int attl_val = htonl(attl);
if (strlen(setname) >= IPSET_MAXNAMELEN)
{
@@ -116,12 +106,12 @@
memset(buffer, 0, BUFF_SZ);
nlh = (struct nlmsghdr *)buffer;
- nlh->nlmsg_len = NL_ALIGN(sizeof(struct nlmsghdr));
+ nlh->nlmsg_len = NLA_ALIGN(sizeof(struct nlmsghdr));
nlh->nlmsg_type = (remove ? IPSET_CMD_DEL : IPSET_CMD_ADD) | (NFNL_SUBSYS_IPSET << 8);
nlh->nlmsg_flags = NLM_F_REQUEST;
- nfg = (struct my_nfgenmsg *)(buffer + nlh->nlmsg_len);
- nlh->nlmsg_len += NL_ALIGN(sizeof(struct my_nfgenmsg));
+ nfg = (struct nfgenmsg *)(buffer + nlh->nlmsg_len);
+ nlh->nlmsg_len += NLA_ALIGN(sizeof(struct nfgenmsg));
nfg->nfgen_family = af;
nfg->version = NFNETLINK_V0;
nfg->res_id = htons(0);
@@ -129,17 +119,17 @@
proto = IPSET_PROTOCOL;
add_attr(nlh, IPSET_ATTR_PROTOCOL, sizeof(proto), &proto);
add_attr(nlh, IPSET_ATTR_SETNAME, strlen(setname) + 1, setname);
- nested[0] = (struct my_nlattr *)(buffer + NL_ALIGN(nlh->nlmsg_len));
- nlh->nlmsg_len += NL_ALIGN(sizeof(struct my_nlattr));
- nested[0]->nla_type = NLA_F_NESTED | IPSET_ATTR_DATA;
- nested[1] = (struct my_nlattr *)(buffer + NL_ALIGN(nlh->nlmsg_len));
- nlh->nlmsg_len += NL_ALIGN(sizeof(struct my_nlattr));
- nested[1]->nla_type = NLA_F_NESTED | IPSET_ATTR_IP;
- add_attr(nlh,
+ ipset_attr_data_ptr = add_attr(nlh, NLA_F_NESTED | IPSET_ATTR_DATA, 0, NULL);
+ ipset_attr_ip_ptr = add_attr(nlh, NLA_F_NESTED | IPSET_ATTR_IP, 0, NULL);
+ ipset_attr_data_ptr->nla_len += ipset_attr_ip_ptr->nla_len;
+ ipset_attr_ip_addr_ptr = add_attr(nlh,
(af == AF_INET ? IPSET_ATTR_IPADDR_IPV4 : IPSET_ATTR_IPADDR_IPV6) | NLA_F_NET_BYTEORDER,
addrsz, ipaddr);
- nested[1]->nla_len = (void *)buffer + NL_ALIGN(nlh->nlmsg_len) - (void *)nested[1];
- nested[0]->nla_len = (void *)buffer + NL_ALIGN(nlh->nlmsg_len) - (void *)nested[0];
+ ipset_attr_ip_ptr->nla_len += ipset_attr_ip_addr_ptr->nla_len;
+ ipset_attr_data_ptr->nla_len += ipset_attr_ip_addr_ptr->nla_len;
+ ipset_attr_timeout_ptr = add_attr(nlh, IPSET_ATTR_TIMEOUT | NLA_F_NET_BYTEORDER,
+ sizeof(attl_val), &attl_val);
+ ipset_attr_data_ptr->nla_len += ipset_attr_timeout_ptr->nla_len;
while (retry_send(sendto(ipset_sock, buffer, nlh->nlmsg_len, 0,
(struct sockaddr *)&snl, sizeof(snl))));
@@ -189,7 +179,8 @@
-int add_to_ipset(const char *setname, const union all_addr *ipaddr, int flags, int remove)
+int add_to_ipset(const char *setname, const union all_addr *ipaddr, int flags,
+ int remove, unsigned long attl)
{
int ret = 0, af = AF_INET;
@@ -205,7 +196,7 @@
}
if (ret != -1)
- ret = old_kernel ? old_add_to_ipset(setname, ipaddr, remove) : new_add_to_ipset(setname, ipaddr, af, remove);
+ ret = old_kernel ? old_add_to_ipset(setname, ipaddr, remove) : new_add_to_ipset(setname, ipaddr, af, remove, attl);
if (ret == -1)
my_syslog(LOG_ERR, _("failed to update ipset %s: %s"), setname, strerror(errno));
diff --git a/src/opendns.c b/src/opendns.c
new file mode 100644
index 0000000..d0b4348
--- /dev/null
+++ b/src/opendns.c
@@ -0,0 +1,231 @@
+
+#include "dnsmasq.h"
+#include "opendns.h"
+
+const unsigned char *
+opendns_ssid_to_device_id(size_t * const device_id_len_p,
+ const unsigned char * const ssid,
+ const size_t ssid_len)
+{
+ unsigned char *map = daemon->ssid_device_id_map.map;
+ unsigned char *map_tmp;
+ unsigned char *ssid_tmp;
+ unsigned char *device_id_tmp;
+ size_t ssid_tmp_len;
+ size_t device_id_len;
+
+ if (map == NULL || ssid == NULL) {
+ return NULL;
+ }
+ map_tmp = map;
+ while ((ssid_tmp_len = (size_t) *map_tmp) > 0U) {
+ ssid_tmp = ++map_tmp;
+ map_tmp += ssid_tmp_len;
+ device_id_len = (size_t) *map_tmp;
+ device_id_tmp = ++map_tmp;
+ if (ssid_tmp_len == ssid_len &&
+ memcmp(ssid_tmp, ssid, ssid_len) == 0) {
+ *device_id_len_p = device_id_len;
+ return device_id_tmp;
+ }
+ map_tmp += device_id_len;
+ }
+ return NULL;
+}
+
+static int
+opendns_parse_hex_char(unsigned char * const device_id,
+ size_t * const device_id_pos_p, int * const state_p,
+ unsigned char * const val_p, size_t device_id_len_max,
+ const int c)
+{
+ unsigned char c_val;
+
+ switch (*state_p) {
+ case 0:
+ case 1:
+ if (isspace(c) || (c == ':' && *state_p == 0)) {
+ break;
+ }
+ if (c == '#') {
+ *state_p = 2;
+ break;
+ }
+ if (!isxdigit(c)) {
+ return -1;
+ }
+ c_val = (c >= '0' && c <= '9') ? c - '0' : c - 'a' + 10;
+ if (*state_p == 0) {
+ *val_p = c_val * 16U;
+ *state_p = 1;
+ } else {
+ *val_p |= c_val;
+ device_id[(*device_id_pos_p)++] = *val_p;
+ if (*device_id_pos_p >= device_id_len_max) {
+ return 0;
+ }
+ *state_p = 0;
+ }
+ break;
+ case 2:
+ if (c == '\n') {
+ *state_p = 0;
+ }
+ }
+ return 1;
+}
+
+size_t
+opendns_parse_hex(const char * const device_id_hex,
+ unsigned char * const device_id,
+ const size_t device_id_len_max)
+{
+ const char *p = device_id_hex;
+ size_t device_id_pos = (size_t) 0U;
+ int c;
+ int ret;
+ int state = 0;
+ unsigned char val = 0U;
+
+ if (device_id_hex == NULL) {
+ return (size_t) 0U;
+ }
+ while ((c = tolower((int) (unsigned char) *p)) != 0) {
+ ret = opendns_parse_hex_char(device_id, &device_id_pos,
+ &state, &val, device_id_len_max, c);
+ if (ret < 0) {
+ return (size_t) 0U;
+ }
+ if (ret == 0) {
+ break;
+ }
+ p++;
+ }
+ return device_id_pos;
+}
+
+char *
+opendns_parse_device_id_opt(char * const arg)
+{
+ struct ssid_device_id_map * const ssid_device_id_map =
+ &daemon->ssid_device_id_map;
+ unsigned char *device_id;
+ unsigned char *tmp_map;
+ char *device_id_hex;
+ char *sep;
+ char *ssid;
+ size_t device_id_hex_len;
+ size_t device_id_len;
+ size_t device_id_len_max;
+ size_t pos;
+ size_t ssid_len;
+ size_t tmp_map_size;
+
+ if ((sep = strrchr(arg, ',')) == NULL ||
+ *(device_id_hex = sep + 1) == 0) {
+ return _("missing device id");
+ }
+ *sep = 0;
+ if (*(ssid = arg) == 0) {
+ return _("missing SSID");
+ }
+ ssid_len = (size_t) (sep - ssid);
+ if (ssid_len > 255U) {
+ return _("SSID too long");
+ }
+ device_id_hex_len = strlen(device_id_hex);
+ device_id_len_max = (device_id_hex_len + 1U) / 2U;
+ device_id = safe_malloc(device_id_len_max);
+ if (device_id_len_max > 255U) {
+ free(device_id);
+ return _("device id too long");
+ }
+ device_id_len = opendns_parse_hex(device_id_hex, device_id,
+ device_id_len_max);
+ if (device_id_len <= 0U) {
+ free(device_id);
+ return _("unable to parse a hex device id");
+ }
+ if (device_id_len > device_id_len_max) {
+ free(device_id);
+ return _("parsed device id too long");
+ }
+ tmp_map_size = ssid_device_id_map->map_size + 1U +
+ ssid_len + 1U + device_id_len;
+ if (ssid_device_id_map->map == NULL) {
+ tmp_map_size++;
+ }
+ if ((tmp_map = realloc(ssid_device_id_map->map,
+ tmp_map_size)) == NULL) {
+ die(_("could not get memory"), NULL, EC_NOMEM);
+ }
+ if (ssid_device_id_map->map_size <= 0U) {
+ pos = 0U;
+ } else {
+ pos = ssid_device_id_map->map_size - 1U;
+ }
+ tmp_map[pos++] = (unsigned char) ssid_len;
+ memcpy(&tmp_map[pos], ssid, ssid_len);
+ pos += ssid_len;
+ tmp_map[pos++] = (unsigned char) device_id_len;
+ memcpy(&tmp_map[pos], device_id, device_id_len);
+ pos += device_id_len;
+ free(device_id);
+ tmp_map[pos] = 0U;
+ ssid_device_id_map->map = tmp_map;
+ ssid_device_id_map->map_size = tmp_map_size;
+
+ return NULL;
+}
+
+int
+opendns_pop_tag_from_query(unsigned char * const packet,
+ size_t * const packet_size_p,
+ unsigned char * * const edns_options,
+ size_t *edns_options_len_p)
+{
+ static unsigned char edns_options_tpl[2U + 2U + 7U + 255U] = {
+ 0U, 4U, 0U, 0U, 'O', 'p', 'e', 'n', 'D', 'N', 'S'
+ };
+ const unsigned char *device_id;
+ const unsigned char *tag;
+ unsigned char *tmp;
+ size_t edns_options_len;
+ size_t device_id_len;
+ size_t packet_size = *packet_size_p;
+ size_t tag_len;
+
+ if (packet_size <= 0U) {
+ *edns_options = NULL;
+ *edns_options_len_p = 0U;
+ return -1;
+ }
+ tag_len = (size_t) packet[packet_size - 1U];
+ if (tag_len >= packet_size) {
+ *edns_options = NULL;
+ *edns_options_len_p = 0U;
+ return -1;
+ }
+ tag = &packet[packet_size - tag_len - 1U];
+ if ((device_id = opendns_ssid_to_device_id(&device_id_len,
+ tag, tag_len)) == NULL) {
+ return -1;
+ }
+ edns_options_len = 2U + 2U + sizeof "OpenDNS" - 1U + device_id_len;
+ if (edns_options_len > sizeof edns_options_tpl) {
+ return -1;
+ }
+ memcpy(edns_options_tpl + 2U + 2U + sizeof "OpenDNS" - 1U,
+ device_id, device_id_len);
+ if (packet_size <= 1U + device_id_len) {
+ return -1;
+ }
+ tmp = edns_options_tpl + 2U;
+ PUTSHORT(sizeof "OpenDNS" - 1U + device_id_len, tmp);
+ *edns_options = edns_options_tpl;
+ *edns_options_len_p = edns_options_len;
+ packet_size -= 1U + tag_len;
+ *packet_size_p = packet_size;
+
+ return 0;
+}
diff --git a/src/opendns.h b/src/opendns.h
new file mode 100644
index 0000000..cfcae29
--- /dev/null
+++ b/src/opendns.h
@@ -0,0 +1,15 @@
+#ifndef __OPENDNS_H__
+#define __OPENDNS_H__ 1
+
+char *opendns_parse_device_id_opt(char * const arg);
+
+const unsigned char *
+opendns_ssid_to_device_id(size_t * const device_id_len_p,
+ const unsigned char * const ssid,
+ const size_t ssid_len);
+
+int opendns_pop_tag_from_query(unsigned char * const packet,
+ size_t * const packet_size_p,
+ unsigned char * * const edns_options,
+ size_t *edns_options_len_p);
+#endif
diff --git a/src/option.c b/src/option.c
index f4ff7c0..27353b9 100644
--- a/src/option.c
+++ b/src/option.c
@@ -18,6 +18,10 @@
#define SYSLOG_NAMES
#include "dnsmasq.h"
#include <setjmp.h>
+/* CRADLEPOINT */
+#include "unescape_c_string.h"
+#include "opendns.h"
+/* CRADLEPOINT */
static volatile int mem_recover = 0;
static jmp_buf mem_jmp;
@@ -192,6 +196,13 @@
#define LOPT_NO_DHCP4 383
#define LOPT_MAX_PROCS 384
#define LOPT_DNSSEC_LIMITS 385
+/* CRADLEPOINT */
+#define LOPT_EDNS_OPTION 386
+#define LOPT_SSID_MAP 387
+#define LOPT_EDNS_RESTRICT 388
+/* CRADLEPOINT */
+#define LOPT_RA_ADV_DISABLE 389
+#define LOPT_FORCE_RA 390
#ifdef HAVE_GETOPT_LONG
static const struct option opts[] =
@@ -388,6 +399,13 @@
{ "use-stale-cache", 2, 0 , LOPT_STALE_CACHE },
{ "no-ident", 0, 0, LOPT_NO_IDENT },
{ "max-tcp-connections", 1, 0, LOPT_MAX_PROCS },
+/* CRADLEPOINT */
+ { "edns-option", 1, 0, LOPT_EDNS_OPTION },
+ { "ssid-map", 1, 0, LOPT_SSID_MAP },
+ { "edns-restrict", 0, 0, LOPT_EDNS_RESTRICT },
+ { "ra-adv-disable", 1, 0, LOPT_RA_ADV_DISABLE },
+ { "force-ra", 0, 0, LOPT_FORCE_RA},
+/* CRADLEPOINT */
{ NULL, 0, 0, 0 }
};
@@ -547,6 +565,7 @@
{ LOPT_CONNTRACK, OPT_CONNTRACK, NULL, gettext_noop("Copy connection-track mark from queries to upstream connections."), NULL },
{ LOPT_FQDN, OPT_FQDN_UPDATE, NULL, gettext_noop("Allow DHCP clients to do their own DDNS updates."), NULL },
{ LOPT_RA, OPT_RA, NULL, gettext_noop("Send router-advertisements for interfaces doing DHCPv6"), NULL },
+ { LOPT_FORCE_RA, OPT_FORCE_RA, NULL, gettext_noop("Force Router Advertisements on the provided forced interface"), NULL },
{ LOPT_DUID, ARG_ONE, "<enterprise>,<duid>", gettext_noop("Specify DUID_EN-type DHCPv6 server DUID"), NULL },
{ LOPT_HOST_REC, ARG_DUP, "<name>,<address>[,<ttl>]", gettext_noop("Specify host (A/AAAA and PTR) records"), NULL },
{ LOPT_DYNHOST, ARG_DUP, "<name>,[<IPv4>][,<IPv6>],<interface-name>", gettext_noop("Specify host record in interface subnet"), NULL },
@@ -571,7 +590,7 @@
{ LOPT_DNSSEC_TIME, OPT_DNSSEC_TIME, NULL, gettext_noop("Don't check DNSSEC signature timestamps until first cache-reload"), NULL },
{ LOPT_DNSSEC_STAMP, ARG_ONE, "<path>", gettext_noop("Timestamp file to verify system clock for DNSSEC"), NULL },
{ LOPT_DNSSEC_LIMITS, ARG_ONE, "<limit>,..", gettext_noop("Set resource limits for DNSSEC validation"), NULL },
- { LOPT_RA_PARAM, ARG_DUP, "<iface>,[mtu:<value>|<interface>|off,][<prio>,]<intval>[,<lifetime>]", gettext_noop("Set MTU, priority, resend-interval and router-lifetime"), NULL },
+ { LOPT_RA_PARAM, ARG_DUP, "<iface>,[mtu:<value>|<interface>|off,][<prio>,]<intval>[,<lifetime>][,<reachable_time>][,<hop_limit>][,<retrans_time>][,<min_ra_adv_interval>][,<max_ra_adv_interval>]", gettext_noop("Set MTU, priority, resend-interval, router-lifetime, reachable_time, hop_limit, retrans_time, min_ra_adv_interval and max_ra_adv_interval"), NULL },
{ LOPT_QUIET_DHCP, OPT_QUIET_DHCP, NULL, gettext_noop("Do not log routine DHCP."), NULL },
{ LOPT_QUIET_DHCP6, OPT_QUIET_DHCP6, NULL, gettext_noop("Do not log routine DHCPv6."), NULL },
{ LOPT_QUIET_RA, OPT_QUIET_RA, NULL, gettext_noop("Do not log RA."), NULL },
@@ -591,6 +610,7 @@
{ LOPT_NO_IDENT, OPT_NO_IDENT, NULL, gettext_noop("Do not add CHAOS TXT records."), NULL },
{ LOPT_CACHE_RR, ARG_DUP, "<RR-type>", gettext_noop("Cache this DNS resource record type."), NULL },
{ LOPT_MAX_PROCS, ARG_ONE, "<integer>", gettext_noop("Maximum number of concurrent tcp connections."), NULL },
+ { LOPT_RA_ADV_DISABLE, ARG_DUP, "<interface>", gettext_noop("Specify interface(s) to disable RA. This parameter should be subset of interfaces and should be provided after interfaces option"), NULL },
{ 0, 0, NULL, NULL, NULL }
};
@@ -1294,6 +1314,10 @@
"interface=" to disable all interfaces except loop. */
new->name = opt_string_alloc(ifname);
new->flags = 0;
+ new->is_ra_adv_dis = false;
+ new->first_ra_delayed = false;
+ new->final_ra_sent_count = 0;
+ time(&new->last_ra_for_rtr_sol);
}
#ifdef HAVE_DHCP
@@ -2854,6 +2878,21 @@
} while (arg);
break;
+ case LOPT_RA_ADV_DISABLE: ; /* --ra-adv-disable */
+ struct iname *tmp;
+ do {
+ comma = split(arg);
+ for (tmp = daemon->if_names; tmp; tmp = tmp->next)
+ {
+ if (strcmp(tmp->name, arg) == 0) {
+ tmp->is_ra_adv_dis = true;
+ tmp->final_ra_sent_count = 0;
+ }
+ }
+ arg = comma;
+ } while (arg);
+ break;
+
case LOPT_TFTP: /* --enable-tftp */
set_option_bool(OPT_TFTP);
if (!arg)
@@ -3682,7 +3721,7 @@
case 'F': /* --dhcp-range */
{
int k, leasepos = 2;
- char *cp, *a[8] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
+ char *cp, *a[9] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
struct dhcp_context *new = opt_malloc(sizeof(struct dhcp_context));
memset (new, 0, sizeof(*new));
@@ -3697,7 +3736,17 @@
if (*cp != ',' && (comma = split(arg)))
{
- if (is_tag_prefix(arg))
+/* CRADLEPOINT */
+ if (strstr(arg, "forcedinterface:") == arg)
+ new->forcedinterface = opt_string_alloc(arg+16);
+ else if (strstr(arg, "forcedaddress:") == arg)
+ new->forcedaddress.s_addr = inet_addr(arg+14);
+ else if (strstr(arg, "forcedinterface6:") == arg)
+ new->forcedinterface6 = opt_string_alloc(arg+17);
+ else if (strstr(arg, "forcedaddress6:") == arg)
+ inet_pton(AF_INET6, arg+15, &(new->forcedaddress6));
+ else if (is_tag_prefix(arg))
+/* CRADLEPOINT */
{
/* ignore empty tag */
if (arg[4])
@@ -3722,7 +3771,7 @@
}
}
- for (k = 1; k < 8; k++)
+ for (k = 1; k < 9; k++)
if (!(a[k] = split(a[k-1])))
break;
@@ -3784,6 +3833,7 @@
new->prefix = 64; /* default */
new->end6 = new->start6;
new->lease_time = DEFLEASE6;
+ new->valid_lifetime = DEFLEASE6;
new->next = daemon->dhcp6;
daemon->dhcp6 = new;
@@ -3801,6 +3851,8 @@
new->flags |= CONTEXT_RA_STATELESS | CONTEXT_DHCP | CONTEXT_RA;
else if (strcmp(a[leasepos], "off-link") == 0)
new->flags |= CONTEXT_RA_OFF_LINK;
+ else if (strcmp(a[leasepos], "auto-off") == 0)
+ new->flags |= CONTEXT_RA_AUTO_OFF;
else if (leasepos == 1 && inet_pton(AF_INET6, a[leasepos], &new->end6))
new->flags |= CONTEXT_DHCP;
else if (strstr(a[leasepos], "constructor:") == a[leasepos])
@@ -3876,12 +3928,14 @@
if (leasepos < k)
{
+
+#ifndef HAVE_DHCP6
if (leasepos != k-1)
{
dhcp_context_free(new);
ret_err(_("bad dhcp-range"));
}
-
+#endif
if (strcmp(a[leasepos], "infinite") == 0)
{
new->lease_time = 0xffffffff;
@@ -3921,7 +3975,7 @@
if (!(*cp >= '0' && *cp <= '9'))
break;
- if (*cp || (leasepos+1 < k))
+ if (*cp)
ret_err_free(_("bad dhcp-range"), new);
new->lease_time = atoi(a[leasepos]) * fac;
@@ -3932,8 +3986,63 @@
new->lease_time = 120;
}
}
+ leasepos++;
}
+#ifdef HAVE_DHCP6
+ if (leasepos < k)
+ {
+ if (leasepos != k-1)
+ {
+ dhcp_context_free(new);
+ ret_err(_("bad dhcp-range"));
+ }
+ if (strcmp(a[leasepos], "infinite") == 0)
+ {
+ new->valid_lifetime = 0xffffffff;
+ new->flags |= CONTEXT_SETVALID;
+ }
+ else
+ {
+ int fac = 1;
+ if (strlen(a[leasepos]) > 0)
+ {
+ switch (a[leasepos][strlen(a[leasepos]) - 1])
+ {
+ case 'w':
+ case 'W':
+ fac *= 7;
+ /* fall through */
+ case 'd':
+ case 'D':
+ fac *= 24;
+ /* fall through */
+ case 'h':
+ case 'H':
+ fac *= 60;
+ /* fall through */
+ case 'm':
+ case 'M':
+ fac *= 60;
+ /* fall through */
+ case 's':
+ case 'S':
+ a[leasepos][strlen(a[leasepos]) - 1] = 0;
+ }
+
+ for (cp = a[leasepos]; *cp; cp++)
+ if (!(*cp >= '0' && *cp <= '9'))
+ break;
+ if (*cp || (leasepos+1 < k))
+ ret_err_free(_("bad dhcp-range"), new);
+
+ new->valid_lifetime = atoi(a[leasepos]) * fac;
+ new->flags |= CONTEXT_SETVALID;
+ }
+ }
+ leasepos++;
+ }
+#endif
break;
}
@@ -4726,45 +4835,71 @@
#ifdef HAVE_DHCP6
case LOPT_RA_PARAM: /* --ra-param */
if ((comma = split(arg)))
- {
- struct ra_interface *new = opt_malloc(sizeof(struct ra_interface));
- new->lifetime = -1;
- new->prio = 0;
- new->mtu = 0;
- new->mtu_name = NULL;
- new->name = opt_string_alloc(arg);
- if (strcasestr(comma, "mtu:") == comma)
- {
- arg = comma + 4;
- if (!(comma = split(comma)))
- goto err;
- if (!strcasecmp(arg, "off"))
- new->mtu = -1;
- else if (!atoi_check(arg, &new->mtu))
- new->mtu_name = opt_string_alloc(arg);
- else if (new->mtu < 1280)
- goto err;
- }
- if (strcasestr(comma, "high") == comma || strcasestr(comma, "low") == comma)
- {
- if (*comma == 'l' || *comma == 'L')
- new->prio = 0x18;
- else
- new->prio = 0x08;
- comma = split(comma);
- }
- arg = split(comma);
- if (!atoi_check(comma, &new->interval) ||
- (arg && !atoi_check(arg, &new->lifetime)))
+ {
+ struct ra_interface *new = opt_malloc(sizeof(struct ra_interface));
+ new->lifetime = -1;
+ new->prio = 0;
+ new->mtu = 0;
+ new->mtu_name = NULL;
+ new->name = opt_string_alloc(arg);
+ new->reachable_time = 0;
+ new->hop_limit = 0;
+ new->retrans_time = 0;
+ new->ra_sent_count = 0;
+ new->max_ra_adv_interval = DEFAULT_RTR_ADV_INTERVAL;
+ new->min_ra_adv_interval = new->max_ra_adv_interval/3;
+ if (strcasestr(comma, "mtu:") == comma)
+ {
+ arg = comma + 4;
+ if (!(comma = split(comma)))
+ goto err;
+ if (!strcasecmp(arg, "off"))
+ new->mtu = -1;
+ else if (!atoi_check(arg, &new->mtu))
+ new->mtu_name = opt_string_alloc(arg);
+ else if ((new->mtu < 1280) && (new->mtu != 0))
+ goto err;
+ }
+ if (strcasestr(comma, "high") == comma || strcasestr(comma, "low") == comma)
+ {
+ if (*comma == 'l' || *comma == 'L')
+ new->prio = 0x18;
+ else
+ new->prio = 0x08;
+ comma = split(comma);
+ }
+ arg = split(comma);
+ if (comma && !atoi_check(comma, &new->interval))
+ goto err;
+ comma = split(arg);
+ if (arg && !atoi_check(arg, &new->lifetime))
+ goto err;
+ arg = split(comma);
+ if (comma && !atoi_check(comma, (int *)&new->reachable_time))
+ goto err;
+ comma = split(arg);
+ if (arg && (!atoi_check(arg, (int *)&new->hop_limit)))
+ goto err;
+ arg = split(comma);
+ if (comma && !atoi_check(comma, (int *)&new->retrans_time))
+ goto err;
+ comma = split(arg);
+ if ((arg && !atoi_check(arg, (int *)&new->min_ra_adv_interval)) ||
+ (comma && (!atoi_check(comma, (int *)&new->max_ra_adv_interval))))
{
err:
- free(new->name);
- ret_err_free(_("bad RA-params"), new);
+ free(new->name);
+ ret_err_free(_("bad RA-params"), new);
}
-
- new->next = daemon->ra_interfaces;
- daemon->ra_interfaces = new;
- }
+ if (new->hop_limit != 0)
+ {
+ setsockopt(daemon->icmp6fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS,
+ &new->hop_limit, sizeof(new->hop_limit));
+ }
+
+ new->next = daemon->ra_interfaces;
+ daemon->ra_interfaces = new;
+ }
break;
case LOPT_DUID: /* --dhcp-duid */
@@ -5361,6 +5496,75 @@
break;
}
+/* CRADLEPOINT */
+/* This option (OPT_EDNS_OPTION) is required to tack a(n) owner record onto a DNS request:
+ * Option Code: Owner (reserved) 4 (0x00 0x04)
+ * Option Length: 19 ("Cradlepoint" + device_id (obtained from OpenDNS)) (0x00 0x13)
+ * Option Data: "Cradlepoint" + device_id (an 4 byte identifier)
+ *
+ * The EDNS option is passed in like this:
+ * --edns-option=4,CradlepointAABBCCDD
+ */
+ case LOPT_EDNS_OPTION:
+ {
+ unsigned char *tmp;
+ int option_code, option_len = 0;
+
+ comma = split(arg);
+
+ if (*arg == 0 || !atoi_check16(arg, &option_code))
+ {
+ option = '?';
+ ret_err(_("invalid EDNS option code"));
+ break;
+ }
+
+ if (comma &&
+ (option_len = str_unescape_c_string(comma, comma)) <= 0)
+ {
+ option = '?';
+ ret_err(_("invalid EDNS option data"));
+ break;
+ }
+ tmp = opt_malloc(daemon->edns_options_len + 4 + option_len);
+
+ if (daemon->edns_options_len > 0)
+ {
+ memcpy(tmp, daemon->edns_options, daemon->edns_options_len);
+ free(daemon->edns_options);
+ }
+ daemon->edns_options = tmp;
+ tmp += daemon->edns_options_len;
+ PUTSHORT(option_code, tmp);
+ PUTSHORT(option_len, tmp);
+ memcpy(tmp, comma, option_len);
+ daemon->edns_options_len += 4 + option_len;
+ my_syslog(LOG_DEBUG, _("EDNS Option %d added length %d"), option_code,
+ daemon->edns_options_len);
+
+ break;
+ }
+
+ case LOPT_SSID_MAP:
+ {
+ char *err;
+
+ unhide_metas(arg);
+
+ if ((err = opendns_parse_device_id_opt(arg)) != NULL) {
+ /* int vs pointer warning return err; */
+ ret_err(_("opendns parse device id failed"));
+ }
+
+ break;
+ }
+ case LOPT_EDNS_RESTRICT:
+ {
+ set_option_bool(OPT_EDNS_RESTRICT);
+ break;
+ }
+/* CRADLEPOINT */
+
default:
ret_err(_("unsupported option (check that dnsmasq was compiled with DHCP/TFTP/DNSSEC/DBus support)"));
diff --git a/src/radv.c b/src/radv.c
index d2d3390..0457496 100644
--- a/src/radv.c
+++ b/src/radv.c
@@ -64,7 +64,11 @@
static unsigned int calc_lifetime(struct ra_interface *ra);
static unsigned int calc_interval(struct ra_interface *ra);
static unsigned int calc_prio(struct ra_interface *ra);
+static unsigned char calc_hop_limit(struct ra_interface *ra);
+static unsigned int calc_reachable_time(struct ra_interface *ra);
+static unsigned int calc_retrans_time(struct ra_interface *ra);
static struct ra_interface *find_iface_param(char *iface);
+int64_t timespecdiff(struct timespec const *a, struct timespec const *b);
static int hop_limit;
@@ -76,7 +80,10 @@
int class = IPTOS_CLASS_CS6;
#endif
int val = 255; /* radvd uses this value */
- socklen_t len = sizeof(int);
+#ifdef IPV6_RECVHOPLIMIT
+ int on_ipv6_recvhoplimit = 1;
+#endif
+ socklen_t len = sizeof(hop_limit);
struct dhcp_context *context;
/* ensure this is around even if we're not doing DHCPv6 */
@@ -106,6 +113,9 @@
!set_ipv6pktinfo(fd) ||
setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val, sizeof(val)) ||
setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, sizeof(val)) ||
+#ifdef IPV6_RECVHOPLIMIT
+ setsockopt(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on_ipv6_recvhoplimit, sizeof(on_ipv6_recvhoplimit)) ||
+#endif
setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter)) == -1)
die (_("cannot create ICMPv6 socket: %s"), NULL, EC_BADNET);
@@ -113,6 +123,9 @@
if (daemon->doing_ra)
ra_start_unsolicited(now, NULL);
+
+ /* Intializes random number generator */
+ srand(time(0));
}
void ra_start_unsolicited(time_t now, struct dhcp_context *context)
@@ -143,11 +156,14 @@
char interface[IF_NAMESIZE+1];
ssize_t sz;
int if_index = 0;
+ int hoplimit = 255;
struct cmsghdr *cmptr;
struct msghdr msg;
union {
- struct cmsghdr align; /* this ensures alignment */
- char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))];
+ struct cmsghdr align;
+ /* Extending control6 array to include hoplimit information
+ in addition to in6_pktinfo information */
+ char control6[CMSG_SPACE(sizeof(struct in6_pktinfo)) + CMSG_SPACE(sizeof(int))];
} control_u;
struct sockaddr_in6 from;
unsigned char *packet;
@@ -167,7 +183,15 @@
packet = (unsigned char *)daemon->outpacket.iov_base;
- for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
+ for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) {
+#ifdef IPV6_HOPLIMIT
+ if (cmptr->cmsg_level == IPPROTO_IPV6 && (cmptr->cmsg_type == IPV6_HOPLIMIT)) {
+ if ((cmptr->cmsg_len == CMSG_LEN(sizeof(int))) &&
+ (*(int *)CMSG_DATA(cmptr) >= 0) && (*(int *)CMSG_DATA(cmptr) < 256)) {
+ hoplimit = *(int *)CMSG_DATA(cmptr);
+ }
+ }
+#endif /* IPV6_HOPLIMIT */
if (cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == daemon->v6pktinfo)
{
union {
@@ -178,6 +202,7 @@
if_index = p.p->ipi6_ifindex;
}
+ }
if (!indextoname(daemon->icmp6fd, if_index, interface))
return;
@@ -195,13 +220,19 @@
if (packet[0] == ICMP6_ECHO_REPLY)
lease_ping_reply(&from.sin6_addr, packet, interface);
- else if (packet[0] == ND_ROUTER_SOLICIT)
+ else if ((packet[0] == ND_ROUTER_SOLICIT) && (hoplimit == 255))
+ {
+ if (!((sz > 8) && (packet[9] == 0))) {
+ inet_ntop(AF_INET6, &from.sin6_addr, daemon->addrbuff, ADDRSTRLEN);
+ if (!((strncmp(daemon->addrbuff, "::",2)==0) && (packet[8] == ICMP6_OPT_SOURCE_MAC)))
{
char *mac = "";
struct dhcp_bridge *bridge, *alias;
ssize_t rem;
unsigned char *p;
int opt_sz;
+ struct timespec ts;
+ int tms = 0;
#ifdef HAVE_DUMPFILE
dump_packet_icmp(DUMP_RA, (void *)packet, sz, (union mysockaddr *)&from, NULL);
@@ -251,7 +282,13 @@
if (!bridge)
/* source address may not be valid in solicit request. */
send_ra(now, if_index, interface, !IN6_IS_ADDR_UNSPECIFIED(&from.sin6_addr) ? &from.sin6_addr : NULL);
+ tms = random_num_in_range(MAX_RA_DELAY_TIME);
+ ts.tv_sec = 0;
+ ts.tv_nsec = tms * 1000000;
+ nanosleep(&ts, NULL);
}
+ }
+ }
}
static void send_ra_alias(time_t now, int iface, char *iface_name, struct in6_addr *dest, int send_iface)
@@ -268,7 +305,10 @@
#ifdef HAVE_LINUX_NETWORK
FILE *f;
#endif
-
+
+ struct iname *if_tmp;
+ time_t ts;
+ struct timespec ts_delay;
parm.ind = iface;
parm.managed = 0;
parm.other = 0;
@@ -288,17 +328,31 @@
ra->type = ND_ROUTER_ADVERT;
ra->code = 0;
- ra->hop_limit = hop_limit;
ra->flags = parm.prio;
ra->lifetime = htons(calc_lifetime(ra_param));
- ra->reachable_time = 0;
- ra->retrans_time = 0;
+ ra->reachable_time = htonl(calc_reachable_time(ra_param));
+ ra->hop_limit = calc_hop_limit(ra_param);
+ ra->retrans_time = htonl(calc_retrans_time(ra_param));
/* set tag with name == interface */
iface_id.net = iface_name;
iface_id.next = NULL;
parm.tags = &iface_id;
+ for (if_tmp = daemon->if_names; if_tmp; if_tmp = if_tmp->next)
+ {
+ if ((strcmp(if_tmp->name, iface_name) == 0) && (if_tmp->is_ra_adv_dis == true)) {
+ if (if_tmp->final_ra_sent_count < MAX_FINAL_RTR_ADVERTISEMENTS) {
+ ra->lifetime = htons(0);
+ if_tmp->final_ra_sent_count++;
+ break;
+ }
+ else {
+ return;
+ }
+ }
+ }
+
for (context = daemon->dhcp6; context; context = context->next)
{
context->flags &= ~CONTEXT_RA_DONE;
@@ -385,7 +439,8 @@
/* autonomous only if we're not doing dhcp, set
"on-link" unless "off-link" was specified */
opt->flags = (do_slaac ? 0x40 : 0) |
- ((context->flags & CONTEXT_RA_OFF_LINK) ? 0 : 0x80);
+ ((context->flags & CONTEXT_RA_OFF_LINK) ? 0 : 0x80) |
+ ((context->flags & CONTEXT_RA_AUTO_OFF) ? 0 : 0x40);
opt->valid_lifetime = htonl(context->saved_valid - old);
opt->preferred_lifetime = htonl(0);
opt->reserved = 0;
@@ -424,13 +479,9 @@
/* Set the MTU from ra_param if any, an MTU of 0 mean automatic for linux, */
/* an MTU of -1 prevents the option from being sent. */
- if (ra_param)
- mtu = ra_param->mtu;
#ifdef HAVE_LINUX_NETWORK
/* Note that IPv6 MTU is not necessarily the same as the IPv4 MTU
available from SIOCGIFMTU */
- if (mtu == 0)
- {
char *mtu_name = ra_param ? ra_param->mtu_name : NULL;
sprintf(daemon->namebuff, "/proc/sys/net/ipv6/conf/%s/mtu", mtu_name ? mtu_name : iface_name);
if ((f = fopen(daemon->namebuff, "r")))
@@ -439,8 +490,9 @@
mtu = atoi(daemon->namebuff);
fclose(f);
}
- }
#endif
+if (ra_param)
+ mtu = ra_param->mtu;
if (mtu > 0)
{
put_opt6_char(ICMP6_OPT_MTU);
@@ -570,10 +622,26 @@
}
#endif
+ for (if_tmp = daemon->if_names; if_tmp; if_tmp = if_tmp->next)
+ {
+ if ((strcmp(if_tmp->name, iface_name) == 0)) {
+ time(&ts);
+ if (difftime(ts, if_tmp->last_ra_for_rtr_sol) < MIN_DELAY_BETWEEN_RAS) {
+ /* last RA for RTR Solicit was sent only a few moments ago,
+ don't send another immediately. */
+ ts_delay.tv_sec = MIN_DELAY_BETWEEN_RAS;
+ ts_delay.tv_nsec = 0;
+ nanosleep(&ts_delay, NULL);
+ }
+ time(&if_tmp->last_ra_for_rtr_sol);
+ }
+ }
while (retry_send(sendto(daemon->icmp6fd, daemon->outpacket.iov_base,
save_counter(-1), 0, (struct sockaddr *)&addr,
sizeof(addr))));
+ if(ra_param)
+ ra_param->ra_sent_count++;
}
static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *dest)
@@ -588,8 +656,24 @@
unsigned int preferred, unsigned int valid, void *vparam)
{
struct ra_param *param = vparam;
+ char ifr_namebuff[IF_NAMESIZE];
+ int force_ra = 0;
+ struct in6_addr forcedaddress6;
(void)scope; /* warning */
+
+ struct iname *if_tmp;
+ for (if_tmp = daemon->if_names; if_tmp; if_tmp = if_tmp->next)
+ {
+ if ((strcmp(if_tmp->name, param->if_name) == 0) && (if_tmp->is_ra_adv_dis == true)) {
+ if (if_tmp->final_ra_sent_count < MAX_FINAL_RTR_ADVERTISEMENTS) {
+ break;
+ }
+ else {
+ return 1;
+ }
+ }
+ }
if (if_index == param->ind)
{
@@ -604,8 +688,9 @@
param->link_local = *local;
}
}
- else if (!IN6_IS_ADDR_LOOPBACK(local) &&
- !IN6_IS_ADDR_MULTICAST(local))
+ /* Force RA's for the link local address */
+ if ((!IN6_IS_ADDR_LOOPBACK(local) && !IN6_IS_ADDR_MULTICAST(local)) ||
+ (option_bool(OPT_FORCE_RA) && IN6_IS_ADDR_LINKLOCAL(local)))
{
int real_prefix = 0;
int do_slaac = 0;
@@ -613,14 +698,24 @@
int constructed = 0;
int adv_router = 0;
int off_link = 0;
+ int auto_off = 0;
+ unsigned int valid_lifetime = 0xffffffff;
unsigned int time = 0xffffffff;
struct dhcp_context *context;
-
- for (context = daemon->dhcp6; context; context = context->next)
- if (!(context->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) &&
- prefix <= context->prefix &&
- is_same_net6(local, &context->start6, context->prefix) &&
- is_same_net6(local, &context->end6, context->prefix))
+
+ for (context = daemon->dhcp6; context; context = context->next) {
+ memset(ifr_namebuff, 0, IF_NAMESIZE);
+ force_ra = context->forcedinterface6 &&
+ indextoname(daemon->dhcpfd, if_index, ifr_namebuff) &&
+ (strncmp(context->forcedinterface6, ifr_namebuff, IF_NAMESIZE) == 0) &&
+ option_bool(OPT_FORCE_RA);
+ if (force_ra)
+ forcedaddress6 = context->forcedaddress6;
+ if (!(context->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) &&
+ prefix <= context->prefix &&
+ ((is_same_net6(local, &context->start6, context->prefix) &&
+ is_same_net6(local, &context->end6, context->prefix)) ||
+ force_ra))
{
context->saved_valid = valid;
@@ -659,6 +754,12 @@
if ((context->flags & CONTEXT_SETLEASE) &&
time > context->lease_time)
{
+
+ if ((context->flags & CONTEXT_SETVALID))
+ valid_lifetime = context->valid_lifetime;
+ else
+ valid_lifetime = context->lease_time;
+
time = context->lease_time;
if (time < ((unsigned int)(3 * param->adv_interval)))
time = 3 * param->adv_interval;
@@ -689,6 +790,7 @@
context->flags |= CONTEXT_RA_DONE;
real_prefix = context->prefix;
off_link = (context->flags & CONTEXT_RA_OFF_LINK);
+ auto_off = (context->flags & CONTEXT_RA_AUTO_OFF);
}
param->first = 0;
@@ -696,10 +798,10 @@
more than one, it's not the first. */
param->found_context = context;
}
-
+ }
/* configured time is ceiling */
if (!constructed || valid > time)
- valid = time;
+ valid = valid_lifetime;
if (flags & IFACE_DEPRECATED)
preferred = 0;
@@ -744,14 +846,17 @@
/* autonomous only if we're not doing dhcp, set
"on-link" unless "off-link" was specified */
opt->flags = (off_link ? 0 : 0x80);
- if (do_slaac)
+ if (do_slaac && !auto_off)
opt->flags |= 0x40;
if (adv_router)
opt->flags |= 0x20;
opt->valid_lifetime = htonl(valid);
opt->preferred_lifetime = htonl(preferred);
opt->reserved = 0;
- opt->prefix = *local;
+ if (force_ra)
+ opt->prefix = forcedaddress6;
+ else
+ opt->prefix = *local;
inet_ntop(AF_INET6, local, daemon->addrbuff, ADDRSTRLEN);
if (!option_bool(OPT_QUIET_RA))
@@ -841,14 +946,21 @@
break;
if (!tmp)
{
- send_ra(now, param.iface, param.name, NULL);
+ struct iname *if_tmp;
+ for (if_tmp = daemon->if_names; if_tmp; if_tmp = if_tmp->next)
+ {
+ if ((strcmp(if_tmp->name, param.name) == 0) &&
+ ((if_tmp->is_ra_adv_dis == false) ||
+ (if_tmp->final_ra_sent_count < MAX_FINAL_RTR_ADVERTISEMENTS)) &&
+ (if_tmp->first_ra_delayed)) {
+ send_ra(now, param.iface, param.name, NULL);
- /* Also send on all interfaces that are aliases of this
+ /* Also send on all interfaces that are aliases of this
one. */
- for (aparam.bridge = daemon->bridges;
+ for (aparam.bridge = daemon->bridges;
aparam.bridge;
aparam.bridge = aparam.bridge->next)
- if ((int)if_nametoindex(aparam.bridge->iface) == param.iface)
+ if ((int)if_nametoindex(aparam.bridge->iface) == param.iface)
{
/* Count the number of alias interfaces for this
'bridge', by calling iface_enumerate with
@@ -890,6 +1002,10 @@
one --bridge-interface. */
break;
}
+ } else {
+ if_tmp->first_ra_delayed = true;
+ }
+ }
}
}
}
@@ -972,14 +1088,13 @@
static void new_timeout(struct dhcp_context *context, char *iface_name, time_t now)
{
- if (difftime(now, context->ra_short_period_start) < 60.0)
- /* range 5 - 20 */
- context->ra_time = now + 5 + (rand16()/4400);
+ struct ra_interface *ra = find_iface_param(iface_name);
+ if (ra && ra->ra_sent_count < (MAX_INITIAL_RTR_ADVERTISEMENTS-1))
+ context->ra_time = now + MAX_INITIAL_RTR_ADVERT_INTERVAL;
else
{
- /* range 3/4 - 1 times MaxRtrAdvInterval */
- unsigned int adv_interval = calc_interval(find_iface_param(iface_name));
- context->ra_time = now + (3 * adv_interval)/4 + ((adv_interval * (unsigned int)rand16()) >> 18);
+ unsigned int adv_interval = calc_interval(ra);
+ context->ra_time = now + adv_interval;
}
}
@@ -996,17 +1111,25 @@
static unsigned int calc_interval(struct ra_interface *ra)
{
- int interval = 600;
+ int interval = DEFAULT_RTR_ADV_INTERVAL;
+ if (ra) {
+ unsigned int min_rtr_interval = ra->min_ra_adv_interval;
+ unsigned int max_rtr_interval = ra->max_ra_adv_interval;
+
+ if (max_rtr_interval > MAX_RTR_ADV_INTERVAL_UPPER_BOUND)
+ max_rtr_interval = MAX_RTR_ADV_INTERVAL_UPPER_BOUND;
+ else if (max_rtr_interval < MAX_RTR_ADV_INTERVAL_LOWER_BOUND)
+ max_rtr_interval = MAX_RTR_ADV_INTERVAL_LOWER_BOUND;
+
+ if (min_rtr_interval < MIN_RTR_ADV_INTERVAL)
+ min_rtr_interval = MIN_RTR_ADV_INTERVAL;
+
+ interval = (rand() % (max_rtr_interval - min_rtr_interval + 1)) + min_rtr_interval;
- if (ra && ra->interval != 0)
- {
- interval = ra->interval;
- if (interval > 1800)
- interval = 1800;
- else if (interval < 4)
- interval = 4;
- }
-
+ if (((ra->ra_sent_count + 1) < MAX_INITIAL_RTR_ADVERTISEMENTS) &&
+ interval > MAX_INITIAL_RTR_ADVERT_INTERVAL)
+ interval = MAX_INITIAL_RTR_ADVERT_INTERVAL;
+ }
return (unsigned int)interval;
}
@@ -1036,4 +1159,35 @@
return 0;
}
+static unsigned char calc_hop_limit(struct ra_interface *ra)
+{
+ if (ra) {
+ return ra->hop_limit;
+ }
+ return (unsigned char)hop_limit;
+}
+
+static unsigned int calc_retrans_time(struct ra_interface *ra)
+{
+ if (ra) {
+ return ra->retrans_time;
+ }
+ return 0;
+}
+
+static unsigned int calc_reachable_time(struct ra_interface *ra)
+{
+ if (ra) {
+ return ra->reachable_time;
+ }
+ return 0;
+}
+
+int64_t timespecdiff(struct timespec const *a, struct timespec const *b)
+{
+ int64_t msec;
+ msec = ((int64_t)a->tv_sec - b->tv_sec) * 1000;
+ msec += ((int64_t)a->tv_nsec - b->tv_nsec) / (1000 * 1000);
+ return msec;
+}
#endif
diff --git a/src/rfc1035.c b/src/rfc1035.c
index 387d894..70910e1 100644
--- a/src/rfc1035.c
+++ b/src/rfc1035.c
@@ -994,7 +994,7 @@
#ifdef HAVE_IPSET
if (ipsets && (flags & (F_IPV4 | F_IPV6)))
for (ipsets_cur = ipsets->sets; *ipsets_cur; ipsets_cur++)
- if (add_to_ipset(*ipsets_cur, &addr, flags, 0) == 0)
+ if (add_to_ipset(*ipsets_cur, &addr, flags, 0, attl) == 0)
log_query((flags & (F_IPV4 | F_IPV6)) | F_IPSET, ipsets->domain, &addr, *ipsets_cur, 1);
#endif
#ifdef HAVE_NFTSET
diff --git a/src/unescape_c_string.c b/src/unescape_c_string.c
new file mode 100644
index 0000000..bf4f9d3
--- /dev/null
+++ b/src/unescape_c_string.c
@@ -0,0 +1,230 @@
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include "unescape_c_string.h"
+
+#ifndef isoctal
+# define isoctal(c) ((c) >= '0' && (c) <= '7')
+#endif
+#ifndef digittoint
+# define digittoint(c) (((c) >= '0' && (c) <= '9') ? (c) - '0' : tolower(c) - 'a' + 10)
+#endif
+
+typedef enum C_STRING_STATE {
+ C_STRING_STATE_GROUND,
+ C_STRING_STATE_START,
+ C_STRING_STATE_OCTAL2,
+ C_STRING_STATE_OCTAL3,
+ C_STRING_STATE_HEX1,
+ C_STRING_STATE_HEX2
+} C_STRING_STATE;
+
+int
+unescape_c_string(char *cp, char c, int *astate, int flag)
+{
+ if (flag & C_STRING_FLAG_END) {
+ switch (*astate) {
+ case C_STRING_STATE_OCTAL2:
+ case C_STRING_STATE_OCTAL3:
+ case C_STRING_STATE_HEX1:
+ case C_STRING_STATE_HEX2:
+ *astate = C_STRING_STATE_GROUND;
+ return C_STRING_RESULT_VALID;
+ case C_STRING_STATE_GROUND:
+ return C_STRING_RESULT_NOCHAR;
+ default:
+ return C_STRING_RESULT_SYNBAD;
+ }
+ }
+
+ switch (*astate) {
+ case C_STRING_STATE_GROUND:
+ *cp = 0;
+ if (c == '\\') {
+ *astate = C_STRING_STATE_START;
+ return C_STRING_RESULT_PENDING;
+ }
+ *cp = c;
+ return C_STRING_RESULT_VALID;
+
+ case C_STRING_STATE_START:
+ *cp = 0;
+ switch (c) {
+ case '\\':
+ *cp = c;
+ *astate = C_STRING_STATE_GROUND;
+ return C_STRING_RESULT_VALID;
+
+ case '0': case '1': case '2': case '3':
+ case '4': case '5': case '6': case '7':
+ *cp = (c - '0');
+ *astate = C_STRING_STATE_OCTAL2;
+ return C_STRING_RESULT_PENDING;
+
+ case 'x':
+ *astate = C_STRING_STATE_HEX1;
+ return C_STRING_RESULT_PENDING;
+
+ case 'n':
+ *cp = '\n';
+ *astate = C_STRING_STATE_GROUND;
+ return C_STRING_RESULT_VALID;
+ case 'r':
+ *cp = '\r';
+ *astate = C_STRING_STATE_GROUND;
+ return C_STRING_RESULT_VALID;
+ case 'b':
+ *cp = '\b';
+ *astate = C_STRING_STATE_GROUND;
+ return C_STRING_RESULT_VALID;
+ case 'a':
+ *cp = '\a';
+ *astate = C_STRING_STATE_GROUND;
+ return C_STRING_RESULT_VALID;
+ case 'v':
+ *cp = '\v';
+ *astate = C_STRING_STATE_GROUND;
+ return C_STRING_RESULT_VALID;
+ case 't':
+ *cp = '\t';
+ *astate = C_STRING_STATE_GROUND;
+ return C_STRING_RESULT_VALID;
+ case 'f':
+ *cp = '\f';
+ *astate = C_STRING_STATE_GROUND;
+ return C_STRING_RESULT_VALID;
+ case 's':
+ *cp = ' ';
+ *astate = C_STRING_STATE_GROUND;
+ return C_STRING_RESULT_VALID;
+ case 'E':
+ *cp = '\033';
+ *astate = C_STRING_STATE_GROUND;
+ return C_STRING_RESULT_VALID;
+
+ case '\n':
+ case '$':
+ *astate = C_STRING_STATE_GROUND;
+ return C_STRING_RESULT_NOCHAR;
+ }
+ *astate = C_STRING_STATE_GROUND;
+ return C_STRING_RESULT_SYNBAD;
+
+ case C_STRING_STATE_OCTAL2:
+ if (isoctal((int) (unsigned char) c)) {
+ *cp = (*cp << 3) + (c - '0');
+ *astate = C_STRING_STATE_OCTAL3;
+ return C_STRING_RESULT_PENDING;
+ }
+ *astate = C_STRING_STATE_GROUND;
+ return C_STRING_RESULT_VALIDPUSH;
+
+ case C_STRING_STATE_OCTAL3:
+ *astate = C_STRING_STATE_GROUND;
+ if (isoctal((int) (unsigned char) c)) {
+ *cp = (*cp << 3) + (c - '0');
+ return C_STRING_RESULT_VALID;
+ }
+ return C_STRING_RESULT_VALIDPUSH;
+
+ case C_STRING_STATE_HEX1:
+ if (isxdigit((int) (unsigned char) c)) {
+ *cp = digittoint((int) (unsigned char) c);
+ *astate = C_STRING_STATE_HEX2;
+ return C_STRING_RESULT_PENDING;
+ }
+ *astate = C_STRING_STATE_GROUND;
+ return C_STRING_RESULT_VALIDPUSH;
+
+ case C_STRING_STATE_HEX2:
+ *astate = C_STRING_STATE_GROUND;
+ if (isxdigit((int) (unsigned char) c)) {
+ *cp = (*cp << 4) + digittoint((int) (unsigned char) c);
+ return C_STRING_RESULT_VALID;
+ }
+ return C_STRING_RESULT_VALIDPUSH;
+
+ default:
+ *astate = C_STRING_STATE_GROUND;
+ return C_STRING_RESULT_SYNBAD;
+ }
+}
+
+int
+str_unescape_c_string(char *dst, const char *src)
+{
+ char c;
+ char *start = dst;
+ int state = 0;
+
+ while ((c = *src++)) {
+again:
+ switch (unescape_c_string(dst, c, &state, 0)) {
+ case C_STRING_RESULT_VALID:
+ dst++;
+ break;
+ case C_STRING_RESULT_VALIDPUSH:
+ dst++;
+ goto again;
+ case C_STRING_RESULT_PENDING:
+ case C_STRING_RESULT_NOCHAR:
+ break;
+ default:
+ *dst = 0;
+ return -1;
+ }
+ }
+ if (unescape_c_string(dst, c, &state, C_STRING_FLAG_END) == C_STRING_RESULT_VALID) {
+ dst++;
+ }
+ *dst = 0;
+ return dst - start;
+}
+
+ssize_t
+strn_unescape_c_string(char *dst, const char *src, size_t sz)
+{
+ char c, p;
+ char *start = dst, *end = dst + sz - 1;
+ int state = 0;
+
+ if (sz > 0) {
+ *end = 0;
+ }
+ while ((c = *src++)) {
+again:
+ switch (unescape_c_string(&p, c, &state, 0)) {
+ case C_STRING_RESULT_VALID:
+ if (dst < end) {
+ *dst = p;
+ }
+ dst++;
+ break;
+ case C_STRING_RESULT_VALIDPUSH:
+ if (dst < end) {
+ *dst = p;
+ }
+ dst++;
+ goto again;
+ case 0:
+ case C_STRING_RESULT_NOCHAR:
+ break;
+ default:
+ if (dst <= end) {
+ *dst = 0;
+ }
+ return -1;
+ }
+ }
+ if (unescape_c_string(&p, c, &state, C_STRING_FLAG_END) == C_STRING_RESULT_VALID) {
+ if (dst < end) {
+ *dst = p;
+ }
+ dst++;
+ }
+ if (dst <= end) {
+ *dst = 0;
+ }
+ return dst - start;
+}
diff --git a/src/unescape_c_string.h b/src/unescape_c_string.h
new file mode 100644
index 0000000..46ac055
--- /dev/null
+++ b/src/unescape_c_string.h
@@ -0,0 +1,23 @@
+
+#ifndef UNESCAPE_C_STRING_H
+# define UNESCAPE_C_STRING_H
+
+typedef enum C_STRING_RESULT {
+ C_STRING_RESULT_END,
+ C_STRING_RESULT_NOCHAR,
+ C_STRING_RESULT_PENDING,
+ C_STRING_RESULT_SYNBAD,
+ C_STRING_RESULT_VALID,
+ C_STRING_RESULT_VALIDPUSH
+} C_STRING_RESULT;
+
+typedef enum C_STRING_FLAG {
+ C_STRING_FLAG_NONE,
+ C_STRING_FLAG_END
+} C_STRING_FLAG;
+
+int unescape_c_string(char *cp, char c, int *astate, int flag);
+int str_unescape_c_string(char *dst, const char *src);
+ssize_t strn_unescape_c_string(char *dst, const char *src, size_t sz);
+
+#endif
diff --git a/src/util.c b/src/util.c
index 0c7de44..fd2a935 100644
--- a/src/util.c
+++ b/src/util.c
@@ -476,6 +476,17 @@
die(_("cannot read monotonic clock: %s"), NULL, EC_MISC);
return ts.tv_sec;
+/* CRADLEPOINT */
+#elif defined(CPLINUX)
+ struct timespec tv;
+
+ if (clock_gettime(CLOCK_MONOTONIC, &tv) == -1) {
+ my_syslog(LOG_ERR, _("Failed to get micro uptime: %s"), strerror(errno));
+ return -1;
+ }
+
+ return tv.tv_sec;
+/* CRADLEPOINT */
#else
return time(NULL);
#endif