bgpd: Regularise BGP NLRI sanity checks a bit

* bgp_route.h: (bgp_nlri_sanity_check) The bulk of the args are equivalent
  to a (struct bgp_nlri), consolidate.
* bgp_route.c: (bgp_nlri_sanity_check) Make this a frontend for all afi/safis.
  Including SAFI_MPLS_LABELED_VPN.
  (bgp_nlri_sanity_check_ip) Regular IP NLRI sanity check based on the
  existing code, and adjusted for (struct bgp_nlri *) arg.
* bgp_attr.c: (bgp_mp_reach_parse) Adjust for passing (struct bgp_nlri *)
  to bgp_nlri_sanity_check.
  Get rid of special-casing to not sanity check VPN.
  (bgp_mp_unreach_parse) Ditto.

* bgp_mplsvpn.c: Use the same VPN parsing code for both the sanity
  check and the actual parse.

  (bgp_nlri_parse_vpn) renamed to bgp_nlri_parse_vpn_body and made
  internal.

  (bgp_nlri_parse_vpn_body) Added (bool) argument to control whether it
  is sanity checking or whether it should update routing state for each
  NLRI.  Send a NOTIFY and reset the session, if there's a parsing
  error, as bgp_nlri_sanity_check_ip does, and as is required by the
  RFC.

  (bgp_nlri_parse_vpn) now a wrapper to call _body with update.

  (bgp_nlri_sanity_check_vpn) wrapper to call parser without
  updating.

* bgp_mplsvpn.h: (bgp_nlri_sanity_check_vpn) export for
  bgp_nlri_sanity_check.

* bgp_packet.c: (bgp_update_receive) Adjust for bgp_nlri_sanity_check
  argument changes.

* test/bgp_mp_attr_test.c: Extend to also test the NLRI parsing functions,
  if the initial MP-attr parsing has succeeded.  Fix the NLRI in the
  VPN cases.  Add further VPN tests.

* tests/bgpd.tests/testbgpmpattr.exp: Add the new test cases.

This commit a joint effort of:

Lou Berger <lberger@labn.net>
Donald Sharp <sharpd@cumulusnetworks.com>
Paul Jakma <paul.jakma@hpe.com> / <paul@jakma.org>
diff --git a/bgpd/bgp_attr.c b/bgpd/bgp_attr.c
index 888f11a..98571da 100644
--- a/bgpd/bgp_attr.c
+++ b/bgpd/bgp_attr.c
@@ -1725,23 +1725,20 @@
                  __func__, peer->host);
       return BGP_ATTR_PARSE_ERROR_NOTIFYPLS;
     }
- 
-  if (safi != SAFI_MPLS_LABELED_VPN)
-    {
-      ret = bgp_nlri_sanity_check (peer, afi, safi, stream_pnt (s), nlri_len);
-      if (ret < 0) 
-        {
-          zlog_info ("%s: (%s) NLRI doesn't pass sanity check",
-                     __func__, peer->host);
-	  return BGP_ATTR_PARSE_ERROR_NOTIFYPLS;
-	}
-    }
-
+  
   mp_update->afi = afi;
   mp_update->safi = safi;
   mp_update->nlri = stream_pnt (s);
   mp_update->length = nlri_len;
 
+  ret = bgp_nlri_sanity_check (peer, mp_update);
+  if (ret < 0) 
+    {
+      zlog_info ("%s: (%s) NLRI doesn't pass sanity check",
+                 __func__, peer->host);
+      return BGP_ATTR_PARSE_ERROR_NOTIFYPLS;
+    }
+
   stream_forward_getp (s, nlri_len);
 
   attr->flag |= ATTR_FLAG_BIT (BGP_ATTR_MP_REACH_NLRI);
@@ -1759,7 +1756,6 @@
   afi_t afi;
   safi_t safi;
   u_int16_t withdraw_len;
-  int ret;
   struct peer *const peer = args->peer;  
   struct attr *const attr = args->attr;
   const bgp_size_t length = args->length;
@@ -1775,18 +1771,14 @@
   
   withdraw_len = length - BGP_MP_UNREACH_MIN_SIZE;
 
-  if (safi != SAFI_MPLS_LABELED_VPN)
-    {
-      ret = bgp_nlri_sanity_check (peer, afi, safi, stream_pnt (s), withdraw_len);
-      if (ret < 0)
-	return BGP_ATTR_PARSE_ERROR_NOTIFYPLS;
-    }
-
   mp_withdraw->afi = afi;
   mp_withdraw->safi = safi;
   mp_withdraw->nlri = stream_pnt (s);
   mp_withdraw->length = withdraw_len;
 
+  if (bgp_nlri_sanity_check (peer, mp_withdraw) < 0)
+    return BGP_ATTR_PARSE_ERROR_NOTIFYPLS;
+
   stream_forward_getp (s, withdraw_len);
 
   attr->flag |= ATTR_FLAG_BIT (BGP_ATTR_MP_UNREACH_NLRI);
diff --git a/bgpd/bgp_mplsvpn.c b/bgpd/bgp_mplsvpn.c
index f8b43df..900bc48 100644
--- a/bgpd/bgp_mplsvpn.c
+++ b/bgpd/bgp_mplsvpn.c
@@ -32,6 +32,7 @@
 #include "bgpd/bgp_route.h"
 #include "bgpd/bgp_attr.h"
 #include "bgpd/bgp_mplsvpn.h"
+#include "bgpd/bgp_packet.h"
 
 static u_int16_t
 decode_rd_type (u_char *pnt)
@@ -91,9 +92,9 @@
   rd_ip->val |= (u_int16_t) *pnt;
 }
 
-int
-bgp_nlri_parse_vpn (struct peer *peer, struct attr *attr,
-                    struct bgp_nlri *packet)
+static int
+bgp_nlri_parse_vpn_body (struct peer *peer, struct attr *attr, 
+                         struct bgp_nlri *packet, bool update)
 {
   u_char *pnt;
   u_char *lim;
@@ -129,30 +130,53 @@
       psize = PSIZE (prefixlen);
       
       /* sanity check against packet data */
-      if (prefixlen < VPN_PREFIXLEN_MIN_BYTES*8 || (pnt + psize) > lim)
+      if (prefixlen < VPN_PREFIXLEN_MIN_BYTES*8)
         {
-          zlog_err ("prefix length (%d) is less than 88"
-                    " or larger than received (%u)",
+          plog_err (peer->log, 
+                    "%s [Error] Update packet error / VPNv4"
+                     " (prefix length %d less than VPNv4 min length)",
+                    peer->host, prefixlen);
+          bgp_notify_send (peer, BGP_NOTIFY_UPDATE_ERR, 
+                           BGP_NOTIFY_UPDATE_OPT_ATTR_ERR);
+          return -1;
+        }
+      if ((pnt + psize) > lim)
+        {
+          plog_err (peer->log,
+                    "%s [Error] Update packet error / VPNv4"
+                    " (psize %u exceeds packet size (%u)",
+                    peer->host, 
                     prefixlen, (uint)(lim-pnt));
+          bgp_notify_send (peer, BGP_NOTIFY_UPDATE_ERR, 
+                           BGP_NOTIFY_UPDATE_OPT_ATTR_ERR);
           return -1;
         }
       
       /* sanity check against storage for the IP address portion */
       if ((psize - VPN_PREFIXLEN_MIN_BYTES) > (ssize_t) sizeof(p.u))
         {
-          zlog_err ("prefix length (%d) exceeds prefix storage (%zu)",
+          plog_err (peer->log,
+                    "%s [Error] Update packet error / VPNv4"
+                    " (psize %u exceeds storage size (%zu)",
+                    peer->host,
                     prefixlen - VPN_PREFIXLEN_MIN_BYTES*8, sizeof(p.u));
+          bgp_notify_send (peer, BGP_NOTIFY_UPDATE_ERR, 
+                           BGP_NOTIFY_UPDATE_OPT_ATTR_ERR);
           return -1;
         }
       
       /* Sanity check against max bitlen of the address family */
       if ((psize - VPN_PREFIXLEN_MIN_BYTES) > prefix_blen (&p))
         {
-          zlog_err ("prefix length (%d) exceeds family (%u) max byte length (%u)",
+          plog_err (peer->log,
+                    "%s [Error] Update packet error / VPNv4"
+                    " (psize %u exceeds family (%u) max byte len %u)",
+                    peer->host,
                     prefixlen - VPN_PREFIXLEN_MIN_BYTES*8, 
                     p.family, prefix_blen (&p));
+          bgp_notify_send (peer, BGP_NOTIFY_UPDATE_ERR, 
+                           BGP_NOTIFY_UPDATE_OPT_ATTR_ERR);
           return -1;
-                  
         }
       
       /* Copyr label to prefix. */
@@ -187,22 +211,46 @@
       memcpy (&p.u.prefix, pnt + VPN_PREFIXLEN_MIN_BYTES, 
               psize - VPN_PREFIXLEN_MIN_BYTES);
 
-      if (attr)
-        bgp_update (peer, &p, attr, packet->afi, SAFI_MPLS_VPN,
-                    ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, &prd, tagpnt, 0);
-      else
-        bgp_withdraw (peer, &p, attr, packet->afi, SAFI_MPLS_VPN,
-                      ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, &prd, tagpnt);
+      if (update)
+        {
+          if (attr)
+            bgp_update (peer, &p, attr, packet->afi, SAFI_MPLS_VPN,
+                        ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, &prd, tagpnt, 0);
+          else
+            bgp_withdraw (peer, &p, attr, packet->afi, SAFI_MPLS_VPN,
+                          ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, &prd, tagpnt);
+        }
     }
   /* Packet length consistency check. */
   if (pnt != lim)
-    return -1;
+    {
+      plog_err (peer->log,
+                "%s [Error] Update packet error / VPNv4"
+                " (%zu data remaining after parsing)",
+                peer->host, lim - pnt);
+      bgp_notify_send (peer, BGP_NOTIFY_UPDATE_ERR, 
+                       BGP_NOTIFY_UPDATE_OPT_ATTR_ERR);
+      return -1;
+    }
   
   return 0;
 #undef VPN_PREFIXLEN_MIN_BYTES
 }
 
 int
+bgp_nlri_sanity_check_vpn (struct peer *peer, struct bgp_nlri *nlri)
+{
+  return bgp_nlri_parse_vpn_body (peer, NULL, nlri, false);
+}
+
+int
+bgp_nlri_parse_vpn (struct peer *peer, struct attr *attr, 
+                    struct bgp_nlri *packet)
+{
+  return bgp_nlri_parse_vpn_body (peer, attr, packet, true);
+}
+
+int
 str2prefix_rd (const char *str, struct prefix_rd *prd)
 {
   int ret; /* ret of called functions */
diff --git a/bgpd/bgp_mplsvpn.h b/bgpd/bgp_mplsvpn.h
index 3299b9c..b89b6d8 100644
--- a/bgpd/bgp_mplsvpn.h
+++ b/bgpd/bgp_mplsvpn.h
@@ -43,6 +43,7 @@
 
 extern void bgp_mplsvpn_init (void);
 extern int bgp_nlri_parse_vpn (struct peer *, struct attr *, struct bgp_nlri *);
+extern int bgp_nlri_sanity_check_vpn (struct peer *, struct bgp_nlri *);
 extern u_int32_t decode_label (u_char *);
 extern int str2prefix_rd (const char *, struct prefix_rd *);
 extern int str2tag (const char *, u_char *);
diff --git a/bgpd/bgp_packet.c b/bgpd/bgp_packet.c
index b8a38fa..d40c24c 100644
--- a/bgpd/bgp_packet.c
+++ b/bgpd/bgp_packet.c
@@ -1660,17 +1660,17 @@
   /* Unfeasible Route packet format check. */
   if (withdraw_len > 0)
     {
-      ret = bgp_nlri_sanity_check (peer, AFI_IP, SAFI_UNICAST, stream_pnt (s), withdraw_len);
+      withdraw.afi = AFI_IP;
+      withdraw.safi = SAFI_UNICAST;
+      withdraw.nlri = stream_pnt (s);
+      withdraw.length = withdraw_len;
+      ret = bgp_nlri_sanity_check (peer, &withdraw);
       if (ret < 0)
 	return -1;
 
       if (BGP_DEBUG (packet, PACKET_RECV))
 	zlog_debug ("%s [Update:RECV] Unfeasible NLRI received", peer->host);
 
-      withdraw.afi = AFI_IP;
-      withdraw.safi = SAFI_UNICAST;
-      withdraw.nlri = stream_pnt (s);
-      withdraw.length = withdraw_len;
       stream_forward_getp (s, withdraw_len);
     }
   
@@ -1751,8 +1751,14 @@
 
   if (update_len)
     {
+      /* Set NLRI portion to structure. */
+      update.afi = AFI_IP;
+      update.safi = SAFI_UNICAST;
+      update.nlri = stream_pnt (s);
+      update.length = update_len;
+      
       /* Check NLRI packet format and prefix length. */
-      ret = bgp_nlri_sanity_check (peer, AFI_IP, SAFI_UNICAST, stream_pnt (s), update_len);
+      ret = bgp_nlri_sanity_check (peer, &update);
       if (ret < 0)
         {
           bgp_attr_unintern_sub (&attr);
@@ -1760,11 +1766,6 @@
 	  return -1;
 	}
 
-      /* Set NLRI portion to structure. */
-      update.afi = AFI_IP;
-      update.safi = SAFI_UNICAST;
-      update.nlri = stream_pnt (s);
-      update.length = update_len;
       stream_forward_getp (s, update_len);
     }
 
diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c
index 0dcae61..c1e5e59 100644
--- a/bgpd/bgp_route.c
+++ b/bgpd/bgp_route.c
@@ -3322,16 +3322,16 @@
   return 0;
 }
 
-/* NLRI encode syntax check routine. */
-int
-bgp_nlri_sanity_check (struct peer *peer, int afi, safi_t safi,
-                       u_char *pnt, bgp_size_t length)
+static int
+bgp_nlri_sanity_check_ip (struct peer *peer, struct bgp_nlri *nlri)
 {
   u_char *end;
   u_char prefixlen;
   int psize;
-
-  end = pnt + length;
+  u_char *pnt = nlri->nlri;
+  afi_t afi = nlri->afi;
+  safi_t safi = nlri->safi;
+  end = pnt + nlri->length;
 
   /* RFC1771 6.3 The NLRI field in the UPDATE message is checked for
      syntactic validity.  If the field is syntactically incorrect,
@@ -3395,6 +3395,20 @@
   return 0;
 }
 
+int
+bgp_nlri_sanity_check (struct peer *peer, struct bgp_nlri *nlri)
+{
+   switch (nlri->safi)
+     {
+       case SAFI_MPLS_LABELED_VPN:
+         return bgp_nlri_sanity_check_vpn (peer, nlri);
+       case SAFI_UNICAST:
+       case SAFI_MULTICAST:
+         return bgp_nlri_sanity_check_ip (peer, nlri);
+       default: return -1;
+     }
+}
+
 static struct bgp_static *
 bgp_static_new (void)
 {
diff --git a/bgpd/bgp_route.h b/bgpd/bgp_route.h
index 8e9bcbd..c16eca7 100644
--- a/bgpd/bgp_route.h
+++ b/bgpd/bgp_route.h
@@ -196,7 +196,7 @@
 extern void bgp_info_set_flag (struct bgp_node *, struct bgp_info *, u_int32_t);
 extern void bgp_info_unset_flag (struct bgp_node *, struct bgp_info *, u_int32_t);
 
-extern int bgp_nlri_sanity_check (struct peer *, int, safi_t, u_char *, bgp_size_t);
+extern int bgp_nlri_sanity_check (struct peer *, struct bgp_nlri *);
 extern int bgp_nlri_parse (struct peer *, struct attr *, struct bgp_nlri *);
 
 extern int bgp_maximum_prefix_overflow (struct peer *, afi_t, safi_t, int);