BSD sockets UDP uses 'headers' for all packets
authorBarret Rhoden <brho@cs.berkeley.edu>
Thu, 5 Jun 2014 20:40:36 +0000 (13:40 -0700)
committerBarret Rhoden <brho@cs.berkeley.edu>
Thu, 5 Jun 2014 20:40:36 +0000 (13:40 -0700)
BSD sockets wants to do both:
socket, bind, {sendto,recvfrom}
and
socket, bind, connect, {send,recv}

'headers' mode allows us to blindly sendto and recvfrom.  The last
chance we have to set this is in bind (though it makes more sense to do
so in socket).

We can only announce or connect once.  To be able to receive, we need to
have announced by the end of bind so that our entry is in the hash
table before the application knows which local port we're on.  So we'll
never be able to connect after doing a bind.

Connect still sets the sockets raddr (in the Rock), so all of our
send/recv calls will just go through sendto/recvfrom, and extract the
raddr from the Rock and put it in the 'headers'.

We could consider having calls to connect() clear the headers, and then
connect as normal, but that would require the kernel to let us change
from announced to connected.  I'm not willing to do that yet.

Also, this way, we can connect multiple times, changing the default
remote end, which is what you're supposed to be able to do.  You can't
send multiple connect messages to the kernel.

Note that the Rock code is not threadsafe, and that every send/recv
needs to extract the rock, which is O(number of rocks/fds).

kern/src/net/udp.c
user/bsd/bind.c
user/bsd/connect.c
user/bsd/send.c
user/bsd/sendto.c
user/bsd/socket.c

index 25f3802..787868f 100644 (file)
@@ -200,6 +200,7 @@ void udpkick(void *x, struct block *bp)
        struct conv *rc;
 
        upriv = c->p->priv;
+       assert(upriv);
        f = c->p->f;
 
        netlog(c->p->f, Logudp, "udp: kick\n");
index 1942df4..abcb532 100644 (file)
@@ -64,10 +64,19 @@ int bind (int fd, __CONST_SOCKADDR_ARG a, socklen_t alen)
                close(cfd);
                return -1;
        }
-       close(cfd);
-
        if(lip->sin_port <= 0)
                _sock_ingetaddr(r, lip, &len, "local");
-
+       /* UDP sockets are in headers mode, and need to be announced.  This isn't a
+        * full announce, in that the kernel UDP stack doesn't expect someone to
+        * open the listen file or anything like that. */
+       if ((r->domain == PF_INET) && (r->stype == SOCK_DGRAM)) {
+               n = snprintf(msg, sizeof(msg), "announce *!%d", ntohs(lip->sin_port));
+               n = write(cfd, msg, n);
+               if (n < 0) {
+                       perror("bind-announce failed");
+                       return -1;
+               }
+       }
+       close(cfd);
        return 0;
 }
index 2696e00..d9032be 100644 (file)
@@ -46,6 +46,12 @@ int connect (int fd, __CONST_SOCKADDR_ARG a, socklen_t alen)
 
        switch(r->domain){
        case PF_INET:
+               /* UDP sockets are already announced (during bind), so we can't issue
+                * a connect message.  Either connect or announce, not both.  All sends
+                * will later do a sendto, based off the contents of r->raddr, so we're
+                * already done here */
+               if (r->stype == SOCK_DGRAM)
+                       return 0;
                /* set up a tcp or udp connection */
                cfd = open(r->ctl, O_RDWR);
                if(cfd < 0){
index 67afb98..62f766f 100644 (file)
 
 ssize_t send (int fd, __const void *a, size_t n, int flags)
 {
-       if(flags & MSG_OOB){
-               errno = EOPNOTSUPP;
-               return -1;
-       }
-       return write(fd, a, n);
+       return sendto(fd, a, n, flags, 0, 0);
 }
 
-ssize_t
-recv(int fd, void *a, size_t n, int flags)
+ssize_t recv(int fd, void *a, size_t n, int flags)
 {
-       if(flags & MSG_OOB){
-               errno = EOPNOTSUPP;
-               return -1;
-       }
-       return read(fd, a, n);
+       socklen_t dummy;
+       return recvfrom(fd, a, n, flags, 0, &dummy);
 }
index 42a1a65..ab0fab2 100644 (file)
@@ -11,6 +11,7 @@
 #include <unistd.h>
 #include <fcntl.h>
 #include <errno.h>
+#include <stdlib.h>
 
 /* bsd extensions */
 #include <sys/uio.h>
 
 #include "priv.h"
 
+/* The plan9 UDP header looks like:
+ *
+ * 52 bytes
+ *             raddr (16 b)
+ *             laddr (16 b)
+ *             IFC addr (ignored if user says it)  (16 b)
+ *             rport (2 b) (network ordering)
+ *             lport (ignored if user says it) (2b)
+ *
+ * The v4 addr format is 10 bytes of 0s, then two 0xff, then 4 bytes of addr. */
+
+#define P9_UDP_HDR_SZ 52
+/* Takes network-byte ordered IPv4 addr and writes it into buf, in the plan 9 IP
+ * addr format */
+static void naddr_to_plan9addr(uint32_t sin_addr, uint8_t *buf)
+{
+       uint8_t *sin_bytes = (uint8_t*)&sin_addr;
+       memset(buf, 0, 10);
+       buf += 10;
+       buf[0] = 0xff;
+       buf[1] = 0xff;
+       buf += 2;
+       buf[0] = sin_bytes[0];  /* e.g. 192 */
+       buf[1] = sin_bytes[1];  /* e.g. 168 */
+       buf[2] = sin_bytes[2];  /* e.g.   0 */
+       buf[3] = sin_bytes[3];  /* e.g.   1 */
+}
+
+/* does v4 only */
+static uint32_t plan9addr_to_naddr(uint8_t *buf)
+{
+       buf += 12;
+       return (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | (buf[0] << 0);
+}
+
+/* Returns a rock* if the socket exists and is UDP */
+static Rock *udp_sock_get_rock(int fd)
+{
+       Rock *r = _sock_findrock(fd, 0);
+       if (!r) {
+               errno = ENOTSOCK;
+               return 0;
+       }
+       if ((r->domain == PF_INET) && (r->stype == SOCK_DGRAM))
+               return r;
+       else
+               return 0;
+}
+
 ssize_t sendto (int fd, __const void *a, size_t n,
                        int flags, __CONST_SOCKADDR_ARG to,
                        socklen_t tolen)
 {
-       /* actually, should do connect if not done already */
-       return send(fd, a, n, flags);
+       Rock *r;
+       if (flags & MSG_OOB) {
+               errno = EOPNOTSUPP;
+               return -1;
+       }
+       /* UDP sockets need to have headers added to the payload for all packets,
+        * since we're supporting blind sendto/recvfrom. */
+       if ((r = udp_sock_get_rock(fd))) {
+               int ret;
+               uint32_t remote_addr;
+               uint16_t remote_port;
+               char *p, *newbuf;
+               /* Might not have a to if we were called from send() */
+               if (!to) {
+                       /* if they didn't connect yet, then there's no telling what raddr
+                        * will be.  TODO: check a state flag or something? */
+                       to = (struct sockaddr*)(&r->raddr);
+               }
+               remote_addr = ((struct sockaddr_in*)to)->sin_addr.s_addr;
+               remote_port = ((struct sockaddr_in*)to)->sin_port;
+               newbuf = malloc(n + P9_UDP_HDR_SZ);
+               if (!newbuf) {
+                       errno = ENOMEM;
+                       return -1;
+               }
+               p = newbuf;
+               naddr_to_plan9addr(remote_addr, p);
+               p += 16;
+               /* we don't need to specify an address.  if we don't specify a valid
+                * local IP addr, the kernel will pick the one closest to dest */
+               p += 16;
+               p += 16; /* skip ipifc */
+               *(uint16_t*)p = remote_port;
+               p += 2;
+               p += 2; /* skip local port */
+               memcpy(p, a, n);
+               ret = write(fd, newbuf, n + P9_UDP_HDR_SZ);
+               free(newbuf);
+               if (ret < 0)
+                       return -1;
+               return ret - P9_UDP_HDR_SZ;
+       }
+       return write(fd, a, n);
 }
 
-
 ssize_t recvfrom (int fd, void *__restrict a, size_t n,
                          int flags, __SOCKADDR_ARG from,
                          socklen_t *__restrict fromlen)
 {
-       if(getsockname(fd, from, fromlen) < 0)
+       Rock *r;
+       if (flags & MSG_OOB) {
+               errno = EOPNOTSUPP;
+               return -1;
+       }
+       if (from && getsockname(fd, from, fromlen) < 0)
                return -1;
-       return recv(fd, a, n, flags);
+       /* UDP sockets need to have headers added to the payload for all packets,
+        * since we're supporting blind sendto/recvfrom. */
+       if ((r = udp_sock_get_rock(fd))) {
+               int ret;
+               struct sockaddr_in *remote = (struct sockaddr_in*)from;
+               char *p, *newbuf = malloc(n + P9_UDP_HDR_SZ);
+               if (!newbuf) {
+                       errno = ENOMEM;
+                       return -1;
+               }
+               ret = read(fd, newbuf, n + P9_UDP_HDR_SZ);
+               /* subtracting before, so that we error out if we got less than the
+                * headers needed */
+               ret -= P9_UDP_HDR_SZ;
+               if (ret < 0) {
+                       free(newbuf);
+                       return -1;
+               }
+               memcpy(a, newbuf + P9_UDP_HDR_SZ, n);
+               /* Might not have a remote, if we were called via recv().  Could assert
+                * that it's the same remote that we think we connected to, and that we
+                * were already connected. (TODO) */
+               if (remote) {
+                       p = newbuf;
+                       remote->sin_addr.s_addr = plan9addr_to_naddr(p);
+                       p += 16;
+                       p += 16;        /* skip local addr */
+                       p += 16;        /* skip ipifc */
+                       remote->sin_port = (p[0] << 0) | (p[1] << 8);
+                       remote->sin_port = *(uint16_t*)p;
+                       *fromlen = sizeof(struct sockaddr_in);
+               }
+               free(newbuf);
+               return ret;
+       }
+       return read(fd, a, n);
 }
index 7dd926b..7c2294d 100644 (file)
@@ -124,6 +124,7 @@ socket(int domain, int stype, int protocol)
        int cfd, fd, n;
        int pfd[2];
        char *net;
+       char msg[128];
 
        switch(domain){
        case PF_INET:
@@ -132,6 +133,16 @@ socket(int domain, int stype, int protocol)
                case SOCK_DGRAM:
                        net = "udp";
                        cfd = open("/net/udp/clone", O_RDWR);
+                       /* All BSD UDP sockets are in 'headers' mode, where each packet has
+                        * the remote addr:port, local addr:port and other info. */
+                       if (!(cfd < 0)) {
+                               n = snprintf(msg, sizeof(msg), "headers");
+                               n = write(cfd, msg, n);
+                               if (n < 0) {
+                                       perror("UDP socket headers failed");
+                                       return -1;
+                               }
+                       }
                        break;
                case SOCK_STREAM:
                        net = "tcp";