summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorIan Jackson <ijackson@chiark.greenend.org.uk>2014-06-03 20:42:01 +0100
committerIan Jackson <ijackson@chiark.greenend.org.uk>2014-10-19 21:08:41 +0100
commit5806b74528bbe1d70e5e1795224b1c6ecd8c4341 (patch)
treee9f5e5b1dd74a2995f4807f7e469851541a057cb /src
parent07554ccd8286775b4f88a608ba3b94ff7b9efaf9 (diff)
New public-facing functions for address/text conversions.
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>
Diffstat (limited to 'src')
-rw-r--r--src/addrfam.c233
-rw-r--r--src/adns.h58
2 files changed, 291 insertions, 0 deletions
diff --git a/src/addrfam.c b/src/addrfam.c
index f606612..a357f9e 100644
--- a/src/addrfam.c
+++ b/src/addrfam.c
@@ -28,11 +28,15 @@
#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 <net/if.h>
#include "internal.h"
@@ -210,3 +214,232 @@ void adns__sockaddr_inject(const union gen_addr *a, int port,
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;
+}
diff --git a/src/adns.h b/src/adns.h
index 2c03416..d978d60 100644
--- a/src/adns.h
+++ b/src/adns.h
@@ -66,6 +66,7 @@
#include <netinet/in.h>
#include <sys/time.h>
#include <unistd.h>
+#include <net/if.h>
#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_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;
@@ -631,6 +635,60 @@ void adns_finish(adns_state ads);
* 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);