diff options
author | Ian Jackson <ijackson@chiark.greenend.org.uk> | 2014-06-03 20:42:01 +0100 |
---|---|---|
committer | Ian Jackson <ijackson@chiark.greenend.org.uk> | 2014-10-19 21:08:41 +0100 |
commit | 5806b74528bbe1d70e5e1795224b1c6ecd8c4341 (patch) | |
tree | e9f5e5b1dd74a2995f4807f7e469851541a057cb /src | |
parent | 07554ccd8286775b4f88a608ba3b94ff7b9efaf9 (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.c | 233 | ||||
-rw-r--r-- | src/adns.h | 58 |
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; +} @@ -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); |