From 84856762fd231cd3c3d48783e70182cfd6d9bb22 Mon Sep 17 00:00:00 2001 From: Gerrit Renker Date: Fri, 30 Nov 2007 10:23:51 +0100 Subject: [PATCH] 01_IPv6-Basic-Support.diff 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 --- dccp.c | 6 +- error.h | 1 + net.c | 174 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- net.h | 21 +++++++ 4 files changed, 193 insertions(+), 9 deletions(-) diff --git a/dccp.c b/dccp.c index 52782732..f5f940a6 100644 --- a/dccp.c +++ b/dccp.c @@ -18,11 +18,7 @@ #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 8a30110f..fada9b30 100644 --- 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 cffd250e..3de57f28 100644 --- a/net.c +++ b/net.c @@ -6,7 +6,7 @@ /** \file net.c Networking-related helper functions. */ -#include /* hostent */ +#include #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 8b5d4a1a..e78b7629 100644 --- a/net.h +++ b/net.h @@ -16,6 +16,27 @@ #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); -- 2.39.5