[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