]> git.proxmox.com Git - mirror_frr.git/blobdiff - zebra/rt_netlink.c
zebra: fix IPv6 RA wrt interface removal (BZ#480)
[mirror_frr.git] / zebra / rt_netlink.c
index f1c1a300b8004981cb631fe4782c05d03f0d4fe7..eb9eae3aab0ac5b1a70cf8ae62b4855f9693b87b 100644 (file)
@@ -52,7 +52,7 @@ struct nlsock
 } netlink      = { -1, 0, {0}, "netlink-listen"},     /* kernel messages */
   netlink_cmd  = { -1, 0, {0}, "netlink-cmd"};        /* command channel */
 
-struct message nlmsg_str[] = {
+static const struct message nlmsg_str[] = {
   {RTM_NEWROUTE, "RTM_NEWROUTE"},
   {RTM_DELROUTE, "RTM_DELROUTE"},
   {RTM_GETROUTE, "RTM_GETROUTE"},
@@ -65,7 +65,7 @@ struct message nlmsg_str[] = {
   {0, NULL}
 };
 
-const char *nexthop_types_desc[] =  
+static const char *nexthop_types_desc[] =
 {
   "none",
   "Directly connected",
@@ -79,7 +79,6 @@ const char *nexthop_types_desc[] =
   "Null0 nexthop",
 };
 
-
 extern struct zebra_t zebrad;
 
 extern struct zebra_privs_t zserv_privs;
@@ -113,73 +112,61 @@ set_ifindex(struct interface *ifp, unsigned int ifi_index)
   ifp->ifindex = ifi_index;
 }
 
-/* Make socket for Linux netlink interface. */
 static int
-netlink_socket (struct nlsock *nl, unsigned long groups)
+netlink_recvbuf (struct nlsock *nl, uint32_t newsize)
 {
+  u_int32_t oldsize;
+  socklen_t newlen = sizeof(newsize);
+  socklen_t oldlen = sizeof(oldsize);
   int ret;
-  struct sockaddr_nl snl;
-  int sock;
-  int namelen;
-  int save_errno;
 
-  sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
-  if (sock < 0)
+  ret = getsockopt(nl->sock, SOL_SOCKET, SO_RCVBUF, &oldsize, &oldlen);
+  if (ret < 0)
     {
-      zlog (NULL, LOG_ERR, "Can't open %s socket: %s", nl->name,
-            safe_strerror (errno));
+      zlog (NULL, LOG_ERR, "Can't get %s receive buffer size: %s", nl->name,
+           safe_strerror (errno));
       return -1;
     }
 
-  ret = fcntl (sock, F_SETFL, O_NONBLOCK);
+  ret = setsockopt(nl->sock, SOL_SOCKET, SO_RCVBUF, &nl_rcvbufsize,
+                  sizeof(nl_rcvbufsize));
   if (ret < 0)
     {
-      zlog (NULL, LOG_ERR, "Can't set %s socket flags: %s", nl->name,
-            safe_strerror (errno));
-      close (sock);
+      zlog (NULL, LOG_ERR, "Can't set %s receive buffer size: %s", nl->name,
+           safe_strerror (errno));
       return -1;
     }
 
-  /* Set receive buffer size if it's set from command line */
-  if (nl_rcvbufsize)
+  ret = getsockopt(nl->sock, SOL_SOCKET, SO_RCVBUF, &newsize, &newlen);
+  if (ret < 0)
     {
-      u_int32_t oldsize, oldlen;
-      u_int32_t newsize, newlen;
+      zlog (NULL, LOG_ERR, "Can't get %s receive buffer size: %s", nl->name,
+           safe_strerror (errno));
+      return -1;
+    }
 
-      oldlen = sizeof(oldsize);
-      newlen = sizeof(newsize);
+  zlog (NULL, LOG_INFO,
+       "Setting netlink socket receive buffer size: %u -> %u",
+       oldsize, newsize);
+  return 0;
+}
 
-      ret = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, &oldsize, &oldlen);
-      if (ret < 0)
-       {
-         zlog (NULL, LOG_ERR, "Can't get %s receive buffer size: %s", nl->name,
-               safe_strerror (errno));
-         close (sock);
-         return -1;
-       }
-
-      ret = setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &nl_rcvbufsize,
-                      sizeof(nl_rcvbufsize));
-      if (ret < 0)
-       {
-         zlog (NULL, LOG_ERR, "Can't set %s receive buffer size: %s", nl->name,
-               safe_strerror (errno));
-         close (sock);
-         return -1;
-       }
-
-      ret = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, &newsize, &newlen);
-      if (ret < 0)
-       {
-         zlog (NULL, LOG_ERR, "Can't get %s receive buffer size: %s", nl->name,
-               safe_strerror (errno));
-         close (sock);
-         return -1;
-       }
+/* Make socket for Linux netlink interface. */
+static int
+netlink_socket (struct nlsock *nl, unsigned long groups)
+{
+  int ret;
+  struct sockaddr_nl snl;
+  int sock;
+  int namelen;
+  int save_errno;
 
-      zlog (NULL, LOG_INFO,
-           "Setting netlink socket receive buffer size: %u -> %u",
-           oldsize, newsize);
+  sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+  if (sock < 0)
+    {
+      zlog (NULL, LOG_ERR, "Can't open %s socket: %s", nl->name,
+            safe_strerror (errno));
+      return -1;
     }
 
   memset (&snl, 0, sizeof snl);
@@ -222,41 +209,6 @@ netlink_socket (struct nlsock *nl, unsigned long groups)
   return ret;
 }
 
-int
-set_netlink_blocking (struct nlsock *nl, int *flags)
-{
-
-  /* Change socket flags for blocking I/O.  */
-  if ((*flags = fcntl (nl->sock, F_GETFL, 0)) < 0)
-    {
-      zlog (NULL, LOG_ERR, "%s:%i F_GETFL error: %s",
-            __FUNCTION__, __LINE__, safe_strerror (errno));
-      return -1;
-    }
-  *flags &= ~O_NONBLOCK;
-  if (fcntl (nl->sock, F_SETFL, *flags) < 0)
-    {
-      zlog (NULL, LOG_ERR, "%s:%i F_SETFL error: %s",
-            __FUNCTION__, __LINE__, safe_strerror (errno));
-      return -1;
-    }
-  return 0;
-}
-
-int
-set_netlink_nonblocking (struct nlsock *nl, int *flags)
-{
-  /* Restore socket flags for nonblocking I/O */
-  *flags |= O_NONBLOCK;
-  if (fcntl (nl->sock, F_SETFL, *flags) < 0)
-    {
-      zlog (NULL, LOG_ERR, "%s:%i F_SETFL error: %s",
-            __FUNCTION__, __LINE__, safe_strerror (errno));
-      return -1;
-    }
-  return 0;
-}
-
 /* Get type specified information from netlink. */
 static int
 netlink_request (int family, int type, struct nlsock *nl)
@@ -286,7 +238,7 @@ netlink_request (int family, int type, struct nlsock *nl)
   req.nlh.nlmsg_len = sizeof req;
   req.nlh.nlmsg_type = type;
   req.nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST;
-  req.nlh.nlmsg_pid = 0;
+  req.nlh.nlmsg_pid = nl->snl.nl_pid;
   req.nlh.nlmsg_seq = ++nl->seq;
   req.g.rtgen_family = family;
 
@@ -333,25 +285,16 @@ netlink_parse_info (int (*filter) (struct sockaddr_nl *, struct nlmsghdr *),
       struct sockaddr_nl snl;
       struct msghdr msg = { (void *) &snl, sizeof snl, &iov, 1, NULL, 0, 0 };
       struct nlmsghdr *h;
-      int save_errno;
-
-      if (zserv_privs.change (ZPRIVS_RAISE))
-        zlog (NULL, LOG_ERR, "Can't raise privileges");
 
       status = recvmsg (nl->sock, &msg, 0);
-      save_errno = errno;
-
-      if (zserv_privs.change (ZPRIVS_LOWER))
-        zlog (NULL, LOG_ERR, "Can't lower privileges");
-
       if (status < 0)
         {
-          if (save_errno == EINTR)
+          if (errno == EINTR)
             continue;
-          if (save_errno == EWOULDBLOCK || save_errno == EAGAIN)
+          if (errno == EWOULDBLOCK || errno == EAGAIN)
             break;
           zlog (NULL, LOG_ERR, "%s recvmsg overrun: %s",
-               nl->name, safe_strerror(save_errno));
+               nl->name, safe_strerror(errno));
           continue;
         }
 
@@ -368,13 +311,6 @@ netlink_parse_info (int (*filter) (struct sockaddr_nl *, struct nlmsghdr *),
           return -1;
         }
       
-      /* JF: Ignore messages that aren't from the kernel */
-      if ( snl.nl_pid != 0 )
-        {
-          zlog ( NULL, LOG_ERR, "Ignoring message from pid %u", snl.nl_pid );
-          continue;
-        }
-
       for (h = (struct nlmsghdr *) buf; NLMSG_OK (h, (unsigned int) status);
            h = NLMSG_NEXT (h, status))
         {
@@ -386,6 +322,8 @@ netlink_parse_info (int (*filter) (struct sockaddr_nl *, struct nlmsghdr *),
           if (h->nlmsg_type == NLMSG_ERROR)
             {
               struct nlmsgerr *err = (struct nlmsgerr *) NLMSG_DATA (h);
+             int errnum = err->error;
+             int msg_type = err->msg.nlmsg_type;
 
               /* If the error field is zero, then this is an ACK */
               if (err->error == 0)
@@ -414,27 +352,24 @@ netlink_parse_info (int (*filter) (struct sockaddr_nl *, struct nlmsghdr *),
                   return -1;
                 }
 
-              /* Deal with Error Noise  - MAG */
-              {
-                int loglvl = LOG_ERR;
-                int errnum = err->error;
-                int msg_type = err->msg.nlmsg_type;
-
-                if (nl == &netlink_cmd
-                    && (-errnum == ENODEV || -errnum == ESRCH)
-                    && (msg_type == RTM_NEWROUTE || msg_type == RTM_DELROUTE))
-                  loglvl = LOG_DEBUG;
-
-                zlog (NULL, loglvl, "%s error: %s, type=%s(%u), "
-                      "seq=%u, pid=%u",
-                      nl->name, safe_strerror (-errnum),
-                      lookup (nlmsg_str, msg_type),
-                      msg_type, err->msg.nlmsg_seq, err->msg.nlmsg_pid);
-              }
-              /*
-                 ret = -1;
-                 continue;
-               */
+              /* Deal with errors that occur because of races in link handling */
+             if (nl == &netlink_cmd
+                 && ((msg_type == RTM_DELROUTE &&
+                      (-errnum == ENODEV || -errnum == ESRCH))
+                     || (msg_type == RTM_NEWROUTE && -errnum == EEXIST)))
+               {
+                 if (IS_ZEBRA_DEBUG_KERNEL)
+                   zlog_debug ("%s: error: %s type=%s(%u), seq=%u, pid=%u",
+                               nl->name, safe_strerror (-errnum),
+                               lookup (nlmsg_str, msg_type),
+                               msg_type, err->msg.nlmsg_seq, err->msg.nlmsg_pid);
+                 return 0;
+               }
+
+             zlog_err ("%s error: %s, type=%s(%u), seq=%u, pid=%u",
+                       nl->name, safe_strerror (-errnum),
+                       lookup (nlmsg_str, msg_type),
+                       msg_type, err->msg.nlmsg_seq, err->msg.nlmsg_pid);
               return -1;
             }
 
@@ -493,7 +428,7 @@ netlink_parse_rtattr (struct rtattr **tb, int max, struct rtattr *rta,
 
 /* Called from interface_lookup_netlink().  This function is only used
    during bootstrap. */
-int
+static int
 netlink_interface (struct sockaddr_nl *snl, struct nlmsghdr *h)
 {
   int len;
@@ -534,7 +469,7 @@ netlink_interface (struct sockaddr_nl *snl, struct nlmsghdr *h)
   ifp = if_get_by_name (name);
   set_ifindex(ifp, ifi->ifi_index);
   ifp->flags = ifi->ifi_flags & 0x0000fffff;
-  ifp->mtu6 = ifp->mtu = *(int *) RTA_DATA (tb[IFLA_MTU]);
+  ifp->mtu6 = ifp->mtu = *(uint32_t *) RTA_DATA (tb[IFLA_MTU]);
   ifp->metric = 1;
 
   /* Hardware type and address. */
@@ -570,15 +505,15 @@ netlink_interface (struct sockaddr_nl *snl, struct nlmsghdr *h)
 }
 
 /* Lookup interface IPv4/IPv6 address. */
-int
+static int
 netlink_interface_addr (struct sockaddr_nl *snl, struct nlmsghdr *h)
 {
   int len;
   struct ifaddrmsg *ifa;
   struct rtattr *tb[IFA_MAX + 1];
   struct interface *ifp;
-  void *addr = NULL;
-  void *broad = NULL;
+  void *addr;
+  void *broad;
   u_char flags = 0;
   char *label = NULL;
 
@@ -637,40 +572,31 @@ netlink_interface_addr (struct sockaddr_nl *snl, struct nlmsghdr *h)
         }
     }
   
+  /* logic copied from iproute2/ip/ipaddress.c:print_addrinfo() */
+  if (tb[IFA_LOCAL] == NULL)
+    tb[IFA_LOCAL] = tb[IFA_ADDRESS];
   if (tb[IFA_ADDRESS] == NULL)
     tb[IFA_ADDRESS] = tb[IFA_LOCAL];
   
-  if (ifp->flags & IFF_POINTOPOINT)
+  /* local interface address */
+  addr = (tb[IFA_LOCAL] ? RTA_DATA(tb[IFA_LOCAL]) : NULL);
+
+  /* is there a peer address? */
+  if (tb[IFA_ADDRESS] &&
+      memcmp(RTA_DATA(tb[IFA_ADDRESS]), RTA_DATA(tb[IFA_LOCAL]), RTA_PAYLOAD(tb[IFA_ADDRESS])))
     {
-      if (tb[IFA_LOCAL])
-        {
-          addr = RTA_DATA (tb[IFA_LOCAL]);
-          if (tb[IFA_ADDRESS] &&
-             memcmp(RTA_DATA(tb[IFA_ADDRESS]),RTA_DATA(tb[IFA_LOCAL]),4))
-           /* if IFA_ADDRESS != IFA_LOCAL, then it's the peer address */
-            broad = RTA_DATA (tb[IFA_ADDRESS]);
-          else
-            broad = NULL;
-        }
-      else
-        {
-          if (tb[IFA_ADDRESS])
-            addr = RTA_DATA (tb[IFA_ADDRESS]);
-          else
-            addr = NULL;
-        }
+      broad = RTA_DATA(tb[IFA_ADDRESS]);
+      SET_FLAG (flags, ZEBRA_IFA_PEER);
     }
   else
+    /* seeking a broadcast address */
+    broad = (tb[IFA_BROADCAST] ? RTA_DATA(tb[IFA_BROADCAST]) : NULL);
+
+  /* addr is primary key, SOL if we don't have one */
+  if (addr == NULL)
     {
-      if (tb[IFA_ADDRESS])
-        addr = RTA_DATA (tb[IFA_ADDRESS]);
-      else
-        addr = NULL;
-      
-      if (tb[IFA_BROADCAST])
-        broad = RTA_DATA(tb[IFA_BROADCAST]);
-      else
-        broad = NULL;
+      zlog_debug ("%s: NULL address", __func__);
+      return -1;
     }
 
   /* Flags. */
@@ -700,7 +626,7 @@ netlink_interface_addr (struct sockaddr_nl *snl, struct nlmsghdr *h)
   if (ifa->ifa_family == AF_INET6)
     {
       if (h->nlmsg_type == RTM_NEWADDR)
-        connected_add_ipv6 (ifp,
+        connected_add_ipv6 (ifp, flags,
                             (struct in6_addr *) addr, ifa->ifa_prefixlen,
                             (struct in6_addr *) broad, label);
       else
@@ -714,7 +640,7 @@ netlink_interface_addr (struct sockaddr_nl *snl, struct nlmsghdr *h)
 }
 
 /* Looking up routing table by netlink interface. */
-int
+static int
 netlink_routing_table (struct sockaddr_nl *snl, struct nlmsghdr *h)
 {
   int len;
@@ -730,6 +656,7 @@ netlink_routing_table (struct sockaddr_nl *snl, struct nlmsghdr *h)
 
   void *dest;
   void *gate;
+  void *src;
 
   rtm = NLMSG_DATA (h);
 
@@ -769,6 +696,7 @@ netlink_routing_table (struct sockaddr_nl *snl, struct nlmsghdr *h)
   metric = 0;
   dest = NULL;
   gate = NULL;
+  src = NULL;
 
   if (tb[RTA_OIF])
     index = *(int *) RTA_DATA (tb[RTA_OIF]);
@@ -778,6 +706,9 @@ netlink_routing_table (struct sockaddr_nl *snl, struct nlmsghdr *h)
   else
     dest = anyaddr;
 
+  if (tb[RTA_PREFSRC])
+    src = RTA_DATA (tb[RTA_PREFSRC]);
+
   /* Multipath treatment is needed. */
   if (tb[RTA_GATEWAY])
     gate = RTA_DATA (tb[RTA_GATEWAY]);
@@ -792,7 +723,7 @@ netlink_routing_table (struct sockaddr_nl *snl, struct nlmsghdr *h)
       memcpy (&p.prefix, dest, 4);
       p.prefixlen = rtm->rtm_dst_len;
 
-      rib_add_ipv4 (ZEBRA_ROUTE_KERNEL, flags, &p, gate, index, table, metric, 0);
+      rib_add_ipv4 (ZEBRA_ROUTE_KERNEL, flags, &p, gate, src, index, table, metric, 0);
     }
 #ifdef HAVE_IPV6
   if (rtm->rtm_family == AF_INET6)
@@ -810,7 +741,7 @@ netlink_routing_table (struct sockaddr_nl *snl, struct nlmsghdr *h)
   return 0;
 }
 
-struct message rtproto_str[] = {
+static const struct message rtproto_str[] = {
   {RTPROT_REDIRECT, "redirect"},
   {RTPROT_KERNEL,   "kernel"},
   {RTPROT_BOOT,     "boot"},
@@ -826,7 +757,7 @@ struct message rtproto_str[] = {
 };
 
 /* Routing information change from the kernel. */
-int
+static int
 netlink_route_change (struct sockaddr_nl *snl, struct nlmsghdr *h)
 {
   int len;
@@ -837,8 +768,11 @@ netlink_route_change (struct sockaddr_nl *snl, struct nlmsghdr *h)
 
   int index;
   int table;
+  int metric;
+
   void *dest;
   void *gate;
+  void *src;
 
   rtm = NLMSG_DATA (h);
 
@@ -893,8 +827,10 @@ netlink_route_change (struct sockaddr_nl *snl, struct nlmsghdr *h)
     }
 
   index = 0;
+  metric = 0;
   dest = NULL;
   gate = NULL;
+  src = NULL;
 
   if (tb[RTA_OIF])
     index = *(int *) RTA_DATA (tb[RTA_OIF]);
@@ -907,6 +843,12 @@ netlink_route_change (struct sockaddr_nl *snl, struct nlmsghdr *h)
   if (tb[RTA_GATEWAY])
     gate = RTA_DATA (tb[RTA_GATEWAY]);
 
+  if (tb[RTA_PREFSRC])
+    src = RTA_DATA (tb[RTA_PREFSRC]);
+
+  if (h->nlmsg_type == RTM_NEWROUTE && tb[RTA_PRIORITY])
+    metric = *(int *) RTA_DATA(tb[RTA_PRIORITY]);
+
   if (rtm->rtm_family == AF_INET)
     {
       struct prefix_ipv4 p;
@@ -925,7 +867,7 @@ netlink_route_change (struct sockaddr_nl *snl, struct nlmsghdr *h)
         }
 
       if (h->nlmsg_type == RTM_NEWROUTE)
-        rib_add_ipv4 (ZEBRA_ROUTE_KERNEL, 0, &p, gate, index, table, 0, 0);
+        rib_add_ipv4 (ZEBRA_ROUTE_KERNEL, 0, &p, gate, src, index, table, metric, 0);
       else
         rib_delete_ipv4 (ZEBRA_ROUTE_KERNEL, 0, &p, gate, index, table);
     }
@@ -953,16 +895,16 @@ netlink_route_change (struct sockaddr_nl *snl, struct nlmsghdr *h)
         }
 
       if (h->nlmsg_type == RTM_NEWROUTE)
-        rib_add_ipv6 (ZEBRA_ROUTE_KERNEL, 0, &p, gate, index, 0, 0, 0);
+        rib_add_ipv6 (ZEBRA_ROUTE_KERNEL, 0, &p, gate, index, table, metric, 0);
       else
-        rib_delete_ipv6 (ZEBRA_ROUTE_KERNEL, 0, &p, gate, index, 0);
+        rib_delete_ipv6 (ZEBRA_ROUTE_KERNEL, 0, &p, gate, index, table);
     }
 #endif /* HAVE_IPV6 */
 
   return 0;
 }
 
-int
+static int
 netlink_link_change (struct sockaddr_nl *snl, struct nlmsghdr *h)
 {
   int len;
@@ -1063,9 +1005,16 @@ netlink_link_change (struct sockaddr_nl *snl, struct nlmsghdr *h)
   return 0;
 }
 
-int
+static int
 netlink_information_fetch (struct sockaddr_nl *snl, struct nlmsghdr *h)
 {
+  /* JF: Ignore messages that aren't from the kernel */
+  if ( snl->nl_pid != 0 )
+    {
+      zlog ( NULL, LOG_ERR, "Ignoring message from pid %u", snl->nl_pid );
+      return 0;
+    }
+
   switch (h->nlmsg_type)
     {
     case RTM_NEWROUTE:
@@ -1098,18 +1047,6 @@ int
 interface_lookup_netlink (void)
 {
   int ret;
-  int flags;
-  int snb_ret;
-
-  /* 
-   * Change netlink socket flags to blocking to ensure we get 
-   * a reply via nelink_parse_info
-   */
-  snb_ret = set_netlink_blocking (&netlink_cmd, &flags);
-  if (snb_ret < 0)
-    zlog (NULL, LOG_WARNING,
-          "%s:%i Warning: Could not set netlink socket to blocking.",
-          __FUNCTION__, __LINE__);
 
   /* Get interface information. */
   ret = netlink_request (AF_PACKET, RTM_GETLINK, &netlink_cmd);
@@ -1137,9 +1074,6 @@ interface_lookup_netlink (void)
     return ret;
 #endif /* HAVE_IPV6 */
 
-  /* restore socket flags */
-  if (snb_ret == 0)
-    set_netlink_nonblocking (&netlink_cmd, &flags);
   return 0;
 }
 
@@ -1149,18 +1083,6 @@ int
 netlink_route_read (void)
 {
   int ret;
-  int flags;
-  int snb_ret;
-
-  /* 
-   * Change netlink socket flags to blocking to ensure we get 
-   * a reply via nelink_parse_info
-   */
-  snb_ret = set_netlink_blocking (&netlink_cmd, &flags);
-  if (snb_ret < 0)
-    zlog (NULL, LOG_WARNING,
-          "%s:%i Warning: Could not set netlink socket to blocking.",
-          __FUNCTION__, __LINE__);
 
   /* Get IPv4 routing table. */
   ret = netlink_request (AF_INET, RTM_GETROUTE, &netlink_cmd);
@@ -1180,15 +1102,12 @@ netlink_route_read (void)
     return ret;
 #endif /* HAVE_IPV6 */
 
-  /* restore flags */
-  if (snb_ret == 0)
-    set_netlink_nonblocking (&netlink_cmd, &flags);
   return 0;
 }
 
 /* Utility function  comes from iproute2. 
    Authors:    Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> */
-int
+static int
 addattr_l (struct nlmsghdr *n, int maxlen, int type, void *data, int alen)
 {
   int len;
@@ -1208,7 +1127,7 @@ addattr_l (struct nlmsghdr *n, int maxlen, int type, void *data, int alen)
   return 0;
 }
 
-int
+static int
 rta_addattr_l (struct rtattr *rta, int maxlen, int type, void *data, int alen)
 {
   int len;
@@ -1230,7 +1149,7 @@ rta_addattr_l (struct rtattr *rta, int maxlen, int type, void *data, int alen)
 
 /* Utility function comes from iproute2. 
    Authors:    Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> */
-int
+static int
 addattr32 (struct nlmsghdr *n, int maxlen, int type, int data)
 {
   int len;
@@ -1258,15 +1177,13 @@ netlink_talk_filter (struct sockaddr_nl *snl, struct nlmsghdr *h)
 }
 
 /* sendmsg() to netlink socket then recvmsg(). */
-int
+static int
 netlink_talk (struct nlmsghdr *n, struct nlsock *nl)
 {
   int status;
   struct sockaddr_nl snl;
   struct iovec iov = { (void *) n, n->nlmsg_len };
   struct msghdr msg = { (void *) &snl, sizeof snl, &iov, 1, NULL, 0, 0 };
-  int flags = 0;
-  int snb_ret;
   int save_errno;
 
   memset (&snl, 0, sizeof snl);
@@ -1297,31 +1214,16 @@ netlink_talk (struct nlmsghdr *n, struct nlsock *nl)
       return -1;
     }
 
-  /* 
-   * Change socket flags for blocking I/O. 
-   * This ensures we wait for a reply in netlink_parse_info().
-   */
-  snb_ret = set_netlink_blocking (nl, &flags);
-  if (snb_ret < 0)
-    zlog (NULL, LOG_WARNING,
-          "%s:%i Warning: Could not set netlink socket to blocking.",
-          __FUNCTION__, __LINE__);
 
   /* 
    * Get reply from netlink socket. 
    * The reply should either be an acknowlegement or an error.
    */
-  status = netlink_parse_info (netlink_talk_filter, nl);
-
-  /* Restore socket flags for nonblocking I/O */
-  if (snb_ret == 0)
-    set_netlink_nonblocking (nl, &flags);
-
-  return status;
+  return netlink_parse_info (netlink_talk_filter, nl);
 }
 
 /* Routing table change via netlink interface. */
-int
+static int
 netlink_route (int cmd, int family, void *dest, int length, void *gate,
                int index, int zebra_flags, int table)
 {
@@ -1347,6 +1249,8 @@ netlink_route (int cmd, int family, void *dest, int length, void *gate,
   req.r.rtm_family = family;
   req.r.rtm_table = table;
   req.r.rtm_dst_len = length;
+  req.r.rtm_protocol = RTPROT_ZEBRA;
+  req.r.rtm_scope = RT_SCOPE_UNIVERSE;
 
   if ((zebra_flags & ZEBRA_FLAG_BLACKHOLE)
       || (zebra_flags & ZEBRA_FLAG_REJECT))
@@ -1356,9 +1260,6 @@ netlink_route (int cmd, int family, void *dest, int length, void *gate,
 
   if (cmd == RTM_NEWROUTE)
     {
-      req.r.rtm_protocol = RTPROT_ZEBRA;
-      req.r.rtm_scope = RT_SCOPE_UNIVERSE;
-
       if (discard)
         {
           if (zebra_flags & ZEBRA_FLAG_BLACKHOLE)
@@ -1396,7 +1297,7 @@ netlink_route (int cmd, int family, void *dest, int length, void *gate,
 }
 
 /* Routing table change via netlink interface. */
-int
+static int
 netlink_route_multipath (int cmd, struct prefix *p, struct rib *rib,
                          int family)
 {
@@ -1423,6 +1324,8 @@ netlink_route_multipath (int cmd, struct prefix *p, struct rib *rib,
   req.r.rtm_family = family;
   req.r.rtm_table = rib->table;
   req.r.rtm_dst_len = p->prefixlen;
+  req.r.rtm_protocol = RTPROT_ZEBRA;
+  req.r.rtm_scope = RT_SCOPE_UNIVERSE;
 
   if ((rib->flags & ZEBRA_FLAG_BLACKHOLE) || (rib->flags & ZEBRA_FLAG_REJECT))
     discard = 1;
@@ -1431,9 +1334,6 @@ netlink_route_multipath (int cmd, struct prefix *p, struct rib *rib,
 
   if (cmd == RTM_NEWROUTE)
     {
-      req.r.rtm_protocol = RTPROT_ZEBRA;
-      req.r.rtm_scope = RT_SCOPE_UNIVERSE;
-
       if (discard)
         {
           if (rib->flags & ZEBRA_FLAG_BLACKHOLE)
@@ -1494,7 +1394,9 @@ netlink_route_multipath (int cmd, struct prefix *p, struct rib *rib,
                    {
                      addattr_l (&req.n, sizeof req, RTA_GATEWAY,
                                 &nexthop->rgate.ipv4, bytelen);
-
+                      if (nexthop->src.ipv4.s_addr)
+                         addattr_l(&req.n, sizeof req, RTA_PREFSRC,
+                                    &nexthop->src.ipv4, bytelen);
                      if (IS_ZEBRA_DEBUG_KERNEL)
                        zlog_debug("netlink_route_multipath() (recursive, "
                                   "1 hop): nexthop via %s if %u",
@@ -1524,6 +1426,11 @@ netlink_route_multipath (int cmd, struct prefix *p, struct rib *rib,
                    {
                      addattr32 (&req.n, sizeof req, RTA_OIF,
                                 nexthop->rifindex);
+                      if ((nexthop->rtype == NEXTHOP_TYPE_IPV4_IFINDEX
+                           || nexthop->rtype == NEXTHOP_TYPE_IFINDEX)
+                          && nexthop->src.ipv4.s_addr)
+                        addattr_l (&req.n, sizeof req, RTA_PREFSRC,
+                                &nexthop->src.ipv4, bytelen);
 
                      if (IS_ZEBRA_DEBUG_KERNEL)
                        zlog_debug("netlink_route_multipath() (recursive, "
@@ -1552,6 +1459,9 @@ netlink_route_multipath (int cmd, struct prefix *p, struct rib *rib,
                    {
                      addattr_l (&req.n, sizeof req, RTA_GATEWAY,
                                 &nexthop->gate.ipv4, bytelen);
+                     if (nexthop->src.ipv4.s_addr)
+                        addattr_l (&req.n, sizeof req, RTA_PREFSRC,
+                                &nexthop->src.ipv4, bytelen);
 
                      if (IS_ZEBRA_DEBUG_KERNEL)
                        zlog_debug("netlink_route_multipath() (single hop): "
@@ -1576,8 +1486,19 @@ netlink_route_multipath (int cmd, struct prefix *p, struct rib *rib,
 #endif /* HAVE_IPV6 */
                   if (nexthop->type == NEXTHOP_TYPE_IFINDEX
                       || nexthop->type == NEXTHOP_TYPE_IFNAME
-                      || nexthop->type == NEXTHOP_TYPE_IPV4_IFINDEX
-                      || nexthop->type == NEXTHOP_TYPE_IPV6_IFINDEX
+                      || nexthop->type == NEXTHOP_TYPE_IPV4_IFINDEX)
+                   {
+                     addattr32 (&req.n, sizeof req, RTA_OIF, nexthop->ifindex);
+
+                     if (nexthop->src.ipv4.s_addr)
+                        addattr_l (&req.n, sizeof req, RTA_PREFSRC,
+                                &nexthop->src.ipv4, bytelen);
+
+                     if (IS_ZEBRA_DEBUG_KERNEL)
+                       zlog_debug("netlink_route_multipath() (single hop): "
+                                  "nexthop via if %u", nexthop->ifindex);
+                   }
+                  else if (nexthop->type == NEXTHOP_TYPE_IPV6_IFINDEX
                       || nexthop->type == NEXTHOP_TYPE_IPV6_IFNAME)
                    {
                      addattr32 (&req.n, sizeof req, RTA_OIF, nexthop->ifindex);
@@ -1601,6 +1522,7 @@ netlink_route_multipath (int cmd, struct prefix *p, struct rib *rib,
       char buf[1024];
       struct rtattr *rta = (void *) buf;
       struct rtnexthop *rtnh;
+      union g_addr *src = NULL;
 
       rta->rta_type = RTA_MULTIPATH;
       rta->rta_len = RTA_LENGTH (0);
@@ -1645,6 +1567,9 @@ netlink_route_multipath (int cmd, struct prefix *p, struct rib *rib,
                                      &nexthop->rgate.ipv4, bytelen);
                       rtnh->rtnh_len += sizeof (struct rtattr) + 4;
 
+                     if (nexthop->src.ipv4.s_addr)
+                        src = &nexthop->src;
+
                      if (IS_ZEBRA_DEBUG_KERNEL)
                        zlog_debug("netlink_route_multipath() (recursive, "
                                   "multihop): nexthop via %s if %u",
@@ -1667,10 +1592,20 @@ netlink_route_multipath (int cmd, struct prefix *p, struct rib *rib,
                    }
 #endif /* HAVE_IPV6 */
                   /* ifindex */
-                  if (nexthop->rtype == NEXTHOP_TYPE_IFINDEX
-                      || nexthop->rtype == NEXTHOP_TYPE_IFNAME
-                      || nexthop->rtype == NEXTHOP_TYPE_IPV4_IFINDEX
-                      || nexthop->rtype == NEXTHOP_TYPE_IPV6_IFINDEX
+                  if (nexthop->rtype == NEXTHOP_TYPE_IPV4_IFINDEX
+                     || nexthop->rtype == NEXTHOP_TYPE_IFINDEX
+                      || nexthop->rtype == NEXTHOP_TYPE_IFNAME)
+                   {
+                     rtnh->rtnh_ifindex = nexthop->rifindex;
+                      if (nexthop->src.ipv4.s_addr)
+                        src = &nexthop->src;
+
+                     if (IS_ZEBRA_DEBUG_KERNEL)
+                       zlog_debug("netlink_route_multipath() (recursive, "
+                                  "multihop): nexthop via if %u",
+                                  nexthop->rifindex);
+                   }
+                 else if (nexthop->rtype == NEXTHOP_TYPE_IPV6_IFINDEX
                       || nexthop->rtype == NEXTHOP_TYPE_IPV6_IFNAME)
                    {
                      rtnh->rtnh_ifindex = nexthop->rifindex;
@@ -1706,6 +1641,9 @@ netlink_route_multipath (int cmd, struct prefix *p, struct rib *rib,
                                     &nexthop->gate.ipv4, bytelen);
                      rtnh->rtnh_len += sizeof (struct rtattr) + 4;
 
+                     if (nexthop->src.ipv4.s_addr)
+                        src = &nexthop->src;
+
                       if (IS_ZEBRA_DEBUG_KERNEL)
                        zlog_debug("netlink_route_multipath() (multihop): "
                                   "nexthop via %s if %u",
@@ -1728,10 +1666,18 @@ netlink_route_multipath (int cmd, struct prefix *p, struct rib *rib,
                    }
 #endif /* HAVE_IPV6 */
                   /* ifindex */
-                  if (nexthop->type == NEXTHOP_TYPE_IFINDEX
-                      || nexthop->type == NEXTHOP_TYPE_IFNAME
-                      || nexthop->type == NEXTHOP_TYPE_IPV4_IFINDEX
-                      || nexthop->type == NEXTHOP_TYPE_IPV6_IFNAME
+                  if (nexthop->type == NEXTHOP_TYPE_IPV4_IFINDEX
+                     || nexthop->type == NEXTHOP_TYPE_IFINDEX
+                      || nexthop->type == NEXTHOP_TYPE_IFNAME)
+                    {
+                     rtnh->rtnh_ifindex = nexthop->ifindex;
+                     if (nexthop->src.ipv4.s_addr)
+                       src = &nexthop->src;
+                     if (IS_ZEBRA_DEBUG_KERNEL)
+                       zlog_debug("netlink_route_multipath() (multihop): "
+                                  "nexthop via if %u", nexthop->ifindex);
+                   }
+                  else if (nexthop->type == NEXTHOP_TYPE_IPV6_IFNAME
                       || nexthop->type == NEXTHOP_TYPE_IPV6_IFINDEX)
                    {
                      rtnh->rtnh_ifindex = nexthop->ifindex;
@@ -1751,6 +1697,8 @@ netlink_route_multipath (int cmd, struct prefix *p, struct rib *rib,
                 SET_FLAG (nexthop->flags, NEXTHOP_FLAG_FIB);
             }
         }
+      if (src)
+        addattr_l (&req.n, sizeof req, RTA_PREFSRC, &src->ipv4, bytelen);
 
       if (rta->rta_len > RTA_LENGTH (0))
         addattr_l (&req.n, 1024, RTA_MULTIPATH, RTA_DATA (rta),
@@ -1811,7 +1759,7 @@ kernel_delete_ipv6_old (struct prefix_ipv6 *dest, struct in6_addr *gate,
 #endif /* HAVE_IPV6 */
 \f
 /* Interface address modification. */
-int
+static int
 netlink_address (int cmd, int family, struct interface *ifp,
                  struct connected *ifc)
 {
@@ -1842,7 +1790,7 @@ netlink_address (int cmd, int family, struct interface *ifp,
 
   if (family == AF_INET && cmd == RTM_NEWADDR)
     {
-      if (if_is_broadcast (ifp) && ifc->destination)
+      if (!CONNECTED_PEER(ifc) && ifc->destination)
         {
           p = ifc->destination;
           addattr_l (&req.n, sizeof req, IFA_BROADCAST, &p->u.prefix,
@@ -1876,7 +1824,7 @@ kernel_address_delete_ipv4 (struct interface *ifp, struct connected *ifc)
 extern struct thread_master *master;
 
 /* Kernel route reflection. */
-int
+static int
 kernel_read (struct thread *thread)
 {
   int ret;
@@ -1889,6 +1837,37 @@ kernel_read (struct thread *thread)
   return 0;
 }
 
+/* Filter out messages from self that occur on listener socket,
+   caused by our actions on the command socket
+ */
+static void netlink_install_filter (int sock, __u32 pid)
+{
+  struct sock_filter filter[] = {
+    /* 0: ldh [4]                */
+    BPF_STMT(BPF_LD|BPF_ABS|BPF_H, offsetof(struct nlmsghdr, nlmsg_type)),
+    /* 1: jeq 0x18 jt 3 jf 6  */
+    BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, htons(RTM_NEWROUTE), 1, 0),
+    /* 2: jeq 0x19 jt 3 jf 6  */
+    BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, htons(RTM_DELROUTE), 0, 3),
+    /* 3: ldw [12]               */
+    BPF_STMT(BPF_LD|BPF_ABS|BPF_W, offsetof(struct nlmsghdr, nlmsg_pid)),
+    /* 4: jeq XX  jt 5 jf 6   */
+    BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, htonl(pid), 0, 1),
+    /* 5: ret 0    (skip)     */
+    BPF_STMT(BPF_RET|BPF_K, 0),
+    /* 6: ret 0xffff (keep)   */
+    BPF_STMT(BPF_RET|BPF_K, 0xffff),
+  };
+
+  struct sock_fprog prog = {
+    .len = sizeof(filter) / sizeof(filter[0]),
+    .filter = filter,
+  };
+
+  if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &prog, sizeof(prog)) < 0)
+    zlog_warn ("Can't install socket filter: %s\n", safe_strerror(errno));
+}
+
 /* Exported interface function.  This function simply calls
    netlink_socket (). */
 void
@@ -1905,5 +1884,17 @@ kernel_init (void)
 
   /* Register kernel socket. */
   if (netlink.sock > 0)
-    thread_add_read (zebrad.master, kernel_read, NULL, netlink.sock);
+    {
+      /* Only want non-blocking on the netlink event socket */
+      if (fcntl (netlink.sock, F_SETFL, O_NONBLOCK) < 0)
+       zlog (NULL, LOG_ERR, "Can't set %s socket flags: %s", netlink.name,
+               safe_strerror (errno));
+
+      /* Set receive buffer size if it's set from command line */
+      if (nl_rcvbufsize)
+       netlink_recvbuf (&netlink, nl_rcvbufsize);
+
+      netlink_install_filter (netlink.sock, netlink_cmd.snl.nl_pid);
+      thread_add_read (zebrad.master, kernel_read, NULL, netlink.sock);
+    }
 }