diff options
Diffstat (limited to 'lib/sockunion.c')
| -rw-r--r-- | lib/sockunion.c | 1289 |
1 files changed, 810 insertions, 479 deletions
diff --git a/lib/sockunion.c b/lib/sockunion.c index a5382a72..b34d7047 100644 --- a/lib/sockunion.c +++ b/lib/sockunion.c @@ -16,7 +16,7 @@ * You should have received a copy of the GNU General Public License * along with GNU Zebra; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * 02111-1307, USA. */ #include <zebra.h> @@ -28,6 +28,8 @@ #include "str.h" #include "log.h" +#include "symtab.h" + #ifndef HAVE_INET_ATON int inet_aton (const char *cp, struct in_addr *inaddr) @@ -95,13 +97,13 @@ inet_ntop (int family, const void *addrptr, char *strptr, size_t len) { unsigned char *p = (unsigned char *) addrptr; - if (family == AF_INET) + if (family == AF_INET) { char temp[INET_ADDRSTRLEN]; snprintf(temp, sizeof(temp), "%d.%d.%d.%d", p[0], p[1], p[2], p[3]); - if (strlen(temp) >= len) + if (strlen(temp) >= len) { errno = ENOSPC; return NULL; @@ -115,460 +117,563 @@ inet_ntop (int family, const void *addrptr, char *strptr, size_t len) } #endif /* ! HAVE_INET_NTOP */ -const char * -inet_sutop (union sockunion *su, char *str) +/*------------------------------------------------------------------------------ + * Set the sockunion size (sin_len or sin6_len), if required. + * + * NB: POSIX does not require this and Stevens et al say that even where it + * is supported, the application need not worry about it. + * + * However... the code as found does this. + * + * TODO: is it *really* necessary to set sin_len or sin6_len ?? + * + * Returns: the sockunion size + */ +inline static int +sockunion_sin_len(sockunion su) +{ + return +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + su->sin.sin_len = +#endif + sizeof(struct sockaddr_in); +} ; + +#ifdef HAVE_IPV6 +inline static int +sockunion_sin6_len(sockunion su) +{ + return +#ifdef SIN6_LEN + su->sin6.sin6_len = +#endif + sizeof(struct sockaddr_in6); +} ; +#endif + +/*------------------------------------------------------------------------------ + * Set the address family for the given sockunion. + * + * If sin_len or sin6_len entry is present, fill that in too. + * + * Assumes the address family is valid ! + * + * Returns: 0 + */ +inline static int +sockunion_set_family(sockunion su, sa_family_t family) +{ + su->sa.sa_family = family ; + +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + if (family == AF_INET) + sockunion_sin_len(su) ; +#endif +#if defined(HAVE_IPV6) && defined(SIN6_LEN) + if (family == AF_INET6) + sockunion_sin6_len(su) ; +#endif + + return 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Set the given sockunion address to "any" + */ +static void +sockunion_set_addr_any(sockunion su) { switch (su->sa.sa_family) - { + { case AF_INET: - inet_ntop (AF_INET, &su->sin.sin_addr, str, INET_ADDRSTRLEN); - break; + su->sin.sin_addr.s_addr = htonl (INADDR_ANY); + return ; + #ifdef HAVE_IPV6 case AF_INET6: - inet_ntop (AF_INET6, &su->sin6.sin6_addr, str, INET6_ADDRSTRLEN); - break; -#endif /* HAVE_IPV6 */ - } - return str; -} +# if defined(LINUX_IPV6) || defined(NRL) + memset (&su->sin6.sin6_addr, 0, sizeof (struct in6_addr)); +# else + su->sin6.sin6_addr = in6addr_any; +# endif /* LINUX_IPV6 || defined(NRL) */ + return ; +#endif -int -str2sockunion (const char *str, union sockunion *su) -{ - int ret; + default: + return ; + } ; +} ; - memset (su, 0, sizeof (union sockunion)); +/*------------------------------------------------------------------------------ + * Set the port number in the given sockunion. + * + * For good measure, set the size (if that's required) and return same. + */ +extern int +sockunion_set_port(sockunion su, in_port_t port) +{ + switch (su->sa.sa_family) + { + case AF_INET: + su->sin.sin_port = htons(port) ; + return sockunion_sin_len(su) ; - ret = inet_pton (AF_INET, str, &su->sin.sin_addr); - if (ret > 0) /* Valid IPv4 address format. */ - { - su->sin.sin_family = AF_INET; -#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN - su->sin.sin_len = sizeof(struct sockaddr_in); -#endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ - return 0; - } #ifdef HAVE_IPV6 - ret = inet_pton (AF_INET6, str, &su->sin6.sin6_addr); - if (ret > 0) /* Valid IPv6 address format. */ - { - su->sin6.sin6_family = AF_INET6; -#ifdef SIN6_LEN - su->sin6.sin6_len = sizeof(struct sockaddr_in6); -#endif /* SIN6_LEN */ - return 0; - } -#endif /* HAVE_IPV6 */ - return -1; -} + case AF_INET6: + su->sin6.sin6_port = htons(port) ; + return sockunion_sin6_len(su) ; +#endif -const char * -sockunion2str (union sockunion *su, char *buf, size_t len) + default: + return 0 ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Initialise a new sockunion -- for the given address family (if any) + * + * Allocates a sockunion if required. + * + * Result is set "any". + * + * Advice is to zeroize sockaddr_in6, in particular. + */ +extern sockunion +sockunion_init_new(sockunion su, sa_family_t family) { - if (su->sa.sa_family == AF_INET) - return inet_ntop (AF_INET, &su->sin.sin_addr, buf, len); + if (su == NULL) + su = XCALLOC(MTYPE_SOCKUNION, sizeof(union sockunion)) ; + else + memset(su, 0, sizeof(union sockunion)) ; + + if (family != AF_UNSPEC) + sockunion_set_family(su, family) ; + else + confirm(AF_UNSPEC == 0) ; + + return su ; +} ; + +/*------------------------------------------------------------------------------ + * Get the length of the address in the given sockunion. + * + * Returns zero if AF_UNSPEC or not any known address family. + */ +extern int +sockunion_get_len(sockunion su) +{ + switch (su->sa.sa_family) + { + case AF_INET: + return sizeof(struct sockaddr_in) ; + #ifdef HAVE_IPV6 - else if (su->sa.sa_family == AF_INET6) - return inet_ntop (AF_INET6, &su->sin6.sin6_addr, buf, len); -#endif /* HAVE_IPV6 */ - return NULL; -} + case AF_INET6: + return sizeof(struct sockaddr_in6) ; +#endif -union sockunion * -sockunion_str2su (const char *str) + default: + return 0 ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * From the given string, fill in the given sockunion. + * + * Returns: 0 => OK -- sockunion filled in + * -1 => not a valid address (or not a known address family) + */ +int +str2sockunion (const char *str, union sockunion *su) { int ret; - union sockunion *su; - su = XCALLOC (MTYPE_SOCKUNION, sizeof (union sockunion)); + assert(su != NULL) ; + + sockunion_init_new(su, AF_UNSPEC) ; ret = inet_pton (AF_INET, str, &su->sin.sin_addr); if (ret > 0) /* Valid IPv4 address format. */ - { - su->sin.sin_family = AF_INET; -#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN - su->sin.sin_len = sizeof(struct sockaddr_in); -#endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ - return su; - } + return sockunion_set_family(su, AF_INET) ; + #ifdef HAVE_IPV6 ret = inet_pton (AF_INET6, str, &su->sin6.sin6_addr); if (ret > 0) /* Valid IPv6 address format. */ - { - su->sin6.sin6_family = AF_INET6; -#ifdef SIN6_LEN - su->sin6.sin6_len = sizeof(struct sockaddr_in6); -#endif /* SIN6_LEN */ - return su; - } + return sockunion_set_family(su, AF_INET6) ; #endif /* HAVE_IPV6 */ - XFREE (MTYPE_SOCKUNION, su); - return NULL; + return -1; } -char * -sockunion_su2str (union sockunion *su) +/*------------------------------------------------------------------------------ + * Construct string for sockunion IP address. + * + * Requires buffer of at least SU_ADDRSTRLEN characters. + */ +const char * +sockunion2str (union sockunion *su, char *buf, size_t size) { - char str[SU_ADDRSTRLEN]; + assert(size >= SU_ADDRSTRLEN) ; switch (su->sa.sa_family) - { + { case AF_INET: - inet_ntop (AF_INET, &su->sin.sin_addr, str, sizeof (str)); + inet_ntop (AF_INET, &su->sin.sin_addr, buf, size); break; #ifdef HAVE_IPV6 case AF_INET6: - inet_ntop (AF_INET6, &su->sin6.sin6_addr, str, sizeof (str)); + inet_ntop (AF_INET6, &su->sin6.sin6_addr, buf, size); break; #endif /* HAVE_IPV6 */ - } - return XSTRDUP (MTYPE_TMP, str); + default: + snprintf (buf, size, "?af=%d?", (int)su->sa.sa_family) ; + } ; + + return buf; } -/* Convert IPv4 compatible IPv6 address to IPv4 address. */ -static void -sockunion_normalise_mapped (union sockunion *su) +/*------------------------------------------------------------------------------ + * Fill in and return a sockunion_string + */ +extern sockunion_string_t +sutoa(sockunion su) { - struct sockaddr_in sin; - -#ifdef HAVE_IPV6 - if (su->sa.sa_family == AF_INET6 - && IN6_IS_ADDR_V4MAPPED (&su->sin6.sin6_addr)) - { - memset (&sin, 0, sizeof (struct sockaddr_in)); - sin.sin_family = AF_INET; - sin.sin_port = su->sin6.sin6_port; - memcpy (&sin.sin_addr, ((char *)&su->sin6.sin6_addr) + 12, 4); - memcpy (su, &sin, sizeof (struct sockaddr_in)); - } -#endif /* HAVE_IPV6 */ -} + sockunion_string_t sus ; -/* Return socket of sockunion. */ -int -sockunion_socket (union sockunion *su) + sockunion2str(su, sus.str, sizeof(sus.str)) ; + return sus ; +} ; + +/*------------------------------------------------------------------------------ + * From the given string, construct and fill in a sockunion. + * + * Returns: NULL => not a valid address (or not a known address family) + * otherwise is address of new sockunion. + * + * NB: the caller is responsible for freeing the sockunion created. + */ +union sockunion * +sockunion_str2su (const char *str) { - int sock; + union sockunion *su; - sock = socket (su->sa.sa_family, SOCK_STREAM, 0); - if (sock < 0) - { - zlog (NULL, LOG_WARNING, "Can't make socket : %s", safe_strerror (errno)); - return -1; - } + su = XMALLOC (MTYPE_SOCKUNION, sizeof(union sockunion)); - return sock; + if (str2sockunion (str, su) != 0) + XFREE (MTYPE_SOCKUNION, su); /* sets su = NULL */ + + return su ; } -/* Return accepted new socket file descriptor. */ -int -sockunion_accept (int sock, union sockunion *su) +/*------------------------------------------------------------------------------ + * Convert given sockunion to string, and return a new piece of memory + * containing same. + * + * It is the callers responsibility to free the memory in due course. + */ +extern char * +sockunion_su2str (union sockunion *su, enum MTYPE type) { - socklen_t len; - int client_sock; - - len = sizeof (union sockunion); - client_sock = accept (sock, (struct sockaddr *) su, &len); - - sockunion_normalise_mapped (su); - return client_sock; + return XSTRDUP (type, sutoa(su).str) ; } -/* Return sizeof union sockunion. */ -static int -sockunion_sizeof (union sockunion *su) +/*------------------------------------------------------------------------------ + * If have an IPv6 mapped IPv4 address, convert it to an IPv4 address. + */ +extern void +sockunion_unmap_ipv4 (sockunion su) { - int ret; - - ret = 0; - switch (su->sa.sa_family) - { - case AF_INET: - ret = sizeof (struct sockaddr_in); - break; #ifdef HAVE_IPV6 - case AF_INET6: - ret = sizeof (struct sockaddr_in6); - break; -#endif /* AF_INET6 */ + if ( (sockunion_family(su) == AF_INET6) + && IN6_IS_ADDR_V4MAPPED (&su->sin6.sin6_addr) ) + { + union sockunion sux[1] ; + + sockunion_init_new(sux, AF_INET) ; + memcpy (&sux->sin.sin_addr, &su->sin6.sin6_addr.s6_addr[12], 4) ; + sux->sin.sin_port = su->sin6.sin6_port ; + memcpy (su, sux, sizeof(*sux)) ; + confirm(sizeof(*su) == sizeof(*sux)) ; } - return ret; +#endif /* HAVE_IPV6 */ } -/* return sockunion structure : this function should be revised. */ -static char * -sockunion_log (union sockunion *su) +/*------------------------------------------------------------------------------ + * If have an IPv4 address, convert it to an IPv6 mapped IPv4 address. + */ +extern void +sockunion_map_ipv4 (sockunion su) { - static char buf[SU_ADDRSTRLEN]; - - switch (su->sa.sa_family) - { - case AF_INET: - snprintf (buf, SU_ADDRSTRLEN, "%s", inet_ntoa (su->sin.sin_addr)); - break; #ifdef HAVE_IPV6 - case AF_INET6: - snprintf (buf, SU_ADDRSTRLEN, "%s", - inet_ntop (AF_INET6, &(su->sin6.sin6_addr), buf, SU_ADDRSTRLEN)); - break; -#endif /* HAVE_IPV6 */ - default: - snprintf (buf, SU_ADDRSTRLEN, "af_unknown %d ", su->sa.sa_family); - break; + if (sockunion_family(su) == AF_INET) + { + union sockunion sux[1] ; + + sockunion_init_new(sux, AF_INET6) ; + memset (&sux->sin6.sin6_addr.s6_addr[10], 0xFF, 2) ; + memcpy (&sux->sin6.sin6_addr.s6_addr[12], &su->sin.sin_addr, 4) ; + sux->sin6.sin6_port = su->sin.sin_port ; + memcpy (su, sux, sizeof(*sux)) ; + confirm(sizeof(*su) == sizeof(*sux)) ; } - return (XSTRDUP (MTYPE_TMP, buf)); +#endif /* HAVE_IPV6 */ } -/* sockunion_connect returns - -1 : error occured - 0 : connect success - 1 : connect is in progress */ -enum connect_result -sockunion_connect (int fd, union sockunion *peersu, unsigned short port, - unsigned int ifindex) +/*------------------------------------------------------------------------------ + * Return accepted new socket file descriptor. + * + * The following errors should be ignored: + * + * EAGAIN, EWOULDBLOCK or ECONNABORTED -- connection aborted before got + * around to it (or not ready, anyway). + * + * EINTR -- the usual suspect. + * + * Sets the given sockunion to the result of the accept(), converting any + * IPv6 mapped IPv4 addresses to IPv4 form. (Hiding the family for the socket.) + * + * Returns: >= 0 -- OK, this is the fd (socket) + * -1 -- error -- not one of the above + * -2 -- error -- one of the above + */ +extern int +sockunion_accept (int sock_fd, union sockunion *su) { - int ret; - int val; - union sockunion su; - - memcpy (&su, peersu, sizeof (union sockunion)); - - switch (su.sa.sa_family) - { - case AF_INET: - su.sin.sin_port = port; - break; -#ifdef HAVE_IPV6 - case AF_INET6: - su.sin6.sin6_port = port; -#ifdef KAME - if (IN6_IS_ADDR_LINKLOCAL(&su.sin6.sin6_addr) && ifindex) - { -#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_SCOPE_ID - /* su.sin6.sin6_scope_id = ifindex; */ -#ifdef MUSICA - su.sin6.sin6_scope_id = ifindex; -#endif -#endif /* HAVE_STRUCT_SOCKADDR_IN6_SIN6_SCOPE_ID */ -#ifndef MUSICA - SET_IN6_LINKLOCAL_IFINDEX (su.sin6.sin6_addr, ifindex); -#endif - } -#endif /* KAME */ - break; -#endif /* HAVE_IPV6 */ - } - - /* Make socket non-block. */ - val = fcntl (fd, F_GETFL, 0); - fcntl (fd, F_SETFL, val|O_NONBLOCK); + socklen_t len; + int new_fd, err ; - /* Call connect function. */ - ret = connect (fd, (struct sockaddr *) &su, sockunion_sizeof (&su)); + len = sizeof(*su); + memset(su, 0, len) ; + new_fd = accept(sock_fd, &su->sa, &len) ; - /* Immediate success */ - if (ret == 0) + if (new_fd >= 0) { - fcntl (fd, F_SETFL, val); - return connect_success; - } + sockunion_unmap_ipv4(su); + return new_fd ; /* OK -- got socket */ + } ; + + err = errno ; + return ( (err == EAGAIN) + || (err == EWOULDBLOCK) + || (err == ECONNABORTED) + || (err == EINTR) ) ? -2 : -1 ; +} ; + +/*------------------------------------------------------------------------------ + * Make socket for given family, type and protocol + * + * Returns: -1 : failed -- see errno + * otherwise : socket + * + * Logs a LOG_ERR message if fails. + */ +extern int +sockunion_socket(sockunion su, int type, int protocol) +{ + int sock_fd ; + int err ; - /* If connect is in progress then return 1 else it's real error. */ - if (ret < 0) - { - if (errno != EINPROGRESS) - { - zlog_info ("can't connect to %s fd %d : %s", - sockunion_log (&su), fd, safe_strerror (errno)); - return connect_error; - } - } + sock_fd = socket(sockunion_family(su), type, protocol); - fcntl (fd, F_SETFL, val); + if (sock_fd >= 0) + return sock_fd ; - return connect_in_progress; + err = errno ; + zlog_err("Cannot make socket family=%d, type=%d, protocol=%d: %s", + (int)sockunion_family(su), type, protocol, errtoa(err, 0).str) ; + errno = err ; + return -1; } -/* Make socket from sockunion union. */ -int -sockunion_stream_socket (union sockunion *su) +/*------------------------------------------------------------------------------ + * Make socket for family from given sockunion, type=SOCK_STREAM, protocol=0. + * + * Returns: -1 : failed -- see errno + * otherwise : socket + * + * Logs a LOG_ERR message if fails. + */ +extern int +sockunion_stream_socket (sockunion su) { - int sock; - if (su->sa.sa_family == 0) su->sa.sa_family = AF_INET_UNION; - sock = socket (su->sa.sa_family, SOCK_STREAM, 0); - - if (sock < 0) - zlog (NULL, LOG_WARNING, "can't make socket sockunion_stream_socket"); - - return sock; + return sockunion_socket (su, SOCK_STREAM, 0); } -/* Bind socket to specified address. */ -int -sockunion_bind (int sock, union sockunion *su, unsigned short port, - union sockunion *su_addr) +/*------------------------------------------------------------------------------ + * Initiate a connection + * + * Reports EINPROGRESS as success. + * + * TODO: discover how the ifindex thing is supposed to work !! + * + * Returns: 0 : OK (so far so good) + * < 0 : failed -- see errno + * + * Logs a LOG_INFO message if fails. + */ +extern int +sockunion_connect(int sock_fd, union sockunion* peer_su, unsigned short port, + unsigned int ifindex) { - int size = 0; - int ret; + union sockunion su ; + int ret, err ; + int sa_len ; + + memcpy(&su, peer_su, sizeof(union sockunion)) ; + + sa_len = sockunion_set_port(&su, port) ; - if (su->sa.sa_family == AF_INET) - { - size = sizeof (struct sockaddr_in); - su->sin.sin_port = htons (port); -#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN - su->sin.sin_len = size; -#endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ - if (su_addr == NULL) - su->sin.sin_addr.s_addr = htonl (INADDR_ANY); - } #ifdef HAVE_IPV6 - else if (su->sa.sa_family == AF_INET6) +# ifdef KAME + if (su.sa.sa_family == AF_INET6) { - size = sizeof (struct sockaddr_in6); - su->sin6.sin6_port = htons (port); -#ifdef SIN6_LEN - su->sin6.sin6_len = size; -#endif /* SIN6_LEN */ - if (su_addr == NULL) + if (IN6_IS_ADDR_LINKLOCAL(&su.sin6.sin6_addr) && ifindex) { -#if defined(LINUX_IPV6) || defined(NRL) - memset (&su->sin6.sin6_addr, 0, sizeof (struct in6_addr)); -#else - su->sin6.sin6_addr = in6addr_any; -#endif /* LINUX_IPV6 */ +# ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_SCOPE_ID + /* su.sin6.sin6_scope_id = ifindex; */ +# ifdef MUSICA + su.sin6.sin6_scope_id = ifindex; +# endif +# endif /* HAVE_STRUCT_SOCKADDR_IN6_SIN6_SCOPE_ID */ +# ifndef MUSICA + SET_IN6_LINKLOCAL_IFINDEX (su.sin6.sin6_addr, ifindex); +# endif } - } + } ; +# endif /* KAME */ #endif /* HAVE_IPV6 */ - - ret = bind (sock, (struct sockaddr *)su, size); - if (ret < 0) - zlog (NULL, LOG_WARNING, "can't bind socket : %s", safe_strerror (errno)); + ret = connect(sock_fd, &su.sa, sa_len) ; + err = (ret >= 0) ? 0 : errno ; - return ret; -} + if ((err == 0) || (err == EINPROGRESS)) + return 0 ; /* instant success or EINPROGRESS as expected */ -int -sockopt_reuseaddr (int sock) -{ - int ret; - int on = 1; + zlog_info("cannot connect to %s port %d socket %d: %s", + sutoa(&su).str, port, sock_fd, errtoa(err, 0).str) ; + errno = err ; - ret = setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, - (void *) &on, sizeof (on)); - if (ret < 0) - { - zlog (NULL, LOG_WARNING, "can't set sockopt SO_REUSEADDR to socket %d", sock); - return -1; - } - return 0; -} + return ret ; +} ; -#ifdef SO_REUSEPORT -int -sockopt_reuseport (int sock) +/*------------------------------------------------------------------------------ + * Start listening on given socket + * + * Returns: >= 0 : OK (so far so good) + * < 0 : failed -- see errno + * + * Logs a LOG_ERR message if fails. + */ +extern int +sockunion_listen(int sock_fd, int backlog) { - int ret; - int on = 1; + int ret ; + + ret = listen(sock_fd, backlog) ; - ret = setsockopt (sock, SOL_SOCKET, SO_REUSEPORT, - (void *) &on, sizeof (on)); if (ret < 0) { - zlog (NULL, LOG_WARNING, "can't set sockopt SO_REUSEPORT to socket %d", sock); - return -1; - } - return 0; -} -#else -int -sockopt_reuseport (int sock) -{ - return 0; -} -#endif /* 0 */ + int err = errno ; + zlog_err("cannot listen on socket %d: %s", sock_fd, errtoa(err, 0).str) ; + errno = err ; + } ; -int -sockopt_ttl (int family, int sock, int ttl) + return ret ; +} ; + +/*------------------------------------------------------------------------------ + * Bind socket to address/port. + * + * If the 'any' parameter is true, sets the given sockunion to INADDR_ANY or + * the *socket* address family equivalent. + * + * Sets the given port into the sockunion su. + * + * For good measure, sets sin_len or family equivalent if required. + * + * If not 'any', and the given su does not have the same address family as the + * socket, then attempts to convert the su to the same family as the socket, + * by mapping or unmapping IPv4. + * + * Performs bind() and logs a LOG_WARNING message if fails. + * + * Returns: >= 0 => OK + * < 0 => failed -- see errno + */ +extern int +sockunion_bind(int sock_fd, sockunion su, unsigned short port, bool any) { - int ret; + int sa_len ; + int ret ; + int sock_family ; -#ifdef IP_TTL - if (family == AF_INET) + sock_family = sockunion_getsockfamily(sock_fd) ; + + if (any) { - ret = setsockopt (sock, IPPROTO_IP, IP_TTL, - (void *) &ttl, sizeof (int)); - if (ret < 0) - { - zlog (NULL, LOG_WARNING, "can't set sockopt IP_TTL %d to socket %d", ttl, sock); - return -1; - } - return 0; + /* Create an "any" -- of same family as the socket */ + sockunion_init_new(su, sock_family) ; + sockunion_set_addr_any(su) ; } -#endif /* IP_TTL */ -#ifdef HAVE_IPV6 - if (family == AF_INET6) + else { - ret = setsockopt (sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, - (void *) &ttl, sizeof (int)); - if (ret < 0) - { - zlog (NULL, LOG_WARNING, "can't set sockopt IPV6_UNICAST_HOPS %d to socket %d", - ttl, sock); - return -1; - } - return 0; - } -#endif /* HAVE_IPV6 */ - return 0; -} - -int -sockopt_cork (int sock, int onoff) -{ -#ifdef TCP_CORK - return setsockopt (sock, IPPROTO_TCP, TCP_CORK, &onoff, sizeof(onoff)); -#else - return 0; + /* Want to bind to a specific address. + * + * We provide bind with an address which matches the address family of + * the *socket*. + * + * If the socket is AF_INET, address may be AF_INET, or an AF_INET6 + * *provided* it is an IPv4 mapped address. + * + * If the socket is AF_INET6, address may be AF_INET or AF_NET6, and + * will map any IPv4 address. + * + * If we don't HAVE_IPV6, or we don't recognise an address family, + * then do nothing and let bind() return some sort of error. + */ +#ifdef HAVE_IPV6 + if (sock_family != sockunion_family(su)) + { + switch (sock_family) + { + case AF_INET: + sockunion_unmap_ipv4(su) ; /* unmap if AF_INET6 mapped IPv4 */ + break ; + + case AF_INET6: + sockunion_map_ipv4(su) ; /* map if AF_INET */ + break ; + + default: + break ; + } ; + } ; #endif -} + } ; -int -sockopt_minttl (int family, int sock, int minttl) -{ -#ifdef IP_MINTTL - if (family == AF_INET) - { - int ret = setsockopt (sock, IPPROTO_IP, IP_MINTTL, &minttl, sizeof(minttl)); - if (ret < 0) - zlog (NULL, LOG_WARNING, - "can't set sockopt IP_MINTTL to %d on socket %d: %s", - minttl, sock, safe_strerror (errno)); - return ret; - } -#endif /* IP_MINTTL */ -#ifdef IPV6_MINHOPCNT - if (family == AF_INET6) + sa_len = sockunion_set_port(su, port) ; + + ret = bind (sock_fd, &su->sa, sa_len); + if (ret < 0) { - int ret = setsockopt (sock, IPPROTO_IPV6, IPV6_MINHOPCNT, &minttl, sizeof(minttl)); - if (ret < 0) - zlog (NULL, LOG_WARNING, - "can't set sockopt IPV6_MINHOPCNT to %d on socket %d: %s", - minttl, sock, safe_strerror (errno)); - return ret; - } -#endif + int err = errno ; + zlog_warn("cannot bind to %s port %d socket %d: %s", + sutoa(su).str, port, sock_fd, errtoa(err, 0).str) ; + errno = err ; + } ; - errno = EOPNOTSUPP; - return -1; + return ret; } -/* If same family and same prefix return 1. */ -int +/*------------------------------------------------------------------------------ + * If same (known) family and same prefix return 1, otherwise return 0. + * + * Returns 0 if same family, but not a known family. + */ +extern int sockunion_same (union sockunion *su1, union sockunion *su2) { int ret = 0; @@ -577,126 +682,174 @@ sockunion_same (union sockunion *su1, union sockunion *su2) return 0; switch (su1->sa.sa_family) - { + { case AF_INET: - ret = memcmp (&su1->sin.sin_addr, &su2->sin.sin_addr, - sizeof (struct in_addr)); - break; + return (su1->sin.sin_addr.s_addr == su2->sin.sin_addr.s_addr) ; + #ifdef HAVE_IPV6 case AF_INET6: ret = memcmp (&su1->sin6.sin6_addr, &su2->sin6.sin6_addr, - sizeof (struct in6_addr)); - break; + sizeof (struct in6_addr)); + return (ret == 0) ; #endif /* HAVE_IPV6 */ - } - if (ret == 0) - return 1; - else - return 0; -} -/* After TCP connection is established. Get local address and port. */ -union sockunion * -sockunion_getsockname (int fd) + default: + return 0 ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Get local (getsockname) or remote (getpeername) address and port. + * + * Returns: >= 0 == the address family (AF_UNSPEC if fd sock_fd < 0) + * < 0 => failed -- see errno + * + * If "unmap": if address is an IPv4 mapped IPv6 address, returns AF_INET. + * + * NB: returns EAFNOSUPPORT if don't recognise the address family. + * + * Logs a LOG_ERR message if fails in getsockname/getpeername. + */ +static int +sockunion_get_name(int sock_fd, union sockunion* su, bool local, bool unmap) { - int ret; - socklen_t len; + int ret ; + socklen_t len ; union { - struct sockaddr sa; - struct sockaddr_in sin; -#ifdef HAVE_IPV6 - struct sockaddr_in6 sin6; -#endif /* HAVE_IPV6 */ + union sockunion su ; char tmp_buffer[128]; - } name; - union sockunion *su; + } name ; + + memset(su, 0, sizeof(union sockunion)) ; + + confirm(AF_UNSPEC == 0) ; - memset (&name, 0, sizeof name); - len = sizeof name; + if (sock_fd < 0) + return AF_UNSPEC ; + + len = sizeof(name) ; + memset(&name, 0, len); + + if (local) + ret = getsockname(sock_fd, &name.su.sa, &len) ; + else + ret = getpeername(sock_fd, &name.su.sa, &len) ; - ret = getsockname (fd, (struct sockaddr *)&name, &len); if (ret < 0) { - zlog_warn ("Can't get local address and port by getsockname: %s", - safe_strerror (errno)); - return NULL; + int err = errno ; + zlog_err("failed in %s for socket %d: %s", + local ? "getsockname" : "getpeername", + sock_fd, errtoa(err, 0).str) ; + errno = err ; } - - if (name.sa.sa_family == AF_INET) + else { - su = XCALLOC (MTYPE_SOCKUNION, sizeof (union sockunion)); - memcpy (su, &name, sizeof (struct sockaddr_in)); - return su; - } + ret = name.su.sa.sa_family ; + + switch (ret) + { + case AF_INET: + su->sin = name.su.sin ; + break ; + #ifdef HAVE_IPV6 - if (name.sa.sa_family == AF_INET6) - { - su = XCALLOC (MTYPE_SOCKUNION, sizeof (union sockunion)); - memcpy (su, &name, sizeof (struct sockaddr_in6)); - sockunion_normalise_mapped (su); - return su; - } + case AF_INET6: + su->sin6 = name.su.sin6 ; + if (unmap) + sockunion_unmap_ipv4(su) ; + break ; #endif /* HAVE_IPV6 */ - return NULL; -} -/* After TCP connection is established. Get remote address and port. */ -union sockunion * -sockunion_getpeername (int fd) + default: + errno = EAFNOSUPPORT ; + ret = -1 ; + } ; + } ; + + return ret ; +} ; + +/*------------------------------------------------------------------------------ + * Get the address family the given socket is set to. + * + * Returns: >= 0 == the address family (AF_UNSPEC if fd sock_fd < 0) + * < 0 => failed -- see errno + * + * NB: gets the actual address family -- does NOT look for mapped IPv4. + */ +extern int +sockunion_getsockfamily(int sock_fd) { - int ret; - socklen_t len; - union - { - struct sockaddr sa; - struct sockaddr_in sin; -#ifdef HAVE_IPV6 - struct sockaddr_in6 sin6; -#endif /* HAVE_IPV6 */ - char tmp_buffer[128]; - } name; - union sockunion *su; + union sockunion su[1] ; + int ret ; - memset (&name, 0, sizeof name); - len = sizeof name; - ret = getpeername (fd, (struct sockaddr *)&name, &len); - if (ret < 0) - { - zlog (NULL, LOG_WARNING, "Can't get remote address and port: %s", - safe_strerror (errno)); - return NULL; - } + ret = sockunion_get_name(sock_fd, su, true, /* true => local */ + false) ; /* false => don't unmap */ + return (ret >= 0) ? sockunion_family(su) : ret ; +} ; - if (name.sa.sa_family == AF_INET) - { - su = XCALLOC (MTYPE_SOCKUNION, sizeof (union sockunion)); - memcpy (su, &name, sizeof (struct sockaddr_in)); - return su; - } -#ifdef HAVE_IPV6 - if (name.sa.sa_family == AF_INET6) - { - su = XCALLOC (MTYPE_SOCKUNION, sizeof (union sockunion)); - memcpy (su, &name, sizeof (struct sockaddr_in6)); - sockunion_normalise_mapped (su); - return su; - } -#endif /* HAVE_IPV6 */ - return NULL; -} +/*------------------------------------------------------------------------------ + * Get the address family the given socket's protocol is set to. + * + * If this is an AF_INET, that's easy. + * + * If this is an AF_INET6, then needs to look out for IN6_IS_ADDR_V4MAPPED. + * + * Returns: >= 0 == the address family (AF_UNSPEC if fd sock_fd < 0) + * < 0 => failed -- see errno + * + * NB: gets the underlying address family -- ie: looks for mapped IPv4. + */ +extern int +sockunion_getprotofamily(int sock_fd) +{ + union sockunion su[1] ; + int ret ; + + ret = sockunion_get_name(sock_fd, su, true, /* true => local */ + true) ; /* true => unmap */ + return (ret >= 0) ? sockunion_family(su) : ret ; +} ; + +/*------------------------------------------------------------------------------ + * Get local address and port -- ie getsockname(), except unmaps IPv4 mapped. + * + * See: sockunion_get_name() + */ +extern int +sockunion_getsockname(int sock_fd, sockunion su_local) +{ + return sockunion_get_name(sock_fd, su_local, true, /* true => local */ + true) ; /* true => unmap */ +} ; + +/*------------------------------------------------------------------------------ + * Get remote address and port -- ie getpeername(), except unmaps IPv4 mapped. + * + * See: sockunion_get_name() + */ +extern int +sockunion_getpeername (int sock_fd, sockunion su_remote) +{ + return sockunion_get_name(sock_fd, su_remote, false, /* false => remote */ + true) ; /* true => unmap */ +} ; -/* Print sockunion structure */ +/*------------------------------------------------------------------------------ + * Print sockunion structure to stdout + */ static void __attribute__ ((unused)) sockunion_print (union sockunion *su) { if (su == NULL) return; - switch (su->sa.sa_family) + switch (su->sa.sa_family) { case AF_INET: - printf ("%s\n", inet_ntoa (su->sin.sin_addr)); + printf ("%s\n", safe_inet_ntoa (su->sin.sin_addr)); break; #ifdef HAVE_IPV6 case AF_INET6: @@ -725,27 +878,15 @@ sockunion_print (union sockunion *su) } } -#ifdef HAVE_IPV6 -static int -in6addr_cmp (struct in6_addr *addr1, struct in6_addr *addr2) -{ - unsigned int i; - u_char *p1, *p2; - - p1 = (u_char *)addr1; - p2 = (u_char *)addr2; - - for (i = 0; i < sizeof (struct in6_addr); i++) - { - if (p1[i] > p2[i]) - return 1; - else if (p1[i] < p2[i]) - return -1; - } - return 0; -} -#endif /* HAVE_IPV6 */ - +/*------------------------------------------------------------------------------ + * Compare two sockunion values + * + * Compares family values first, then the body of the address. + * + * Returns: +1 => su1 > su2 + * 0 => su1 == su2 (or same, but unknown, family) + * -1 => su1 < su2 + */ int sockunion_cmp (union sockunion *su1, union sockunion *su2) { @@ -754,23 +895,33 @@ sockunion_cmp (union sockunion *su1, union sockunion *su2) if (su1->sa.sa_family < su2->sa.sa_family) return -1; - if (su1->sa.sa_family == AF_INET) - { - if (ntohl (su1->sin.sin_addr.s_addr) == ntohl (su2->sin.sin_addr.s_addr)) - return 0; - if (ntohl (su1->sin.sin_addr.s_addr) > ntohl (su2->sin.sin_addr.s_addr)) - return 1; + switch (su1->sa.sa_family) + { + case AF_INET: + if (su1->sin.sin_addr.s_addr == su2->sin.sin_addr.s_addr) + return 0; + if (ntohl(su1->sin.sin_addr.s_addr) > ntohl(su2->sin.sin_addr.s_addr)) + return +1; else return -1; - } + #ifdef HAVE_IPV6 - if (su1->sa.sa_family == AF_INET6) - return in6addr_cmp (&su1->sin6.sin6_addr, &su2->sin6.sin6_addr); + case AF_INET6: + return memcmp(&su1->sin6.sin6_addr, &su2->sin6.sin6_addr, + sizeof(struct in6_addr)) ; #endif /* HAVE_IPV6 */ - return 0; + + default: + return 0 ; + } ; } -/* Duplicate sockunion. */ +/*------------------------------------------------------------------------------ + * Create copy of existing sockunion. + * + * It is the caller's responsibility to free the sockunion at some point --see + * sockunion_free() + */ union sockunion * sockunion_dup (union sockunion *su) { @@ -779,8 +930,188 @@ sockunion_dup (union sockunion *su) return dup; } +/*------------------------------------------------------------------------------ + * Copy one sockunion to another + */ +extern void +sockunion_copy (sockunion dst, sockunion src) +{ + memcpy (dst, src, sizeof(*dst)) ; +} ; + +/*------------------------------------------------------------------------------ + * Free given sockunion (if any). + */ void sockunion_free (union sockunion *su) { - XFREE (MTYPE_SOCKUNION, su); + if (su != NULL) + XFREE (MTYPE_SOCKUNION, su); +} + +/*============================================================================== + * Sockunion reference utilities + */ + +/*------------------------------------------------------------------------------ + * Set sockunion from given prefix -- allocate new sockunion, if required. + * + * It is the caller's responsibility to free the sockunion at some point. + * (See sockunion_free() or sockunion_unset().) + * + * For unknown family, returns an empty sockunion of that family. + */ +extern sockunion +sockunion_new_prefix(sockunion su, struct prefix* p) +{ + sa_family_t family ; + + family = (p != NULL) ? p->family : 0 ; + + su = sockunion_init_new(su, family) ; + + switch (family) + { + case 0: + break ; + + case AF_INET: + su->sin.sin_addr = p->u.prefix4 ; + break ; + +#ifdef HAVE_IPV6 + case AF_INET6: + su->sin6.sin6_addr = p->u.prefix6 ; + break ; +#endif + + default: + break ; + } ; + + return su ; +} ; + +/*------------------------------------------------------------------------------ + * Create new sockunion from given sockaddr -- taking only the address part + * + * It is the caller's responsibility to free the sockunion at some point. + * (See sockunion_free() or sockunion_unset().) + * + * For unknown family, returns an empty sockunion of that family. + */ +extern sockunion +sockunion_new_sockaddr(sockunion su, struct sockaddr* sa) +{ + sa_family_t family ; + + family = (sa != NULL) ? sa->sa_family : AF_UNSPEC ; + + su = sockunion_init_new(su, family) ; + + switch (family) + { + case AF_UNSPEC: + break ; + + case AF_INET: + su->sin.sin_addr = ((struct sockaddr_in*)sa)->sin_addr ; + break ; + +#ifdef HAVE_IPV6 + case AF_INET6: + su->sin6.sin6_addr = ((struct sockaddr_in6*)sa)->sin6_addr ; + break ; +#endif + + default: + break ; + } ; + + return su ; +} ; + +/*------------------------------------------------------------------------------ + * Unset pointer to sockunion -- free any sockunion referenced + * + * Does nothing if there is no sockunion + */ +extern void +sockunion_unset(sockunion* p_su) +{ + if (*p_su != NULL) + XFREE(MTYPE_SOCKUNION, *p_su) ; /* sets *p_su NULL */ +} ; + +/*------------------------------------------------------------------------------ + * Set pointer to sockunion (if any) + * + * Frees any existing sockunion at the destination. + * + * NB: copies the source pointer -- so must be clear about responsibility + * for the sockunion. + */ +extern void +sockunion_set(sockunion* p_dst, sockunion su) +{ + sockunion_unset(p_dst) ; + *p_dst = su ; } + +/*------------------------------------------------------------------------------ + * Set pointer to a *copy* of the given sockunion + * + * Frees any existing sockunion at the destination. + * + * NB: copies the source pointer -- so must be clear about responsibility + * for the sockunion structure. + */ +extern void +sockunion_set_dup(sockunion* p_dst, sockunion su) +{ + sockunion_set(p_dst, sockunion_dup(su)) ; +} ; + +/*------------------------------------------------------------------------------ + * Set pointer to sockunion (if any) and unset source pointer. + * + * Frees any existing sockunion at the destination. + * + * NB: responsibility for the sockunion passes to the destination. + */ +extern void +sockunion_set_mov(sockunion* p_dst, sockunion* p_src) +{ + sockunion_unset(p_dst) ; + *p_dst = *p_src ; + *p_src = NULL ; +} ; + +/*============================================================================== + * Symbol Table Hash function -- for symbols whose name is an address. + */ +extern void +sockunion_symbol_hash(symbol_hash p_hash, const void* name) +{ + const union sockunion* su = name ; + + switch (su->sa.sa_family) + { + case AF_INET: + confirm(sizeof(p_hash->hash) == sizeof(su->sin.sin_addr.s_addr)) ; + p_hash->hash = su->sin.sin_addr.s_addr ; + p_hash->name = (const void*)&su->sin.sin_addr.s_addr ; + p_hash->name_len = sizeof(su->sin.sin_addr.s_addr) ; + p_hash->name_copy_len = sizeof(su->sin.sin_addr.s_addr) ; + break ; + +#ifdef HAVE_IPV6 + case AF_INET6: + symbol_hash_bytes(p_hash, (const void*)&su->sin6.sin6_addr, + sizeof(su->sin6.sin6_addr)) ; + break ; +#endif /* HAVE_IPV6 */ + default: + zabort("Unknown address family") ; + } ; +} ; |
