[Dnsmasq-discuss] [PATCH v2 2/3] ipset: Parse new --ipset option and match domains in forward.c
Jason A. Donenfeld
Jason at zx2c4.com
Sun Feb 17 22:16:40 GMT 2013
From: "Jason A. Donenfeld" <Jason at zx2c4.com>
The matching logic is the same as for --address. extract_address is
responsible for calling out to add_to_ipset.
---
src/dnsmasq.h | 10 +++++++++-
src/forward.c | 20 ++++++++++++++++++-
src/option.c | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/rfc1035.c | 24 +++++++++++++++++++++-
4 files changed, 115 insertions(+), 3 deletions(-)
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index 832e04a..9732487 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -430,6 +430,12 @@ struct server {
struct server *next;
};
+struct ipsets {
+ char **sets;
+ char *domain;
+ struct ipsets *next;
+};
+
struct irec {
union mysockaddr addr;
struct in_addr netmask; /* only valid for IPv4 */
@@ -779,6 +785,7 @@ extern struct daemon {
struct iname *if_names, *if_addrs, *if_except, *dhcp_except, *auth_peers;
struct bogus_addr *bogus_addr;
struct server *servers;
+ struct ipsets *ipsets;
int log_fac; /* log facility */
char *log_file; /* optional log file */
int max_logs; /* queue limit */
@@ -903,7 +910,8 @@ size_t setup_reply(struct dns_header *header, size_t qlen,
struct all_addr *addrp, unsigned int flags,
unsigned long local_ttl);
int extract_addresses(struct dns_header *header, size_t qlen, char *namebuff,
- time_t now, int is_sign, int checkrebind, int checking_disabled);
+ time_t now, char **ipsets, int is_sign, int checkrebind,
+ int checking_disabled);
size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
struct in_addr local_addr, struct in_addr local_netmask, time_t now);
int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name,
diff --git a/src/forward.c b/src/forward.c
index fb0b4c4..f5e95a1 100644
--- a/src/forward.c
+++ b/src/forward.c
@@ -439,9 +439,27 @@ static size_t process_reply(struct dns_header *header, time_t now,
struct server *server, size_t n, int check_rebind, int checking_disabled)
{
unsigned char *pheader, *sizep;
+ char **sets = 0;
int munged = 0, is_sign;
size_t plen;
+#ifdef HAVE_IPSET
+ /* Similar algorithm to search_servers. */
+ struct ipsets *ipset_pos;
+ unsigned int namelen = strlen(daemon->namebuff);
+ unsigned int matchlen = 0;
+ for (ipset_pos = daemon->ipsets; ipset_pos; ipset_pos = ipset_pos->next) {
+ unsigned int domainlen = strlen(ipset_pos->domain);
+ char *matchstart = daemon->namebuff + namelen - domainlen;
+ if (namelen >= domainlen && hostname_isequal(matchstart, ipset_pos->domain) &&
+ (domainlen == 0 || namelen == domainlen || *(matchstart - 1) == '.' ) &&
+ domainlen >= matchlen) {
+ matchlen = domainlen;
+ sets = ipset_pos->sets;
+ }
+ }
+#endif
+
/* If upstream is advertising a larger UDP packet size
than we allow, trim it so that we don't get overlarge
requests for the client. We can't do this for signed packets. */
@@ -494,7 +512,7 @@ static size_t process_reply(struct dns_header *header, time_t now,
SET_RCODE(header, NOERROR);
}
- if (extract_addresses(header, n, daemon->namebuff, now, is_sign, check_rebind, checking_disabled))
+ if (extract_addresses(header, n, daemon->namebuff, now, sets, is_sign, check_rebind, checking_disabled))
{
my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff);
munged = 1;
diff --git a/src/option.c b/src/option.c
index 3fc3e03..899f4a1 100644
--- a/src/option.c
+++ b/src/option.c
@@ -127,6 +127,7 @@ struct myoption {
#define LOPT_AUTHSOA 316
#define LOPT_AUTHSFS 317
#define LOPT_AUTHPEER 318
+#define LOPT_IPSET 319
#ifdef HAVE_GETOPT_LONG
static const struct option opts[] =
@@ -259,6 +260,7 @@ static const struct myoption opts[] =
{ "auth-soa", 1, 0, LOPT_AUTHSOA },
{ "auth-sec-servers", 1, 0, LOPT_AUTHSFS },
{ "auth-peer", 1, 0, LOPT_AUTHPEER },
+ { "ipset", 1, 0, LOPT_IPSET },
{ NULL, 0, 0, 0 }
};
@@ -397,6 +399,7 @@ static struct {
{ LOPT_AUTHSOA, ARG_ONE, "<serial>[,...]", gettext_noop("Set authoritive zone information"), NULL },
{ LOPT_AUTHSFS, ARG_DUP, "<NS>[,<NS>...]", gettext_noop("Secondary authoritative nameservers for forward domains"), NULL },
{ LOPT_AUTHPEER, ARG_DUP, "<ipaddr>[,<ipaddr>...]", gettext_noop("Peers which are allowed to do zone transfer"), NULL },
+ { LOPT_IPSET, ARG_DUP, "/<domain>/<ipset>[,<ipset>...]", gettext_noop("Specify ipsets to which matching domains should be added"), NULL },
{ 0, 0, NULL, NULL, NULL }
};
@@ -2021,6 +2024,67 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
daemon->servers = newlist;
break;
}
+ case LOPT_IPSET:
+#ifndef HAVE_IPSET
+ ret_err(_("recompile with HAVE_IPSET defined to enable ipset directives"));
+ break;
+#else
+ {
+ struct ipsets ipsets_head;
+ struct ipsets *ipsets = &ipsets_head;
+ int size;
+ char *end;
+ char **sets, **sets_pos;
+ memset(ipsets, 0, sizeof(struct ipsets));
+ unhide_metas(arg);
+ if (arg && *arg == '/') {
+ arg++;
+ while ((end = split_chr(arg, '/'))) {
+ char *domain = NULL;
+ /* elide leading dots - they are implied in the search algorithm */
+ while (*arg == '.')
+ arg++;
+ /* # matches everything and becomes a zero length domain string */
+ if (strcmp(arg, "#") == 0 || !*arg)
+ domain = "";
+ else if (strlen(arg) != 0 && !(domain = canonicalise_opt(arg)))
+ option = '?';
+ ipsets->next = opt_malloc(sizeof(struct ipsets));
+ ipsets = ipsets->next;
+ memset(ipsets, 0, sizeof(struct ipsets));
+ ipsets->domain = domain;
+ arg = end;
+ }
+ } else {
+ ipsets->next = opt_malloc(sizeof(struct ipsets));
+ ipsets = ipsets->next;
+ memset(ipsets, 0, sizeof(struct ipsets));
+ ipsets->domain = "";
+ }
+ if (!arg || !*arg) {
+ option = '?';
+ break;
+ }
+ size = 2;
+ for (end = arg; *end; ++end) {
+ if (*end == ',')
+ ++size;
+ }
+ sets = sets_pos = opt_malloc(sizeof(char *) * size);
+ do {
+ end = split(arg);
+ *sets_pos++ = opt_string_alloc(arg);
+ arg = end;
+ } while (end);
+ *sets_pos = 0;
+ for (ipsets = &ipsets_head; ipsets->next; ipsets = ipsets->next)
+ ipsets->next->sets = sets;
+ ipsets->next = daemon->ipsets;
+ daemon->ipsets = ipsets_head.next;
+
+ break;
+ }
+#endif
case 'c': /* --cache-size */
{
diff --git a/src/rfc1035.c b/src/rfc1035.c
index 721cd61..6676bdf 100644
--- a/src/rfc1035.c
+++ b/src/rfc1035.c
@@ -777,9 +777,14 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name)
expired and cleaned out that way.
Return 1 if we reject an address because it look like part of dns-rebinding attack. */
int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t now,
- int is_sign, int check_rebind, int checking_disabled)
+ char **ipsets, int is_sign, int check_rebind, int checking_disabled)
{
unsigned char *p, *p1, *endrr, *namep;
+#ifdef HAVE_IPSET
+ char **ipsets_cur;
+#else
+ (void)ipsets; /* unused */
+#endif
int i, j, qtype, qclass, aqtype, aqclass, ardlen, res, searched_soa = 0;
unsigned long ttl = 0;
struct all_addr addr;
@@ -966,6 +971,23 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
(flags & F_IPV4) &&
private_net(addr.addr.addr4, !option_bool(OPT_LOCAL_REBIND)))
return 1;
+#ifdef HAVE_IPSET
+ if (ipsets && (flags & F_IPV4
+#ifdef HAVE_IPV6
+ || flags & F_IPV6
+#endif
+ )) {
+ ipsets_cur = ipsets;
+ while (*ipsets_cur)
+ add_to_ipset(*ipsets_cur++, &addr,
+#ifdef HAVE_IPV6
+ flags & F_IPV4 ? AF_INET : AF_INET6,
+#else
+ AF_INET,
+#endif
+ 0);
+ }
+#endif
newc = cache_insert(name, &addr, now, attl, flags | F_FORWARD);
if (newc && cpp)
--
1.8.1.2
More information about the Dnsmasq-discuss
mailing list