chiark / gitweb /
Support IPv6 PTR lookups.
authorMark Wooding <mdw@distorted.org.uk>
Mon, 5 May 2014 10:27:54 +0000 (11:27 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Sat, 10 May 2014 13:32:26 +0000 (14:32 +0100)
  * Support parsing of reversed IPv6 addresses, so as to check the
    forward address against the result of the reverse lookup.

  * Support reversing AF_INET6 socket addresses in adns_submit_reverse.

  * Parse IPv6 literal addresses in adnshost `-i' arguments and pass
    them on.

A side effect of this work is that PTR lookups now issue adns_r_a or
adns_r_aaaa child queries, as opposed to adns_a_addr queries.  I found
that this made things simpler -- especially as we don't have adns_r_addr
support for IPv6 yet.

Knowledge about the reverse-lookup zones is not kept in addrfam.  I want
to leave open the possibility of direct support for the obsolete
`ip6.int' zone, and other similar wrinkles.  This knowledge has moved
from adns_submit_reverse to adns_submit_reverse_any, which also needs
to find the appropriate name-reversal algorithm, and it seemed sensible
to keep them together.  The magical `default_zone' token used to pass on
this responsibility is a little grim.

Signed-off-by: Mark Wooding <mdw@distorted.org.uk>
client/adh-query.c
client/adnshost.h
src/addrfam.c
src/internal.h
src/query.c
src/types.c

index 125bb332dc4646968da03801bcc469ec76e6780a..35e43b96ab0d8c0587b6959939a04f3c74ba1b2b 100644 (file)
@@ -100,21 +100,26 @@ static void prep_query(struct query_node **qun_r, int *quflags_r) {
 void of_ptr(const struct optioninfo *oi, const char *arg, const char *arg2) {
   struct query_node *qun;
   int quflags, r;
-  struct sockaddr_in sa;
+  struct addrinfo *ai, ai_hint = { 0 };
+  int err;
 
-  memset(&sa,0,sizeof(sa));
-  sa.sin_family= AF_INET;
-  if (!inet_aton(arg,&sa.sin_addr)) usageerr("invalid IP address %s",arg);
+  ai_hint.ai_family = AF_UNSPEC;
+  ai_hint.ai_socktype = SOCK_DGRAM;
+  ai_hint.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
+
+  err = getaddrinfo(arg, 0, &ai_hint, &ai);
+  if (err) usageerr("invalid IP address %s",arg);
 
   prep_query(&qun,&quflags);
   qun->owner= xstrsave(arg);
   r= adns_submit_reverse(ads,
-                        (struct sockaddr*)&sa,
+                        ai->ai_addr,
                         ov_type == adns_r_none ? adns_r_ptr : ov_type,
                         quflags,
                         qun,
                         &qun->qu);
   if (r) sysfail("adns_submit_reverse",r);
+  freeaddrinfo(ai);
 
   LIST_LINK_TAIL(outstanding,qun);
 }
@@ -122,22 +127,27 @@ void of_ptr(const struct optioninfo *oi, const char *arg, const char *arg2) {
 void of_reverse(const struct optioninfo *oi, const char *arg, const char *arg2) {
   struct query_node *qun;
   int quflags, r;
-  struct sockaddr_in sa;
+  struct addrinfo *ai, ai_hint = { 0 };
+  int err;
+
+  ai_hint.ai_family = AF_UNSPEC;
+  ai_hint.ai_socktype = SOCK_DGRAM;
+  ai_hint.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
 
-  memset(&sa,0,sizeof(sa));
-  sa.sin_family= AF_INET;
-  if (!inet_aton(arg,&sa.sin_addr)) usageerr("invalid IP address %s",arg);
+  err = getaddrinfo(arg, 0, &ai_hint, &ai);
+  if (err) usageerr("invalid IP address %s",arg);
 
   prep_query(&qun,&quflags);
   qun->owner= xmalloc(strlen(arg) + strlen(arg2) + 2);
   sprintf(qun->owner, "%s %s", arg,arg2);
   r= adns_submit_reverse_any(ads,
-                            (struct sockaddr*)&sa, arg2,
+                            ai->ai_addr, arg2,
                             ov_type == adns_r_none ? adns_r_txt : ov_type,
                             quflags,
                             qun,
                             &qun->qu);
   if (r) sysfail("adns_submit_reverse",r);
+  freeaddrinfo(ai);
 
   LIST_LINK_TAIL(outstanding,qun);
 }
index fcc96a3186ab9292461f42b18a09b8baaf4840bc..c46323e02d665d6206a1eae3c9cb68eb519eb2db 100644 (file)
@@ -41,6 +41,7 @@
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
+#include <netdb.h>
 
 #include "config.h"
 #include "adns.h"
index 40b8f2b6134bbd277a2158d4dd182ca2d1783965..ff533c939ca0fd6baf76c2e29d77a7230d8fa50f 100644 (file)
@@ -64,9 +64,41 @@ static int inet_matchp(const union gen_addr *addr,
                       const union gen_addr *mask)
   { return (addr->v4.s_addr & mask->v4.s_addr) == base->v4.s_addr; }
 
+static int inet_rev_parsecomp(const char *p, size_t n)
+{
+  int i = 0;
+  if (n > 3) return -1;
+
+  while (n--) {
+    if ('0' <= *p && *p <= '9') i = 10*i + *p++ - '0';
+    else return -1;
+  }
+  return i;
+}
+
+static void inet_rev_mkaddr(union gen_addr *addr, const byte *ipv)
+{
+  addr->v4.s_addr = htonl((ipv[3]<<24) | (ipv[2]<<16) |
+                         (ipv[1]<<8) | (ipv[0]));
+}
+
+static char *inet_rev_mkname(struct sockaddr *sa, char *buf)
+{
+  unsigned long a = ntohl(SIN(sa)->sin_addr.s_addr);
+  int i;
+
+  for (i = 0; i < 4; i++) {
+    if (i) *buf++ = '.';
+    buf += sprintf(buf, "%d", (int)(a & 0xff));
+    a >>= 8;
+  }
+  return buf;
+}
+
 const afinfo adns__inet_afinfo = {
-  AF_INET, 32, '.',
-  inet_sockaddr_to_inaddr, inet_prefix_mask, inet_guess_len, inet_matchp
+  AF_INET, 32, '.', 4, 3, adns_r_a,
+  inet_sockaddr_to_inaddr, inet_prefix_mask, inet_guess_len, inet_matchp,
+  inet_rev_parsecomp, inet_rev_mkaddr, inet_rev_mkname
 };
 
 /*
@@ -106,7 +138,45 @@ static int inet6_matchp(const union gen_addr *addr,
   return 1;
 }
 
+static int inet6_rev_parsecomp(const char *p, size_t n)
+{
+  if (n != 1) return -1;
+  else if ('0' <= *p && *p <= '9') return *p - '0';
+  else if ('a' <= *p && *p <= 'f') return *p - 'a' + 10;
+  else if ('A' <= *p && *p <= 'F') return *p - 'a' + 10;
+  else return -1;
+}
+
+static void inet6_rev_mkaddr(union gen_addr *addr, const byte *ipv)
+{
+  unsigned char *a = addr->v6.s6_addr;
+  int i;
+
+  for (i = 0; i < 16; i++)
+    a[i] = (ipv[31-2*i] << 4) | (ipv[30-2*i] << 0);
+}
+
+static char *inet6_rev_mkname(struct sockaddr *sa, char *buf)
+{
+  unsigned char *a = SIN6(sa)->sin6_addr.s6_addr + 16;
+  unsigned c, y;
+  int i, j;
+
+  for (i = 0; i < 16; i++) {
+    c = *--a;
+    for (j = 0; j < 2; j++) {
+      if (i || j) *buf++ = '.';
+      y = c & 0xf;
+      if (y < 10) *buf++ = y + '0';
+      else *buf++ = y - 10 + 'a';
+      c >>= 4;
+    }
+  }
+  return buf;
+}
+
 const afinfo adns__inet6_afinfo = {
-  AF_INET6, 128, ':',
-  inet6_sockaddr_to_inaddr, inet6_prefix_mask, inet6_guess_len, inet6_matchp
+  AF_INET6, 128, ':', 32, 1, adns_r_aaaa,
+  inet6_sockaddr_to_inaddr, inet6_prefix_mask, inet6_guess_len, inet6_matchp,
+  inet6_rev_parsecomp, inet6_rev_mkaddr, inet6_rev_mkname
 };
index 4f4f87eb0c4ddebd4f7d5b0efea01fb2cf3d6f8b..7cf4cec9daa94f190f63fd94ba149eaee6fdec3d 100644 (file)
@@ -69,6 +69,7 @@ typedef unsigned char byte;
 #define DNS_CLASS_IN 1
 
 #define DNS_INADDR_ARPA "in-addr", "arpa"
+#define DNS_IP6_ARPA "ip6", "arpa"
 
 #define MAX_POLLFDS  ADNS_POLLFDS_RECOMMENDED
 
@@ -121,13 +122,21 @@ typedef struct {
   int af;
   int width;
   int delim;
+  int nrevcomp;
+  int revcompwd;
+  adns_rrtype rrtype;
   void *(*sockaddr_to_inaddr)(struct sockaddr *sa);
   void (*prefix_mask)(int len, union gen_addr *a);
   int (*guess_len)(const union gen_addr *a);
   int (*matchp)(const union gen_addr *addr,
                const union gen_addr *base, const union gen_addr *mask);
+  int (*rev_parsecomp)(const char *p, size_t n);
+  void (*rev_mkaddr)(union gen_addr *addr, const byte *ipv);
+  char *(*rev_mkname)(struct sockaddr *sa, char *buf);
 } afinfo;
 
+struct afinfo_addr { const afinfo *ai; union gen_addr addr; };
+
 typedef struct typeinfo {
   adns_rrtype typekey;
   const char *rrtname;
@@ -207,7 +216,7 @@ typedef struct {
   void *ext;
   void (*callback)(adns_query parent, adns_query child);
   union {
-    adns_rr_addr ptr_parent_addr;
+    struct afinfo_addr ptr_parent_addr;
     adns_rr_hostaddr *hostaddr;
   } info;
 } qcontext;
index d09702e3702f555d8fc1c85b1c8a129d911ce55a..a54451853f1e2ba0d14d244065c1843678a94679 100644 (file)
@@ -274,6 +274,8 @@ int adns_submit(adns_state ads,
   return r;
 }
 
+static const char *default_zone = "<magic>";
+
 int adns_submit_reverse_any(adns_state ads,
                            const struct sockaddr *addr,
                            const char *zone,
@@ -281,27 +283,39 @@ int adns_submit_reverse_any(adns_state ads,
                            adns_queryflags flags,
                            void *context,
                            adns_query *query_r) {
-  const unsigned char *iaddr;
-  char *buf, *buf_free;
+  char *buf, *buf_free, *p;
   char shortbuf[100];
+  const afinfo *ai;
   int r, lreq;
 
   flags &= ~adns_qf_search;
 
-  if (addr->sa_family != AF_INET) return ENOSYS;
-  iaddr= (const unsigned char*)
-    &(((const struct sockaddr_in*)addr) -> sin_addr);
+  switch (addr->sa_family) {
+    case AF_INET:
+      ai = &adns__inet_afinfo;
+      if (zone == default_zone) zone = "in-addr.arpa";
+      break;
+    case AF_INET6:
+      ai = &adns__inet6_afinfo;
+      if (zone == default_zone) zone = "ip6.arpa";
+      break;
+    default:
+      return ENOSYS;
+  }
 
-  lreq= strlen(zone) + 4*4 + 1;
+  lreq= strlen(zone) + ai->nrevcomp*(ai->revcompwd + 1) + 1;
   if (lreq > sizeof(shortbuf)) {
-    buf= malloc(strlen(zone) + 4*4 + 1);
+    buf= malloc(lreq);
     if (!buf) return errno;
     buf_free= buf;
   } else {
     buf= shortbuf;
     buf_free= 0;
   }
-  sprintf(buf, "%d.%d.%d.%d.%s", iaddr[3], iaddr[2], iaddr[1], iaddr[0], zone);
+
+  p = ai->rev_mkname(addr, buf);
+  *p++ = '.';
+  strcpy(p, zone);
 
   r= adns_submit(ads,buf,type,flags,context,query_r);
   free(buf_free);
@@ -315,7 +329,7 @@ int adns_submit_reverse(adns_state ads,
                        void *context,
                        adns_query *query_r) {
   if (type != adns_r_ptr && type != adns_r_ptr_raw) return EINVAL;
-  return adns_submit_reverse_any(ads,addr,"in-addr.arpa",
+  return adns_submit_reverse_any(ads,addr,default_zone,
                                 type,flags,context,query_r);
 }
 
index 0cbbdef7088916559bb52d6a5ddf296769101b57..a34f046cd111165d55f47707407d1223727c9be6 100644 (file)
@@ -764,7 +764,8 @@ static adns_status cs_inthost(vbuf *vb, const void *datap) {
 
 static void icb_ptr(adns_query parent, adns_query child) {
   adns_answer *cans= child->answer;
-  const adns_rr_addr *queried, *found;
+  const union gen_addr *queried;
+  const unsigned char *found;
   adns_state ads= parent->ads;
   int i;
 
@@ -776,10 +777,9 @@ static void icb_ptr(adns_query parent, adns_query child) {
     return;
   }
 
-  queried= &parent->ctx.info.ptr_parent_addr;
-  for (i=0, found=cans->rrs.addr; i<cans->nrrs; i++, found++) {
-    if (queried->len == found->len &&
-       !memcmp(&queried->addr,&found->addr,queried->len)) {
+  queried= &parent->ctx.info.ptr_parent_addr.addr;
+  for (i=0, found=cans->rrs.bytes; i<cans->nrrs; i++, found += cans->rrsz) {
+    if (!memcmp(queried,found,cans->rrsz)) {
       if (!parent->children.head) {
        adns__query_done(parent);
        return;
@@ -795,16 +795,22 @@ static void icb_ptr(adns_query parent, adns_query child) {
 
 static adns_status pa_ptr(const parseinfo *pai, int dmstart,
                          int max, void *datap) {
-  static const char *const (expectdomain[])= { DNS_INADDR_ARPA };
+  static const struct {
+    const afinfo *ai;
+    const char *const tail[3];
+  } expectdomain[] = {
+    { &adns__inet_afinfo, { DNS_INADDR_ARPA, 0 } },
+    { &adns__inet6_afinfo, { DNS_IP6_ARPA, 0 } }
+  };
+  enum { n_ed = sizeof(expectdomain)/sizeof(expectdomain[0]) };
   
   char **rrp= datap;
   adns_status st;
-  adns_rr_addr *ap;
+  struct afinfo_addr *ap;
   findlabel_state fls;
-  char *ep;
-  byte ipv[4];
-  char labbuf[4];
-  int cbyte, i, lablen, labstart, l, id;
+  byte ipv[n_ed][32];
+  int cbyte, i, j, foundj = -1, lablen, labstart, id, f, ac;
+  const char *tp;
   adns_query nqu;
   qcontext ctx;
 
@@ -815,46 +821,62 @@ static adns_status pa_ptr(const parseinfo *pai, int dmstart,
   if (cbyte != max) return adns_s_invaliddata;
 
   ap= &pai->qu->ctx.info.ptr_parent_addr;
-  if (!ap->len) {
+  if (!ap->ai) {
     adns__findlabel_start(&fls, pai->ads, -1, pai->qu,
                          pai->qu->query_dgram, pai->qu->query_dglen,
                          pai->qu->query_dglen, DNS_HDRSIZE, 0);
-    for (i=0; i<4; i++) {
+
+    f = (1 << n_ed) - 1; /* superposition of address types */
+    for (i = 0;; i++) {
       st= adns__findlabel_next(&fls,&lablen,&labstart); assert(!st);
-      if (lablen<=0 || lablen>3) return adns_s_querydomainwrong;
-      memcpy(labbuf, pai->qu->query_dgram + labstart, lablen);
-      labbuf[lablen]= 0;
-      ipv[3-i]= strtoul(labbuf,&ep,10);
-      if (*ep) return adns_s_querydomainwrong;
-      if (lablen>1 && pai->qu->query_dgram[labstart]=='0')
-       return adns_s_querydomainwrong;
+      if (lablen <= 0) break;
+      for (j = 0; j < n_ed; j++) {
+       if (!(f & (1 << j))) continue;
+       if (i < expectdomain[j].ai->nrevcomp) {
+         ac = expectdomain[j].ai->rev_parsecomp(
+           pai->qu->query_dgram + labstart, lablen);
+         if (ac < 0) goto mismatch;
+         assert(i < sizeof(ipv[j]));
+         ipv[j][i] = ac;
+       } else {
+         tp = expectdomain[j].tail[i - expectdomain[j].ai->nrevcomp];
+         if (!tp ||
+             strncmp(pai->qu->query_dgram + labstart, tp, lablen) != 0 ||
+             tp[lablen] != 0)
+           goto mismatch;
+       }
+       continue;
+
+      mismatch:
+       f &= ~(1 << j);
+       if (!f) return adns_s_querydomainwrong;
+      }
     }
-    for (i=0; i<sizeof(expectdomain)/sizeof(*expectdomain); i++) {
-      st= adns__findlabel_next(&fls,&lablen,&labstart); assert(!st);
-      l= strlen(expectdomain[i]);
-      if (lablen != l ||
-         memcmp(pai->qu->query_dgram + labstart, expectdomain[i], l))
-       return adns_s_querydomainwrong;
+
+    if (lablen < 0) return adns_s_querydomainwrong;
+    for (j = 0; j < n_ed; j++) {
+      if (!(f & (1 << j))) continue;
+      if (i >= expectdomain[j].ai->nrevcomp &&
+         !expectdomain[j].tail[i - expectdomain[j].ai->nrevcomp])
+       { foundj = j; continue; }
+      f &= ~(1 << j);
+      if (!f) return adns_s_querydomainwrong;
     }
-    st= adns__findlabel_next(&fls,&lablen,0); assert(!st);
-    if (lablen) return adns_s_querydomainwrong;
-    
-    ap->len= sizeof(struct sockaddr_in);
-    memset(&ap->addr,0,sizeof(ap->addr.inet));
-    ap->addr.inet.sin_family= AF_INET;
-    ap->addr.inet.sin_addr.s_addr=
-      htonl((ipv[0]<<24) | (ipv[1]<<16) | (ipv[2]<<8) | (ipv[3]));
+    assert(foundj >= 0 && f == (1 << foundj)); /* collapsed to a single type */
+
+    ap->ai = expectdomain[foundj].ai;
+    ap->ai->rev_mkaddr(&ap->addr, ipv[foundj]);
   }
 
   st= adns__mkquery_frdgram(pai->ads, &pai->qu->vb, &id,
                            pai->dgram, pai->dglen, dmstart,
-                           adns_r_addr, adns_qf_quoteok_query);
+                           ap->ai->rrtype, adns_qf_quoteok_query);
   if (st) return st;
 
   ctx.ext= 0;
   ctx.callback= icb_ptr;
   memset(&ctx.info,0,sizeof(ctx.info));
-  st= adns__internal_submit(pai->ads, &nqu, adns__findtype(adns_r_addr),
+  st= adns__internal_submit(pai->ads, &nqu, adns__findtype(ap->ai->rrtype),
                            &pai->qu->vb, id,
                            adns_qf_quoteok_query, pai->now, &ctx);
   if (st) return st;