]> git.proxmox.com Git - mirror_frr.git/blobdiff - lib/sockopt.c
[administrivia] Add .gitignore files, based on .cvsignores.
[mirror_frr.git] / lib / sockopt.c
index 8c518f824a486adf4cc7e74710670f992e04a7bd..a2038a5c150b205829d0c343f629bce1f21da125 100644 (file)
@@ -22,6 +22,7 @@
 #include <zebra.h>
 #include "log.h"
 #include "sockopt.h"
+#include "sockunion.h"
 
 int
 setsockopt_so_recvbuf (int sock, int size)
@@ -36,13 +37,42 @@ 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)
@@ -152,22 +182,40 @@ getsockopt_ipv6_ifindex (struct msghdr *msgh)
 #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)
     {
@@ -184,7 +232,24 @@ setsockopt_multicast_ipv4(int sock,
       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:
@@ -200,16 +265,22 @@ setsockopt_multicast_ipv4(int sock,
   /* 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;
 
@@ -217,13 +288,25 @@ setsockopt_multicast_ipv4(int sock,
     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:
@@ -280,63 +363,74 @@ setsockopt_ifindex (int af, int sock, int val)
   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;
 }
 
@@ -387,3 +481,70 @@ sockopt_iphdrincl_swab_systoh (struct ip *iph)
 
   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 */
+}