Work toward IPv6 support in BGP: implement decoding of the
BGP UPDATE attributes: MP_REACH_NLRI and MP_UNREACH_NLRI (RFC 4760).

Note: currently, the IPv6 NLRI is decoded, but it not used.

This work is in the context of ONOS-422.

Change-Id: Ia61b94dedfe0b1a7d7f563e805a3086f56d4da03
diff --git a/apps/sdnip/src/main/java/org/onosproject/sdnip/bgp/BgpConstants.java b/apps/sdnip/src/main/java/org/onosproject/sdnip/bgp/BgpConstants.java
index 1d7d4c5..b009c17 100644
--- a/apps/sdnip/src/main/java/org/onosproject/sdnip/bgp/BgpConstants.java
+++ b/apps/sdnip/src/main/java/org/onosproject/sdnip/bgp/BgpConstants.java
@@ -376,6 +376,46 @@
             /** BGP UPDATE Attributes Type Code AGGREGATOR length. */
             public static final int LENGTH = 6;
         }
+
+        /**
+         * BGP UPDATE: MP_REACH_NLRI related constants.
+         */
+        public static final class MpReachNlri {
+            /**
+             * Default constructor.
+             * <p>
+             * The constructor is private to prevent creating an instance of
+             * this utility class.
+             */
+            private MpReachNlri() {
+            }
+
+            /** BGP UPDATE Attributes Type Code MP_REACH_NLRI. */
+            public static final int TYPE = 14;
+
+            /** BGP UPDATE Attributes Type Code MP_REACH_NLRI min length. */
+            public static final int MIN_LENGTH = 5;
+        }
+
+        /**
+         * BGP UPDATE: MP_UNREACH_NLRI related constants.
+         */
+        public static final class MpUnreachNlri {
+            /**
+             * Default constructor.
+             * <p>
+             * The constructor is private to prevent creating an instance of
+             * this utility class.
+             */
+            private MpUnreachNlri() {
+            }
+
+            /** BGP UPDATE Attributes Type Code MP_UNREACH_NLRI. */
+            public static final int TYPE = 15;
+
+            /** BGP UPDATE Attributes Type Code MP_UNREACH_NLRI min length. */
+            public static final int MIN_LENGTH = 3;
+        }
     }
 
     /**
diff --git a/apps/sdnip/src/main/java/org/onosproject/sdnip/bgp/BgpSession.java b/apps/sdnip/src/main/java/org/onosproject/sdnip/bgp/BgpSession.java
index 52c6827..15c4e3a 100644
--- a/apps/sdnip/src/main/java/org/onosproject/sdnip/bgp/BgpSession.java
+++ b/apps/sdnip/src/main/java/org/onosproject/sdnip/bgp/BgpSession.java
@@ -36,6 +36,7 @@
 import org.jboss.netty.util.TimerTask;
 import org.onlab.packet.Ip4Address;
 import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.Ip6Prefix;
 import org.onosproject.sdnip.bgp.BgpConstants.Notifications;
 import org.onosproject.sdnip.bgp.BgpConstants.Notifications.HoldTimerExpired;
 import org.slf4j.Logger;
@@ -62,6 +63,8 @@
     private long remoteAs4Octet;                // 4 octets
     private long remoteHoldtime;                // 2 octets
     private Ip4Address remoteBgpId;             // 4 octets -> IPv4 address
+    private boolean remoteMpExtensions;         // Peer Multiprotocol
+                                                // Extensions enabled: RFC 4760
     private boolean remoteIpv4Unicast;          // Peer IPv4/UNICAST AFI/SAFI
     private boolean remoteIpv4Multicast;        // Peer IPv4/MULTICAST AFI/SAFI
     private boolean remoteIpv6Unicast;          // Peer IPv6/UNICAST AFI/SAFI
@@ -74,6 +77,8 @@
     private long localAs;                       // 2 octets
     private long localHoldtime;                 // 2 octets
     private Ip4Address localBgpId;              // 4 octets -> IPv4 address
+    private boolean localMpExtensions;          // Local Multiprotocol
+                                                // Extensions enabled: RFC 4760
     private boolean localIpv4Unicast;        // Local IPv4/UNICAST AFI/SAFI
     private boolean localIpv4Multicast;      // Local IPv4/MULTICAST AFI/SAFI
     private boolean localIpv6Unicast;        // Local IPv6/UNICAST AFI/SAFI
@@ -88,7 +93,9 @@
     private volatile Timeout sessionTimeout;    // Session timeout
 
     // BGP RIB-IN routing entries from this peer
-    private ConcurrentMap<Ip4Prefix, BgpRouteEntry> bgpRibIn =
+    private ConcurrentMap<Ip4Prefix, BgpRouteEntry> bgpRibIn4 =
+        new ConcurrentHashMap<>();
+    private ConcurrentMap<Ip6Prefix, BgpRouteEntry> bgpRibIn6 =
         new ConcurrentHashMap<>();
 
     /**
@@ -113,22 +120,41 @@
     }
 
     /**
-     * Gets the BGP RIB-IN routing entries.
+     * Gets the BGP RIB-IN IPv4 routing entries.
      *
-     * @return the BGP RIB-IN routing entries
+     * @return the BGP RIB-IN IPv4 routing entries
      */
-    public Map<Ip4Prefix, BgpRouteEntry> bgpRibIn() {
-        return bgpRibIn;
+    public Map<Ip4Prefix, BgpRouteEntry> bgpRibIn4() {
+        return bgpRibIn4;
     }
 
     /**
-     * Finds a BGP routing entry in the BGP RIB-IN.
+     * Gets the BGP RIB-IN IPv6 routing entries.
      *
-     * @param prefix the prefix of the route to search for
-     * @return the BGP routing entry if found, otherwise null
+     * @return the BGP RIB-IN IPv6 routing entries
+     */
+    public Map<Ip6Prefix, BgpRouteEntry> bgpRibIn6() {
+        return bgpRibIn6;
+    }
+
+    /**
+     * Finds a BGP IPv4 routing entry in the BGP RIB-IN.
+     *
+     * @param prefix the IPv4 prefix of the route to search for
+     * @return the IPv4 BGP routing entry if found, otherwise null
      */
     public BgpRouteEntry findBgpRouteEntry(Ip4Prefix prefix) {
-        return bgpRibIn.get(prefix);
+        return bgpRibIn4.get(prefix);
+    }
+
+    /**
+     * Finds a BGP IPv6 routing entry in the BGP RIB-IN.
+     *
+     * @param prefix the IPv6 prefix of the route to search for
+     * @return the IPv6 BGP routing entry if found, otherwise null
+     */
+    public BgpRouteEntry findBgpRouteEntry(Ip6Prefix prefix) {
+        return bgpRibIn6.get(prefix);
     }
 
     /**
@@ -256,6 +282,16 @@
     }
 
     /**
+     * Gets the BGP Multiprotocol Extensions for the session.
+     *
+     * @return true if the BGP Multiprotocol Extensions are enabled for the
+     * session, otherwise false
+     */
+     public boolean getMpExtensions() {
+        return remoteMpExtensions && localMpExtensions;
+    }
+
+    /**
      * Gets the BGP session remote AFI/SAFI configuration for IPv4 unicast.
      *
      * @return the BGP session remote AFI/SAFI configuration for IPv4 unicast
@@ -268,10 +304,11 @@
      * Sets the BGP session remote AFI/SAFI configuration for IPv4 unicast.
      */
     void setRemoteIpv4Unicast() {
+        this.remoteMpExtensions = true;
         this.remoteIpv4Unicast = true;
         // Copy the remote AFI/SAFI setting to the local configuration
-        // NOTE: Uncomment the line below if the AFI/SAFI is supported locally
-        // this.localIpv4Unicast = true;
+        this.localMpExtensions = true;
+        this.localIpv4Unicast = true;
     }
 
     /**
@@ -287,10 +324,11 @@
      * Sets the BGP session remote AFI/SAFI configuration for IPv4 multicast.
      */
     void setRemoteIpv4Multicast() {
+        this.remoteMpExtensions = true;
         this.remoteIpv4Multicast = true;
         // Copy the remote AFI/SAFI setting to the local configuration
-        // NOTE: Uncomment the line below if the AFI/SAFI is supported locally
-        // this.localIpv4Multicast = true;
+        this.localMpExtensions = true;
+        this.localIpv4Multicast = true;
     }
 
     /**
@@ -306,10 +344,11 @@
      * Sets the BGP session remote AFI/SAFI configuration for IPv6 unicast.
      */
     void setRemoteIpv6Unicast() {
+        this.remoteMpExtensions = true;
         this.remoteIpv6Unicast = true;
         // Copy the remote AFI/SAFI setting to the local configuration
-        // NOTE: Uncomment the line below if the AFI/SAFI is supported locally
-        // this.localIpv6Unicast = true;
+        this.localMpExtensions = true;
+        this.localIpv6Unicast = true;
     }
 
     /**
@@ -325,10 +364,11 @@
      * Sets the BGP session remote AFI/SAFI configuration for IPv6 multicast.
      */
     void setRemoteIpv6Multicast() {
+        this.remoteMpExtensions = true;
         this.remoteIpv6Multicast = true;
         // Copy the remote AFI/SAFI setting to the local configuration
-        // NOTE: Uncomment the line below if the AFI/SAFI is supported locally
-        // this.localIpv6Multicast = true;
+        this.localMpExtensions = true;
+        this.localIpv6Multicast = true;
     }
 
     /**
@@ -576,14 +616,17 @@
         // for further processing. Otherwise, the BGP Decision Process
         // will use those routes again.
         //
-        Collection<BgpRouteEntry> deletedRoutes = bgpRibIn.values();
-        bgpRibIn = new ConcurrentHashMap<>();
+        Collection<BgpRouteEntry> deletedRoutes4 = bgpRibIn4.values();
+        Collection<BgpRouteEntry> deletedRoutes6 = bgpRibIn6.values();
+        bgpRibIn4 = new ConcurrentHashMap<>();
+        bgpRibIn6 = new ConcurrentHashMap<>();
 
         // Push the updates to the BGP Merged RIB
         BgpSessionManager.BgpRouteSelector bgpRouteSelector =
             bgpSessionManager.getBgpRouteSelector();
         Collection<BgpRouteEntry> addedRoutes = Collections.emptyList();
-        bgpRouteSelector.routeUpdates(this, addedRoutes, deletedRoutes);
+        bgpRouteSelector.routeUpdates(this, addedRoutes, deletedRoutes4);
+        bgpRouteSelector.routeUpdates(this, addedRoutes, deletedRoutes6);
 
         bgpSessionManager.peerDisconnected(this);
     }
diff --git a/apps/sdnip/src/main/java/org/onosproject/sdnip/bgp/BgpUpdate.java b/apps/sdnip/src/main/java/org/onosproject/sdnip/bgp/BgpUpdate.java
index f7bfa32..2dabfd9 100644
--- a/apps/sdnip/src/main/java/org/onosproject/sdnip/bgp/BgpUpdate.java
+++ b/apps/sdnip/src/main/java/org/onosproject/sdnip/bgp/BgpUpdate.java
@@ -25,9 +25,13 @@
 import org.jboss.netty.buffer.ChannelBuffers;
 import org.jboss.netty.channel.ChannelHandlerContext;
 import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip6Address;
 import org.onlab.packet.Ip4Prefix;
-import org.onosproject.sdnip.bgp.BgpConstants.Update.AsPath;
+import org.onlab.packet.Ip6Prefix;
 import org.onosproject.sdnip.bgp.BgpConstants.Notifications.UpdateMessageError;
+import org.onosproject.sdnip.bgp.BgpConstants.Open.Capabilities.MultiprotocolExtensions;
+import org.onosproject.sdnip.bgp.BgpConstants.Update;
+import org.onosproject.sdnip.bgp.BgpConstants.Update.AsPath;
 import org.onosproject.sdnip.bgp.BgpMessage.BgpParseException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -57,8 +61,7 @@
     static void processBgpUpdate(BgpSession bgpSession,
                                  ChannelHandlerContext ctx,
                                  ChannelBuffer message) {
-        Collection<BgpRouteEntry> addedRoutes = null;
-        Map<Ip4Prefix, BgpRouteEntry> deletedRoutes = new HashMap<>();
+        DecodedBgpRoutes decodedBgpRoutes = new DecodedBgpRoutes();
 
         int minLength =
             BgpConstants.BGP_UPDATE_MIN_LENGTH - BgpConstants.BGP_HEADER_LENGTH;
@@ -97,8 +100,8 @@
         }
         Collection<Ip4Prefix> withdrawnPrefixes = null;
         try {
-            withdrawnPrefixes = parsePackedPrefixes(withdrawnRoutesLength,
-                                                    message);
+            withdrawnPrefixes = parsePackedIp4Prefixes(withdrawnRoutesLength,
+                                                       message);
         } catch (BgpParseException e) {
             // ERROR: Invalid Network Field
             log.debug("Exception parsing Withdrawn Prefixes from BGP peer {}: ",
@@ -109,9 +112,10 @@
         for (Ip4Prefix prefix : withdrawnPrefixes) {
             log.debug("BGP RX UPDATE message WITHDRAWN from {}: {}",
                       bgpSession.getRemoteAddress(), prefix);
-            BgpRouteEntry bgpRouteEntry = bgpSession.bgpRibIn().get(prefix);
+            BgpRouteEntry bgpRouteEntry = bgpSession.bgpRibIn4().get(prefix);
             if (bgpRouteEntry != null) {
-                deletedRoutes.put(prefix, bgpRouteEntry);
+                decodedBgpRoutes.deletedUnicastRoutes4.put(prefix,
+                                                           bgpRouteEntry);
             }
         }
 
@@ -119,31 +123,53 @@
         // Parse the Path Attributes
         //
         try {
-            addedRoutes = parsePathAttributes(bgpSession, ctx, message);
+            parsePathAttributes(bgpSession, ctx, message, decodedBgpRoutes);
         } catch (BgpParseException e) {
             log.debug("Exception parsing Path Attributes from BGP peer {}: ",
                       bgpSession.getRemoteBgpId(), e);
             // NOTE: The session was already closed, so nothing else to do
             return;
         }
-        // Ignore WITHDRAWN routes that are ADDED
-        for (BgpRouteEntry bgpRouteEntry : addedRoutes) {
-            deletedRoutes.remove(bgpRouteEntry.prefix());
-        }
 
+        //
         // Update the BGP RIB-IN
-        for (BgpRouteEntry bgpRouteEntry : deletedRoutes.values()) {
-            bgpSession.bgpRibIn().remove(bgpRouteEntry.prefix());
+        //
+        Collection<BgpRouteEntry> bgpRoutes;
+        //
+        bgpRoutes = decodedBgpRoutes.deletedUnicastRoutes4.values();
+        for (BgpRouteEntry bgpRouteEntry : bgpRoutes) {
+            bgpSession.bgpRibIn4().remove(bgpRouteEntry.prefix());
         }
-        for (BgpRouteEntry bgpRouteEntry : addedRoutes) {
-            bgpSession.bgpRibIn().put(bgpRouteEntry.prefix(), bgpRouteEntry);
+        //
+        bgpRoutes = decodedBgpRoutes.addedUnicastRoutes4.values();
+        for (BgpRouteEntry bgpRouteEntry : bgpRoutes) {
+            bgpSession.bgpRibIn4().put(bgpRouteEntry.prefix(), bgpRouteEntry);
         }
+        //
+        bgpRoutes = decodedBgpRoutes.deletedUnicastRoutes6.values();
+        for (BgpRouteEntry bgpRouteEntry : bgpRoutes) {
+            bgpSession.bgpRibIn6().remove(bgpRouteEntry.prefix());
+        }
+        //
+        bgpRoutes = decodedBgpRoutes.addedUnicastRoutes6.values();
+        // TODO: fix/enable for IPv6
+        /*
+        for (BgpRouteEntry bgpRouteEntry : bgpRoutes) {
+            bgpSession.bgpRibIn6().put(bgpRouteEntry.prefix(), bgpRouteEntry);
+        }
+        */
 
+        //
         // Push the updates to the BGP Merged RIB
+        //
         BgpSessionManager.BgpRouteSelector bgpRouteSelector =
             bgpSession.getBgpSessionManager().getBgpRouteSelector();
-        bgpRouteSelector.routeUpdates(bgpSession, addedRoutes,
-                                      deletedRoutes.values());
+        bgpRouteSelector.routeUpdates(bgpSession,
+                                decodedBgpRoutes.addedUnicastRoutes4.values(),
+                                decodedBgpRoutes.deletedUnicastRoutes4.values());
+        bgpRouteSelector.routeUpdates(bgpSession,
+                                decodedBgpRoutes.addedUnicastRoutes6.values(),
+                                decodedBgpRoutes.deletedUnicastRoutes6.values());
 
         // Start the Session Timeout timer
         bgpSession.restartSessionTimeoutTimer(ctx);
@@ -155,27 +181,34 @@
      * @param bgpSession the BGP Session to use
      * @param ctx the Channel Handler Context
      * @param message the message to parse
-     * @return a collection of the result BGP Route Entries
+     * @param decodedBgpRoutes the container to store the decoded BGP Route
+     * Entries. It might already contain some route entries such as withdrawn
+     * IPv4 prefixes
      * @throws BgpParseException
      */
-    private static Collection<BgpRouteEntry> parsePathAttributes(
-                                                BgpSession bgpSession,
-                                                ChannelHandlerContext ctx,
-                                                ChannelBuffer message)
+    // CHECKSTYLE IGNORE MethodLength FOR NEXT 300 LINES
+    private static void parsePathAttributes(
+                                        BgpSession bgpSession,
+                                        ChannelHandlerContext ctx,
+                                        ChannelBuffer message,
+                                        DecodedBgpRoutes decodedBgpRoutes)
         throws BgpParseException {
-        Map<Ip4Prefix, BgpRouteEntry> addedRoutes = new HashMap<>();
 
         //
         // Parsed values
         //
         Short origin = -1;                      // Mandatory
         BgpRouteEntry.AsPath asPath = null;     // Mandatory
-        Ip4Address nextHop = null;              // Mandatory
+        // Legacy NLRI (RFC 4271). Mandatory NEXT_HOP if legacy NLRI is used
+        MpNlri legacyNlri = new MpNlri(MultiprotocolExtensions.AFI_IPV4,
+                                       MultiprotocolExtensions.SAFI_UNICAST);
         long multiExitDisc =                    // Optional
-            BgpConstants.Update.MultiExitDisc.LOWEST_MULTI_EXIT_DISC;
+            Update.MultiExitDisc.LOWEST_MULTI_EXIT_DISC;
         Long localPref = null;                  // Mandatory
         Long aggregatorAsNumber = null;         // Optional: unused
         Ip4Address aggregatorIpAddress = null;  // Optional: unused
+        Collection<MpNlri> mpNlriReachList = new ArrayList<>();   // Optional
+        Collection<MpNlri> mpNlriUnreachList = new ArrayList<>(); // Optional
 
         //
         // Get and verify the Path Attributes Length
@@ -188,7 +221,7 @@
             throw new BgpParseException(errorMsg);
         }
         if (pathAttributeLength == 0) {
-            return addedRoutes.values();
+            return;
         }
 
         //
@@ -244,28 +277,29 @@
             //
             switch (attrTypeCode) {
 
-            case BgpConstants.Update.Origin.TYPE:
+            case Update.Origin.TYPE:
                 // Attribute Type Code ORIGIN
                 origin = parseAttributeTypeOrigin(bgpSession, ctx,
                                                   attrTypeCode, attrLen,
                                                   attrFlags, message);
                 break;
 
-            case BgpConstants.Update.AsPath.TYPE:
+            case Update.AsPath.TYPE:
                 // Attribute Type Code AS_PATH
                 asPath = parseAttributeTypeAsPath(bgpSession, ctx,
                                                   attrTypeCode, attrLen,
                                                   attrFlags, message);
                 break;
 
-            case BgpConstants.Update.NextHop.TYPE:
+            case Update.NextHop.TYPE:
                 // Attribute Type Code NEXT_HOP
-                nextHop = parseAttributeTypeNextHop(bgpSession, ctx,
-                                                    attrTypeCode, attrLen,
-                                                    attrFlags, message);
+                legacyNlri.nextHop4 =
+                    parseAttributeTypeNextHop(bgpSession, ctx,
+                                              attrTypeCode, attrLen,
+                                              attrFlags, message);
                 break;
 
-            case BgpConstants.Update.MultiExitDisc.TYPE:
+            case Update.MultiExitDisc.TYPE:
                 // Attribute Type Code MULTI_EXIT_DISC
                 multiExitDisc =
                     parseAttributeTypeMultiExitDisc(bgpSession, ctx,
@@ -273,7 +307,7 @@
                                                     attrFlags, message);
                 break;
 
-            case BgpConstants.Update.LocalPref.TYPE:
+            case Update.LocalPref.TYPE:
                 // Attribute Type Code LOCAL_PREF
                 localPref =
                     parseAttributeTypeLocalPref(bgpSession, ctx,
@@ -281,7 +315,7 @@
                                                 attrFlags, message);
                 break;
 
-            case BgpConstants.Update.AtomicAggregate.TYPE:
+            case Update.AtomicAggregate.TYPE:
                 // Attribute Type Code ATOMIC_AGGREGATE
                 parseAttributeTypeAtomicAggregate(bgpSession, ctx,
                                                   attrTypeCode, attrLen,
@@ -289,7 +323,7 @@
                 // Nothing to do: this attribute is primarily informational
                 break;
 
-            case BgpConstants.Update.Aggregator.TYPE:
+            case Update.Aggregator.TYPE:
                 // Attribute Type Code AGGREGATOR
                 Pair<Long, Ip4Address> aggregator =
                     parseAttributeTypeAggregator(bgpSession, ctx,
@@ -299,6 +333,29 @@
                 aggregatorIpAddress = aggregator.getRight();
                 break;
 
+            case Update.MpReachNlri.TYPE:
+                // Attribute Type Code MP_REACH_NLRI
+                MpNlri mpNlriReach =
+                    parseAttributeTypeMpReachNlri(bgpSession, ctx,
+                                                  attrTypeCode,
+                                                  attrLen,
+                                                  attrFlags, message);
+                if (mpNlriReach != null) {
+                    mpNlriReachList.add(mpNlriReach);
+                }
+                break;
+
+            case Update.MpUnreachNlri.TYPE:
+                // Attribute Type Code MP_UNREACH_NLRI
+                MpNlri mpNlriUnreach =
+                    parseAttributeTypeMpUnreachNlri(bgpSession, ctx,
+                                                    attrTypeCode, attrLen,
+                                                    attrFlags, message);
+                if (mpNlriUnreach != null) {
+                    mpNlriUnreachList.add(mpNlriUnreach);
+                }
+                break;
+
             default:
                 // NOTE: Parse any new Attribute Types if needed
                 if (!optionalBit) {
@@ -320,17 +377,15 @@
             }
         }
 
-        // Verify the Well-known Attributes
-        verifyBgpUpdateWellKnownAttributes(bgpSession, ctx, origin, asPath,
-                                           nextHop, localPref);
-
         //
         // Parse the NLRI (Network Layer Reachability Information)
         //
-        Collection<Ip4Prefix> addedPrefixes = null;
         int nlriLength = message.readableBytes();
         try {
-            addedPrefixes = parsePackedPrefixes(nlriLength, message);
+            Collection<Ip4Prefix> addedPrefixes4 =
+                parsePackedIp4Prefixes(nlriLength, message);
+            // Store it inside the legacy NLRI wrapper
+            legacyNlri.nlri4 = addedPrefixes4;
         } catch (BgpParseException e) {
             // ERROR: Invalid Network Field
             log.debug("Exception parsing NLRI from BGP peer {}: ",
@@ -340,25 +395,92 @@
             throw e;
         }
 
-        // Generate the added routes
-        for (Ip4Prefix prefix : addedPrefixes) {
-            BgpRouteEntry bgpRouteEntry =
-                new BgpRouteEntry(bgpSession, prefix, nextHop,
-                                  origin.byteValue(), asPath, localPref);
-            bgpRouteEntry.setMultiExitDisc(multiExitDisc);
-            if (bgpRouteEntry.hasAsPathLoop(bgpSession.getLocalAs())) {
-                log.debug("BGP RX UPDATE message IGNORED from {}: {} " +
-                          "nextHop {}: contains AS Path loop",
-                          bgpSession.getRemoteAddress(), prefix, nextHop);
-                continue;
-            } else {
-                log.debug("BGP RX UPDATE message ADDED from {}: {} nextHop {}",
-                          bgpSession.getRemoteAddress(), prefix, nextHop);
+        // Verify the Well-known Attributes
+        verifyBgpUpdateWellKnownAttributes(bgpSession, ctx, origin, asPath,
+                                           localPref, legacyNlri,
+                                           mpNlriReachList);
+
+        //
+        // Generate the deleted routes
+        //
+        for (MpNlri mpNlri : mpNlriUnreachList) {
+            BgpRouteEntry bgpRouteEntry;
+
+            // The deleted IPv4 routes
+            for (Ip4Prefix prefix : mpNlri.nlri4) {
+                bgpRouteEntry = bgpSession.bgpRibIn4().get(prefix);
+                if (bgpRouteEntry != null) {
+                    decodedBgpRoutes.deletedUnicastRoutes4.put(prefix,
+                                                               bgpRouteEntry);
+                }
             }
-            addedRoutes.put(prefix, bgpRouteEntry);
+
+            // The deleted IPv6 routes
+            for (Ip6Prefix prefix : mpNlri.nlri6) {
+                bgpRouteEntry = bgpSession.bgpRibIn6().get(prefix);
+                if (bgpRouteEntry != null) {
+                    decodedBgpRoutes.deletedUnicastRoutes6.put(prefix,
+                                                               bgpRouteEntry);
+                }
+            }
         }
 
-        return addedRoutes.values();
+        //
+        // Generate the added routes
+        //
+        mpNlriReachList.add(legacyNlri);
+        for (MpNlri mpNlri : mpNlriReachList) {
+            BgpRouteEntry bgpRouteEntry;
+
+            // The added IPv4 routes
+            for (Ip4Prefix prefix : mpNlri.nlri4) {
+                bgpRouteEntry =
+                    new BgpRouteEntry(bgpSession, prefix, mpNlri.nextHop4,
+                                      origin.byteValue(), asPath, localPref);
+                bgpRouteEntry.setMultiExitDisc(multiExitDisc);
+                if (bgpRouteEntry.hasAsPathLoop(bgpSession.getLocalAs())) {
+                    log.debug("BGP RX UPDATE message IGNORED from {}: {} " +
+                              "nextHop {}: contains AS Path loop",
+                              bgpSession.getRemoteAddress(), prefix,
+                              mpNlri.nextHop4);
+                    continue;
+                } else {
+                    log.debug("BGP RX UPDATE message ADDED from {}: {} nextHop {}",
+                              bgpSession.getRemoteAddress(), prefix,
+                              mpNlri.nextHop4);
+                }
+                // Remove from the collection of deleted routes
+                decodedBgpRoutes.deletedUnicastRoutes4.remove(prefix);
+                decodedBgpRoutes.addedUnicastRoutes4.put(prefix,
+                                                         bgpRouteEntry);
+            }
+
+            // The added IPv6 routes
+            // TODO: fix/enable for IPv6
+            /*
+            for (Ip6Prefix prefix : mpNlri.nlri6) {
+                bgpRouteEntry =
+                    new BgpRouteEntry(bgpSession, prefix, mpNlri.nextHop6,
+                                      origin.byteValue(), asPath, localPref);
+                bgpRouteEntry.setMultiExitDisc(multiExitDisc);
+                if (bgpRouteEntry.hasAsPathLoop(bgpSession.getLocalAs())) {
+                    log.debug("BGP RX UPDATE message IGNORED from {}: {} " +
+                              "nextHop {}: contains AS Path loop",
+                              bgpSession.getRemoteAddress(), prefix,
+                              mpNlri.nextHop6);
+                    continue;
+                } else {
+                    log.debug("BGP RX UPDATE message ADDED from {}: {} nextHop {}",
+                              bgpSession.getRemoteAddress(), prefix,
+                              mpNlri.nextHop6);
+                }
+                // Remove from the collection of deleted routes
+                decodedBgpRoutes.deletedUnicastRoutes6.remove(prefix);
+                decodedBgpRoutes.addedUnicastRoutes6.put(prefix,
+                                                         bgpRouteEntry);
+            }
+            */
+        }
     }
 
     /**
@@ -368,8 +490,10 @@
      * @param ctx the Channel Handler Context
      * @param origin the ORIGIN well-known mandatory attribute
      * @param asPath the AS_PATH well-known mandatory attribute
-     * @param nextHop the NEXT_HOP well-known mandatory attribute
      * @param localPref the LOCAL_PREF required attribute
+     * @param legacyNlri the legacy NLRI. Encapsulates the NEXT_HOP well-known
+     * mandatory attribute (mandatory if legacy NLRI is used).
+     * @param mpNlriReachList the Multiprotocol NLRI attributes
      * @throws BgpParseException
      */
     private static void verifyBgpUpdateWellKnownAttributes(
@@ -377,41 +501,65 @@
                                 ChannelHandlerContext ctx,
                                 Short origin,
                                 BgpRouteEntry.AsPath asPath,
-                                Ip4Address nextHop,
-                                Long localPref)
+                                Long localPref,
+                                MpNlri legacyNlri,
+                                Collection<MpNlri> mpNlriReachList)
         throws BgpParseException {
+        boolean hasNlri = false;
+        boolean hasLegacyNlri = false;
+
+        //
+        // Convenience flags that are used to check for missing attributes.
+        //
+        // NOTE: The hasLegacyNlri flag is always set to true if the
+        // Multiprotocol Extensions are not enabled, even if the UPDATE
+        // message doesn't contain the legacy NLRI (per RFC 4271).
+        //
+        if (!bgpSession.getMpExtensions()) {
+            hasNlri = true;
+            hasLegacyNlri = true;
+        } else {
+            if (!legacyNlri.nlri4.isEmpty()) {
+                hasNlri = true;
+                hasLegacyNlri = true;
+            }
+            if (!mpNlriReachList.isEmpty()) {
+                hasNlri = true;
+            }
+        }
+
         //
         // Check for Missing Well-known Attributes
         //
-        if ((origin == null) || (origin == -1)) {
+        if (hasNlri && ((origin == null) || (origin == -1))) {
             // Missing Attribute Type Code ORIGIN
-            int type = BgpConstants.Update.Origin.TYPE;
+            int type = Update.Origin.TYPE;
             actionsBgpUpdateMissingWellKnownAttribute(bgpSession, ctx, type);
             String errorMsg = "Missing Well-known Attribute: ORIGIN";
             throw new BgpParseException(errorMsg);
         }
-        if (asPath == null) {
+        if (hasNlri && (asPath == null)) {
             // Missing Attribute Type Code AS_PATH
-            int type = BgpConstants.Update.AsPath.TYPE;
+            int type = Update.AsPath.TYPE;
             actionsBgpUpdateMissingWellKnownAttribute(bgpSession, ctx, type);
             String errorMsg = "Missing Well-known Attribute: AS_PATH";
             throw new BgpParseException(errorMsg);
         }
-        if (nextHop == null) {
-            // Missing Attribute Type Code NEXT_HOP
-            int type = BgpConstants.Update.NextHop.TYPE;
-            actionsBgpUpdateMissingWellKnownAttribute(bgpSession, ctx, type);
-            String errorMsg = "Missing Well-known Attribute: NEXT_HOP";
-            throw new BgpParseException(errorMsg);
-        }
-        if (localPref == null) {
+        if (hasNlri && (localPref == null)) {
             // Missing Attribute Type Code LOCAL_PREF
             // NOTE: Required for iBGP
-            int type = BgpConstants.Update.LocalPref.TYPE;
+            int type = Update.LocalPref.TYPE;
             actionsBgpUpdateMissingWellKnownAttribute(bgpSession, ctx, type);
             String errorMsg = "Missing Well-known Attribute: LOCAL_PREF";
             throw new BgpParseException(errorMsg);
         }
+        if (hasLegacyNlri && (legacyNlri.nextHop4 == null)) {
+            // Missing Attribute Type Code NEXT_HOP
+            int type = Update.NextHop.TYPE;
+            actionsBgpUpdateMissingWellKnownAttribute(bgpSession, ctx, type);
+            String errorMsg = "Missing Well-known Attribute: NEXT_HOP";
+            throw new BgpParseException(errorMsg);
+        }
     }
 
     /**
@@ -440,34 +588,42 @@
         String typeName = "UNKNOWN";
         boolean isWellKnown = false;
         switch (attrTypeCode) {
-        case BgpConstants.Update.Origin.TYPE:
+        case Update.Origin.TYPE:
             isWellKnown = true;
             typeName = "ORIGIN";
             break;
-        case BgpConstants.Update.AsPath.TYPE:
+        case Update.AsPath.TYPE:
             isWellKnown = true;
             typeName = "AS_PATH";
             break;
-        case BgpConstants.Update.NextHop.TYPE:
+        case Update.NextHop.TYPE:
             isWellKnown = true;
             typeName = "NEXT_HOP";
             break;
-        case BgpConstants.Update.MultiExitDisc.TYPE:
+        case Update.MultiExitDisc.TYPE:
             isWellKnown = false;
             typeName = "MULTI_EXIT_DISC";
             break;
-        case BgpConstants.Update.LocalPref.TYPE:
+        case Update.LocalPref.TYPE:
             isWellKnown = true;
             typeName = "LOCAL_PREF";
             break;
-        case BgpConstants.Update.AtomicAggregate.TYPE:
+        case Update.AtomicAggregate.TYPE:
             isWellKnown = true;
             typeName = "ATOMIC_AGGREGATE";
             break;
-        case BgpConstants.Update.Aggregator.TYPE:
+        case Update.Aggregator.TYPE:
             isWellKnown = false;
             typeName = "AGGREGATOR";
             break;
+        case Update.MpReachNlri.TYPE:
+            isWellKnown = false;
+            typeName = "MP_REACH_NLRI";
+            break;
+        case Update.MpUnreachNlri.TYPE:
+            isWellKnown = false;
+            typeName = "MP_UNREACH_NLRI";
+            break;
         default:
             isWellKnown = false;
             typeName = "UNKNOWN(" + attrTypeCode + ")";
@@ -521,7 +677,7 @@
         throws BgpParseException {
 
         // Check the Attribute Length
-        if (attrLen != BgpConstants.Update.Origin.LENGTH) {
+        if (attrLen != Update.Origin.LENGTH) {
             // ERROR: Attribute Length Error
             actionsBgpUpdateAttributeLengthError(
                 bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
@@ -532,11 +688,11 @@
         message.markReaderIndex();
         short origin = message.readUnsignedByte();
         switch (origin) {
-        case BgpConstants.Update.Origin.IGP:
+        case Update.Origin.IGP:
             // FALLTHROUGH
-        case BgpConstants.Update.Origin.EGP:
+        case Update.Origin.EGP:
             // FALLTHROUGH
-        case BgpConstants.Update.Origin.INCOMPLETE:
+        case Update.Origin.INCOMPLETE:
             break;
         default:
             // ERROR: Invalid ORIGIN Attribute
@@ -590,13 +746,13 @@
 
             // Verify the Path Segment Type
             switch (pathSegmentType) {
-            case BgpConstants.Update.AsPath.AS_SET:
+            case Update.AsPath.AS_SET:
                 // FALLTHROUGH
-            case BgpConstants.Update.AsPath.AS_SEQUENCE:
+            case Update.AsPath.AS_SEQUENCE:
                 // FALLTHROUGH
-            case BgpConstants.Update.AsPath.AS_CONFED_SEQUENCE:
+            case Update.AsPath.AS_CONFED_SEQUENCE:
                 // FALLTHROUGH
-            case BgpConstants.Update.AsPath.AS_CONFED_SET:
+            case Update.AsPath.AS_CONFED_SET:
                 break;
             default:
                 // ERROR: Invalid Path Segment Type
@@ -669,7 +825,7 @@
         throws BgpParseException {
 
         // Check the Attribute Length
-        if (attrLen != BgpConstants.Update.NextHop.LENGTH) {
+        if (attrLen != Update.NextHop.LENGTH) {
             // ERROR: Attribute Length Error
             actionsBgpUpdateAttributeLengthError(
                 bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
@@ -725,7 +881,7 @@
         throws BgpParseException {
 
         // Check the Attribute Length
-        if (attrLen != BgpConstants.Update.MultiExitDisc.LENGTH) {
+        if (attrLen != Update.MultiExitDisc.LENGTH) {
             // ERROR: Attribute Length Error
             actionsBgpUpdateAttributeLengthError(
                 bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
@@ -759,7 +915,7 @@
         throws BgpParseException {
 
         // Check the Attribute Length
-        if (attrLen != BgpConstants.Update.LocalPref.LENGTH) {
+        if (attrLen != Update.LocalPref.LENGTH) {
             // ERROR: Attribute Length Error
             actionsBgpUpdateAttributeLengthError(
                 bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
@@ -792,7 +948,7 @@
         throws BgpParseException {
 
         // Check the Attribute Length
-        if (attrLen != BgpConstants.Update.AtomicAggregate.LENGTH) {
+        if (attrLen != Update.AtomicAggregate.LENGTH) {
             // ERROR: Attribute Length Error
             actionsBgpUpdateAttributeLengthError(
                 bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
@@ -825,7 +981,7 @@
         throws BgpParseException {
 
         // Check the Attribute Length
-        if (attrLen != BgpConstants.Update.Aggregator.LENGTH) {
+        if (attrLen != Update.Aggregator.LENGTH) {
             // ERROR: Attribute Length Error
             actionsBgpUpdateAttributeLengthError(
                 bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
@@ -845,6 +1001,214 @@
     }
 
     /**
+     * Parses BGP UPDATE Attribute Type MP_REACH_NLRI.
+     *
+     * @param bgpSession the BGP Session to use
+     * @param ctx the Channel Handler Context
+     * @param attrTypeCode the attribute type code
+     * @param attrLen the attribute length (in octets)
+     * @param attrFlags the attribute flags
+     * @param message the message to parse
+     * @return the parsed MP_REACH_NLRI information if recognized, otherwise
+     * null
+     * @throws BgpParseException
+     */
+    private static MpNlri parseAttributeTypeMpReachNlri(
+                                                BgpSession bgpSession,
+                                                ChannelHandlerContext ctx,
+                                                int attrTypeCode,
+                                                int attrLen,
+                                                int attrFlags,
+                                                ChannelBuffer message)
+        throws BgpParseException {
+        int attributeEnd = message.readerIndex() + attrLen;
+
+        // Check the Attribute Length
+        if (attrLen < Update.MpReachNlri.MIN_LENGTH) {
+            // ERROR: Attribute Length Error
+            actionsBgpUpdateAttributeLengthError(
+                bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
+            String errorMsg = "Attribute Length Error";
+            throw new BgpParseException(errorMsg);
+        }
+
+        message.markReaderIndex();
+        int afi = message.readUnsignedShort();
+        int safi = message.readUnsignedByte();
+        int nextHopLen = message.readUnsignedByte();
+
+        //
+        // Verify the AFI/SAFI, and skip the attribute if not recognized.
+        // NOTE: Currently, we support only IPv4/IPv6 UNICAST
+        //
+        if (((afi != MultiprotocolExtensions.AFI_IPV4) &&
+             (afi != MultiprotocolExtensions.AFI_IPV6)) ||
+            (safi != MultiprotocolExtensions.SAFI_UNICAST)) {
+            // Skip the attribute
+            message.resetReaderIndex();
+            message.skipBytes(attrLen);
+            return null;
+        }
+
+        //
+        // Verify the next-hop length
+        //
+        int expectedNextHopLen = 0;
+        switch (afi) {
+        case MultiprotocolExtensions.AFI_IPV4:
+            expectedNextHopLen = Ip4Address.BYTE_LENGTH;
+            break;
+        case MultiprotocolExtensions.AFI_IPV6:
+            expectedNextHopLen = Ip6Address.BYTE_LENGTH;
+            break;
+        default:
+            // UNREACHABLE
+            break;
+        }
+        if (nextHopLen != expectedNextHopLen) {
+            // ERROR: Optional Attribute Error
+            message.resetReaderIndex();
+            actionsBgpUpdateOptionalAttributeError(
+                bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
+            String errorMsg = "Invalid next-hop network address length. " +
+                "Received " + nextHopLen + " expected " + expectedNextHopLen;
+            throw new BgpParseException(errorMsg);
+        }
+        // NOTE: We use "+ 1" to take into account the Reserved field (1 octet)
+        if (message.readerIndex() + nextHopLen + 1 >= attributeEnd) {
+            // ERROR: Optional Attribute Error
+            message.resetReaderIndex();
+            actionsBgpUpdateOptionalAttributeError(
+                bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
+            String errorMsg = "Malformed next-hop network address";
+            throw new BgpParseException(errorMsg);
+        }
+
+        //
+        // Get the Next-hop address, skip the Reserved field, and get the NLRI
+        //
+        byte[] nextHopBuffer = new byte[nextHopLen];
+        message.readBytes(nextHopBuffer, 0, nextHopLen);
+        int reserved = message.readUnsignedByte();
+        MpNlri mpNlri = new MpNlri(afi, safi);
+        try {
+            switch (afi) {
+            case MultiprotocolExtensions.AFI_IPV4:
+                // The next-hop address
+                mpNlri.nextHop4 = Ip4Address.valueOf(nextHopBuffer);
+                // The NLRI
+                mpNlri.nlri4 = parsePackedIp4Prefixes(
+                                        attributeEnd - message.readerIndex(),
+                                        message);
+                break;
+            case MultiprotocolExtensions.AFI_IPV6:
+                // The next-hop address
+                mpNlri.nextHop6 = Ip6Address.valueOf(nextHopBuffer);
+                // The NLRI
+                mpNlri.nlri6 = parsePackedIp6Prefixes(
+                                        attributeEnd - message.readerIndex(),
+                                        message);
+                break;
+            default:
+                // UNREACHABLE
+                break;
+            }
+        } catch (BgpParseException e) {
+            // ERROR: Optional Attribute Error
+            message.resetReaderIndex();
+            actionsBgpUpdateOptionalAttributeError(
+                bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
+            String errorMsg = "Malformed network layer reachability information";
+            throw new BgpParseException(errorMsg);
+        }
+
+        return mpNlri;
+    }
+
+    /**
+     * Parses BGP UPDATE Attribute Type MP_UNREACH_NLRI.
+     *
+     * @param bgpSession the BGP Session to use
+     * @param ctx the Channel Handler Context
+     * @param attrTypeCode the attribute type code
+     * @param attrLen the attribute length (in octets)
+     * @param attrFlags the attribute flags
+     * @param message the message to parse
+     * @return the parsed MP_UNREACH_NLRI information if recognized, otherwise
+     * null
+     * @throws BgpParseException
+     */
+    private static MpNlri parseAttributeTypeMpUnreachNlri(
+                                                BgpSession bgpSession,
+                                                ChannelHandlerContext ctx,
+                                                int attrTypeCode,
+                                                int attrLen,
+                                                int attrFlags,
+                                                ChannelBuffer message)
+        throws BgpParseException {
+        int attributeEnd = message.readerIndex() + attrLen;
+
+        // Check the Attribute Length
+        if (attrLen < Update.MpUnreachNlri.MIN_LENGTH) {
+            // ERROR: Attribute Length Error
+            actionsBgpUpdateAttributeLengthError(
+                bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
+            String errorMsg = "Attribute Length Error";
+            throw new BgpParseException(errorMsg);
+        }
+
+        message.markReaderIndex();
+        int afi = message.readUnsignedShort();
+        int safi = message.readUnsignedByte();
+
+        //
+        // Verify the AFI/SAFI, and skip the attribute if not recognized.
+        // NOTE: Currently, we support only IPv4/IPv6 UNICAST
+        //
+        if (((afi != MultiprotocolExtensions.AFI_IPV4) &&
+             (afi != MultiprotocolExtensions.AFI_IPV6)) ||
+            (safi != MultiprotocolExtensions.SAFI_UNICAST)) {
+            // Skip the attribute
+            message.resetReaderIndex();
+            message.skipBytes(attrLen);
+            return null;
+        }
+
+        //
+        // Get the Withdrawn Routes
+        //
+        MpNlri mpNlri = new MpNlri(afi, safi);
+        try {
+            switch (afi) {
+            case MultiprotocolExtensions.AFI_IPV4:
+                // The Withdrawn Routes
+                mpNlri.nlri4 = parsePackedIp4Prefixes(
+                                        attributeEnd - message.readerIndex(),
+                                        message);
+                break;
+            case MultiprotocolExtensions.AFI_IPV6:
+                // The Withdrawn Routes
+                mpNlri.nlri6 = parsePackedIp6Prefixes(
+                                        attributeEnd - message.readerIndex(),
+                                        message);
+                break;
+            default:
+                // UNREACHABLE
+                break;
+            }
+        } catch (BgpParseException e) {
+            // ERROR: Optional Attribute Error
+            message.resetReaderIndex();
+            actionsBgpUpdateOptionalAttributeError(
+                bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
+            String errorMsg = "Malformed withdrawn routes";
+            throw new BgpParseException(errorMsg);
+        }
+
+        return mpNlri;
+    }
+
+    /**
      * Parses a message that contains encoded IPv4 network prefixes.
      * <p>
      * The IPv4 prefixes are encoded in the form:
@@ -857,7 +1221,7 @@
      * @return a collection of parsed IPv4 network prefixes
      * @throws BgpParseException
      */
-    private static Collection<Ip4Prefix> parsePackedPrefixes(
+    private static Collection<Ip4Prefix> parsePackedIp4Prefixes(
                                                 int totalLength,
                                                 ChannelBuffer message)
         throws BgpParseException {
@@ -868,6 +1232,7 @@
         }
 
         // Parse the data
+        byte[] buffer = new byte[Ip4Address.BYTE_LENGTH];
         int dataEnd = message.readerIndex() + totalLength;
         while (message.readerIndex() < dataEnd) {
             int prefixBitlen = message.readUnsignedByte();
@@ -877,17 +1242,52 @@
                 throw new BgpParseException(errorMsg);
             }
 
-            long address = 0;
-            long extraShift = (4 - prefixBytelen) * 8;
-            while (prefixBytelen > 0) {
-                address <<= 8;
-                address |= message.readUnsignedByte();
-                prefixBytelen--;
+            message.readBytes(buffer, 0, prefixBytelen);
+            Ip4Prefix prefix = Ip4Prefix.valueOf(Ip4Address.valueOf(buffer),
+                                                 prefixBitlen);
+            result.add(prefix);
+        }
+
+        return result;
+    }
+
+    /**
+     * Parses a message that contains encoded IPv6 network prefixes.
+     * <p>
+     * The IPv6 prefixes are encoded in the form:
+     * <Length, Prefix> where Length is the length in bits of the IPv6 prefix,
+     * and Prefix is the IPv6 prefix (padded with trailing bits to the end
+     * of an octet).
+     *
+     * @param totalLength the total length of the data to parse
+     * @param message the message with data to parse
+     * @return a collection of parsed IPv6 network prefixes
+     * @throws BgpParseException
+     */
+    private static Collection<Ip6Prefix> parsePackedIp6Prefixes(
+                                                int totalLength,
+                                                ChannelBuffer message)
+        throws BgpParseException {
+        Collection<Ip6Prefix> result = new ArrayList<>();
+
+        if (totalLength == 0) {
+            return result;
+        }
+
+        // Parse the data
+        byte[] buffer = new byte[Ip6Address.BYTE_LENGTH];
+        int dataEnd = message.readerIndex() + totalLength;
+        while (message.readerIndex() < dataEnd) {
+            int prefixBitlen = message.readUnsignedByte();
+            int prefixBytelen = (prefixBitlen + 7) / 8;     // Round-up
+            if (message.readerIndex() + prefixBytelen > dataEnd) {
+                String errorMsg = "Malformed Network Prefixes";
+                throw new BgpParseException(errorMsg);
             }
-            address <<= extraShift;
-            Ip4Prefix prefix =
-                Ip4Prefix.valueOf(Ip4Address.valueOf((int) address),
-                                  prefixBitlen);
+
+            message.readBytes(buffer, 0, prefixBytelen);
+            Ip6Prefix prefix = Ip6Prefix.valueOf(Ip6Address.valueOf(buffer),
+                                                 prefixBitlen);
             result.add(prefix);
         }
 
@@ -1078,7 +1478,7 @@
                   bgpSession.getRemoteAddress(), nextHop);
 
         //
-        // ERROR: Invalid ORIGIN Attribute
+        // ERROR: Invalid NEXT_HOP Attribute
         //
         // Send NOTIFICATION and close the connection
         int errorCode = UpdateMessageError.ERROR_CODE;
@@ -1135,6 +1535,45 @@
 
     /**
      * Applies the appropriate actions after detecting BGP UPDATE
+     * Optional Attribute Error: send NOTIFICATION and close
+     * the channel.
+     *
+     * @param bgpSession the BGP Session to use
+     * @param ctx the Channel Handler Context
+     * @param attrTypeCode the attribute type code
+     * @param attrLen the attribute length (in octets)
+     * @param attrFlags the attribute flags
+     * @param message the message with the data
+     */
+    private static void actionsBgpUpdateOptionalAttributeError(
+                                BgpSession bgpSession,
+                                ChannelHandlerContext ctx,
+                                int attrTypeCode,
+                                int attrLen,
+                                int attrFlags,
+                                ChannelBuffer message) {
+        log.debug("BGP RX UPDATE Error from {}: Optional Attribute Error: {}",
+                  bgpSession.getRemoteAddress(), attrTypeCode);
+
+        //
+        // ERROR: Optional Attribute Error
+        //
+        // Send NOTIFICATION and close the connection
+        int errorCode = UpdateMessageError.ERROR_CODE;
+        int errorSubcode =
+            UpdateMessageError.OPTIONAL_ATTRIBUTE_ERROR;
+        ChannelBuffer data =
+            prepareBgpUpdateNotificationDataPayload(attrTypeCode, attrLen,
+                                                    attrFlags, message);
+        ChannelBuffer txMessage =
+            BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
+                                                   data);
+        ctx.getChannel().write(txMessage);
+        bgpSession.closeSession(ctx);
+    }
+
+    /**
+     * Applies the appropriate actions after detecting BGP UPDATE
      * Attribute Length Error: send NOTIFICATION and close the channel.
      *
      * @param bgpSession the BGP Session to use
@@ -1227,4 +1666,42 @@
         data.writeBytes(message, attrLen);
         return data;
     }
+
+    /**
+     * Helper class for storing Multiprotocol Network Layer Reachability
+     * information.
+     */
+    private static final class MpNlri {
+        private final int afi;
+        private final int safi;
+        private Ip4Address nextHop4;
+        private Ip6Address nextHop6;
+        private Collection<Ip4Prefix> nlri4 = new ArrayList<>();
+        private Collection<Ip6Prefix> nlri6 = new ArrayList<>();
+
+        /**
+         * Constructor.
+         *
+         * @param afi the Address Family Identifier
+         * @param safi the Subsequent Address Family Identifier
+         */
+        private MpNlri(int afi, int safi) {
+            this.afi = afi;
+            this.safi = safi;
+        }
+    }
+
+    /**
+     * Helper class for storing decoded BGP routing information.
+     */
+    private static final class DecodedBgpRoutes {
+        private final Map<Ip4Prefix, BgpRouteEntry> addedUnicastRoutes4 =
+            new HashMap<>();
+        private final Map<Ip6Prefix, BgpRouteEntry> addedUnicastRoutes6 =
+            new HashMap<>();
+        private final Map<Ip4Prefix, BgpRouteEntry> deletedUnicastRoutes4 =
+            new HashMap<>();
+        private final Map<Ip6Prefix, BgpRouteEntry> deletedUnicastRoutes6 =
+            new HashMap<>();
+    }
 }
diff --git a/apps/sdnip/src/main/java/org/onosproject/sdnip/cli/BgpRoutesListCommand.java b/apps/sdnip/src/main/java/org/onosproject/sdnip/cli/BgpRoutesListCommand.java
index d7b7cc7..56ad480 100644
--- a/apps/sdnip/src/main/java/org/onosproject/sdnip/cli/BgpRoutesListCommand.java
+++ b/apps/sdnip/src/main/java/org/onosproject/sdnip/cli/BgpRoutesListCommand.java
@@ -81,7 +81,8 @@
 
         // Print the routes
         if (foundBgpSession != null) {
-            printRoutes(foundBgpSession.bgpRibIn().values());
+            printRoutes(foundBgpSession.bgpRibIn4().values());
+            printRoutes(foundBgpSession.bgpRibIn6().values());
         } else {
             printRoutes(service.getBgpRoutes());
         }
diff --git a/apps/sdnip/src/test/java/org/onosproject/sdnip/bgp/BgpSessionManagerTest.java b/apps/sdnip/src/test/java/org/onosproject/sdnip/bgp/BgpSessionManagerTest.java
index 0eaaa22..a4e0bab 100644
--- a/apps/sdnip/src/test/java/org/onosproject/sdnip/bgp/BgpSessionManagerTest.java
+++ b/apps/sdnip/src/test/java/org/onosproject/sdnip/bgp/BgpSessionManagerTest.java
@@ -303,7 +303,7 @@
     private Collection<BgpRouteEntry> waitForBgpRibIn(BgpSession bgpSession,
                                                       long expectedRoutes)
         throws InterruptedException {
-        Collection<BgpRouteEntry> bgpRibIn = bgpSession.bgpRibIn().values();
+        Collection<BgpRouteEntry> bgpRibIn = bgpSession.bgpRibIn4().values();
 
         final int maxChecks = 500;              // Max wait of 5 seconds
         for (int i = 0; i < maxChecks; i++) {
@@ -311,7 +311,7 @@
                 break;
             }
             Thread.sleep(10);
-            bgpRibIn = bgpSession.bgpRibIn().values();
+            bgpRibIn = bgpSession.bgpRibIn4().values();
         }
 
         return bgpRibIn;