chiark / gitweb /
udp: Support IPv6 (mostly)
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Tue, 2 Sep 2014 08:05:30 +0000 (09:05 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Thu, 2 Oct 2014 15:30:21 +0000 (16:30 +0100)
Specifically:

 * struct udp now contains an array of (up to three) pairs of iaddr,
   fd.  Code which deals with the fd and addr has been updated to use
   loops etc. as appropriate.

 * The sockets are created with the right protocol family value.
   For AF_INET6, we set IPV6_V6ONLY.

 * Specifically, when transmitting, we try all appropriate sockets and
   compute the persistent-failure indication as required.

 * And a comm_addr now contains an `int ix' for udp.c's benefit; this
   allows udp to note in the comm_addr which socket an incoming packet
   was received on (which is required for logging etc.).  (NB that the
   socket index is ignored when sending; this is so that we can
   continue to construct a comm_addr in the current way; it will
   simply show up as notionally attached to the first of the udp's
   interfaces.)

 * We use text2iaddr to convert the string to a socket address, rather
   than string_item_to_ipaddr.  The latter can cope only with IPv4
   (and is now used only for private vpn addrs, proxies, etc.).

 * The default is now to create both IPv6 and IPv4 sockets.

Left undone are:

 * The special secnet proxy protocol has a 4-byte address prepended
   which implies IPv4.  I don't intend to fix this.

 * The authbind support for IPv6 will be in a future patch.

Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
README
resolver.c
secnet.h
udp.c

diff --git a/README b/README
index 962755e..de2e6b3 100644 (file)
--- a/README
+++ b/README
@@ -194,7 +194,7 @@ Defines:
   udp (closure => comm closure)
 
 udp: dict argument
-  address (string): IP address to listen and send on
+  address (string list): IPv6 or IPv4 addresses to listen and send on
   port (integer): UDP port to listen and send on
   buffer (buffer closure): buffer for incoming packets
   authbind (string): optional, path to authbind-helper program
index f7cd373..9f71716 100644 (file)
@@ -44,6 +44,7 @@ static bool_t resolve_request(void *sst, cstring_t name,
        struct comm_addr ca;
        FILLZERO(ca);
        ca.comm=comm;
+       ca.ix=-1;
        ca.ia.sin.sin_family=AF_INET;
        ca.ia.sin.sin_port=htons(port);
        if (inet_aton(trimmed,&ca.ia.sin.sin_addr))
index a046830..81ffbcb 100644 (file)
--- a/secnet.h
+++ b/secnet.h
@@ -342,6 +342,7 @@ struct comm_addr {
        equivalent. */
     struct comm_if *comm;
     union iaddr ia;
+    int ix;
 };
 
 /* Return True if the packet was processed, and shouldn't be passed to
diff --git a/udp.c b/udp.c
index 899a2c8..334670f 100644 (file)
--- a/udp.c
+++ b/udp.c
@@ -36,12 +36,19 @@ struct notify_list {
     struct notify_list *next;
 };
 
+#define MAX_SOCKETS 3 /* 2 ought to do really */
+
+struct udpsock {
+    union iaddr addr;
+    int fd;
+};
+
 struct udp {
     closure_t cl;
     struct comm_if ops;
     struct cloc loc;
-    union iaddr addr;
-    int fd;
+    int n_socks;
+    struct udpsock socks[MAX_SOCKETS];
     string_t authbind;
     struct buffer_if *rbuf;
     struct notify_list *notify;
@@ -52,23 +59,30 @@ struct udp {
 static const char *addr_to_string(void *commst, const struct comm_addr *ca) {
     struct udp *st=commst;
     static char sbuf[100];
+    int ix=ca->ix>=0 ? ca->ix : 0;
 
-    snprintf(sbuf, sizeof(sbuf), "udp:%s-%s",
-            iaddr_to_string(&st->addr), iaddr_to_string(&ca->ia));
+    assert(ix>=0 && ix<st->n_socks);
+    snprintf(sbuf, sizeof(sbuf), "udp:%d:%s-%s",
+            ca->ix,
+            iaddr_to_string(&st->socks[ix].addr),
+            iaddr_to_string(&ca->ia));
     return sbuf;
 }
 
 static int udp_beforepoll(void *state, struct pollfd *fds, int *nfds_io,
                          int *timeout_io)
 {
+    int i;
     struct udp *st=state;
-    if (*nfds_io<1) {
-       *nfds_io=1;
+    if (*nfds_io<st->n_socks) {
+       *nfds_io=st->n_socks;
        return ERANGE;
     }
-    *nfds_io=1;
-    fds->fd=st->fd;
-    fds->events=POLLIN;
+    *nfds_io=st->n_socks;
+    for (i=0; i<st->n_socks; i++) {
+       fds[i].fd=st->socks[i].fd;
+       fds[i].events=POLLIN;
+    }
     return 0;
 }
 
@@ -80,15 +94,20 @@ static void udp_afterpoll(void *state, struct pollfd *fds, int nfds)
     struct notify_list *n;
     bool_t done;
     int rv;
+    int i;
 
-    if (nfds && (fds->revents & POLLIN)) {
+    for (i=0; i<st->n_socks; i++) {
+       if (i>=nfds) continue;
+       if (!(fds[i].revents & POLLIN)) continue;
+       assert(fds[i].fd == st->socks[i].fd);
+       int fd=st->socks[i].fd;
        do {
            FILLZERO(from);
            fromlen=sizeof(from);
            BUF_ASSERT_FREE(st->rbuf);
            BUF_ALLOC(st->rbuf,"udp_afterpoll");
            buffer_init(st->rbuf,calculate_max_start_pad());
-           rv=recvfrom(st->fd, st->rbuf->start,
+           rv=recvfrom(fd, st->rbuf->start,
                        buf_remaining_space(st->rbuf),
                        0, (struct sockaddr *)&from, &fromlen);
            if (rv>0) {
@@ -114,6 +133,7 @@ static void udp_afterpoll(void *state, struct pollfd *fds, int nfds)
                FILLZERO(ca);
                ca.comm=&st->ops;
                ca.ia=from;
+               ca.ix=i;
                done=False;
                for (n=st->notify; n; n=n->next) {
                    if (n->fn(n->state, st->rbuf, &ca)) {
@@ -192,21 +212,33 @@ static bool_t udp_sendmsg(void *commst, struct buffer_if *buf,
        memcpy(sa,&dest->ia.sin.sin_addr,4);
        memset(sa+4,0,4);
        memcpy(sa+6,&dest->ia.sin.sin_port,2);
-       sendto(st->fd,sa,buf->size+8,0,(struct sockaddr *)&st->proxy,
+       sendto(st->socks[0].fd,sa,buf->size+8,0,(struct sockaddr *)&st->proxy,
               sizeof(st->proxy));
        buf_unprepend(buf,8);
     } else {
-       sendto(st->fd, buf->start, buf->size, 0,
-              &dest->ia.sa, iaddr_socklen(&dest->ia));
+       int i,r;
+       bool_t allunsupported=True;
+       for (i=0; i<st->n_socks; i++) {
+           if (dest->ia.sa.sa_family != st->socks[i].addr.sa.sa_family)
+               /* no point even trying */
+               continue;
+           r=sendto(st->socks[i].fd, buf->start, buf->size, 0,
+                    &dest->ia.sa, iaddr_socklen(&dest->ia));
+           if (!r) return True;
+           if (!(errno==EAFNOSUPPORT || errno==ENETUNREACH))
+               /* who knows what that error means? */
+               allunsupported=False;
+       }
+       return !allunsupported; /* see doc for comm_sendmsg_fn in secnet.h */
     }
 
     return True;
 }
 
-static void udp_make_socket(struct udp *st, struct udp *us)
+static void udp_make_socket(struct udp *st, struct udpsock *us)
 {
     const union iaddr *addr=&us->addr;
-    us->fd=socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
+    us->fd=socket(addr->sa.sa_family, SOCK_DGRAM, IPPROTO_UDP);
     if (us->fd<0) {
        fatal_perror("udp (%s:%d): socket",st->loc.file,st->loc.line);
     }
@@ -215,6 +247,16 @@ static void udp_make_socket(struct udp *st, struct udp *us)
                     st->loc.file,st->loc.line);
     }
     setcloexec(us->fd);
+#ifdef CONFIG_IPV6
+    if (addr->sa.sa_family==AF_INET6) {
+       int r;
+       int optval=1;
+       socklen_t optlen=sizeof(optval);
+       r=setsockopt(us->fd,IPPROTO_IPV6,IPV6_V6ONLY,&optval,optlen);
+       if (r) fatal_perror("udp (%s:%d): setsockopt(,IPV6_V6ONLY,&1,)",
+                           st->loc.file,st->loc.line);
+    }
+#endif
 
     if (st->authbind) {
        pid_t c;
@@ -267,8 +309,11 @@ static void udp_make_socket(struct udp *st, struct udp *us)
 static void udp_phase_hook(void *sst, uint32_t new_phase)
 {
     struct udp *st=sst;
-    udp_make_socket(st,st);
-    register_for_poll(st,udp_beforepoll,udp_afterpoll,1,"udp");
+    int i;
+    for (i=0; i<st->n_socks; i++)
+       udp_make_socket(st,&st->socks[i]);
+
+    register_for_poll(st,udp_beforepoll,udp_afterpoll,MAX_SOCKETS,"udp");
 }
 
 static list_t *udp_apply(closure_t *self, struct cloc loc, dict_t *context,
@@ -276,10 +321,11 @@ static list_t *udp_apply(closure_t *self, struct cloc loc, dict_t *context,
 {
     struct udp *st;
     item_t *item;
-    item_t *j;
+    list_t *caddrl;
     dict_t *d;
     list_t *l;
     uint32_t a;
+    int i;
 
     st=safe_malloc(sizeof(*st),"udp_apply(st)");
     st->loc=loc;
@@ -292,7 +338,6 @@ static list_t *udp_apply(closure_t *self, struct cloc loc, dict_t *context,
     st->ops.release_notify=release_notify;
     st->ops.sendmsg=udp_sendmsg;
     st->ops.addr_to_string=addr_to_string;
-    FILLZERO(st->addr);
     st->use_proxy=False;
 
     item=list_elem(args,0);
@@ -301,10 +346,37 @@ static list_t *udp_apply(closure_t *self, struct cloc loc, dict_t *context,
     }
     d=item->data.dict;
 
-    st->addr.sa.sa_family=AF_INET;
-    j=dict_find_item(d,"address",False,"udp",st->loc);
-    st->addr.sin.sin_addr.s_addr=j?string_item_to_ipaddr(j, "udp"):INADDR_ANY;
-    st->addr.sin.sin_port=dict_read_number(d,"port",True,"udp",st->loc,0);
+    int port=dict_read_number(d,"port",True,"udp",st->loc,0);
+
+    union iaddr defaultaddrs[] = {
+#ifdef CONFIG_IPV6
+       { .sin6 = { .sin6_family=AF_INET6,
+                   .sin6_port=htons(port),
+                   .sin6_addr=IN6ADDR_ANY_INIT } },
+#endif
+       { .sin = { .sin_family=AF_INET,
+                  .sin_port=htons(port),
+                  .sin_addr= { .s_addr=INADDR_ANY } } }
+    };
+
+    caddrl=dict_lookup(d,"address");
+    st->n_socks=caddrl ? list_length(caddrl) : (int)ARRAY_SIZE(defaultaddrs);
+    if (st->n_socks<=0 || st->n_socks>MAX_SOCKETS)
+       cfgfatal(st->loc,"udp","`address' must be 1..%d addresses",
+                MAX_SOCKETS);
+
+    for (i=0; i<st->n_socks; i++) {
+       struct udpsock *us=&st->socks[i];
+       FILLZERO(us->addr);
+       if (!list_length(caddrl)) {
+           us->addr=defaultaddrs[i];
+       } else {
+           text2iaddr(list_elem(caddrl,i),port,
+                      &us->addr,"udp");
+       }
+       us->fd=-1;
+    }
+
     st->rbuf=find_cl_if(d,"buffer",CL_BUFFER,True,"udp",st->loc);
     st->authbind=dict_read_string(d,"authbind",False,"udp",st->loc);
     l=dict_lookup(d,"proxy");