01_IPv6-Basic-Support.diff
authorGerrit Renker <gerrit@erg.abdn.ac.uk>
Fri, 30 Nov 2007 09:23:51 +0000 (10:23 +0100)
committerAndre Noll <maan@systemlinux.org>
Fri, 30 Nov 2007 09:23:51 +0000 (10:23 +0100)
This patch provides an algorithm which, given a hostname and a numeric port identifier,
will look up all matching IPv4/IPv6 addresses and either bind it (for passive sockets)
or connect to it (for active sockets).

Certain socket options need to be set before a connection is established. Since in
paraslash the demand for such options is not very big at this time, the only present
case (setting SO_REUSEADDR on passive sockets) has been integrated into the main loop.

A more sophisticated variant for setting pre-connection socket options exists and is
available as part of the DCCP library. The current solution is extensible, i.e. if one
wants to later add more options, the alternative (more sophisticated approach) can be
used. But for now, that would just have meant code bloat.

The subsequent patches in this set will step by step replace the old infrastructure
with the use of this algorithm.

Signed-off-by: Gerrit Renker <gerrit@erg.abdn.ac.uk>
dccp.c
error.h
net.c
net.h

diff --git a/dccp.c b/dccp.c
index 5278273..f5f940a 100644 (file)
--- a/dccp.c
+++ b/dccp.c
 #include "error.h"
 #include "dccp.h"
 #include "fd.h"
-
-/** \cond some magic dccp constants */
-#define SOCK_DCCP 6
-#define IPPROTO_DCCP 33
-/** \endcond */
+#include "net.h"
 
 /**
  * obtain a dccp socket for sending/receiving
diff --git a/error.h b/error.h
index 8a30110..fada9b3 100644 (file)
--- a/error.h
+++ b/error.h
@@ -176,6 +176,7 @@ extern const char **para_errlist[];
 #define NET_ERRORS \
        PARA_ERROR(CONNECT, "connect error"), \
        PARA_ERROR(NAME_TOO_LONG, "name too long for struct sockaddr_un"), \
+       PARA_ERROR(ADDRESS_LOOKUP, "address lookup / socket creation failed"), \
        PARA_ERROR(CHMOD, "failed to set socket mode"), \
        PARA_ERROR(SENDMSG, "sendmsg() failed"), \
        PARA_ERROR(RECVMSG, "recvmsg() failed"), \
diff --git a/net.c b/net.c
index cffd250..3de57f2 100644 (file)
--- a/net.c
+++ b/net.c
@@ -6,7 +6,7 @@
 
 /** \file net.c Networking-related helper functions. */
 
-#include <netdb.h> /* hostent */
+#include <netdb.h>
 
 #include "para.h"
 #include "error.h"
@@ -96,6 +96,175 @@ static void init_sockaddr(struct sockaddr_in *addr, int port, const struct hoste
        memset(&addr->sin_zero, '\0', 8);
 }
 
+/**
+ * Determine the socket type for a given layer-4 protocol.
+ *
+ * \param l4type The symbolic name of the transport-layer protocol.
+ *
+ * \sa ip(7), socket(2)
+ */
+static inline int sock_type(const unsigned l4type)
+{
+       switch (l4type) {
+       case IPPROTO_UDP:       return SOCK_DGRAM;
+       case IPPROTO_TCP:       return SOCK_STREAM;
+       case IPPROTO_DCCP:      return SOCK_DCCP;
+       }
+       return -1;              /* not supported here */
+}
+
+/**
+ * Pretty-print transport-layer name.
+ */
+static const char *layer4_name(const unsigned l4type)
+{
+       switch (l4type) {
+       case IPPROTO_UDP:       return "UDP";
+       case IPPROTO_TCP:       return "TCP";
+       case IPPROTO_DCCP:      return "DCCP";
+       }
+       return "UNKNOWN PROTOCOL";
+}
+
+/**
+ * Resolve IPv4/IPv6 address and create a ready-to-use active or passive socket.
+ *
+ * @param l3type       The layer-3 type (\p AF_INET, \p AF_INET6, \p AF_UNSPEC)
+ * @param l4type       The layer-4 type (\p IPPROTO_xxx).
+ * @param passive      Whether this is a passive (1) or active (0) socket/
+ * @param host         Remote or local hostname or IPv/6 address string.
+ * @param port_number   Decimal port number.
+ *
+ * This creates a ready-made IPv4/v6 socket structure after looking up the necessary
+ * parameters. The interpretation of \a host depends on the value of \a passive:
+ *   - on a passive socket host is interpreted as an interface IPv4/6 address
+ *     (can be left NULL);
+ *   - on an active socket, \a host is the peer DNS name or IPv4/6 address to connect to;
+ *   - \a port_number is in either case the numeric port number (not service string).
+ * Furthermore, bind(2) is called on passive sockets, and connect(2) on active sockets.
+ * The algorithm tries all possible address combinations until it succeeds.
+ *
+ * \return This function returns 1 on success and \a -E_ADDRESS_LOOKUP when no matching
+ * connection could be set up (with details in the error log).
+ *
+ *  \sa ipv6(7), getaddrinfo(3), bind(2), connect(2)
+ */
+int makesock(unsigned l3type, unsigned l4type, int passive,
+            const char *host, unsigned short port_number)
+{
+       struct addrinfo *local = NULL, *src,
+                       *remote = NULL, *dst, hints;
+       char            *port = make_message("%u", port_number);
+       int             rc, on = 1, sockfd = -1,
+                       socktype = sock_type(l4type);
+
+       /*
+        *      Set up address hint structure
+        */
+       memset(&hints, 0, sizeof(hints));
+       hints.ai_family = l3type;
+       /* getaddrinfo does not really work well with SOCK_DCCP */
+       if (socktype == SOCK_DGRAM || socktype == SOCK_STREAM)
+               hints.ai_socktype = socktype;
+
+       /* only use addresses available on the host */
+       hints.ai_flags = AI_ADDRCONFIG;
+       if (l3type == AF_INET6)
+               /* use v4-mapped-v6 if no v6 addresses found */
+               hints.ai_flags |= AI_V4MAPPED | AI_ALL;
+
+       if (passive && host == NULL)
+               hints.ai_flags |= AI_PASSIVE;
+
+       /*
+        *      Obtain local/remote address information
+        */
+       if ((rc = getaddrinfo(host, port, &hints, passive ? &local : &remote))) {
+               PARA_ERROR_LOG("can not resolve %s address %s#%s: %s.\n",
+                               layer4_name(l4type),
+                               host?  : (passive? "[loopback]" : "[localhost]"),
+                               port, gai_strerror(rc));
+               return -E_ADDRESS_LOOKUP;
+       }
+
+       /*
+        *      Iterate over all src/dst combination, exhausting dst first
+        */
+       for (src = local, dst = remote; src != NULL || dst != NULL; /* no op */ ) {
+               if (src && dst && src->ai_family == AF_INET
+                              && dst->ai_family == AF_INET6)   /* v4 -> v6 is not possible */
+                       goto get_next_dst;
+
+               sockfd = socket(src ? src->ai_family : dst->ai_family, socktype, l4type);
+               if (sockfd < 0)
+                       goto get_next_dst;
+
+               /*
+                * Set those options that need to be set before establishing the connection
+                */
+               /* Reuse the address on passive (listening) sockets to avoid failure on restart */
+               if (passive && setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
+                       PARA_ERROR_LOG("can not set SO_REUSEADDR: %s\n", strerror(errno));
+                       return -ERRNO_TO_PARA_ERROR(errno);
+               }
+
+               if (src) {
+                       if (bind(sockfd, src->ai_addr, src->ai_addrlen) < 0) {
+                               close(sockfd);
+                               goto get_next_src;
+                       }
+                       if (!dst)
+                               break;  /* bind-only completed successfully */
+               }
+
+               if (dst && connect(sockfd, dst->ai_addr, dst->ai_addrlen) == 0)
+                       break;          /* connection completed successfully */
+               close(sockfd);
+get_next_dst:
+               if (dst && (dst = dst->ai_next))
+                       continue;
+get_next_src:
+               if (src && (src = src->ai_next))
+                       dst = remote;   /* restart inner loop */
+       }
+       if (local)
+               freeaddrinfo(local);
+       if (remote)
+               freeaddrinfo(remote);
+
+       if (src == NULL && dst == NULL) {
+               PARA_ERROR_LOG("can not create %s socket %s#%s.\n", layer4_name(l4type),
+                               host?  : (passive? "[loopback]" : "[localhost]"), port);
+               return -ERRNO_TO_PARA_ERROR(errno);
+       }
+       return sockfd;
+}
+
+/**
+ * Create a passive / listening socket.
+ * \param l3type       The network-layer type (\p AF_xxx)
+ * \param l4type       The transport-layer type (\p IPPROTO_xxx).
+ * \param port         The decimal port number to listen on.
+ *
+ * \return Positive integer (socket descriptor) on success, negative value otherwise.
+ * \sa makesock(), ip(7), ipv6(7), bind(2), listen(2).
+ */
+int para_listen(unsigned l3type, unsigned l4type, unsigned short port)
+{
+       int ret, fd = makesock(l3type, l4type, 1, NULL, port);
+
+       if (fd > 0) {
+               ret = listen(fd, BACKLOG);
+               if (ret < 0) {
+                       close(fd);
+                       return -ERRNO_TO_PARA_ERROR(errno);
+               }
+               PARA_INFO_LOG("listening on %s port %u, fd %d\n",
+                             layer4_name(l4type), port, fd);
+       }
+       return fd;
+}
+
 /*
  * Send out a buffer, resend on short writes.
  *
@@ -510,9 +679,6 @@ int recv_cred_buffer(int fd, char *buf, size_t size)
 }
 #endif /* HAVE_UCRED */
 
-/** how many pending connections queue will hold */
-#define BACKLOG 10
-
 /**
  * Create a tcp socket, bind it and listen on the given port.
  *
diff --git a/net.h b/net.h
index 8b5d4a1..e78b762 100644 (file)
--- a/net.h
+++ b/net.h
 #define UNIX_PATH_MAX 108
 #endif
 
+/** \cond Userland defines for Linux DCCP support. */
+#ifndef IPPROTO_DCCP
+#define IPPROTO_DCCP   33      /**< IANA assigned value */
+#define SOCK_DCCP      6       /**< Linux socket type   */
+#define SOL_DCCP        269    /**< Linux socket level  */
+#endif
+/** \endcond */
+
+/**
+ * Generic socket creation (passive and active sockets).
+ */
+extern int makesock(unsigned l3type, unsigned l4type, int passive,
+                   const char *host, unsigned short port_number);
+
+/**
+ * Functions to support listening sockets.
+ */
+/** How many pending connections queue of a listening server will hold. */
+#define BACKLOG        10
+extern int para_listen(unsigned l3type, unsigned l4type, unsigned short port);
+
 /** used to crypt the communication between para_server and para_client */
 typedef void crypt_function(unsigned long len,
        const unsigned char *indata, unsigned char *outdata, void *private_data);