#include <zebra.h>
#include "log.h"
#include "sockopt.h"
+#include "sockunion.h"
int
setsockopt_so_recvbuf (int sock, int size)
return ret;
}
+int
+setsockopt_so_sendbuf (const int sock, int size)
+{
+ int ret = setsockopt (sock, SOL_SOCKET, SO_SNDBUF,
+ (char *)&size, sizeof (int));
+
+ if (ret < 0)
+ zlog_err ("fd %d: can't setsockopt SO_SNDBUF to %d: %s",
+ sock, size, safe_strerror (errno));
+
+ return ret;
+}
+
+int
+getsockopt_so_sendbuf (const int sock)
+{
+ u_int32_t optval;
+ socklen_t optlen = sizeof (optval);
+ int ret = getsockopt (sock, SOL_SOCKET, SO_SNDBUF,
+ (char *)&optval, &optlen);
+ if (ret < 0)
+ {
+ zlog_err ("fd %d: can't getsockopt SO_SNDBUF: %d (%s)",
+ sock, errno, safe_strerror (errno));
+ return ret;
+ }
+ return optval;
+}
+
static void *
getsockopt_cmsg_data (struct msghdr *msgh, int level, int type)
{
struct cmsghdr *cmsg;
void *ptr = NULL;
- for (cmsg = CMSG_FIRSTHDR(msgh);
+ for (cmsg = ZCMSG_FIRSTHDR(msgh);
cmsg != NULL;
cmsg = CMSG_NXTHDR(msgh, cmsg))
if (cmsg->cmsg_level == level && cmsg->cmsg_type)
#endif /* HAVE_IPV6 */
-/* Set up a multicast socket options for IPv4
- This is here so that people only have to do their OS multicast mess
- in one place rather than all through zebra, ospfd, and ripd
- NB: This is a hookpoint for specific OS functionality */
+/*
+ * Process multicast socket options for IPv4 in an OS-dependent manner.
+ * Supported options are IP_MULTICAST_IF and IP_{ADD,DROP}_MEMBERSHIP.
+ *
+ * Many operating systems have a limit on the number of groups that
+ * can be joined per socket (where each group and local address
+ * counts). This impacts OSPF, which joins groups on each interface
+ * using a single socket. The limit is typically 20, derived from the
+ * original BSD multicast implementation. Some systems have
+ * mechanisms for increasing this limit.
+ *
+ * In many 4.4BSD-derived systems, multicast group operations are not
+ * allowed on interfaces that are not UP. Thus, a previous attempt to
+ * leave the group may have failed, leaving it still joined, and we
+ * drop/join quietly to recover. This may not be necessary, but aims to
+ * defend against unknown behavior in that we will still return an error
+ * if the second join fails. It is not clear how other systems
+ * (e.g. Linux, Solaris) behave when leaving groups on down interfaces,
+ * but this behavior should not be harmful if they behave the same way,
+ * allow leaves, or implicitly leave all groups joined to down interfaces.
+ */
int
setsockopt_multicast_ipv4(int sock,
int optname,
- struct in_addr if_addr,
+ struct in_addr if_addr /* required */,
unsigned int mcast_addr,
- unsigned int ifindex)
+ unsigned int ifindex /* optional: if non-zero, may be
+ used instead of if_addr */)
{
- /* Linux 2.2.0 and up */
-#if defined(GNU_LINUX) && LINUX_VERSION_CODE > 131584
+#ifdef HAVE_STRUCT_IP_MREQN_IMR_IFINDEX
/* This is better because it uses ifindex directly */
struct ip_mreqn mreqn;
+ int ret;
switch (optname)
{
else
mreqn.imr_address = if_addr;
- return setsockopt(sock, IPPROTO_IP, optname, (void *)&mreqn, sizeof(mreqn));
+ ret = setsockopt(sock, IPPROTO_IP, optname,
+ (void *)&mreqn, sizeof(mreqn));
+ if ((ret < 0) && (optname == IP_ADD_MEMBERSHIP) && (errno == EADDRINUSE))
+ {
+ /* see above: handle possible problem when interface comes back up */
+ char buf[2][INET_ADDRSTRLEN];
+ zlog_info("setsockopt_multicast_ipv4 attempting to drop and "
+ "re-add (fd %d, ifaddr %s, mcast %s, ifindex %u)",
+ sock,
+ inet_ntop(AF_INET, &if_addr, buf[0], sizeof(buf[0])),
+ inet_ntop(AF_INET, &mreqn.imr_multiaddr,
+ buf[1], sizeof(buf[1])), ifindex);
+ setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP,
+ (void *)&mreqn, sizeof(mreqn));
+ ret = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP,
+ (void *)&mreqn, sizeof(mreqn));
+ }
+ return ret;
break;
default:
/* Add your favourite OS here! */
#else /* #if OS_TYPE */
- /* default OS support */
+ /* standard BSD API */
struct in_addr m;
struct ip_mreq mreq;
+ int ret;
+
+#ifdef HAVE_BSD_STRUCT_IP_MREQ_HACK
+ if (ifindex)
+ m.s_addr = htonl(ifindex);
+ else
+#endif
+ m = if_addr;
switch (optname)
{
case IP_MULTICAST_IF:
- m = if_addr;
-
return setsockopt (sock, IPPROTO_IP, optname, (void *)&m, sizeof(m));
break;
case IP_DROP_MEMBERSHIP:
memset (&mreq, 0, sizeof(mreq));
mreq.imr_multiaddr.s_addr = mcast_addr;
- mreq.imr_interface = if_addr;
+ mreq.imr_interface = m;
- return setsockopt (sock,
- IPPROTO_IP,
- optname,
- (void *)&mreq,
- sizeof(mreq));
+ ret = setsockopt (sock, IPPROTO_IP, optname, (void *)&mreq, sizeof(mreq));
+ if ((ret < 0) && (optname == IP_ADD_MEMBERSHIP) && (errno == EADDRINUSE))
+ {
+ /* see above: handle possible problem when interface comes back up */
+ char buf[2][INET_ADDRSTRLEN];
+ zlog_info("setsockopt_multicast_ipv4 attempting to drop and "
+ "re-add (fd %d, ifaddr %s, mcast %s, ifindex %u)",
+ sock,
+ inet_ntop(AF_INET, &if_addr, buf[0], sizeof(buf[0])),
+ inet_ntop(AF_INET, &mreq.imr_multiaddr,
+ buf[1], sizeof(buf[1])), ifindex);
+ setsockopt (sock, IPPROTO_IP, IP_DROP_MEMBERSHIP,
+ (void *)&mreq, sizeof(mreq));
+ ret = setsockopt (sock, IPPROTO_IP, IP_ADD_MEMBERSHIP,
+ (void *)&mreq, sizeof(mreq));
+ }
+ return ret;
break;
default:
return ret;
}
+/*
+ * Requires: msgh is not NULL and points to a valid struct msghdr, which
+ * may or may not have control data about the incoming interface.
+ *
+ * Returns the interface index (small integer >= 1) if it can be
+ * determined, or else 0.
+ */
static int
getsockopt_ipv4_ifindex (struct msghdr *msgh)
{
- /*
- * XXX: This routine's semantics are ill-defined, in particular how
- * the results "can't determine interface due to runtime behavior"
- * and "OS has no support for how to determine behavior" are
- * encoded. For now, return 0 for either case; caller must handle
- * as "don't know".
- */
+ /* XXX: initialize to zero? (Always overwritten, so just cosmetic.) */
int ifindex = -1;
- /*
- * If autoconf found a method to get ifindex, but it didn't work for
- * this packet, or on this OS, this routine can be entered with a
- * NULL cmsghdr pointer. Check msgh before using in each case
- * below, rather than here, to avoid having to ifdef twice, once for
- * declarations and once for code.
- */
-
#if defined(IP_PKTINFO)
/* Linux pktinfo based ifindex retrieval */
struct in_pktinfo *pktinfo;
- if (msgh == NULL)
- return 0;
-
pktinfo =
(struct in_pktinfo *)getsockopt_cmsg_data (msgh, IPPROTO_IP, IP_PKTINFO);
+ /* XXX Can pktinfo be NULL? Clean up post 0.98. */
ifindex = pktinfo->ipi_ifindex;
#elif defined(IP_RECVIF)
-/* BSD/other IP_RECVIF based ifindex retrieval */
+
+ /* retrieval based on IP_RECVIF */
+
#ifndef SUNOS_5
- /* RECVIF, but not SUNOS, so BSD */
+ /* BSD systems use a sockaddr_dl as the control message payload. */
struct sockaddr_dl *sdl;
+#else
+ /* SUNOS_5 uses an integer with the index. */
+ int *ifindex_p;
#endif /* SUNOS_5 */
- /* SUNOS_5 doesn't need a structure to extract ifindex */
-
- if (msgh == NULL)
- return 0;
#ifndef SUNOS_5
+ /* BSD */
sdl =
(struct sockaddr_dl *)getsockopt_cmsg_data (msgh, IPPROTO_IP, IP_RECVIF);
- ifindex = sdl->sdl_index;
-#else /* !SUNOS_5 */
- ifindex = *(uint_t *)getsockopt_cmsg_data (msgh, IPPROTO_IP, IP_RECVIF);
+ if (sdl != NULL)
+ ifindex = sdl->sdl_index;
+ else
+ ifindex = 0;
+#else
+ /*
+ * Solaris. On Solaris 8, IP_RECVIF is defined, but the call to
+ * enable it fails with errno=99, and the struct msghdr has
+ * controllen 0.
+ */
+ ifindex_p = (uint_t *)getsockopt_cmsg_data (msgh, IPPROTO_IP, IP_RECVIF);
+ if (ifindex_p != NULL)
+ ifindex = *ifindex_p;
+ else
+ ifindex = 0;
#endif /* SUNOS_5 */
-#else /* neither IP_PKTINFO nor IP_RECVIF, broken */
-
-#warning "getsockopt_ipv4_pktinfo_ifindex: dont have PKTINFO or RECVIF"
-#warning "things probably will be broken on this platform!"
- /* XXX why not -1 - this is a failure condition. */
+#else
+ /*
+ * Neither IP_PKTINFO nor IP_RECVIF defined - warn at compile time.
+ * XXX Decide if this is a core service, or if daemons have to cope.
+ * Since Solaris 8 and OpenBSD seem not to provide it, it seems that
+ * daemons have to cope.
+ */
+#warning "getsockopt_ipv4_ifindex: Neither IP_PKTINFO nor IP_RECVIF defined."
+#warning "Some daemons may fail to operate correctly!"
ifindex = 0;
+
#endif /* IP_PKTINFO */
+
return ifindex;
}
iph->ip_id = ntohs(iph->ip_id);
}
+
+int
+sockopt_tcp_signature (int sock, union sockunion *su, const char *password)
+{
+#if HAVE_DECL_TCP_MD5SIG
+#ifndef GNU_LINUX
+ /*
+ * XXX Need to do PF_KEY operation here to add/remove an SA entry,
+ * and add/remove an SP entry for this peer's packet flows also.
+ */
+ int md5sig = password && *password ? 1 : 0;
+#else
+ int keylen = password ? strlen (password) : 0;
+ struct tcp_md5sig md5sig;
+ union sockunion *su2, *susock;
+ int ret;
+
+ /* Figure out whether the socket and the sockunion are the same family..
+ * adding AF_INET to AF_INET6 needs to be v4 mapped, you'd think..
+ */
+ if (!(susock = sockunion_getsockname (sock)))
+ return -1;
+
+ if (susock->sa.sa_family == su->sa.sa_family)
+ su2 = su;
+ else
+ {
+ /* oops.. */
+ su2 = susock;
+
+ if (su2->sa.sa_family == AF_INET)
+ {
+ sockunion_free (susock);
+ return -1;
+ };
+
+ /* If this does not work, then all users of this sockopt will need to
+ * differentiate between IPv4 and IPv6, and keep seperate sockets for
+ * each.
+ *
+ * Sadly, it doesn't seem to work at present. It's unknown whether
+ * this is a bug or not.
+ */
+ if (su2->sa.sa_family == AF_INET6
+ && su->sa.sa_family == AF_INET)
+ {
+ su2->sin6.sin6_family = AF_INET6;
+ /* V4Map the address */
+ memset (&su2->sin6.sin6_addr, 0, sizeof (struct in6_addr));
+ su2->sin6.sin6_addr.s6_addr32[2] = htonl(0xffff);
+ memcpy (&su2->sin6.sin6_addr.s6_addr32[3], &su->sin.sin_addr, 4);
+ }
+ }
+
+ memset (&md5sig, 0, sizeof (md5sig));
+ memcpy (&md5sig.tcpm_addr, su2, sizeof (*su2));
+ md5sig.tcpm_keylen = keylen;
+ if (keylen)
+ memcpy (md5sig.tcpm_key, password, keylen);
+#endif /* GNU_LINUX */
+ ret = setsockopt (sock, IPPROTO_TCP, TCP_MD5SIG, &md5sig, sizeof md5sig);
+ sockunion_free (susock);
+ return ret;
+#else /* HAVE_TCP_MD5SIG */
+ return -2;
+#endif /* HAVE_TCP_MD5SIG */
+}