org.onosproject.sdnip.bgp.BgpUpdate.java Source code

Java tutorial

Introduction

Here is the source code for org.onosproject.sdnip.bgp.BgpUpdate.java

Source

/*
 * Copyright 2014 Open Networking Laboratory
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.onosproject.sdnip.bgp;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.tuple.Pair;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.Ip4Prefix;
import org.onlab.packet.Ip6Address;
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;

/**
 * A class for handling BGP UPDATE messages.
 */
final class BgpUpdate {
    private static final Logger log = LoggerFactory.getLogger(BgpUpdate.class);

    /**
     * Default constructor.
     * <p>
     * The constructor is private to prevent creating an instance of
     * this utility class.
     */
    private BgpUpdate() {
    }

    /**
     * Processes BGP UPDATE message.
     *
     * @param bgpSession the BGP Session to use
     * @param ctx the Channel Handler Context
     * @param message the message to process
     */
    static void processBgpUpdate(BgpSession bgpSession, ChannelHandlerContext ctx, ChannelBuffer message) {
        DecodedBgpRoutes decodedBgpRoutes = new DecodedBgpRoutes();

        int minLength = BgpConstants.BGP_UPDATE_MIN_LENGTH - BgpConstants.BGP_HEADER_LENGTH;
        if (message.readableBytes() < minLength) {
            log.debug("BGP RX UPDATE Error from {}: " + "Message length {} too short. Must be at least {}",
                    bgpSession.remoteInfo().address(), message.readableBytes(), minLength);
            //
            // ERROR: Bad Message Length
            //
            // Send NOTIFICATION and close the connection
            ChannelBuffer txMessage = BgpNotification.prepareBgpNotificationBadMessageLength(
                    message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH);
            ctx.getChannel().write(txMessage);
            bgpSession.closeSession(ctx);
            return;
        }

        log.debug("BGP RX UPDATE message from {}", bgpSession.remoteInfo().address());

        //
        // Parse the UPDATE message
        //

        //
        // Parse the Withdrawn Routes
        //
        int withdrawnRoutesLength = message.readUnsignedShort();
        if (withdrawnRoutesLength > message.readableBytes()) {
            // ERROR: Malformed Attribute List
            actionsBgpUpdateMalformedAttributeList(bgpSession, ctx);
            return;
        }
        Collection<Ip4Prefix> withdrawnPrefixes = null;
        try {
            withdrawnPrefixes = parsePackedIp4Prefixes(withdrawnRoutesLength, message);
        } catch (BgpParseException e) {
            // ERROR: Invalid Network Field
            log.debug("Exception parsing Withdrawn Prefixes from BGP peer {}: ", bgpSession.remoteInfo().bgpId(),
                    e);
            actionsBgpUpdateInvalidNetworkField(bgpSession, ctx);
            return;
        }
        for (Ip4Prefix prefix : withdrawnPrefixes) {
            log.debug("BGP RX UPDATE message WITHDRAWN from {}: {}", bgpSession.remoteInfo().address(), prefix);
            BgpRouteEntry bgpRouteEntry = bgpSession.findBgpRoute(prefix);
            if (bgpRouteEntry != null) {
                decodedBgpRoutes.deletedUnicastRoutes4.put(prefix, bgpRouteEntry);
            }
        }

        //
        // Parse the Path Attributes
        //
        try {
            parsePathAttributes(bgpSession, ctx, message, decodedBgpRoutes);
        } catch (BgpParseException e) {
            log.debug("Exception parsing Path Attributes from BGP peer {}: ", bgpSession.remoteInfo().bgpId(), e);
            // NOTE: The session was already closed, so nothing else to do
            return;
        }

        //
        // Update the BGP RIB-IN
        //
        for (Ip4Prefix ip4Prefix : decodedBgpRoutes.deletedUnicastRoutes4.keySet()) {
            bgpSession.removeBgpRoute(ip4Prefix);
        }
        //
        for (BgpRouteEntry bgpRouteEntry : decodedBgpRoutes.addedUnicastRoutes4.values()) {
            bgpSession.addBgpRoute(bgpRouteEntry);
        }
        //
        for (Ip6Prefix ip6Prefix : decodedBgpRoutes.deletedUnicastRoutes6.keySet()) {
            bgpSession.removeBgpRoute(ip6Prefix);
        }
        //
        for (BgpRouteEntry bgpRouteEntry : decodedBgpRoutes.addedUnicastRoutes6.values()) {
            bgpSession.addBgpRoute(bgpRouteEntry);
        }

        //
        // Push the updates to the BGP Merged RIB
        //
        BgpRouteSelector bgpRouteSelector = bgpSession.getBgpSessionManager().getBgpRouteSelector();
        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);
    }

    /**
     * Parse BGP Path Attributes from the BGP UPDATE message.
     *
     * @param bgpSession the BGP Session to use
     * @param ctx the Channel Handler Context
     * @param message the message to parse
     * @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
     */
    // CHECKSTYLE IGNORE MethodLength FOR NEXT 300 LINES
    private static void parsePathAttributes(BgpSession bgpSession, ChannelHandlerContext ctx, ChannelBuffer message,
            DecodedBgpRoutes decodedBgpRoutes) throws BgpParseException {

        //
        // Parsed values
        //
        Short origin = -1; // Mandatory
        BgpRouteEntry.AsPath asPath = 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
                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
        //
        int pathAttributeLength = message.readUnsignedShort();
        if (pathAttributeLength > message.readableBytes()) {
            // ERROR: Malformed Attribute List
            actionsBgpUpdateMalformedAttributeList(bgpSession, ctx);
            String errorMsg = "Malformed Attribute List";
            throw new BgpParseException(errorMsg);
        }
        if (pathAttributeLength == 0) {
            return;
        }

        //
        // Parse the Path Attributes
        //
        int pathAttributeEnd = message.readerIndex() + pathAttributeLength;
        while (message.readerIndex() < pathAttributeEnd) {
            int attrFlags = message.readUnsignedByte();
            if (message.readerIndex() >= pathAttributeEnd) {
                // ERROR: Malformed Attribute List
                actionsBgpUpdateMalformedAttributeList(bgpSession, ctx);
                String errorMsg = "Malformed Attribute List";
                throw new BgpParseException(errorMsg);
            }
            int attrTypeCode = message.readUnsignedByte();

            // The Attribute Flags
            boolean optionalBit = ((0x80 & attrFlags) != 0);
            boolean transitiveBit = ((0x40 & attrFlags) != 0);
            boolean partialBit = ((0x20 & attrFlags) != 0);
            boolean extendedLengthBit = ((0x10 & attrFlags) != 0);

            // The Attribute Length
            int attrLen = 0;
            int attrLenOctets = 1;
            if (extendedLengthBit) {
                attrLenOctets = 2;
            }
            if (message.readerIndex() + attrLenOctets > pathAttributeEnd) {
                // ERROR: Malformed Attribute List
                actionsBgpUpdateMalformedAttributeList(bgpSession, ctx);
                String errorMsg = "Malformed Attribute List";
                throw new BgpParseException(errorMsg);
            }
            if (extendedLengthBit) {
                attrLen = message.readUnsignedShort();
            } else {
                attrLen = message.readUnsignedByte();
            }
            if (message.readerIndex() + attrLen > pathAttributeEnd) {
                // ERROR: Malformed Attribute List
                actionsBgpUpdateMalformedAttributeList(bgpSession, ctx);
                String errorMsg = "Malformed Attribute List";
                throw new BgpParseException(errorMsg);
            }

            // Verify the Attribute Flags
            verifyBgpUpdateAttributeFlags(bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);

            //
            // Extract the Attribute Value based on the Attribute Type Code
            //
            switch (attrTypeCode) {

            case Update.Origin.TYPE:
                // Attribute Type Code ORIGIN
                origin = parseAttributeTypeOrigin(bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
                break;

            case Update.AsPath.TYPE:
                // Attribute Type Code AS_PATH
                asPath = parseAttributeTypeAsPath(bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
                break;

            case Update.NextHop.TYPE:
                // Attribute Type Code NEXT_HOP
                legacyNlri.nextHop4 = parseAttributeTypeNextHop(bgpSession, ctx, attrTypeCode, attrLen, attrFlags,
                        message);
                break;

            case Update.MultiExitDisc.TYPE:
                // Attribute Type Code MULTI_EXIT_DISC
                multiExitDisc = parseAttributeTypeMultiExitDisc(bgpSession, ctx, attrTypeCode, attrLen, attrFlags,
                        message);
                break;

            case Update.LocalPref.TYPE:
                // Attribute Type Code LOCAL_PREF
                localPref = parseAttributeTypeLocalPref(bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
                break;

            case Update.AtomicAggregate.TYPE:
                // Attribute Type Code ATOMIC_AGGREGATE
                parseAttributeTypeAtomicAggregate(bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
                // Nothing to do: this attribute is primarily informational
                break;

            case Update.Aggregator.TYPE:
                // Attribute Type Code AGGREGATOR
                Pair<Long, Ip4Address> aggregator = parseAttributeTypeAggregator(bgpSession, ctx, attrTypeCode,
                        attrLen, attrFlags, message);
                aggregatorAsNumber = aggregator.getLeft();
                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) {
                    // ERROR: Unrecognized Well-known Attribute
                    actionsBgpUpdateUnrecognizedWellKnownAttribute(bgpSession, ctx, attrTypeCode, attrLen,
                            attrFlags, message);
                    String errorMsg = "Unrecognized Well-known Attribute: " + attrTypeCode;
                    throw new BgpParseException(errorMsg);
                }

                // Skip the data from the unrecognized attribute
                log.debug("BGP RX UPDATE message from {}: " + "Unrecognized Attribute Type {}",
                        bgpSession.remoteInfo().address(), attrTypeCode);
                message.skipBytes(attrLen);
                break;
            }
        }

        //
        // Parse the NLRI (Network Layer Reachability Information)
        //
        int nlriLength = message.readableBytes();
        try {
            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 {}: ", bgpSession.remoteInfo().bgpId(), e);
            actionsBgpUpdateInvalidNetworkField(bgpSession, ctx);
            // Rethrow the exception
            throw e;
        }

        // 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.findBgpRoute(prefix);
                if (bgpRouteEntry != null) {
                    decodedBgpRoutes.deletedUnicastRoutes4.put(prefix, bgpRouteEntry);
                }
            }

            // The deleted IPv6 routes
            for (Ip6Prefix prefix : mpNlri.nlri6) {
                bgpRouteEntry = bgpSession.findBgpRoute(prefix);
                if (bgpRouteEntry != null) {
                    decodedBgpRoutes.deletedUnicastRoutes6.put(prefix, bgpRouteEntry);
                }
            }
        }

        //
        // 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.localInfo().asNumber())) {
                    log.debug("BGP RX UPDATE message IGNORED from {}: {} " + "nextHop {}: contains AS Path loop",
                            bgpSession.remoteInfo().address(), prefix, mpNlri.nextHop4);
                    continue;
                } else {
                    log.debug("BGP RX UPDATE message ADDED from {}: {} nextHop {}",
                            bgpSession.remoteInfo().address(), prefix, mpNlri.nextHop4);
                }
                // Remove from the collection of deleted routes
                decodedBgpRoutes.deletedUnicastRoutes4.remove(prefix);
                decodedBgpRoutes.addedUnicastRoutes4.put(prefix, bgpRouteEntry);
            }

            // The added IPv6 routes
            for (Ip6Prefix prefix : mpNlri.nlri6) {
                bgpRouteEntry = new BgpRouteEntry(bgpSession, prefix, mpNlri.nextHop6, origin.byteValue(), asPath,
                        localPref);
                bgpRouteEntry.setMultiExitDisc(multiExitDisc);
                if (bgpRouteEntry.hasAsPathLoop(bgpSession.localInfo().asNumber())) {
                    log.debug("BGP RX UPDATE message IGNORED from {}: {} " + "nextHop {}: contains AS Path loop",
                            bgpSession.remoteInfo().address(), prefix, mpNlri.nextHop6);
                    continue;
                } else {
                    log.debug("BGP RX UPDATE message ADDED from {}: {} nextHop {}",
                            bgpSession.remoteInfo().address(), prefix, mpNlri.nextHop6);
                }
                // Remove from the collection of deleted routes
                decodedBgpRoutes.deletedUnicastRoutes6.remove(prefix);
                decodedBgpRoutes.addedUnicastRoutes6.put(prefix, bgpRouteEntry);
            }
        }
    }

    /**
     * Verifies BGP UPDATE Well-known Attributes.
     *
     * @param bgpSession the BGP Session to use
     * @param ctx the Channel Handler Context
     * @param origin the ORIGIN well-known mandatory attribute
     * @param asPath the AS_PATH 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(BgpSession bgpSession, ChannelHandlerContext ctx,
            Short origin, BgpRouteEntry.AsPath asPath, 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.mpExtensions()) {
            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 (hasNlri && ((origin == null) || (origin == -1))) {
            // Missing Attribute Type Code ORIGIN
            int type = Update.Origin.TYPE;
            actionsBgpUpdateMissingWellKnownAttribute(bgpSession, ctx, type);
            String errorMsg = "Missing Well-known Attribute: ORIGIN";
            throw new BgpParseException(errorMsg);
        }
        if (hasNlri && (asPath == null)) {
            // Missing Attribute Type Code AS_PATH
            int type = Update.AsPath.TYPE;
            actionsBgpUpdateMissingWellKnownAttribute(bgpSession, ctx, type);
            String errorMsg = "Missing Well-known Attribute: AS_PATH";
            throw new BgpParseException(errorMsg);
        }
        if (hasNlri && (localPref == null)) {
            // Missing Attribute Type Code LOCAL_PREF
            // NOTE: Required for iBGP
            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);
        }
    }

    /**
     * Verifies the BGP UPDATE Attribute Flags.
     *
     * @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
     * @throws BgpParseException
     */
    private static void verifyBgpUpdateAttributeFlags(BgpSession bgpSession, ChannelHandlerContext ctx,
            int attrTypeCode, int attrLen, int attrFlags, ChannelBuffer message) throws BgpParseException {

        //
        // Assign the Attribute Type Name and the Well-known flag
        //
        String typeName = "UNKNOWN";
        boolean isWellKnown = false;
        switch (attrTypeCode) {
        case Update.Origin.TYPE:
            isWellKnown = true;
            typeName = "ORIGIN";
            break;
        case Update.AsPath.TYPE:
            isWellKnown = true;
            typeName = "AS_PATH";
            break;
        case Update.NextHop.TYPE:
            isWellKnown = true;
            typeName = "NEXT_HOP";
            break;
        case Update.MultiExitDisc.TYPE:
            isWellKnown = false;
            typeName = "MULTI_EXIT_DISC";
            break;
        case Update.LocalPref.TYPE:
            isWellKnown = true;
            typeName = "LOCAL_PREF";
            break;
        case Update.AtomicAggregate.TYPE:
            isWellKnown = true;
            typeName = "ATOMIC_AGGREGATE";
            break;
        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 + ")";
            break;
        }

        //
        // Verify the Attribute Flags
        //
        boolean optionalBit = ((0x80 & attrFlags) != 0);
        boolean transitiveBit = ((0x40 & attrFlags) != 0);
        boolean partialBit = ((0x20 & attrFlags) != 0);
        if ((isWellKnown && optionalBit) || (isWellKnown && (!transitiveBit)) || (isWellKnown && partialBit)
                || (optionalBit && (!transitiveBit) && partialBit)) {
            //
            // ERROR: The Optional bit cannot be set for Well-known attributes
            // ERROR: The Transtive bit MUST be 1 for well-known attributes
            // ERROR: The Partial bit MUST be 0 for well-known attributes
            // ERROR: The Partial bit MUST be 0 for optional non-transitive
            //        attributes
            //
            actionsBgpUpdateAttributeFlagsError(bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
            String errorMsg = "Attribute Flags Error for " + typeName + ": " + attrFlags;
            throw new BgpParseException(errorMsg);
        }
    }

    /**
     * Parses BGP UPDATE Attribute Type ORIGIN.
     *
     * @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 ORIGIN value
     * @throws BgpParseException
     */
    private static short parseAttributeTypeOrigin(BgpSession bgpSession, ChannelHandlerContext ctx,
            int attrTypeCode, int attrLen, int attrFlags, ChannelBuffer message) throws BgpParseException {

        // Check the Attribute Length
        if (attrLen != Update.Origin.LENGTH) {
            // ERROR: Attribute Length Error
            actionsBgpUpdateAttributeLengthError(bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
            String errorMsg = "Attribute Length Error";
            throw new BgpParseException(errorMsg);
        }

        message.markReaderIndex();
        short origin = message.readUnsignedByte();
        switch (origin) {
        case Update.Origin.IGP:
            // FALLTHROUGH
        case Update.Origin.EGP:
            // FALLTHROUGH
        case Update.Origin.INCOMPLETE:
            break;
        default:
            // ERROR: Invalid ORIGIN Attribute
            message.resetReaderIndex();
            actionsBgpUpdateInvalidOriginAttribute(bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message,
                    origin);
            String errorMsg = "Invalid ORIGIN Attribute: " + origin;
            throw new BgpParseException(errorMsg);
        }

        return origin;
    }

    /**
     * Parses BGP UPDATE Attribute AS Path.
     *
     * @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 AS Path
     * @throws BgpParseException
     */
    private static BgpRouteEntry.AsPath parseAttributeTypeAsPath(BgpSession bgpSession, ChannelHandlerContext ctx,
            int attrTypeCode, int attrLen, int attrFlags, ChannelBuffer message) throws BgpParseException {
        ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();

        //
        // Parse the message
        //
        while (attrLen > 0) {
            if (attrLen < 2) {
                // ERROR: Malformed AS_PATH
                actionsBgpUpdateMalformedAsPath(bgpSession, ctx);
                String errorMsg = "Malformed AS Path";
                throw new BgpParseException(errorMsg);
            }
            // Get the Path Segment Type and Length (in number of ASes)
            short pathSegmentType = message.readUnsignedByte();
            short pathSegmentLength = message.readUnsignedByte();
            attrLen -= 2;

            // Verify the Path Segment Type
            switch (pathSegmentType) {
            case Update.AsPath.AS_SET:
                // FALLTHROUGH
            case Update.AsPath.AS_SEQUENCE:
                // FALLTHROUGH
            case Update.AsPath.AS_CONFED_SEQUENCE:
                // FALLTHROUGH
            case Update.AsPath.AS_CONFED_SET:
                break;
            default:
                // ERROR: Invalid Path Segment Type
                //
                // NOTE: The BGP Spec (RFC 4271) doesn't contain Error Subcode
                // for "Invalid Path Segment Type", hence we return
                // the error as "Malformed AS_PATH".
                //
                actionsBgpUpdateMalformedAsPath(bgpSession, ctx);
                String errorMsg = "Invalid AS Path Segment Type: " + pathSegmentType;
                throw new BgpParseException(errorMsg);
            }

            // 4-octet AS number handling.
            int asPathLen;
            if (bgpSession.isAs4OctetCapable()) {
                asPathLen = AsPath.AS_4OCTET_LENGTH;
            } else {
                asPathLen = AsPath.AS_LENGTH;
            }

            // Parse the AS numbers
            if (asPathLen * pathSegmentLength > attrLen) {
                // ERROR: Malformed AS_PATH
                actionsBgpUpdateMalformedAsPath(bgpSession, ctx);
                String errorMsg = "Malformed AS Path";
                throw new BgpParseException(errorMsg);
            }
            attrLen -= (asPathLen * pathSegmentLength);
            ArrayList<Long> segmentAsNumbers = new ArrayList<>();
            while (pathSegmentLength-- > 0) {
                long asNumber;
                if (asPathLen == AsPath.AS_4OCTET_LENGTH) {
                    asNumber = message.readUnsignedInt();
                } else {
                    asNumber = message.readUnsignedShort();
                }
                segmentAsNumbers.add(asNumber);
            }

            BgpRouteEntry.PathSegment pathSegment = new BgpRouteEntry.PathSegment((byte) pathSegmentType,
                    segmentAsNumbers);
            pathSegments.add(pathSegment);
        }

        return new BgpRouteEntry.AsPath(pathSegments);
    }

    /**
     * Parses BGP UPDATE Attribute Type NEXT_HOP.
     *
     * @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 NEXT_HOP value
     * @throws BgpParseException
     */
    private static Ip4Address parseAttributeTypeNextHop(BgpSession bgpSession, ChannelHandlerContext ctx,
            int attrTypeCode, int attrLen, int attrFlags, ChannelBuffer message) throws BgpParseException {

        // Check the Attribute Length
        if (attrLen != Update.NextHop.LENGTH) {
            // ERROR: Attribute Length Error
            actionsBgpUpdateAttributeLengthError(bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
            String errorMsg = "Attribute Length Error";
            throw new BgpParseException(errorMsg);
        }

        message.markReaderIndex();
        Ip4Address nextHopAddress = Ip4Address.valueOf((int) message.readUnsignedInt());
        //
        // Check whether the NEXT_HOP IP address is semantically correct.
        // As per RFC 4271, Section 6.3:
        //
        //  a) It MUST NOT be the IP address of the receiving speaker
        //  b) In the case of an EBGP ....
        //
        // Here we check only (a), because (b) doesn't apply for us: all our
        // peers are iBGP.
        //
        if (nextHopAddress.equals(bgpSession.localInfo().ip4Address())) {
            // ERROR: Invalid NEXT_HOP Attribute
            message.resetReaderIndex();
            actionsBgpUpdateInvalidNextHopAttribute(bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message,
                    nextHopAddress);
            String errorMsg = "Invalid NEXT_HOP Attribute: " + nextHopAddress;
            throw new BgpParseException(errorMsg);
        }

        return nextHopAddress;
    }

    /**
     * Parses BGP UPDATE Attribute Type MULTI_EXIT_DISC.
     *
     * @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 MULTI_EXIT_DISC value
     * @throws BgpParseException
     */
    private static long parseAttributeTypeMultiExitDisc(BgpSession bgpSession, ChannelHandlerContext ctx,
            int attrTypeCode, int attrLen, int attrFlags, ChannelBuffer message) throws BgpParseException {

        // Check the Attribute Length
        if (attrLen != Update.MultiExitDisc.LENGTH) {
            // ERROR: Attribute Length Error
            actionsBgpUpdateAttributeLengthError(bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
            String errorMsg = "Attribute Length Error";
            throw new BgpParseException(errorMsg);
        }

        long multiExitDisc = message.readUnsignedInt();
        return multiExitDisc;
    }

    /**
     * Parses BGP UPDATE Attribute Type LOCAL_PREF.
     *
     * @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 LOCAL_PREF value
     * @throws BgpParseException
     */
    private static long parseAttributeTypeLocalPref(BgpSession bgpSession, ChannelHandlerContext ctx,
            int attrTypeCode, int attrLen, int attrFlags, ChannelBuffer message) throws BgpParseException {

        // Check the Attribute Length
        if (attrLen != Update.LocalPref.LENGTH) {
            // ERROR: Attribute Length Error
            actionsBgpUpdateAttributeLengthError(bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
            String errorMsg = "Attribute Length Error";
            throw new BgpParseException(errorMsg);
        }

        long localPref = message.readUnsignedInt();
        return localPref;
    }

    /**
     * Parses BGP UPDATE Attribute Type ATOMIC_AGGREGATE.
     *
     * @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
     * @throws BgpParseException
     */
    private static void parseAttributeTypeAtomicAggregate(BgpSession bgpSession, ChannelHandlerContext ctx,
            int attrTypeCode, int attrLen, int attrFlags, ChannelBuffer message) throws BgpParseException {

        // Check the Attribute Length
        if (attrLen != Update.AtomicAggregate.LENGTH) {
            // ERROR: Attribute Length Error
            actionsBgpUpdateAttributeLengthError(bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
            String errorMsg = "Attribute Length Error";
            throw new BgpParseException(errorMsg);
        }

        // Nothing to do: this attribute is primarily informational
    }

    /**
     * Parses BGP UPDATE Attribute Type AGGREGATOR.
     *
     * @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 AGGREGATOR value: a tuple of <AS-Number, IP-Address>
     * @throws BgpParseException
     */
    private static Pair<Long, Ip4Address> parseAttributeTypeAggregator(BgpSession bgpSession,
            ChannelHandlerContext ctx, int attrTypeCode, int attrLen, int attrFlags, ChannelBuffer message)
            throws BgpParseException {

        // Check the Attribute Length
        if (attrLen != Update.Aggregator.LENGTH) {
            // ERROR: Attribute Length Error
            actionsBgpUpdateAttributeLengthError(bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
            String errorMsg = "Attribute Length Error";
            throw new BgpParseException(errorMsg);
        }

        // The AGGREGATOR AS number
        long aggregatorAsNumber = message.readUnsignedShort();
        // The AGGREGATOR IP address
        Ip4Address aggregatorIpAddress = Ip4Address.valueOf((int) message.readUnsignedInt());

        Pair<Long, Ip4Address> aggregator = Pair.of(aggregatorAsNumber, aggregatorIpAddress);
        return aggregator;
    }

    /**
     * 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:
     * <Length, Prefix> where Length is the length in bits of the IPv4 prefix,
     * and Prefix is the IPv4 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 IPv4 network prefixes
     * @throws BgpParseException
     */
    private static Collection<Ip4Prefix> parsePackedIp4Prefixes(int totalLength, ChannelBuffer message)
            throws BgpParseException {
        Collection<Ip4Prefix> result = new ArrayList<>();

        if (totalLength == 0) {
            return result;
        }

        // Parse the data
        byte[] buffer = new byte[Ip4Address.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);
            }

            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);
            }

            message.readBytes(buffer, 0, prefixBytelen);
            Ip6Prefix prefix = Ip6Prefix.valueOf(Ip6Address.valueOf(buffer), prefixBitlen);
            result.add(prefix);
        }

        return result;
    }

    /**
     * Applies the appropriate actions after detecting BGP UPDATE
     * Invalid Network Field Error: send NOTIFICATION and close the channel.
     *
     * @param bgpSession the BGP Session to use
     * @param ctx the Channel Handler Context
     */
    private static void actionsBgpUpdateInvalidNetworkField(BgpSession bgpSession, ChannelHandlerContext ctx) {
        log.debug("BGP RX UPDATE Error from {}: Invalid Network Field", bgpSession.remoteInfo().address());

        //
        // ERROR: Invalid Network Field
        //
        // Send NOTIFICATION and close the connection
        int errorCode = UpdateMessageError.ERROR_CODE;
        int errorSubcode = UpdateMessageError.INVALID_NETWORK_FIELD;
        ChannelBuffer txMessage = BgpNotification.prepareBgpNotification(errorCode, errorSubcode, null);
        ctx.getChannel().write(txMessage);
        bgpSession.closeSession(ctx);
    }

    /**
     * Applies the appropriate actions after detecting BGP UPDATE
     * Malformed Attribute List Error: send NOTIFICATION and close the channel.
     *
     * @param bgpSession the BGP Session to use
     * @param ctx the Channel Handler Context
     */
    private static void actionsBgpUpdateMalformedAttributeList(BgpSession bgpSession, ChannelHandlerContext ctx) {
        log.debug("BGP RX UPDATE Error from {}: Malformed Attribute List", bgpSession.remoteInfo().address());

        //
        // ERROR: Malformed Attribute List
        //
        // Send NOTIFICATION and close the connection
        int errorCode = UpdateMessageError.ERROR_CODE;
        int errorSubcode = UpdateMessageError.MALFORMED_ATTRIBUTE_LIST;
        ChannelBuffer txMessage = BgpNotification.prepareBgpNotification(errorCode, errorSubcode, null);
        ctx.getChannel().write(txMessage);
        bgpSession.closeSession(ctx);
    }

    /**
     * Applies the appropriate actions after detecting BGP UPDATE
     * Missing Well-known Attribute Error: send NOTIFICATION and close the
     * channel.
     *
     * @param bgpSession the BGP Session to use
     * @param ctx the Channel Handler Context
     * @param missingAttrTypeCode the missing attribute type code
     */
    private static void actionsBgpUpdateMissingWellKnownAttribute(BgpSession bgpSession, ChannelHandlerContext ctx,
            int missingAttrTypeCode) {
        log.debug("BGP RX UPDATE Error from {}: Missing Well-known Attribute: {}",
                bgpSession.remoteInfo().address(), missingAttrTypeCode);

        //
        // ERROR: Missing Well-known Attribute
        //
        // Send NOTIFICATION and close the connection
        int errorCode = UpdateMessageError.ERROR_CODE;
        int errorSubcode = UpdateMessageError.MISSING_WELL_KNOWN_ATTRIBUTE;
        ChannelBuffer data = ChannelBuffers.buffer(1);
        data.writeByte(missingAttrTypeCode);
        ChannelBuffer txMessage = BgpNotification.prepareBgpNotification(errorCode, errorSubcode, data);
        ctx.getChannel().write(txMessage);
        bgpSession.closeSession(ctx);
    }

    /**
     * Applies the appropriate actions after detecting BGP UPDATE
     * Invalid ORIGIN 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
     * @param origin the ORIGIN attribute value
     */
    private static void actionsBgpUpdateInvalidOriginAttribute(BgpSession bgpSession, ChannelHandlerContext ctx,
            int attrTypeCode, int attrLen, int attrFlags, ChannelBuffer message, short origin) {
        log.debug("BGP RX UPDATE Error from {}: Invalid ORIGIN Attribute", bgpSession.remoteInfo().address());

        //
        // ERROR: Invalid ORIGIN Attribute
        //
        // Send NOTIFICATION and close the connection
        int errorCode = UpdateMessageError.ERROR_CODE;
        int errorSubcode = UpdateMessageError.INVALID_ORIGIN_ATTRIBUTE;
        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 Flags 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 actionsBgpUpdateAttributeFlagsError(BgpSession bgpSession, ChannelHandlerContext ctx,
            int attrTypeCode, int attrLen, int attrFlags, ChannelBuffer message) {
        log.debug("BGP RX UPDATE Error from {}: Attribute Flags Error", bgpSession.remoteInfo().address());

        //
        // ERROR: Attribute Flags Error
        //
        // Send NOTIFICATION and close the connection
        int errorCode = UpdateMessageError.ERROR_CODE;
        int errorSubcode = UpdateMessageError.ATTRIBUTE_FLAGS_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
     * Invalid NEXT_HOP 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
     * @param nextHop the NEXT_HOP attribute value
     */
    private static void actionsBgpUpdateInvalidNextHopAttribute(BgpSession bgpSession, ChannelHandlerContext ctx,
            int attrTypeCode, int attrLen, int attrFlags, ChannelBuffer message, Ip4Address nextHop) {
        log.debug("BGP RX UPDATE Error from {}: Invalid NEXT_HOP Attribute {}", bgpSession.remoteInfo().address(),
                nextHop);

        //
        // ERROR: Invalid NEXT_HOP Attribute
        //
        // Send NOTIFICATION and close the connection
        int errorCode = UpdateMessageError.ERROR_CODE;
        int errorSubcode = UpdateMessageError.INVALID_NEXT_HOP_ATTRIBUTE;
        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
     * Unrecognized Well-known 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 actionsBgpUpdateUnrecognizedWellKnownAttribute(BgpSession bgpSession,
            ChannelHandlerContext ctx, int attrTypeCode, int attrLen, int attrFlags, ChannelBuffer message) {
        log.debug("BGP RX UPDATE Error from {}: " + "Unrecognized Well-known Attribute Error: {}",
                bgpSession.remoteInfo().address(), attrTypeCode);

        //
        // ERROR: Unrecognized Well-known Attribute
        //
        // Send NOTIFICATION and close the connection
        int errorCode = UpdateMessageError.ERROR_CODE;
        int errorSubcode = UpdateMessageError.UNRECOGNIZED_WELL_KNOWN_ATTRIBUTE;
        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
     * 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.remoteInfo().address(),
                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
     * @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 actionsBgpUpdateAttributeLengthError(BgpSession bgpSession, ChannelHandlerContext ctx,
            int attrTypeCode, int attrLen, int attrFlags, ChannelBuffer message) {
        log.debug("BGP RX UPDATE Error from {}: Attribute Length Error", bgpSession.remoteInfo().address());

        //
        // ERROR: Attribute Length Error
        //
        // Send NOTIFICATION and close the connection
        int errorCode = UpdateMessageError.ERROR_CODE;
        int errorSubcode = UpdateMessageError.ATTRIBUTE_LENGTH_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
     * Malformed AS_PATH Error: send NOTIFICATION and close the channel.
     *
     * @param bgpSession the BGP Session to use
     * @param ctx the Channel Handler Context
     */
    private static void actionsBgpUpdateMalformedAsPath(BgpSession bgpSession, ChannelHandlerContext ctx) {
        log.debug("BGP RX UPDATE Error from {}: Malformed AS Path", bgpSession.remoteInfo().address());

        //
        // ERROR: Malformed AS_PATH
        //
        // Send NOTIFICATION and close the connection
        int errorCode = UpdateMessageError.ERROR_CODE;
        int errorSubcode = UpdateMessageError.MALFORMED_AS_PATH;
        ChannelBuffer txMessage = BgpNotification.prepareBgpNotification(errorCode, errorSubcode, null);
        ctx.getChannel().write(txMessage);
        bgpSession.closeSession(ctx);
    }

    /**
     * Prepares BGP UPDATE Notification data payload.
     *
     * @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
     * @return the buffer with the data payload for the BGP UPDATE Notification
     */
    private static ChannelBuffer prepareBgpUpdateNotificationDataPayload(int attrTypeCode, int attrLen,
            int attrFlags, ChannelBuffer message) {
        // Compute the attribute length field octets
        boolean extendedLengthBit = ((0x10 & attrFlags) != 0);
        int attrLenOctets = 1;
        if (extendedLengthBit) {
            attrLenOctets = 2;
        }
        ChannelBuffer data = ChannelBuffers.buffer(attrLen + attrLenOctets + 1);
        data.writeByte(attrTypeCode);
        if (extendedLengthBit) {
            data.writeShort(attrLen);
        } else {
            data.writeByte(attrLen);
        }
        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<>();
    }
}