chiark / gitweb /
New public-facing functions for address/text conversions.
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Tue, 3 Jun 2014 19:42:01 +0000 (20:42 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Sun, 19 Oct 2014 20:08:41 +0000 (21:08 +0100)
The usual functions for doing these conversions are getaddrinfo(3) and
getnameinfo(3).  Unfortunately, these seem generally to be rather buggy
(with different bugs on different platforms).  For example, the Linux
glibc implementation tries to do complicated things with AF_NETLINK
sockets even though it's only going to do a simple syntactic
transformation.

So we provide our own versions, which only handle conversions between
addresses and their text numerical representations (and don't try to do
anything complicated with DNS).

For compatibility, the functions handle various crazy things which are
generally undesirable:

  * traditional IPv4 text conversions allow degenerate forms A, A.B and
    A.B.C, where the host part is given as a simple number rather than
    being split into octets;

  * traditional IPv4 text conversions allow the individual components to
    be given in bases other than 10, using the usual C prefixes;

  * IPv6 socket addresses (but, annoyingly, not `struct in6_addr') have
    a `scope-id' field (which actually identifies what RFC4007 calls a
    `zone', i.e., the namespace in which the address should be
    interpreted), and these are described by a `%...' suffix; and

  * the `scope-id' may be a name, though the syntax and meaning of such
    names isn't defined anywhere except for the link-local scope, where
    the names and numbers are interface names and indices, which need to
    be looked up.

All of this means that there are a number of options and unfortunate
error conditions, which make the interface more complicated than is
really ideal.

Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
Signed-off-by: Mark Wooding <mdw@distorted.org.uk>
client/Makefile.in
client/addrtext.c [new file with mode: 0644]
src/addrfam.c
src/adns.h

index 01f2fcda4d44ce394c48926a2ffbb97a70cc62ce..05880726e6e378efb00e3bd3819b8b940422989f 100644 (file)
@@ -27,7 +27,7 @@ PROGS_SYSDEP= @PROGS_HAVE_TSEARCH@
 ENABLE_DYNAMIC=        @ENABLE_DYNAMIC@
 
 PROGRAMS=      adnslogres adnsheloex adnshost $(PROGS_SYSDEP)
 ENABLE_DYNAMIC=        @ENABLE_DYNAMIC@
 
 PROGRAMS=      adnslogres adnsheloex adnshost $(PROGS_SYSDEP)
-PROGRAMS_LOCAL=        fanftest adnstest
+PROGRAMS_LOCAL=        fanftest adnstest addrtext
 PROGRAMS_ALL=  $(PROGRAMS) $(PROGRAMS_LOCAL)
 
 STATIC_LIB=    ../src/libadns.a
 PROGRAMS_ALL=  $(PROGRAMS) $(PROGRAMS_LOCAL)
 
 STATIC_LIB=    ../src/libadns.a
diff --git a/client/addrtext.c b/client/addrtext.c
new file mode 100644 (file)
index 0000000..f61aa68
--- /dev/null
@@ -0,0 +1,197 @@
+/*
+  some test cases
+
+
+ ./addrtext_s fe80::1%wlanx
+ ./addrtext_s fe80::1%wlan0
+ ./addrtext_s fe80::1%23
+ ./addrtext_s fe80::1%1
+ ./addrtext_s 2001:ba8:1e3::%wlan0
+ ./addrtext_s 2001:ba8:1e3::%23
+ ./addrtext_s 2001:ba8:1e3::%1   # normally lo
+ ./addrtext_s 127.0.0.1x
+ ./addrtext_s 172.18.45.6
+ ./addrtext_s 12345
+
+
+  */
+
+/*
+ * addrtext.c
+ * - test program for address<->string conversion, not part of the library
+ */
+/*
+ *  This file is part of adns, which is
+ *    Copyright (C) 1997-2000,2003,2006,2014  Ian Jackson
+ *    Copyright (C) 1999-2000,2003,2006  Tony Finch
+ *    Copyright (C) 1991 Massachusetts Institute of Technology
+ *  (See the file INSTALL for full details.)
+ *  
+ *  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; either version 2, or (at your option)
+ *  any later version.
+ *  
+ *  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, write to the Free Software Foundation,
+ *  Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <inttypes.h>
+
+#include "config.h"
+#include "adns.h"
+
+#define PORT 1234
+
+#define STRING(x) STRING2(x)
+#define STRING2(x) #x
+
+static int fails;
+
+static void hex(const void *data_v, int l) {
+  const uint8_t *data= data_v;
+  int i;
+  for (i=0; i<l; i++)
+    printf("%02x",data[i]);
+}
+
+static void dump(const char *pfx, struct sockaddr *sa, socklen_t salen) {
+  int i;
+  printf(" %s: ",pfx);
+  hex(sa, salen);
+
+  for (i=0; i<salen; i++)
+    printf("%02x",((const uint8_t*)sa)[i]);
+
+  printf(" %d ", sa->sa_family);
+
+  switch (sa->sa_family) {
+  case AF_INET: {
+    const struct sockaddr_in *s = (const void*)sa;
+    printf(".port=%d .addr=%08"PRIx32"",
+          ntohs(s->sin_port),
+          (uint32_t)ntohl(s->sin_addr.s_addr));
+    break;
+  }
+  case AF_INET6: {
+    const struct sockaddr_in6 *s = (const void*)sa;
+    printf(".port=%d .flowinfo=%08"PRIx32" .scope_id=%08"PRIx32" .addr=",
+          ntohs(s->sin6_port),
+          (uint32_t)ntohl(s->sin6_flowinfo),
+          (uint32_t)ntohl(s->sin6_scope_id));
+    hex(&s->sin6_addr, sizeof(s->sin6_addr));
+    break;
+  }
+  }
+  printf("\n");
+}
+
+static void dotest(const char *input) {
+  adns_sockaddr ours;
+  socklen_t socklen;
+  struct addrinfo aip;
+  struct addrinfo *air=0;
+  char ourbuf[ADNS_ADDR2TEXT_BUFLEN];
+  char theirbuf[ADNS_ADDR2TEXT_BUFLEN];
+
+  memset(&ours,0,sizeof(ours));
+
+  socklen= sizeof(ours);
+  int our_r= adns_text2addr(input,PORT,0,&ours.sa,&socklen);
+
+  memset(&aip,0,sizeof(aip));
+  aip.ai_flags= AI_NUMERICHOST|AI_NUMERICSERV;
+  aip.ai_socktype= SOCK_DGRAM;
+  aip.ai_protocol= IPPROTO_UDP;
+  air= 0;
+  int libc_r= getaddrinfo(input,STRING(PORT),&aip,&air);
+  printf("`%s': us %s; libc %s, air=%p",
+        input, strerror(our_r), libc_r ? gai_strerror(libc_r) : "0", air);
+  if (air)
+    printf(" .family=%d .socklen=%ld .addr=%p .next=%p",
+          air->ai_family, (long)air->ai_addrlen, air->ai_addr, air->ai_next);
+  printf(":");
+
+  if (libc_r==EAI_NONAME && !air) {
+    if (strchr(input,'%') && (our_r==ENOSYS || our_r==ENXIO)) {
+      printf(" bad-scope");
+      goto ok;
+    }
+    if (strchr(input,'%') && our_r==ENOSYS) {
+      printf(" bad-scope");
+      goto ok;
+    }
+    if (our_r==EINVAL) {
+      printf(" invalid");
+      goto ok;
+    }
+  }
+  printf(" valid");
+
+#define FAIL do{ printf(" | FAIL\n"); fails++; }while(0)
+#define WANT(x) if (!(x)) { printf(" not %s",STRING(x)); FAIL; return; } else;
+
+  WANT(!our_r);
+  WANT(!libc_r);
+  WANT(air);
+  WANT(air->ai_addr);
+  WANT(!air->ai_next);
+  if (air->ai_addrlen!=socklen || memcmp(&ours,air->ai_addr,socklen)) {
+    printf(" mismatch");
+    FAIL;
+    dump("ours",&ours.sa,socklen);
+    dump("libc",air->ai_addr,air->ai_addrlen);
+    return;
+  }
+
+  printf(" |");
+
+  int ourbuflen= sizeof(ourbuf);
+  int ourport;
+  our_r= adns_addr2text(&ours.sa,0, ourbuf,&ourbuflen, &ourport);
+
+  printf(" us %s",strerror(our_r));
+  if (!our_r)
+    printf(" `%s'",ourbuf);
+
+  size_t theirbuflen= sizeof(theirbuf);
+  libc_r= getnameinfo(&ours.sa,socklen, theirbuf,theirbuflen, 0,0,
+                     NI_NUMERICHOST|NI_NUMERICSERV);
+  printf("; libc %s", libc_r ? gai_strerror(libc_r) : "0");
+  if (!libc_r)
+    printf(" `%s'",theirbuf);
+
+  printf(":");
+
+  WANT(!our_r);
+  WANT(!libc_r);
+  WANT(ourport==PORT);
+  if (strcmp(ourbuf,theirbuf)) {
+    printf(" mismatch");
+    FAIL;
+    return;
+  }
+
+ ok:
+  printf(" | PASS\n");
+}
+
+int main(int argc, char **argv) {
+  const char *arg;
+  while ((arg= *++argv)) {
+    dotest(arg);
+  }
+  return !!fails;
+}
index f606612bdf9e5fb1f8b49838968560e4dc3c12b9..a357f9e33bc737e861676bc14c37fe208e881030 100644 (file)
 #include <errno.h>
 #include <limits.h>
 #include <unistd.h>
 #include <errno.h>
 #include <limits.h>
 #include <unistd.h>
+#include <inttypes.h>
+#include <stddef.h>
+#include <stdbool.h>
 
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
+#include <net/if.h>
 
 #include "internal.h"
 
 
 #include "internal.h"
 
@@ -210,3 +214,232 @@ void adns__sockaddr_inject(const union gen_addr *a, int port,
     unknown_af(sa->sa_family);
   }
 }
     unknown_af(sa->sa_family);
   }
 }
+
+/*
+ * addr2text and text2addr
+ */
+
+#define ADDRFAM_DEBUG
+#ifdef ADDRFAM_DEBUG
+static void af_debug_func(const char *fmt, ...) {
+  int esave= errno;
+  va_list al;
+  va_start(al,fmt);
+  vfprintf(stderr,fmt,al);
+  va_end(al);
+  errno= esave;
+}
+# define af_debug(fmt,...) \
+  (af_debug_func("%s: " fmt "\n", __func__, __VA_ARGS__))
+#else
+# define af_debug(fmt,...) ((void)("" fmt "", __VA_ARGS__))
+#endif
+
+static bool addrtext_our_errno(int e) {
+  return
+    e==EAFNOSUPPORT ||
+    e==EINVAL ||
+    e==ENOSPC ||
+    e==ENOSYS;
+}
+
+static bool addrtext_scope_use_ifname(const struct sockaddr *sa) {
+  const struct in6_addr *in6= &CSIN6(sa)->sin6_addr;
+  return
+    IN6_IS_ADDR_LINKLOCAL(in6) ||
+    IN6_IS_ADDR_MC_LINKLOCAL(in6);
+}
+
+int adns_text2addr(const char *text, uint16_t port, adns_queryflags flags,
+                  struct sockaddr *sa, socklen_t *salen_io) {
+  int af;
+  char copybuf[INET6_ADDRSTRLEN];
+  const char *parse=text;
+  const char *scopestr=0;
+  socklen_t needlen;
+  void *dst;
+  uint16_t *portp;
+
+#define INVAL(how) do{                         \
+  af_debug("invalid: %s: `%s'", how, text);    \
+  return EINVAL;                               \
+}while(0)
+
+#define AFCORE(INETx,SINx,sinx)                        \
+    af= AF_##INETx;                            \
+    dst = &SINx(sa)->sinx##_addr;              \
+    portp = &SINx(sa)->sinx##_port;            \
+    needlen= sizeof(*SINx(sa));
+
+  if (!strchr(text, ':')) { /* INET */
+
+    AFCORE(INET,SIN,sin);
+
+  } else { /* INET6 */
+
+    AFCORE(INET6,SIN6,sin6);
+
+    const char *percent= strchr(text, '%');
+    if (percent) {
+      ptrdiff_t lhslen = percent - text;
+      if (lhslen >= INET6_ADDRSTRLEN) INVAL("scoped addr lhs too long");
+      memcpy(copybuf, text, lhslen);
+      copybuf[lhslen]= 0;
+
+      parse= copybuf;
+      scopestr= percent+1;
+
+      af_debug("will parse scoped addr `%s' %% `%s'", parse, scopestr);
+    }
+
+  }
+
+#undef AFCORE
+
+  if (scopestr && (flags & adns_qf_addrlit_scope_forbid))
+    INVAL("scoped addr but _scope_forbid");
+
+  if (*salen_io < needlen) {
+    *salen_io = needlen;
+    return ENOSPC;
+  }
+
+  memset(sa, 0, needlen);
+
+  sa->sa_family= af;
+  *portp = htons(port);
+
+  if (af == AF_INET && !(flags & adns_qf_addrlit_ipv4_quadonly)) {
+    /* we have to use inet_aton to deal with non-dotted-quad literals */
+    int r= inet_aton(parse,&SIN(sa)->sin_addr);
+    if (!r) INVAL("inet_aton rejected");
+  } else {
+    int r= inet_pton(af,parse,dst);
+    if (!r) INVAL("inet_pton rejected");
+    assert(r>0);
+  }
+
+  if (scopestr) {
+    errno=0;
+    char *ep;
+    unsigned long scope= strtoul(scopestr,&ep,10);
+    if (errno==ERANGE) INVAL("numeric scope id too large for unsigned long");
+    assert(!errno);
+    if (!*ep) {
+      if (scope > ~(uint32_t)0)
+       INVAL("numeric scope id too large for uint32_t");
+    } else { /* !!*ep */
+      if (flags & adns_qf_addrlit_scope_numeric)
+       INVAL("non-numeric scope but _scope_numeric");
+      if (!addrtext_scope_use_ifname(sa)) {
+       af_debug("cannot convert non-numeric scope"
+                " in non-link-local addr `%s'", text);
+       return ENOSYS;
+      }
+      errno= 0;
+      scope= if_nametoindex(scopestr);
+      if (!scope) {
+       /* RFC3493 says "No errors are defined".  It's not clear
+        * whether that is supposed to mean if_nametoindex "can't
+        * fail" (other than by the supplied name not being that of an
+        * interface) which seems unrealistic, or that it conflates
+        * all its errors together by failing to set errno, or simply
+        * that they didn't bother to document the errors.
+        *
+        * glibc, FreeBSD and OpenBSD all set errno (to ENXIO when
+        * appropriate).  See Debian bug #749349.
+        *
+        * We attempt to deal with this by clearing errno to start
+        * with, and then perhaps mapping the results. */
+       af_debug("if_nametoindex rejected scope name (errno=%s)",
+                strerror(errno));
+       if (errno==0) {
+         return ENXIO;
+       } else if (addrtext_our_errno(errno)) {
+         /* we use these for other purposes, urgh. */
+         perror("adns: adns_text2addr: if_nametoindex"
+                " failed with unexpected error");
+         return EIO;
+       } else {
+         return errno;
+       }
+      } else { /* ix>0 */
+       if (scope > ~(uint32_t)0) {
+         fprintf(stderr,"adns: adns_text2addr: if_nametoindex"
+                 " returned an interface index >=2^32 which will not fit"
+                 " in sockaddr_in6.sin6_scope_id");
+         return EIO;
+       }
+      }
+    } /* else; !!*ep */
+
+    SIN6(sa)->sin6_scope_id= scope;
+  } /* if (scopestr) */
+
+  *salen_io = needlen;
+  return 0;
+}
+
+int adns_addr2text(const struct sockaddr *sa, adns_queryflags flags,
+                  char *buffer, int *buflen_io, int *port_r) {
+  const void *src;
+  int port;
+
+  if (*buflen_io < ADNS_ADDR2TEXT_BUFLEN) {
+    *buflen_io = ADNS_ADDR2TEXT_BUFLEN;
+    return ENOSPC;
+  }
+
+  switch (sa->sa_family) {
+    AF_CASES(af);
+    af_inet:  src= &CSIN(sa)->sin_addr;    port= CSIN(sa)->sin_port;    break;
+    af_inet6: src= &CSIN6(sa)->sin6_addr;  port= CSIN6(sa)->sin6_port;  break;
+    default: return EAFNOSUPPORT;
+  }
+
+  const char *ok= inet_ntop(sa->sa_family, src, buffer, *buflen_io);
+  assert(ok);
+
+  if (sa->sa_family == AF_INET6) {
+    uint32_t scope = CSIN6(sa)->sin6_scope_id;
+    if (scope) {
+      if (flags & adns_qf_addrlit_scope_forbid)
+       return EINVAL;
+      int scopeoffset = strlen(buffer);
+      int remain = *buflen_io - scopeoffset;
+      char *scopeptr =  buffer + scopeoffset;
+      assert(remain >= IF_NAMESIZE+1/*%*/);
+      *scopeptr++= '%'; remain--;
+      bool parsedname = 0;
+      af_debug("will print scoped addr %s %% %"PRIu32"", buffer, scope);
+      if (scope <= UINT_MAX /* so we can pass it to if_indextoname */
+         && !(flags & adns_qf_addrlit_scope_numeric)
+         && addrtext_scope_use_ifname(sa)) {
+       parsedname = if_indextoname(scope, scopeptr);
+       if (!parsedname) {
+         af_debug("if_indextoname rejected scope (errno=%s)",
+                  strerror(errno));
+         if (errno==ENXIO) {
+           /* fair enough, show it as a number then */
+         } else if (addrtext_our_errno(errno)) {
+           /* we use these for other purposes, urgh. */
+           perror("adns: adns_addr2text: if_indextoname"
+                  " failed with unexpected error");
+           return EIO;
+         } else {
+           return errno;
+         }
+       }
+      }
+      if (!parsedname) {
+       int r = snprintf(scopeptr, remain,
+                        "%"PRIu32"", scope);
+       assert(r < *buflen_io - scopeoffset);
+      }
+      af_debug("printed scoped addr `%s'", buffer);
+    }
+  }
+
+  if (port_r) *port_r= ntohs(port);
+  return 0;
+}
index 2c03416efdf0ba4f21f93b4478b94d1429de940b..d978d603f4d6b559457bc0764541a106bf92260d 100644 (file)
@@ -66,6 +66,7 @@
 #include <netinet/in.h>
 #include <sys/time.h>
 #include <unistd.h>
 #include <netinet/in.h>
 #include <sys/time.h>
 #include <unistd.h>
+#include <net/if.h>
 
 #ifdef __cplusplus
 extern "C" { /* I really dislike this - iwj. */
 
 #ifdef __cplusplus
 extern "C" { /* I really dislike this - iwj. */
@@ -101,6 +102,9 @@ typedef enum { /* In general, or together the desired flags: */
  adns_qf_quotefail_cname=0x00000080,/* refuse if quote-req chars in CNAME we go via */
  adns_qf_cname_loose=    0x00000100,/* allow refs to CNAMEs - without, get _s_cname */
  adns_qf_cname_forbid=   0x00000200,/* don't follow CNAMEs, instead give _s_cname */
  adns_qf_quotefail_cname=0x00000080,/* refuse if quote-req chars in CNAME we go via */
  adns_qf_cname_loose=    0x00000100,/* allow refs to CNAMEs - without, get _s_cname */
  adns_qf_cname_forbid=   0x00000200,/* don't follow CNAMEs, instead give _s_cname */
+ adns_qf_addrlit_scope_forbid=0x00002000,/* forbid %<scope> in IPv6 literals */
+ adns_qf_addrlit_scope_numeric=0x00004000,/* %<scope> may only be numeric */
+ adns_qf_addrlit_ipv4_quadonly=0x00008000,/* reject non-dotted-quad ipv4 */
  adns__qf_internalmask=  0x0ff00000
 } adns_queryflags;
 
  adns__qf_internalmask=  0x0ff00000
 } adns_queryflags;
 
@@ -631,6 +635,60 @@ void adns_finish(adns_state ads);
  * they will be cancelled.
  */
 
  * they will be cancelled.
  */
 
+#define ADNS_ADDR2TEXT_BUFLEN                                  \
+  (INET6_ADDRSTRLEN + 1/*%*/                                   \
+  + ((IF_NAMESIZE-1) > 9 ? (IF_NAMESIZE-1) : 9/*uint32*/)      \
+  + 1/* nul; included in IF_NAMESIZE */)
+
+int adns_text2addr(const char *text, uint16_t port, adns_queryflags flags,
+                  struct sockaddr *sa_r,
+                  socklen_t *salen_io /* updated iff OK or ENOSPC */);
+int adns_addr2text(const struct sockaddr *sa, adns_queryflags flags,
+                  char *buffer, int *buflen_io /* updated ONLY on ENOSPC */,
+                  int *port_r /* may be 0 */);
+  /*
+   * port is always in host byte order and is simply copied to and
+   * from the appropriate sockaddr field (byteswapped as necessary).
+   *
+   * The only flags supported are adns_qf_addrlit_...; others are
+   * ignored.
+   *
+   * Error return values are:
+   *
+   *  ENOSPC    Output buffer is too small.  Can only happen if
+   *            *buflen_io < ADNS_ADDR2TEXT_BUFLEN or
+   *            *salen_io < sizeof(adns_sockaddr).  On return,
+   *            *buflen_io or *salen_io has been updated by adns.
+   *
+   *  EINVAL    text has invalid syntax.
+   *
+   *            text represents an address family not supported by
+   *            this version of adns.
+   *
+   *            Scoped address supplied (text contained "%" or
+   *            sin6_scope_id nonzero) but caller specified
+   *            adns_qf_addrlit_scope_forbid.
+   *
+   *            Scope name (rather than number) supplied in text but
+   *            caller specified adns_qf_addrlit_scope_numeric.
+   *
+   *  EAFNOSUPPORT   sa->sa_family is not supported (addr2text only).
+   *
+   * Only if neither adns_qf_addrlit_scope_forbid nor
+   * adns_qf_addrlit_scope_numeric are set:
+   *
+   *  ENOSYS    Scope name supplied in text but IPv6 address part of
+   *            sockaddr is not a link local address.
+   *
+   *  ENXIO     Scope name supplied in text but if_nametoindex
+   *            said it wasn't a valid local interface name.
+   *
+   *  EIO       Scoped address supplied but if_nametoindex failed
+   *            in an unexpected way; adns has printed a message to
+   *            stderr.
+   *
+   *  any other   if_nametoindex failed in a more-or-less expected way.
+   */
 
 void adns_forallqueries_begin(adns_state ads);
 adns_query adns_forallqueries_next(adns_state ads, void **context_r);
 
 void adns_forallqueries_begin(adns_state ads);
 adns_query adns_forallqueries_next(adns_state ads, void **context_r);