chiark / gitweb /
pkstream/pkstream.c: Use `getaddrinfo' to resolve addresses and services.
authorMark Wooding <mdw@distorted.org.uk>
Thu, 28 Sep 2017 00:41:43 +0000 (01:41 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Thu, 28 Jun 2018 23:23:56 +0000 (00:23 +0100)
This will give us multiple addresses for simple queries, which we must
do something sensible with:

  * for server bind and peer addresses, we accumulate them in our
    address vectors as before;

  * for client bind addresses, and local UDP addresses, we just take
    the first match, and hope that's good enough; and

  * for client connect addresses, and remote UDP addresses, we try to
    connect to each address in turn and take the first one that works.

Some utility functions have been added or enhanced:

  * `pushaddr' has become `pushaddrs', and its job is now to push the
    addresses from a `struct addrinfo' chain onto an address vector; and

  * `copyaddr' has been introduced to do possible partial copies of
    addresses.

Note that everything is still strictly IPv4 throughout.  But almost all
of the pieces are now in place...

pkstream/pkstream.c

index 3a5f3fde573c1a4d49b3be4e45dff8d49456cfd1..580bb73f3cb95ab2c3cbe8c4a0cd66de2c74e8d3 100644 (file)
@@ -139,6 +139,24 @@ static void initaddr(addr *a)
   a->sin.sin_port = 0;
 }
 
+#define caf_addr 1u
+#define caf_port 2u
+static void copyaddr(addr *a, const struct sockaddr *sa, unsigned f)
+{
+  const struct sockaddr_in *sin;
+
+  a->sa.sa_family = sa->sa_family;
+  switch (sa->sa_family) {
+    case AF_INET:
+      sin = (const struct sockaddr_in *)sa;
+      if (f&caf_addr) a->sin.sin_addr = sin->sin_addr;
+      if (f&caf_port) a->sin.sin_port = sin->sin_port;
+      break;
+    default:
+      abort();
+  }
+}
+
 static void dolisten(void);
 
 static void doclose(pkstream *p)
@@ -310,21 +328,27 @@ static void dolisten(void)
     dolisten1(&DA(&cw.me)[i], &cw.sfv[i]);
 }
 
-static void pushaddr(addr_v *av, const addr *a)
+static void pushaddrs(addr_v *av, const struct addrinfo *ailist)
 {
-  DA_ENSURE(av, 1);
-  DA(av)[DA_LEN(av)] = *a;
-  DA_EXTEND(av, 1);
+  const struct addrinfo *ai;
+  size_t i, n;
+
+  for (ai = ailist, n = 0; ai; ai = ai->ai_next) n++;
+  DA_ENSURE(av, n);
+  for (i = DA_LEN(av), ai = ailist; ai; ai = ai->ai_next) {
+    initaddr(&DA(av)[i]);
+    copyaddr(&DA(av)[i++], ai->ai_addr, caf_addr | caf_port);
+  }
+  DA_EXTEND(av, n);
 }
 
 #define paf_parse 1u
-static void parseaddr(const char *host, const char *svc, unsigned f, addr *a)
+static void parseaddr(const struct addrinfo *aihint,
+                     const char *host, const char *svc, unsigned f,
+                     struct addrinfo **ai_out)
 {
   char *alloc = 0, *sep;
-  struct hostent *h;
-  struct servent *s;
-  char *qq;
-  unsigned long n;
+  int err;
 
   if (f&paf_parse) {
     alloc = xstrdup(host);
@@ -333,18 +357,15 @@ static void parseaddr(const char *host, const char *svc, unsigned f, addr *a)
     host = alloc; *sep = 0; svc = sep + 1;
   }
 
-  if (host) {
-    if ((h = gethostbyname(host)) == 0) die(1, "unknown host `%s'", host);
-    memcpy(&a->sin.sin_addr, h->h_addr, sizeof(a->sin.sin_addr));
-  }
-
-  if (svc) {
-    if ((n = strtoul(svc, &qq, 0)) > 0 && !*qq && n <= 0xffff)
-      a->sin.sin_port = htons(n);
-    else if ((s = getservbyname(svc, "tcp")) != 0)
-      a->sin.sin_port = s->s_port;
+  err = getaddrinfo(host, svc, aihint, ai_out);
+  if (err) {
+    if (host && svc) {
+      die(1, "failed to resolve hostname `%s', service `%s': %s",
+         host, svc, gai_strerror(err));
+    } else if (host)
+      die(1, "failed to resolve hostname `%s': %s", host, gai_strerror(err));
     else
-      die(1, "bad service name/number `%s'", svc);
+      die(1, "failed to resolve service `%s': %s", svc, gai_strerror(err));
   }
 
   xfree(alloc);
@@ -389,7 +410,7 @@ int main(int argc, char *argv[])
   const char *bindsvc = 0;
   addr bindaddr;
   const char *connhost = 0;
-  addr tmpaddr;
+  struct addrinfo aihint = { 0 }, *ai, *ailist;
   int fd = -1;
   int len = 65536;
   size_t i, n;
@@ -436,22 +457,28 @@ int main(int argc, char *argv[])
   if (bindsvc && connhost)
     die(1, "can't listen and connect");
 
+  aihint.ai_family = AF_INET;
   DA_CREATE(&cw.me); DA_CREATE(&cw.peer);
 
   n = DA_LEN(&bindhosts);
   if (n || bindsvc) {
+    aihint.ai_socktype = SOCK_STREAM;
+    aihint.ai_protocol = IPPROTO_TCP;
+    aihint.ai_flags = AI_ADDRCONFIG | AI_PASSIVE;
     if (!n) {
-      initaddr(&tmpaddr);
-      parseaddr(0, bindsvc, 0, &tmpaddr);
-      pushaddr(&cw.me, &tmpaddr);
+      parseaddr(&aihint, 0, bindsvc, 0, &ailist);
+      pushaddrs(&cw.me, ailist);
+      freeaddrinfo(ailist);
     } else if (!bindsvc) {
       if (n != 1) die(1, "can only bind to one address as client");
+      parseaddr(&aihint, DA(&bindhosts)[0], 0, 0, &ailist); ai = ailist;
       initaddr(&bindaddr);
-      parseaddr(DA(&bindhosts)[0], 0, 0, &bindaddr);
+      copyaddr(&bindaddr, ai->ai_addr, caf_addr);
+      freeaddrinfo(ailist);
     } else for (i = 0; i < n; i++) {
-      initaddr(&tmpaddr);
-      parseaddr(DA(&bindhosts)[i], bindsvc, 0, &tmpaddr);
-      pushaddr(&cw.me, &tmpaddr);
+      parseaddr(&aihint, DA(&bindhosts)[i], bindsvc, 0, &ailist);
+      pushaddrs(&cw.me, ailist);
+      freeaddrinfo(ailist);
     }
     if (bindsvc) {
       cw.f |= cwf_port;
@@ -462,37 +489,53 @@ int main(int argc, char *argv[])
 
   n = DA_LEN(&peerhosts);
   if (n) {
+    aihint.ai_socktype = SOCK_STREAM;
+    aihint.ai_protocol = IPPROTO_TCP;
+    aihint.ai_flags = AI_ADDRCONFIG;
     for (i = 0; i < n; i++) {
-      initaddr(&tmpaddr);
-      parseaddr(DA(&peerhosts)[0], 0, 0, &tmpaddr);
-      pushaddr(&cw.peer, &tmpaddr);
+      parseaddr(&aihint, DA(&peerhosts)[i], 0, 0, &ailist);
+      pushaddrs(&cw.peer, ailist);
+      freeaddrinfo(ailist);
     }
   }
 
   if (connhost) {
-    initaddr(&tmpaddr);
-    parseaddr(connhost, 0, paf_parse, &tmpaddr);
-    if ((fd = socket(tmpaddr.sa.sa_family, SOCK_STREAM, IPPROTO_TCP)) < 0 ||
-       (DA_LEN(&bindhosts) &&
-        bind(fd, &bindaddr.sa, addrsz(&bindaddr))) ||
-       connect(fd, &tmpaddr.sa, addrsz(&tmpaddr)))
-      die(1, "couldn't connect to TCP server: %s", strerror(errno));
+    aihint.ai_socktype = SOCK_STREAM;
+    aihint.ai_protocol = IPPROTO_TCP;
+    aihint.ai_flags = AI_ADDRCONFIG;
+    parseaddr(&aihint, connhost, 0, paf_parse, &ailist);
+
+    for (ai = ailist; ai; ai = ai->ai_next) {
+      if ((fd = socket(ai->ai_family, SOCK_STREAM, IPPROTO_TCP)) >= 0 &&
+         (!DA_LEN(&bindhosts) ||
+          !bind(fd, &bindaddr.sa, addrsz(&bindaddr))) &&
+         !connect(fd, ai->ai_addr, ai->ai_addrlen))
+       goto conn_tcp;
+      if (fd >= 0) close(fd);
+    }
+    die(1, "couldn't connect to TCP server: %s", strerror(errno));
+  conn_tcp:
     if (nonblockify(fd) || cloexec(fd))
       die(1, "couldn't connect to TCP server: %s", strerror(errno));
   }
 
-  initaddr(&tmpaddr);
-  parseaddr(argv[optind], 0, paf_parse, &tmpaddr);
-  if ((fd_udp = socket(tmpaddr.sa.sa_family, SOCK_DGRAM, IPPROTO_UDP)) < 0 ||
+  aihint.ai_socktype = SOCK_DGRAM;
+  aihint.ai_protocol = IPPROTO_UDP;
+  aihint.ai_flags = AI_ADDRCONFIG | AI_PASSIVE;
+  parseaddr(&aihint, argv[optind], 0, paf_parse, &ailist); ai = ailist;
+  if ((fd_udp = socket(ai->ai_family, SOCK_DGRAM, IPPROTO_UDP)) < 0 ||
       nonblockify(fd_udp) || cloexec(fd_udp) ||
       setsockopt(fd_udp, SOL_SOCKET, SO_RCVBUF, &len, sizeof(len)) ||
       setsockopt(fd_udp, SOL_SOCKET, SO_SNDBUF, &len, sizeof(len)) ||
-      bind(fd_udp, &tmpaddr.sa, addrsz(&tmpaddr)))
-    die(1, "couldn't set up UDP socket: %s", strerror(errno));
-  initaddr(&tmpaddr);
-  parseaddr(argv[optind + 1], 0, paf_parse, &tmpaddr);
-  if (connect(fd_udp, &tmpaddr.sa, addrsz(&tmpaddr)))
+      bind(fd_udp, ai->ai_addr, ai->ai_addrlen))
     die(1, "couldn't set up UDP socket: %s", strerror(errno));
+  freeaddrinfo(ailist);
+  aihint.ai_flags = AI_ADDRCONFIG;
+  parseaddr(&aihint, argv[optind + 1], 0, paf_parse, &ailist);
+  for (ai = ailist; ai; ai = ai->ai_next)
+    if (!connect(fd_udp, ai->ai_addr, ai->ai_addrlen)) goto conn_udp;
+  die(1, "couldn't set up UDP socket: %s", strerror(errno));
+conn_udp:
 
   if (bindsvc) dolisten();
   else if (connhost) dofwd(fd, fd);