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>
Sat, 27 Sep 2014 17:48:13 +0000 (18:48 +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 962755e2ae9775c88344e6df0fb17ebcf2866eee..de2e6b33b26fef0198ea06fb405c948274fcc2ad 100644 (file)
--- a/README
+++ b/README
@@ -194,7 +194,7 @@ Defines:
   udp (closure => comm closure)
 
 udp: dict argument
   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
   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 f7cd373050fc9f8f3cb17becff2e94fe77af0721..9f7171630f0ebb18ae02ace2d075b7b2267d26b8 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;
        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))
        ca.ia.sin.sin_family=AF_INET;
        ca.ia.sin.sin_port=htons(port);
        if (inet_aton(trimmed,&ca.ia.sin.sin_addr))
index 64a20a73a803e159317dcdae1944642deda3f17a..6c9a93b86312b994d525964e539b6d63e0846fa5 100644 (file)
--- a/secnet.h
+++ b/secnet.h
@@ -342,6 +342,7 @@ struct comm_addr {
        equivalent. */
     struct comm_if *comm;
     union iaddr ia;
        equivalent. */
     struct comm_if *comm;
     union iaddr ia;
+    int ix;
 };
 
 /* Return True if the packet was processed, and shouldn't be passed to
 };
 
 /* Return True if the packet was processed, and shouldn't be passed to
diff --git a/udp.c b/udp.c
index 899a2c83499ffd91d2e8323d61beaff72566ce2d..334670f59a03a0a2d994db50dd7303a4d357700c 100644 (file)
--- a/udp.c
+++ b/udp.c
@@ -36,12 +36,19 @@ struct notify_list {
     struct notify_list *next;
 };
 
     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;
 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;
     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];
 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)
 {
     return sbuf;
 }
 
 static int udp_beforepoll(void *state, struct pollfd *fds, int *nfds_io,
                          int *timeout_io)
 {
+    int i;
     struct udp *st=state;
     struct udp *st=state;
-    if (*nfds_io<1) {
-       *nfds_io=1;
+    if (*nfds_io<st->n_socks) {
+       *nfds_io=st->n_socks;
        return ERANGE;
     }
        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;
 }
 
     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;
     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());
        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) {
                        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;
                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)) {
                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);
        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 {
               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;
 }
 
     }
 
     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;
 {
     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);
     }
     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);
                     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;
 
     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;
 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,
 }
 
 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;
 {
     struct udp *st;
     item_t *item;
-    item_t *j;
+    list_t *caddrl;
     dict_t *d;
     list_t *l;
     uint32_t a;
     dict_t *d;
     list_t *l;
     uint32_t a;
+    int i;
 
     st=safe_malloc(sizeof(*st),"udp_apply(st)");
     st->loc=loc;
 
     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;
     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);
     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;
 
     }
     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");
     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");