Port the BGP implementation of SDN-IP.
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/Router.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/Router.java
index be54222..b309a7b 100644
--- a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/Router.java
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/Router.java
@@ -126,6 +126,8 @@
         bgpIntentsSynchronizerExecutor = Executors.newSingleThreadExecutor(
                 new ThreadFactoryBuilder()
                 .setNameFormat("bgp-intents-synchronizer-%d").build());
+
+        this.hostService.addListener(new InternalHostListener());
     }
 
     /**
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/SdnIp.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/SdnIp.java
index a98a84b..3f94cba 100644
--- a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/SdnIp.java
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/SdnIp.java
@@ -10,6 +10,7 @@
 import org.onlab.onos.net.host.HostService;
 import org.onlab.onos.net.intent.IntentService;
 import org.onlab.onos.sdnip.RouteUpdate.Type;
+import org.onlab.onos.sdnip.bgp.BgpSessionManager;
 import org.onlab.onos.sdnip.config.SdnIpConfigReader;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.IpPrefix;
@@ -32,6 +33,7 @@
     private SdnIpConfigReader config;
     private PeerConnectivity peerConnectivity;
     private Router router;
+    private BgpSessionManager bgpSessionManager;
 
     @Activate
     protected void activate() {
@@ -48,6 +50,9 @@
         router = new Router(intentService, hostService, config, interfaceService);
         router.start();
 
+        bgpSessionManager = new BgpSessionManager(router);
+        bgpSessionManager.startUp(2000); // TODO
+
         // TODO need to disable link discovery on external ports
 
         router.update(new RouteUpdate(Type.UPDATE, new RouteEntry(
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpConstants.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpConstants.java
new file mode 100644
index 0000000..1703138
--- /dev/null
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpConstants.java
@@ -0,0 +1,368 @@
+package org.onlab.onos.sdnip.bgp;
+
+/**
+ * BGP related constants.
+ */
+public final class BgpConstants {
+    /**
+     * Default constructor.
+     * <p>
+     * The constructor is private to prevent creating an instance of
+     * this utility class.
+     */
+    private BgpConstants() {
+    }
+
+    /** BGP port number (RFC 4271). */
+    public static final int BGP_PORT = 179;
+
+    /** BGP version. */
+    public static final int BGP_VERSION = 4;
+
+    /** BGP OPEN message type. */
+    public static final int BGP_TYPE_OPEN = 1;
+
+    /** BGP UPDATE message type. */
+    public static final int BGP_TYPE_UPDATE = 2;
+
+    /** BGP NOTIFICATION message type. */
+    public static final int BGP_TYPE_NOTIFICATION = 3;
+
+    /** BGP KEEPALIVE message type. */
+    public static final int BGP_TYPE_KEEPALIVE = 4;
+
+    /** BGP Header Marker field length. */
+    public static final int BGP_HEADER_MARKER_LENGTH = 16;
+
+    /** BGP Header length. */
+    public static final int BGP_HEADER_LENGTH = 19;
+
+    /** BGP message maximum length. */
+    public static final int BGP_MESSAGE_MAX_LENGTH = 4096;
+
+    /** BGP OPEN message minimum length (BGP Header included). */
+    public static final int BGP_OPEN_MIN_LENGTH = 29;
+
+    /** BGP UPDATE message minimum length (BGP Header included). */
+    public static final int BGP_UPDATE_MIN_LENGTH = 23;
+
+    /** BGP NOTIFICATION message minimum length (BGP Header included). */
+    public static final int BGP_NOTIFICATION_MIN_LENGTH = 21;
+
+    /** BGP KEEPALIVE message expected length (BGP Header included). */
+    public static final int BGP_KEEPALIVE_EXPECTED_LENGTH = 19;
+
+    /** BGP KEEPALIVE messages transmitted per Hold interval. */
+    public static final int BGP_KEEPALIVE_PER_HOLD_INTERVAL = 3;
+
+    /** BGP KEEPALIVE messages minimum Holdtime (in seconds). */
+    public static final int BGP_KEEPALIVE_MIN_HOLDTIME = 3;
+
+    /** BGP KEEPALIVE messages minimum transmission interval (in seconds). */
+    public static final int BGP_KEEPALIVE_MIN_INTERVAL = 1;
+
+    /** BGP AS 0 (zero) value. See draft-ietf-idr-as0-06.txt Internet Draft. */
+    public static final long BGP_AS_0 = 0;
+
+    /**
+     * BGP UPDATE related constants.
+     */
+    public static final class Update {
+        /**
+         * Default constructor.
+         * <p>
+         * The constructor is private to prevent creating an instance of
+         * this utility class.
+         */
+        private Update() {
+        }
+
+        /**
+         * BGP UPDATE: ORIGIN related constants.
+         */
+        public static final class Origin {
+            /**
+             * Default constructor.
+             * <p>
+             * The constructor is private to prevent creating an instance of
+             * this utility class.
+             */
+            private Origin() {
+            }
+
+            /** BGP UPDATE Attributes Type Code ORIGIN. */
+            public static final int TYPE = 1;
+
+            /** BGP UPDATE Attributes Type Code ORIGIN length. */
+            public static final int LENGTH = 1;
+
+            /** BGP UPDATE ORIGIN: IGP. */
+            public static final int IGP = 0;
+
+            /** BGP UPDATE ORIGIN: EGP. */
+            public static final int EGP = 1;
+
+            /** BGP UPDATE ORIGIN: INCOMPLETE. */
+            public static final int INCOMPLETE = 2;
+        }
+
+        /**
+         * BGP UPDATE: AS_PATH related constants.
+         */
+        public static final class AsPath {
+            /**
+             * Default constructor.
+             * <p>
+             * The constructor is private to prevent creating an instance of
+             * this utility class.
+             */
+            private AsPath() {
+            }
+
+            /** BGP UPDATE Attributes Type Code AS_PATH. */
+            public static final int TYPE = 2;
+
+            /** BGP UPDATE AS_PATH Type: AS_SET. */
+            public static final int AS_SET = 1;
+
+            /** BGP UPDATE AS_PATH Type: AS_SEQUENCE. */
+            public static final int AS_SEQUENCE = 2;
+        }
+
+        /**
+         * BGP UPDATE: NEXT_HOP related constants.
+         */
+        public static final class NextHop {
+            /**
+             * Default constructor.
+             * <p>
+             * The constructor is private to prevent creating an instance of
+             * this utility class.
+             */
+            private NextHop() {
+            }
+
+            /** BGP UPDATE Attributes Type Code NEXT_HOP. */
+            public static final int TYPE = 3;
+
+            /** BGP UPDATE Attributes Type Code NEXT_HOP length. */
+            public static final int LENGTH = 4;
+        }
+
+        /**
+         * BGP UPDATE: MULTI_EXIT_DISC related constants.
+         */
+        public static final class MultiExitDisc {
+            /**
+             * Default constructor.
+             * <p>
+             * The constructor is private to prevent creating an instance of
+             * this utility class.
+             */
+            private MultiExitDisc() {
+            }
+
+            /** BGP UPDATE Attributes Type Code MULTI_EXIT_DISC. */
+            public static final int TYPE = 4;
+
+            /** BGP UPDATE Attributes Type Code MULTI_EXIT_DISC length. */
+            public static final int LENGTH = 4;
+
+            /** BGP UPDATE Attributes lowest MULTI_EXIT_DISC value. */
+            public static final int LOWEST_MULTI_EXIT_DISC = 0;
+        }
+
+        /**
+         * BGP UPDATE: LOCAL_PREF related constants.
+         */
+        public static final class LocalPref {
+            /**
+             * Default constructor.
+             * <p>
+             * The constructor is private to prevent creating an instance of
+             * this utility class.
+             */
+            private LocalPref() {
+            }
+
+            /** BGP UPDATE Attributes Type Code LOCAL_PREF. */
+            public static final int TYPE = 5;
+
+            /** BGP UPDATE Attributes Type Code LOCAL_PREF length. */
+            public static final int LENGTH = 4;
+        }
+
+        /**
+         * BGP UPDATE: ATOMIC_AGGREGATE related constants.
+         */
+        public static final class AtomicAggregate {
+            /**
+             * Default constructor.
+             * <p>
+             * The constructor is private to prevent creating an instance of
+             * this utility class.
+             */
+            private AtomicAggregate() {
+            }
+
+            /** BGP UPDATE Attributes Type Code ATOMIC_AGGREGATE. */
+            public static final int TYPE = 6;
+
+            /** BGP UPDATE Attributes Type Code ATOMIC_AGGREGATE length. */
+            public static final int LENGTH = 0;
+        }
+
+        /**
+         * BGP UPDATE: AGGREGATOR related constants.
+         */
+        public static final class Aggregator {
+            /**
+             * Default constructor.
+             * <p>
+             * The constructor is private to prevent creating an instance of
+             * this utility class.
+             */
+            private Aggregator() {
+            }
+
+            /** BGP UPDATE Attributes Type Code AGGREGATOR. */
+            public static final int TYPE = 7;
+
+            /** BGP UPDATE Attributes Type Code AGGREGATOR length. */
+            public static final int LENGTH = 6;
+        }
+    }
+
+    /**
+     * BGP NOTIFICATION related constants.
+     */
+    public static final class Notifications {
+        /**
+         * Default constructor.
+         * <p>
+         * The constructor is private to prevent creating an instance of
+         * this utility class.
+         */
+        private Notifications() {
+        }
+
+        /**
+         * BGP NOTIFICATION: Message Header Error constants.
+         */
+        public static final class MessageHeaderError {
+            /**
+             * Default constructor.
+             * <p>
+             * The constructor is private to prevent creating an instance of
+             * this utility class.
+             */
+            private MessageHeaderError() {
+            }
+
+            /** Message Header Error code. */
+            public static final int ERROR_CODE = 1;
+
+            /** Message Header Error subcode: Connection Not Synchronized. */
+            public static final int CONNECTION_NOT_SYNCHRONIZED = 1;
+
+            /** Message Header Error subcode: Bad Message Length. */
+            public static final int BAD_MESSAGE_LENGTH = 2;
+
+            /** Message Header Error subcode: Bad Message Type. */
+            public static final int BAD_MESSAGE_TYPE = 3;
+        }
+
+        /**
+         * BGP NOTIFICATION: OPEN Message Error constants.
+         */
+        public static final class OpenMessageError {
+            /**
+             * Default constructor.
+             * <p>
+             * The constructor is private to prevent creating an instance of
+             * this utility class.
+             */
+            private OpenMessageError() {
+            }
+
+            /** OPEN Message Error code. */
+            public static final int ERROR_CODE = 2;
+
+            /** OPEN Message Error subcode: Unsupported Version Number. */
+            public static final int UNSUPPORTED_VERSION_NUMBER = 1;
+
+            /** OPEN Message Error subcode: Bad PEER AS. */
+            public static final int BAD_PEER_AS = 2;
+
+            /** OPEN Message Error subcode: Unacceptable Hold Time. */
+            public static final int UNACCEPTABLE_HOLD_TIME = 6;
+        }
+
+        /**
+         * BGP NOTIFICATION: UPDATE Message Error constants.
+         */
+        public static final class UpdateMessageError {
+            /**
+             * Default constructor.
+             * <p>
+             * The constructor is private to prevent creating an instance of
+             * this utility class.
+             */
+            private UpdateMessageError() {
+            }
+
+            /** UPDATE Message Error code. */
+            public static final int ERROR_CODE = 3;
+
+            /** UPDATE Message Error subcode: Malformed Attribute List. */
+            public static final int MALFORMED_ATTRIBUTE_LIST = 1;
+
+            /** UPDATE Message Error subcode: Unrecognized Well-known Attribute. */
+            public static final int UNRECOGNIZED_WELL_KNOWN_ATTRIBUTE = 2;
+
+            /** UPDATE Message Error subcode: Missing Well-known Attribute. */
+            public static final int MISSING_WELL_KNOWN_ATTRIBUTE = 3;
+
+           /** UPDATE Message Error subcode: Attribute Flags Error. */
+            public static final int ATTRIBUTE_FLAGS_ERROR = 4;
+
+            /** UPDATE Message Error subcode: Attribute Length Error. */
+            public static final int ATTRIBUTE_LENGTH_ERROR = 5;
+
+            /** UPDATE Message Error subcode: Invalid ORIGIN Attribute. */
+            public static final int INVALID_ORIGIN_ATTRIBUTE = 6;
+
+            /** UPDATE Message Error subcode: Invalid NEXT_HOP Attribute. */
+            public static final int INVALID_NEXT_HOP_ATTRIBUTE = 8;
+
+            /** UPDATE Message Error subcode: Optional Attribute Error. Unused. */
+            public static final int OPTIONAL_ATTRIBUTE_ERROR = 9;
+
+            /** UPDATE Message Error subcode: Invalid Network Field. */
+            public static final int INVALID_NETWORK_FIELD = 10;
+
+            /** UPDATE Message Error subcode: Malformed AS_PATH. */
+            public static final int MALFORMED_AS_PATH = 11;
+        }
+
+        /**
+         * BGP NOTIFICATION: Hold Timer Expired constants.
+         */
+        public static final class HoldTimerExpired {
+            /**
+             * Default constructor.
+             * <p>
+             * The constructor is private to prevent creating an instance of
+             * this utility class.
+             */
+            private HoldTimerExpired() {
+            }
+
+            /** Hold Timer Expired code. */
+            public static final int ERROR_CODE = 4;
+        }
+
+        /** BGP NOTIFICATION message Error subcode: Unspecific. */
+        public static final int ERROR_SUBCODE_UNSPECIFIC = 0;
+    }
+}
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpFrameDecoder.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpFrameDecoder.java
new file mode 100644
index 0000000..938d975
--- /dev/null
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpFrameDecoder.java
@@ -0,0 +1,162 @@
+package org.onlab.onos.sdnip.bgp;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.handler.codec.frame.FrameDecoder;
+import org.onlab.onos.sdnip.bgp.BgpConstants.Notifications.MessageHeaderError;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Class for handling the decoding of the BGP messages.
+ */
+class BgpFrameDecoder extends FrameDecoder {
+    private static final Logger log =
+        LoggerFactory.getLogger(BgpFrameDecoder.class);
+
+    private final BgpSession bgpSession;
+
+    /**
+     * Constructor for a given BGP Session.
+     *
+     * @param bgpSession the BGP session state to use.
+     */
+    BgpFrameDecoder(BgpSession bgpSession) {
+        this.bgpSession = bgpSession;
+    }
+
+    @Override
+    protected Object decode(ChannelHandlerContext ctx,
+                            Channel channel,
+                            ChannelBuffer buf) throws Exception {
+        //
+        // NOTE: If we close the channel during the decoding, we might still
+        // see some incoming messages while the channel closing is completed.
+        //
+        if (bgpSession.isClosed()) {
+            return null;
+        }
+
+        log.trace("BGP Peer: decode(): remoteAddr = {} localAddr = {} " +
+                  "messageSize = {}",
+                  ctx.getChannel().getRemoteAddress(),
+                  ctx.getChannel().getLocalAddress(),
+                  buf.readableBytes());
+
+        // Test for minimum length of the BGP message
+        if (buf.readableBytes() < BgpConstants.BGP_HEADER_LENGTH) {
+            // No enough data received
+            return null;
+        }
+
+        //
+        // Mark the current buffer position in case we haven't received
+        // the whole message.
+        //
+        buf.markReaderIndex();
+
+        //
+        // Read and check the BGP message Marker field: it must be all ones
+        // (See RFC 4271, Section 4.1)
+        //
+        byte[] marker = new byte[BgpConstants.BGP_HEADER_MARKER_LENGTH];
+        buf.readBytes(marker);
+        for (int i = 0; i < marker.length; i++) {
+            if (marker[i] != (byte) 0xff) {
+                log.debug("BGP RX Error: invalid marker {} at position {}",
+                          marker[i], i);
+                //
+                // ERROR: Connection Not Synchronized
+                //
+                // Send NOTIFICATION and close the connection
+                int errorCode = MessageHeaderError.ERROR_CODE;
+                int errorSubcode =
+                    MessageHeaderError.CONNECTION_NOT_SYNCHRONIZED;
+                ChannelBuffer txMessage =
+                    bgpSession.prepareBgpNotification(errorCode, errorSubcode,
+                                                      null);
+                ctx.getChannel().write(txMessage);
+                bgpSession.closeChannel(ctx);
+                return null;
+            }
+        }
+
+        //
+        // Read and check the BGP message Length field
+        //
+        int length = buf.readUnsignedShort();
+        if ((length < BgpConstants.BGP_HEADER_LENGTH) ||
+            (length > BgpConstants.BGP_MESSAGE_MAX_LENGTH)) {
+            log.debug("BGP RX Error: invalid Length field {}. " +
+                      "Must be between {} and {}",
+                      length,
+                      BgpConstants.BGP_HEADER_LENGTH,
+                      BgpConstants.BGP_MESSAGE_MAX_LENGTH);
+            //
+            // ERROR: Bad Message Length
+            //
+            // Send NOTIFICATION and close the connection
+            ChannelBuffer txMessage =
+                bgpSession.prepareBgpNotificationBadMessageLength(length);
+            ctx.getChannel().write(txMessage);
+            bgpSession.closeChannel(ctx);
+            return null;
+        }
+
+        //
+        // Test whether the rest of the message is received:
+        // So far we have read the Marker (16 octets) and the
+        // Length (2 octets) fields.
+        //
+        int remainingMessageLen =
+            length - BgpConstants.BGP_HEADER_MARKER_LENGTH - 2;
+        if (buf.readableBytes() < remainingMessageLen) {
+            // No enough data received
+            buf.resetReaderIndex();
+            return null;
+        }
+
+        //
+        // Read the BGP message Type field, and process based on that type
+        //
+        int type = buf.readUnsignedByte();
+        remainingMessageLen--;      // Adjust after reading the type
+        ChannelBuffer message = buf.readBytes(remainingMessageLen);
+
+        //
+        // Process the remaining of the message based on the message type
+        //
+        switch (type) {
+        case BgpConstants.BGP_TYPE_OPEN:
+            bgpSession.processBgpOpen(ctx, message);
+            break;
+        case BgpConstants.BGP_TYPE_UPDATE:
+            bgpSession.processBgpUpdate(ctx, message);
+            break;
+        case BgpConstants.BGP_TYPE_NOTIFICATION:
+            bgpSession.processBgpNotification(ctx, message);
+            break;
+        case BgpConstants.BGP_TYPE_KEEPALIVE:
+            bgpSession.processBgpKeepalive(ctx, message);
+            break;
+        default:
+            //
+            // ERROR: Bad Message Type
+            //
+            // Send NOTIFICATION and close the connection
+            int errorCode = MessageHeaderError.ERROR_CODE;
+            int errorSubcode = MessageHeaderError.BAD_MESSAGE_TYPE;
+            ChannelBuffer data = ChannelBuffers.buffer(1);
+            data.writeByte(type);
+            ChannelBuffer txMessage =
+                bgpSession.prepareBgpNotification(errorCode, errorSubcode,
+                                                  data);
+            ctx.getChannel().write(txMessage);
+            bgpSession.closeChannel(ctx);
+            return null;
+        }
+        return null;
+    }
+}
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpRouteEntry.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpRouteEntry.java
new file mode 100644
index 0000000..890328a
--- /dev/null
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpRouteEntry.java
@@ -0,0 +1,432 @@
+package org.onlab.onos.sdnip.bgp;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+import org.onlab.onos.sdnip.RouteEntry;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Represents a route in BGP.
+ */
+public class BgpRouteEntry extends RouteEntry {
+    private final BgpSession bgpSession; // The BGP Session the route was
+                                         // received on
+    private final byte origin;          // Route ORIGIN: IGP, EGP, INCOMPLETE
+    private final AsPath asPath;        // The AS Path
+    private final long localPref;       // The local preference for the route
+    private long multiExitDisc =
+        BgpConstants.Update.MultiExitDisc.LOWEST_MULTI_EXIT_DISC;
+
+    /**
+     * Class constructor.
+     *
+     * @param bgpSession the BGP Session the route was received on
+     * @param prefix the prefix of the route
+     * @param nextHop the next hop of the route
+     * @param origin the route origin: 0=IGP, 1=EGP, 2=INCOMPLETE
+     * @param asPath the AS path
+     * @param localPref the route local preference
+     */
+    public BgpRouteEntry(BgpSession bgpSession, IpPrefix prefix,
+                         IpAddress nextHop, byte origin,
+                         BgpRouteEntry.AsPath asPath, long localPref) {
+        super(prefix, nextHop);
+        this.bgpSession = checkNotNull(bgpSession);
+        this.origin = origin;
+        this.asPath = checkNotNull(asPath);
+        this.localPref = localPref;
+    }
+
+    /**
+     * Gets the BGP Session the route was received on.
+     *
+     * @return the BGP Session the route was received on
+     */
+    public BgpSession getBgpSession() {
+        return bgpSession;
+    }
+
+    /**
+     * Gets the route origin: 0=IGP, 1=EGP, 2=INCOMPLETE.
+     *
+     * @return the route origin: 0=IGP, 1=EGP, 2=INCOMPLETE
+     */
+    public byte getOrigin() {
+        return origin;
+    }
+
+    /**
+     * Gets the route AS path.
+     *
+     * @return the route AS path
+     */
+    public BgpRouteEntry.AsPath getAsPath() {
+        return asPath;
+    }
+
+    /**
+     * Gets the route local preference.
+     *
+     * @return the route local preference
+     */
+    public long getLocalPref() {
+        return localPref;
+    }
+
+    /**
+     * Gets the route MED (Multi-Exit Discriminator).
+     *
+     * @return the route MED (Multi-Exit Discriminator)
+     */
+    public long getMultiExitDisc() {
+        return multiExitDisc;
+    }
+
+    /**
+     * Sets the route MED (Multi-Exit Discriminator).
+     *
+     * @param multiExitDisc the route MED (Multi-Exit Discriminator) to set
+     */
+    void setMultiExitDisc(long multiExitDisc) {
+        this.multiExitDisc = multiExitDisc;
+    }
+
+    /**
+     * Tests whether the route is originated from the local AS.
+     * <p/>
+     * The route is considered originated from the local AS if the AS Path
+     * is empty or if it begins with an AS_SET.
+     *
+     * @return true if the route is originated from the local AS, otherwise
+     * false
+     */
+    boolean isLocalRoute() {
+        if (asPath.getPathSegments().isEmpty()) {
+            return true;
+        }
+        PathSegment firstPathSegment = asPath.getPathSegments().get(0);
+        if (firstPathSegment.getType() == BgpConstants.Update.AsPath.AS_SET) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Gets the BGP Neighbor AS number the route was received from.
+     * <p/>
+     * If the router is originated from the local AS, the return value is
+     * zero (BGP_AS_0).
+     *
+     * @return the BGP Neighbor AS number the route was received from.
+     */
+    long getNeighborAs() {
+        if (isLocalRoute()) {
+            return BgpConstants.BGP_AS_0;
+        }
+        PathSegment firstPathSegment = asPath.getPathSegments().get(0);
+        if (firstPathSegment.getSegmentAsNumbers().isEmpty()) {
+            // TODO: Shouldn't happen. Should check during the parsing.
+            return BgpConstants.BGP_AS_0;
+        }
+        return firstPathSegment.getSegmentAsNumbers().get(0);
+    }
+
+    /**
+     * Tests whether the AS Path contains a loop.
+     * <p/>
+     * The test is done by comparing whether the AS Path contains the
+     * local AS number.
+     *
+     * @param localAsNumber the local AS number to compare against
+     * @return true if the AS Path contains a loop, otherwise false
+     */
+    boolean hasAsPathLoop(long localAsNumber) {
+        for (PathSegment pathSegment : asPath.getPathSegments()) {
+            for (Long asNumber : pathSegment.getSegmentAsNumbers()) {
+                if (asNumber.equals(localAsNumber)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Compares this BGP route against another BGP route by using the
+     * BGP Decision Process.
+     * <p/>
+     * NOTE: The comparison needs to be performed only on routes that have
+     * same IP Prefix.
+     *
+     * @param other the BGP route to compare against
+     * @return true if this BGP route is better than the other BGP route
+     * or same, otherwise false
+     */
+    boolean isBetterThan(BgpRouteEntry other) {
+        if (this == other) {
+            return true;        // Return true if same route
+        }
+
+        // Compare the LOCAL_PREF values: larger is better
+        if (getLocalPref() != other.getLocalPref()) {
+            return (getLocalPref() > other.getLocalPref());
+        }
+
+        // Compare the AS number in the path: smaller is better
+        if (getAsPath().getAsPathLength() !=
+            other.getAsPath().getAsPathLength()) {
+            return getAsPath().getAsPathLength() <
+                other.getAsPath().getAsPathLength();
+        }
+
+        // Compare the Origin number: lower is better
+        if (getOrigin() != other.getOrigin()) {
+            return (getOrigin() < other.getOrigin());
+        }
+
+        // Compare the MED if the neighbor AS is same: larger is better
+        medLabel: {
+            boolean thisIsLocalRoute = isLocalRoute();
+            if (thisIsLocalRoute != other.isLocalRoute()) {
+                break medLabel;                 // AS number is different
+            }
+            if (!thisIsLocalRoute) {
+                long thisNeighborAs = getNeighborAs();
+                if (thisNeighborAs != other.getNeighborAs()) {
+                    break medLabel;             // AS number is different
+                }
+                if (thisNeighborAs == BgpConstants.BGP_AS_0) {
+                    break medLabel;             // Invalid AS number
+                }
+            }
+
+            // Compare the MED
+            if (getMultiExitDisc() != other.getMultiExitDisc()) {
+                return (getMultiExitDisc() > other.getMultiExitDisc());
+            }
+        }
+
+        // Compare the peer BGP ID: lower is better
+        IpAddress peerBgpId = getBgpSession().getRemoteBgpId();
+        IpAddress otherPeerBgpId = other.getBgpSession().getRemoteBgpId();
+        if (!peerBgpId.equals(otherPeerBgpId)) {
+            return (peerBgpId.compareTo(otherPeerBgpId) < 0);
+        }
+
+        // Compare the peer BGP address: lower is better
+        IpAddress peerAddress = getBgpSession().getRemoteIp4Address();
+        IpAddress otherPeerAddress =
+            other.getBgpSession().getRemoteIp4Address();
+        if (!peerAddress.equals(otherPeerAddress)) {
+            return (peerAddress.compareTo(otherPeerAddress) < 0);
+        }
+
+        return true;            // Routes are same. Shouldn't happen?
+    }
+
+    /**
+     * A class to represent AS Path Segment.
+     */
+    public static class PathSegment {
+        private final byte type;        // Segment type: AS_SET, AS_SEQUENCE
+        private final ArrayList<Long> segmentAsNumbers;   // Segment AS numbers
+
+        /**
+         * Constructor.
+         *
+         * @param type the Path Segment Type: 1=AS_SET, 2=AS_SEQUENCE
+         * @param segmentAsNumbers the Segment AS numbers
+         */
+        PathSegment(byte type, ArrayList<Long> segmentAsNumbers) {
+            this.type = type;
+            this.segmentAsNumbers = checkNotNull(segmentAsNumbers);
+        }
+
+        /**
+         * Gets the Path Segment Type: AS_SET, AS_SEQUENCE.
+         *
+         * @return the Path Segment Type: AS_SET, AS_SEQUENCE
+         */
+        public byte getType() {
+            return type;
+        }
+
+        /**
+         * Gets the Path Segment AS Numbers.
+         *
+         * @return the Path Segment AS Numbers
+         */
+        public ArrayList<Long> getSegmentAsNumbers() {
+            return segmentAsNumbers;
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (this == other) {
+                return true;
+            }
+
+            if (!(other instanceof PathSegment)) {
+                return false;
+            }
+
+            PathSegment otherPathSegment = (PathSegment) other;
+            return Objects.equals(this.type, otherPathSegment.type) &&
+                Objects.equals(this.segmentAsNumbers,
+                               otherPathSegment.segmentAsNumbers);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(type, segmentAsNumbers);
+        }
+
+        @Override
+        public String toString() {
+            return MoreObjects.toStringHelper(getClass())
+                .add("type", this.type)
+                .add("segmentAsNumbers", this.segmentAsNumbers)
+                .toString();
+        }
+    }
+
+    /**
+     * A class to represent AS Path.
+     */
+    public static class AsPath {
+        private final ArrayList<PathSegment> pathSegments;
+        private final int asPathLength;         // Precomputed AS Path Length
+
+        /**
+         * Constructor.
+         *
+         * @param pathSegments the Path Segments of the Path
+         */
+         AsPath(ArrayList<PathSegment> pathSegments) {
+             this.pathSegments = checkNotNull(pathSegments);
+
+             //
+             // Precompute the AS Path Length:
+             // - AS_SET counts as 1
+             //
+             int pl = 0;
+             for (PathSegment pathSegment : pathSegments) {
+                 if (pathSegment.getType() ==
+                     BgpConstants.Update.AsPath.AS_SET) {
+                     pl++;
+                     continue;
+                 }
+                 pl += pathSegment.getSegmentAsNumbers().size();
+             }
+             asPathLength = pl;
+         }
+
+        /**
+         * Gets the AS Path Segments.
+         *
+         * @return the AS Path Segments
+         */
+        public ArrayList<PathSegment> getPathSegments() {
+            return pathSegments;
+        }
+
+        /**
+         * Gets the AS Path Length as considered by the BGP Decision Process.
+         *
+         * @return the AS Path Length as considered by the BGP Decision Process
+         */
+        int getAsPathLength() {
+            return asPathLength;
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (this == other) {
+                return true;
+            }
+
+            if (!(other instanceof AsPath)) {
+                return false;
+            }
+
+            AsPath otherAsPath = (AsPath) other;
+            return Objects.equals(this.pathSegments, otherAsPath.pathSegments);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(pathSegments);
+        }
+
+        @Override
+        public String toString() {
+            return MoreObjects.toStringHelper(getClass())
+                .add("pathSegments", this.pathSegments)
+                .toString();
+        }
+    }
+
+    /**
+     * Compares whether two objects are equal.
+     * <p/>
+     * NOTE: The bgpSession field is excluded from the comparison.
+     *
+     * @return true if the two objects are equal, otherwise false.
+     */
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+
+        //
+        // NOTE: Subclasses are considered as change of identity, hence
+        // equals() will return false if the class type doesn't match.
+        //
+        if (other == null || getClass() != other.getClass()) {
+            return false;
+        }
+
+        if (!super.equals(other)) {
+            return false;
+        }
+
+        // NOTE: The bgpSession field is excluded from the comparison
+        BgpRouteEntry otherRoute = (BgpRouteEntry) other;
+        return (this.origin == otherRoute.origin) &&
+            Objects.equals(this.asPath, otherRoute.asPath) &&
+            (this.localPref == otherRoute.localPref) &&
+            (this.multiExitDisc == otherRoute.multiExitDisc);
+    }
+
+    /**
+     * Computes the hash code.
+     * <p/>
+     * NOTE: We return the base class hash code to avoid expensive computation
+     *
+     * @return the object hash code
+     */
+    @Override
+    public int hashCode() {
+        return super.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(getClass())
+            .add("prefix", prefix())
+            .add("nextHop", nextHop())
+            .add("bgpId", bgpSession.getRemoteBgpId())
+            .add("origin", origin)
+            .add("asPath", asPath)
+            .add("localPref", localPref)
+            .add("multiExitDisc", multiExitDisc)
+            .toString();
+    }
+}
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSession.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSession.java
new file mode 100644
index 0000000..97dceb5
--- /dev/null
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSession.java
@@ -0,0 +1,1840 @@
+package org.onlab.onos.sdnip.bgp;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+
+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.jboss.netty.channel.ChannelStateEvent;
+import org.jboss.netty.channel.SimpleChannelHandler;
+import org.jboss.netty.util.HashedWheelTimer;
+import org.jboss.netty.util.Timeout;
+import org.jboss.netty.util.Timer;
+import org.jboss.netty.util.TimerTask;
+import org.onlab.onos.sdnip.bgp.BgpConstants.Notifications;
+import org.onlab.onos.sdnip.bgp.BgpConstants.Notifications.HoldTimerExpired;
+import org.onlab.onos.sdnip.bgp.BgpConstants.Notifications.MessageHeaderError;
+import org.onlab.onos.sdnip.bgp.BgpConstants.Notifications.OpenMessageError;
+import org.onlab.onos.sdnip.bgp.BgpConstants.Notifications.UpdateMessageError;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Class for handling the BGP peer sessions.
+ * There is one instance per each BGP peer session.
+ */
+public class BgpSession extends SimpleChannelHandler {
+    private static final Logger log =
+        LoggerFactory.getLogger(BgpSession.class);
+
+    private final BgpSessionManager bgpSessionManager;
+
+    // Local flag to indicate the session is closed.
+    // It is used to avoid the Netty's asynchronous closing of a channel.
+    private boolean isClosed = false;
+
+    private SocketAddress remoteAddress;        // Peer IP addr/port
+    private IpAddress remoteIp4Address;        // Peer IPv4 address
+    private int remoteBgpVersion;               // 1 octet
+    private long remoteAs;                      // 2 octets
+    private long remoteHoldtime;                // 2 octets
+    private IpAddress remoteBgpId;             // 4 octets -> IPv4 address
+    //
+    private SocketAddress localAddress;         // Local IP addr/port
+    private IpAddress localIp4Address;         // Local IPv4 address
+    private int localBgpVersion;                // 1 octet
+    private long localAs;                       // 2 octets
+    private long localHoldtime;                 // 2 octets
+    private IpAddress localBgpId;              // 4 octets -> IPv4 address
+    //
+    private long localKeepaliveInterval;        // Keepalive interval
+
+    // Timers state
+    private Timer timer = new HashedWheelTimer();
+    private volatile Timeout keepaliveTimeout;  // Periodic KEEPALIVE
+    private volatile Timeout sessionTimeout;    // Session timeout
+
+    // BGP RIB-IN routing entries from this peer
+    private ConcurrentMap<IpPrefix, BgpRouteEntry> bgpRibIn =
+        new ConcurrentHashMap<>();
+
+    /**
+     * Constructor for a given BGP Session Manager.
+     *
+     * @param bgpSessionManager the BGP Session Manager to use
+     */
+    BgpSession(BgpSessionManager bgpSessionManager) {
+        this.bgpSessionManager = bgpSessionManager;
+    }
+
+    /**
+     * Gets the BGP RIB-IN routing entries.
+     *
+     * @return the BGP RIB-IN routing entries
+     */
+    public Collection<BgpRouteEntry> getBgpRibIn() {
+        return bgpRibIn.values();
+    }
+
+    /**
+     * Finds a BGP routing entry in the BGP RIB-IN.
+     *
+     * @param prefix the prefix of the route to search for
+     * @return the BGP routing entry if found, otherwise null
+     */
+    public BgpRouteEntry findBgpRouteEntry(IpPrefix prefix) {
+        return bgpRibIn.get(prefix);
+    }
+
+    /**
+     * Gets the BGP session remote address.
+     *
+     * @return the BGP session remote address
+     */
+    public SocketAddress getRemoteAddress() {
+        return remoteAddress;
+    }
+
+    /**
+     * Gets the BGP session remote IPv4 address.
+     *
+     * @return the BGP session remote IPv4 address
+     */
+    public IpAddress getRemoteIp4Address() {
+        return remoteIp4Address;
+    }
+
+    /**
+     * Gets the BGP session remote BGP version.
+     *
+     * @return the BGP session remote BGP version
+     */
+    public int getRemoteBgpVersion() {
+        return remoteBgpVersion;
+    }
+
+    /**
+     * Gets the BGP session remote AS number.
+     *
+     * @return the BGP session remote AS number
+     */
+    public long getRemoteAs() {
+        return remoteAs;
+    }
+
+    /**
+     * Gets the BGP session remote Holdtime.
+     *
+     * @return the BGP session remote Holdtime
+     */
+    public long getRemoteHoldtime() {
+        return remoteHoldtime;
+    }
+
+    /**
+     * Gets the BGP session remote BGP Identifier as an IPv4 address.
+     *
+     * @return the BGP session remote BGP Identifier as an IPv4 address
+     */
+    public IpAddress getRemoteBgpId() {
+        return remoteBgpId;
+    }
+
+    /**
+     * Gets the BGP session local address.
+     *
+     * @return the BGP session local address
+     */
+    public SocketAddress getLocalAddress() {
+        return localAddress;
+    }
+
+    /**
+     * Gets the BGP session local BGP version.
+     *
+     * @return the BGP session local BGP version
+     */
+    public int getLocalBgpVersion() {
+        return localBgpVersion;
+    }
+
+    /**
+     * Gets the BGP session local AS number.
+     *
+     * @return the BGP session local AS number
+     */
+    public long getLocalAs() {
+        return localAs;
+    }
+
+    /**
+     * Gets the BGP session local Holdtime.
+     *
+     * @return the BGP session local Holdtime
+     */
+    public long getLocalHoldtime() {
+        return localHoldtime;
+    }
+
+    /**
+     * Gets the BGP session local BGP Identifier as an IPv4 address.
+     *
+     * @return the BGP session local BGP Identifier as an IPv4 address
+     */
+    public IpAddress getLocalBgpId() {
+        return localBgpId;
+    }
+
+    /**
+     * Tests whether the session is closed.
+     * <p/>
+     * NOTE: We use this method to avoid the Netty's asynchronous closing
+     * of a channel.
+     *
+     * @param return true if the session is closed
+     */
+    boolean isClosed() {
+        return isClosed;
+    }
+
+    /**
+     * Closes the channel.
+     *
+     * @param ctx the Channel Handler Context
+     */
+    void closeChannel(ChannelHandlerContext ctx) {
+        isClosed = true;
+        timer.stop();
+        ctx.getChannel().close();
+    }
+
+    @Override
+    public void channelConnected(ChannelHandlerContext ctx,
+                                 ChannelStateEvent channelEvent) {
+        localAddress = ctx.getChannel().getLocalAddress();
+        remoteAddress = ctx.getChannel().getRemoteAddress();
+
+        // Assign the local and remote IPv4 addresses
+        InetAddress inetAddr;
+        if (localAddress instanceof InetSocketAddress) {
+            inetAddr = ((InetSocketAddress) localAddress).getAddress();
+            localIp4Address = IpAddress.valueOf(inetAddr.getAddress());
+        }
+        if (remoteAddress instanceof InetSocketAddress) {
+            inetAddr = ((InetSocketAddress) remoteAddress).getAddress();
+            remoteIp4Address = IpAddress.valueOf(inetAddr.getAddress());
+        }
+
+        log.debug("BGP Session Connected from {} on {}",
+                  remoteAddress, localAddress);
+        if (!bgpSessionManager.peerConnected(this)) {
+            log.debug("Cannot setup BGP Session Connection from {}. Closing...",
+                      remoteAddress);
+            ctx.getChannel().close();
+        }
+    }
+
+    @Override
+    public void channelDisconnected(ChannelHandlerContext ctx,
+                                    ChannelStateEvent channelEvent) {
+        log.debug("BGP Session Disconnected from {} on {}",
+                  ctx.getChannel().getRemoteAddress(),
+                  ctx.getChannel().getLocalAddress());
+
+        //
+        // Withdraw the routes advertised by this BGP peer
+        //
+        // NOTE: We must initialize the RIB-IN before propagating the withdraws
+        // for further processing. Otherwise, the BGP Decision Process
+        // will use those routes again.
+        //
+        Collection<BgpRouteEntry> deletedRoutes = bgpRibIn.values();
+        bgpRibIn = 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);
+
+        bgpSessionManager.peerDisconnected(this);
+    }
+
+    /**
+     * Processes BGP OPEN message.
+     *
+     * @param ctx the Channel Handler Context
+     * @param message the message to process
+     */
+    void processBgpOpen(ChannelHandlerContext ctx, ChannelBuffer message) {
+        int minLength =
+            BgpConstants.BGP_OPEN_MIN_LENGTH - BgpConstants.BGP_HEADER_LENGTH;
+        if (message.readableBytes() < minLength) {
+            log.debug("BGP RX OPEN Error from {}: " +
+                      "Message length {} too short. Must be at least {}",
+                      remoteAddress, message.readableBytes(), minLength);
+            //
+            // ERROR: Bad Message Length
+            //
+            // Send NOTIFICATION and close the connection
+            ChannelBuffer txMessage = prepareBgpNotificationBadMessageLength(
+                message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH);
+            ctx.getChannel().write(txMessage);
+            closeChannel(ctx);
+            return;
+        }
+
+        //
+        // Parse the OPEN message
+        //
+        // Remote BGP version
+        remoteBgpVersion = message.readUnsignedByte();
+        if (remoteBgpVersion != BgpConstants.BGP_VERSION) {
+            log.debug("BGP RX OPEN Error from {}: " +
+                      "Unsupported BGP version {}. Should be {}",
+                      remoteAddress, remoteBgpVersion,
+                      BgpConstants.BGP_VERSION);
+            //
+            // ERROR: Unsupported Version Number
+            //
+            // Send NOTIFICATION and close the connection
+            int errorCode = OpenMessageError.ERROR_CODE;
+            int errorSubcode = OpenMessageError.UNSUPPORTED_VERSION_NUMBER;
+            ChannelBuffer data = ChannelBuffers.buffer(2);
+            data.writeShort(BgpConstants.BGP_VERSION);
+            ChannelBuffer txMessage =
+                prepareBgpNotification(errorCode, errorSubcode, data);
+            ctx.getChannel().write(txMessage);
+            closeChannel(ctx);
+            return;
+        }
+
+        // Remote AS number
+        remoteAs = message.readUnsignedShort();
+        //
+        // Verify that the AS number is same for all other BGP Sessions
+        // NOTE: This check applies only for our use-case where all BGP
+        // sessions are iBGP.
+        //
+        for (BgpSession bgpSession : bgpSessionManager.getBgpSessions()) {
+            if (remoteAs != bgpSession.getRemoteAs()) {
+                log.debug("BGP RX OPEN Error from {}: Bad Peer AS {}. " +
+                          "Expected {}",
+                          remoteAddress, remoteAs, bgpSession.getRemoteAs());
+                //
+                // ERROR: Bad Peer AS
+                //
+                // Send NOTIFICATION and close the connection
+                int errorCode = OpenMessageError.ERROR_CODE;
+                int errorSubcode = OpenMessageError.BAD_PEER_AS;
+                ChannelBuffer txMessage =
+                    prepareBgpNotification(errorCode, errorSubcode, null);
+                ctx.getChannel().write(txMessage);
+                closeChannel(ctx);
+                return;
+            }
+        }
+
+        // Remote Hold Time
+        remoteHoldtime = message.readUnsignedShort();
+        if ((remoteHoldtime != 0) &&
+            (remoteHoldtime < BgpConstants.BGP_KEEPALIVE_MIN_HOLDTIME)) {
+            log.debug("BGP RX OPEN Error from {}: " +
+                      "Unacceptable Hold Time field {}. " +
+                      "Should be 0 or at least {}",
+                      remoteAddress, remoteHoldtime,
+                      BgpConstants.BGP_KEEPALIVE_MIN_HOLDTIME);
+            //
+            // ERROR: Unacceptable Hold Time
+            //
+            // Send NOTIFICATION and close the connection
+            int errorCode = OpenMessageError.ERROR_CODE;
+            int errorSubcode = OpenMessageError.UNACCEPTABLE_HOLD_TIME;
+            ChannelBuffer txMessage =
+                prepareBgpNotification(errorCode, errorSubcode, null);
+            ctx.getChannel().write(txMessage);
+            closeChannel(ctx);
+            return;
+        }
+
+        // Remote BGP Identifier
+        remoteBgpId = IpAddress.valueOf((int) message.readUnsignedInt());
+
+        // Optional Parameters
+        int optParamLen = message.readUnsignedByte();
+        if (message.readableBytes() < optParamLen) {
+            log.debug("BGP RX OPEN Error from {}: " +
+                      "Invalid Optional Parameter Length field {}. " +
+                      "Remaining Optional Parameters {}",
+                      remoteAddress, optParamLen, message.readableBytes());
+            //
+            // ERROR: Invalid Optional Parameter Length field: Unspecific
+            //
+            // Send NOTIFICATION and close the connection
+            int errorCode = OpenMessageError.ERROR_CODE;
+            int errorSubcode = Notifications.ERROR_SUBCODE_UNSPECIFIC;
+            ChannelBuffer txMessage =
+                prepareBgpNotification(errorCode, errorSubcode, null);
+            ctx.getChannel().write(txMessage);
+            closeChannel(ctx);
+            return;
+        }
+        // TODO: Parse the optional parameters (if needed)
+        message.readBytes(optParamLen);             // NOTE: data ignored
+
+        //
+        // Copy some of the remote peer's state/setup to the local setup:
+        //  - BGP version
+        //  - AS number (NOTE: the peer setup is always iBGP)
+        //  - Holdtime
+        // Also, assign the local BGP ID based on the local setup
+        //
+        localBgpVersion = remoteBgpVersion;
+        localAs = remoteAs;
+        localHoldtime = remoteHoldtime;
+        localBgpId = bgpSessionManager.getMyBgpId();
+
+        // Set the Keepalive interval
+        if (localHoldtime == 0) {
+            localKeepaliveInterval = 0;
+        } else {
+            localKeepaliveInterval = Math.max(localHoldtime /
+                         BgpConstants.BGP_KEEPALIVE_PER_HOLD_INTERVAL,
+                         BgpConstants.BGP_KEEPALIVE_MIN_INTERVAL);
+        }
+
+        log.debug("BGP RX OPEN message from {}: " +
+                  "BGPv{} AS {} BGP-ID {} Holdtime {}",
+                  remoteAddress, remoteBgpVersion, remoteAs,
+                  remoteBgpId, remoteHoldtime);
+
+        // Send my OPEN followed by KEEPALIVE
+        ChannelBuffer txMessage = prepareBgpOpen();
+        ctx.getChannel().write(txMessage);
+        //
+        txMessage = prepareBgpKeepalive();
+        ctx.getChannel().write(txMessage);
+
+        // Start the KEEPALIVE timer
+        restartKeepaliveTimer(ctx);
+
+        // Start the Session Timeout timer
+        restartSessionTimeoutTimer(ctx);
+    }
+
+    /**
+     * Processes BGP UPDATE message.
+     *
+     * @param ctx the Channel Handler Context
+     * @param message the message to process
+     */
+    void processBgpUpdate(ChannelHandlerContext ctx, ChannelBuffer message) {
+        Collection<BgpRouteEntry> addedRoutes = null;
+        Map<IpPrefix, BgpRouteEntry> deletedRoutes = new HashMap<>();
+
+        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 {}",
+                      remoteAddress, message.readableBytes(), minLength);
+            //
+            // ERROR: Bad Message Length
+            //
+            // Send NOTIFICATION and close the connection
+            ChannelBuffer txMessage = prepareBgpNotificationBadMessageLength(
+                message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH);
+            ctx.getChannel().write(txMessage);
+            closeChannel(ctx);
+            return;
+        }
+
+        log.debug("BGP RX UPDATE message from {}", remoteAddress);
+
+        //
+        // Parse the UPDATE message
+        //
+
+        //
+        // Parse the Withdrawn Routes
+        //
+        int withdrawnRoutesLength = message.readUnsignedShort();
+        if (withdrawnRoutesLength > message.readableBytes()) {
+            // ERROR: Malformed Attribute List
+            actionsBgpUpdateMalformedAttributeList(ctx);
+            return;
+        }
+        Collection<IpPrefix> withdrawnPrefixes = null;
+        try {
+            withdrawnPrefixes = parsePackedPrefixes(withdrawnRoutesLength,
+                                                    message);
+        } catch (BgpParseException e) {
+            // ERROR: Invalid Network Field
+            log.debug("Exception parsing Withdrawn Prefixes from BGP peer {}: ",
+                      remoteBgpId, e);
+            actionsBgpUpdateInvalidNetworkField(ctx);
+            return;
+        }
+        for (IpPrefix prefix : withdrawnPrefixes) {
+            log.debug("BGP RX UPDATE message WITHDRAWN from {}: {}",
+                      remoteAddress, prefix);
+            BgpRouteEntry bgpRouteEntry = bgpRibIn.get(prefix);
+            if (bgpRouteEntry != null) {
+                deletedRoutes.put(prefix, bgpRouteEntry);
+            }
+        }
+
+        //
+        // Parse the Path Attributes
+        //
+        try {
+            addedRoutes = parsePathAttributes(ctx, message);
+        } catch (BgpParseException e) {
+            log.debug("Exception parsing Path Attributes from BGP peer {}: ",
+                      remoteBgpId, 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()) {
+            bgpRibIn.remove(bgpRouteEntry.prefix());
+        }
+        for (BgpRouteEntry bgpRouteEntry : addedRoutes) {
+            bgpRibIn.put(bgpRouteEntry.prefix(), bgpRouteEntry);
+        }
+
+        // Push the updates to the BGP Merged RIB
+        BgpSessionManager.BgpRouteSelector bgpRouteSelector =
+            bgpSessionManager.getBgpRouteSelector();
+        bgpRouteSelector.routeUpdates(this, addedRoutes,
+                                      deletedRoutes.values());
+
+        // Start the Session Timeout timer
+        restartSessionTimeoutTimer(ctx);
+    }
+
+    /**
+     * Parse BGP Path Attributes from the BGP UPDATE message.
+     *
+     * @param ctx the Channel Handler Context
+     * @param message the message to parse
+     * @return a collection of the result BGP Route Entries
+     * @throws BgpParseException
+     */
+    private Collection<BgpRouteEntry> parsePathAttributes(
+                                ChannelHandlerContext ctx,
+                                ChannelBuffer message)
+        throws BgpParseException {
+        Map<IpPrefix, BgpRouteEntry> addedRoutes = new HashMap<>();
+
+        //
+        // Parsed values
+        //
+        Short origin = -1;                      // Mandatory
+        BgpRouteEntry.AsPath asPath = null;     // Mandatory
+        IpAddress nextHop = null;              // Mandatory
+        long multiExitDisc =                    // Optional
+            BgpConstants.Update.MultiExitDisc.LOWEST_MULTI_EXIT_DISC;
+        Long localPref = null;                  // Mandatory
+        Long aggregatorAsNumber = null;         // Optional: unused
+        IpAddress aggregatorIpAddress = null;  // Optional: unused
+
+        //
+        // Get and verify the Path Attributes Length
+        //
+        int pathAttributeLength = message.readUnsignedShort();
+        if (pathAttributeLength > message.readableBytes()) {
+            // ERROR: Malformed Attribute List
+            actionsBgpUpdateMalformedAttributeList(ctx);
+            String errorMsg = "Malformed Attribute List";
+            throw new BgpParseException(errorMsg);
+        }
+        if (pathAttributeLength == 0) {
+            return addedRoutes.values();
+        }
+
+        //
+        // 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(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(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(ctx);
+                String errorMsg = "Malformed Attribute List";
+                throw new BgpParseException(errorMsg);
+            }
+
+            //
+            // Verify the Attribute Flags
+            //
+            verifyBgpUpdateAttributeFlags(ctx, attrTypeCode, attrLen,
+                                          attrFlags, message);
+
+            //
+            // Extract the Attribute Value based on the Attribute Type Code
+            //
+            switch (attrTypeCode) {
+
+            case BgpConstants.Update.Origin.TYPE:
+                // Attribute Type Code ORIGIN
+                origin = parseAttributeTypeOrigin(ctx, attrTypeCode, attrLen,
+                                                  attrFlags, message);
+                break;
+
+            case BgpConstants.Update.AsPath.TYPE:
+                // Attribute Type Code AS_PATH
+                asPath = parseAttributeTypeAsPath(ctx, attrTypeCode, attrLen,
+                                                  attrFlags, message);
+                break;
+
+            case BgpConstants.Update.NextHop.TYPE:
+                // Attribute Type Code NEXT_HOP
+                nextHop = parseAttributeTypeNextHop(ctx, attrTypeCode, attrLen,
+                                                    attrFlags, message);
+                break;
+
+            case BgpConstants.Update.MultiExitDisc.TYPE:
+                // Attribute Type Code MULTI_EXIT_DISC
+                multiExitDisc =
+                    parseAttributeTypeMultiExitDisc(ctx, attrTypeCode, attrLen,
+                                                    attrFlags, message);
+                break;
+
+            case BgpConstants.Update.LocalPref.TYPE:
+                // Attribute Type Code LOCAL_PREF
+                localPref =
+                    parseAttributeTypeLocalPref(ctx, attrTypeCode, attrLen,
+                                                attrFlags, message);
+                break;
+
+            case BgpConstants.Update.AtomicAggregate.TYPE:
+                // Attribute Type Code ATOMIC_AGGREGATE
+                parseAttributeTypeAtomicAggregate(ctx, attrTypeCode, attrLen,
+                                                  attrFlags, message);
+                // Nothing to do: this attribute is primarily informational
+                break;
+
+            case BgpConstants.Update.Aggregator.TYPE:
+                // Attribute Type Code AGGREGATOR
+                Pair<Long, IpAddress> aggregator =
+                    parseAttributeTypeAggregator(ctx, attrTypeCode, attrLen,
+                                                 attrFlags, message);
+                aggregatorAsNumber = aggregator.getLeft();
+                aggregatorIpAddress = aggregator.getRight();
+                break;
+
+            default:
+                // TODO: Parse any new Attribute Types if needed
+                if (!optionalBit) {
+                    // ERROR: Unrecognized Well-known Attribute
+                    actionsBgpUpdateUnrecognizedWellKnownAttribute(
+                        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 {}",
+                          remoteAddress, attrTypeCode);
+                message.skipBytes(attrLen);
+                break;
+            }
+        }
+
+        //
+        // Verify the Well-known Attributes
+        //
+        verifyBgpUpdateWellKnownAttributes(ctx, origin, asPath, nextHop,
+                                           localPref);
+
+        //
+        // Parse the NLRI (Network Layer Reachability Information)
+        //
+        Collection<IpPrefix> addedPrefixes = null;
+        int nlriLength = message.readableBytes();
+        try {
+            addedPrefixes = parsePackedPrefixes(nlriLength, message);
+        } catch (BgpParseException e) {
+            // ERROR: Invalid Network Field
+            log.debug("Exception parsing NLRI from BGP peer {}: ",
+                      remoteBgpId, e);
+            actionsBgpUpdateInvalidNetworkField(ctx);
+            // Rethrow the exception
+            throw e;
+        }
+
+        // Generate the added routes
+        for (IpPrefix prefix : addedPrefixes) {
+            BgpRouteEntry bgpRouteEntry =
+                new BgpRouteEntry(this, prefix, nextHop,
+                                  origin.byteValue(), asPath, localPref);
+            bgpRouteEntry.setMultiExitDisc(multiExitDisc);
+            if (bgpRouteEntry.hasAsPathLoop(localAs)) {
+                log.debug("BGP RX UPDATE message IGNORED from {}: {} " +
+                          "nextHop {}: contains AS Path loop",
+                          remoteAddress, prefix, nextHop);
+                continue;
+            } else {
+                log.debug("BGP RX UPDATE message ADDED from {}: {} nextHop {}",
+                          remoteAddress, prefix, nextHop);
+            }
+            addedRoutes.put(prefix, bgpRouteEntry);
+        }
+
+        return addedRoutes.values();
+    }
+
+    /**
+     * Verifies BGP UPDATE Well-known Attributes.
+     *
+     * @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
+     * @throws BgpParseException
+     */
+    private void verifyBgpUpdateWellKnownAttributes(
+                        ChannelHandlerContext ctx,
+                        Short origin,
+                        BgpRouteEntry.AsPath asPath,
+                        IpAddress nextHop,
+                        Long localPref)
+        throws BgpParseException {
+        //
+        // Check for Missing Well-known Attributes
+        //
+        if ((origin == null) || (origin == -1)) {
+            // Missing Attribute Type Code ORIGIN
+            int type = BgpConstants.Update.Origin.TYPE;
+            actionsBgpUpdateMissingWellKnownAttribute(ctx, type);
+            String errorMsg = "Missing Well-known Attribute: ORIGIN";
+            throw new BgpParseException(errorMsg);
+        }
+        if (asPath == null) {
+            // Missing Attribute Type Code AS_PATH
+            int type = BgpConstants.Update.AsPath.TYPE;
+            actionsBgpUpdateMissingWellKnownAttribute(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(ctx, type);
+            String errorMsg = "Missing Well-known Attribute: NEXT_HOP";
+            throw new BgpParseException(errorMsg);
+        }
+        if (localPref == null) {
+            // Missing Attribute Type Code LOCAL_PREF
+            // NOTE: Required for iBGP
+            int type = BgpConstants.Update.LocalPref.TYPE;
+            actionsBgpUpdateMissingWellKnownAttribute(ctx, type);
+            String errorMsg = "Missing Well-known Attribute: LOCAL_PREF";
+            throw new BgpParseException(errorMsg);
+        }
+    }
+
+    /**
+     * Verifies the BGP UPDATE Attribute Flags.
+     *
+     * @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 void verifyBgpUpdateAttributeFlags(
+                        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 BgpConstants.Update.Origin.TYPE:
+            isWellKnown = true;
+            typeName = "ORIGIN";
+            break;
+        case BgpConstants.Update.AsPath.TYPE:
+            isWellKnown = true;
+            typeName = "AS_PATH";
+            break;
+        case BgpConstants.Update.NextHop.TYPE:
+            isWellKnown = true;
+            typeName = "NEXT_HOP";
+            break;
+        case BgpConstants.Update.MultiExitDisc.TYPE:
+            isWellKnown = false;
+            typeName = "MULTI_EXIT_DISC";
+            break;
+        case BgpConstants.Update.LocalPref.TYPE:
+            isWellKnown = true;
+            typeName = "LOCAL_PREF";
+            break;
+        case BgpConstants.Update.AtomicAggregate.TYPE:
+            isWellKnown = true;
+            typeName = "ATOMIC_AGGREGATE";
+            break;
+        case BgpConstants.Update.Aggregator.TYPE:
+            isWellKnown = false;
+            typeName = "AGGREGATOR";
+            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(
+                ctx, attrTypeCode, attrLen, attrFlags, message);
+            String errorMsg = "Attribute Flags Error for " + typeName + ": " +
+                attrFlags;
+            throw new BgpParseException(errorMsg);
+        }
+    }
+
+    /**
+     * Parses BGP UPDATE Attribute Type ORIGIN.
+     *
+     * @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 short parseAttributeTypeOrigin(
+                        ChannelHandlerContext ctx,
+                        int attrTypeCode,
+                        int attrLen,
+                        int attrFlags,
+                        ChannelBuffer message)
+        throws BgpParseException {
+
+        // Check the Attribute Length
+        if (attrLen != BgpConstants.Update.Origin.LENGTH) {
+            // ERROR: Attribute Length Error
+            actionsBgpUpdateAttributeLengthError(
+                        ctx, attrTypeCode, attrLen, attrFlags, message);
+            String errorMsg = "Attribute Length Error";
+            throw new BgpParseException(errorMsg);
+        }
+
+        message.markReaderIndex();
+        short origin = message.readUnsignedByte();
+        switch (origin) {
+        case BgpConstants.Update.Origin.IGP:
+            // FALLTHROUGH
+        case BgpConstants.Update.Origin.EGP:
+            // FALLTHROUGH
+        case BgpConstants.Update.Origin.INCOMPLETE:
+            break;
+        default:
+            // ERROR: Invalid ORIGIN Attribute
+            message.resetReaderIndex();
+            actionsBgpUpdateInvalidOriginAttribute(
+                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 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 BgpRouteEntry.AsPath parseAttributeTypeAsPath(
+                        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(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 BgpConstants.Update.AsPath.AS_SET:
+                // FALLTHROUGH
+            case BgpConstants.Update.AsPath.AS_SEQUENCE:
+                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(ctx);
+                String errorMsg =
+                    "Invalid AS Path Segment Type: " + pathSegmentType;
+                throw new BgpParseException(errorMsg);
+            }
+
+            // Parse the AS numbers
+            if (2 * pathSegmentLength > attrLen) {
+                // ERROR: Malformed AS_PATH
+                actionsBgpUpdateMalformedAsPath(ctx);
+                String errorMsg = "Malformed AS Path";
+                throw new BgpParseException(errorMsg);
+            }
+            attrLen -= (2 * pathSegmentLength);
+            ArrayList<Long> segmentAsNumbers = new ArrayList<>();
+            while (pathSegmentLength-- > 0) {
+                long 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 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 IpAddress parseAttributeTypeNextHop(
+                        ChannelHandlerContext ctx,
+                        int attrTypeCode,
+                        int attrLen,
+                        int attrFlags,
+                        ChannelBuffer message)
+        throws BgpParseException {
+
+        // Check the Attribute Length
+        if (attrLen != BgpConstants.Update.NextHop.LENGTH) {
+            // ERROR: Attribute Length Error
+            actionsBgpUpdateAttributeLengthError(
+                        ctx, attrTypeCode, attrLen, attrFlags, message);
+            String errorMsg = "Attribute Length Error";
+            throw new BgpParseException(errorMsg);
+        }
+
+        message.markReaderIndex();
+        long address = message.readUnsignedInt();
+        IpAddress nextHopAddress = IpAddress.valueOf((int) address);
+        //
+        // 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(localIp4Address)) {
+            // ERROR: Invalid NEXT_HOP Attribute
+            message.resetReaderIndex();
+            actionsBgpUpdateInvalidNextHopAttribute(
+                        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 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 long parseAttributeTypeMultiExitDisc(
+                        ChannelHandlerContext ctx,
+                        int attrTypeCode,
+                        int attrLen,
+                        int attrFlags,
+                        ChannelBuffer message)
+        throws BgpParseException {
+
+        // Check the Attribute Length
+        if (attrLen != BgpConstants.Update.MultiExitDisc.LENGTH) {
+            // ERROR: Attribute Length Error
+            actionsBgpUpdateAttributeLengthError(
+                        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 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 long parseAttributeTypeLocalPref(
+                        ChannelHandlerContext ctx,
+                        int attrTypeCode,
+                        int attrLen,
+                        int attrFlags,
+                        ChannelBuffer message)
+        throws BgpParseException {
+
+        // Check the Attribute Length
+        if (attrLen != BgpConstants.Update.LocalPref.LENGTH) {
+            // ERROR: Attribute Length Error
+            actionsBgpUpdateAttributeLengthError(
+                        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 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 void parseAttributeTypeAtomicAggregate(
+                        ChannelHandlerContext ctx,
+                        int attrTypeCode,
+                        int attrLen,
+                        int attrFlags,
+                        ChannelBuffer message)
+        throws BgpParseException {
+
+        // Check the Attribute Length
+        if (attrLen != BgpConstants.Update.AtomicAggregate.LENGTH) {
+            // ERROR: Attribute Length Error
+            actionsBgpUpdateAttributeLengthError(
+                        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 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 Pair<Long, IpAddress> parseAttributeTypeAggregator(
+                        ChannelHandlerContext ctx,
+                        int attrTypeCode,
+                        int attrLen,
+                        int attrFlags,
+                        ChannelBuffer message)
+        throws BgpParseException {
+
+        // Check the Attribute Length
+        if (attrLen != BgpConstants.Update.Aggregator.LENGTH) {
+            // ERROR: Attribute Length Error
+            actionsBgpUpdateAttributeLengthError(
+                        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
+        long aggregatorAddress = message.readUnsignedInt();
+        IpAddress aggregatorIpAddress =
+                IpAddress.valueOf((int) aggregatorAddress);
+
+        Pair<Long, IpAddress> aggregator = Pair.of(aggregatorAsNumber,
+                                                    aggregatorIpAddress);
+        return aggregator;
+    }
+
+    /**
+     * 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 Collection<IpPrefix> parsePackedPrefixes(int totalLength,
+                                                      ChannelBuffer message)
+        throws BgpParseException {
+        Collection<IpPrefix> result = new ArrayList<>();
+
+        if (totalLength == 0) {
+            return result;
+        }
+
+        // Parse the data
+        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);
+            }
+
+            long address = 0;
+            long extraShift = (4 - prefixBytelen) * 8;
+            while (prefixBytelen > 0) {
+                address <<= 8;
+                address |= message.readUnsignedByte();
+                prefixBytelen--;
+            }
+            address <<= extraShift;
+            IpPrefix prefix =
+                IpPrefix.valueOf(IpAddress.valueOf((int) address).toRealInt(),
+                              (short) 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 ctx the Channel Handler Context
+     */
+    private void actionsBgpUpdateInvalidNetworkField(
+                        ChannelHandlerContext ctx) {
+        log.debug("BGP RX UPDATE Error from {}: Invalid Network Field",
+                  remoteAddress);
+
+        //
+        // ERROR: Invalid Network Field
+        //
+        // Send NOTIFICATION and close the connection
+        int errorCode = UpdateMessageError.ERROR_CODE;
+        int errorSubcode = UpdateMessageError.INVALID_NETWORK_FIELD;
+        ChannelBuffer txMessage =
+            prepareBgpNotification(errorCode, errorSubcode, null);
+        ctx.getChannel().write(txMessage);
+        closeChannel(ctx);
+    }
+
+    /**
+     * Applies the appropriate actions after detecting BGP UPDATE
+     * Malformed Attribute List Error: send NOTIFICATION and close the channel.
+     *
+     * @param ctx the Channel Handler Context
+     */
+    private void actionsBgpUpdateMalformedAttributeList(
+                        ChannelHandlerContext ctx) {
+        log.debug("BGP RX UPDATE Error from {}: Malformed Attribute List",
+                  remoteAddress);
+
+        //
+        // ERROR: Malformed Attribute List
+        //
+        // Send NOTIFICATION and close the connection
+        int errorCode = UpdateMessageError.ERROR_CODE;
+        int errorSubcode = UpdateMessageError.MALFORMED_ATTRIBUTE_LIST;
+        ChannelBuffer txMessage =
+            prepareBgpNotification(errorCode, errorSubcode, null);
+        ctx.getChannel().write(txMessage);
+        closeChannel(ctx);
+    }
+
+    /**
+     * Applies the appropriate actions after detecting BGP UPDATE
+     * Missing Well-known Attribute Error: send NOTIFICATION and close the
+     * channel.
+     *
+     * @param ctx the Channel Handler Context
+     * @param missingAttrTypeCode the missing attribute type code
+     */
+    private void actionsBgpUpdateMissingWellKnownAttribute(
+                        ChannelHandlerContext ctx,
+                        int missingAttrTypeCode) {
+        log.debug("BGP RX UPDATE Error from {}: Missing Well-known Attribute: {}",
+                  remoteAddress, 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 =
+            prepareBgpNotification(errorCode, errorSubcode, data);
+        ctx.getChannel().write(txMessage);
+        closeChannel(ctx);
+    }
+
+    /**
+     * Applies the appropriate actions after detecting BGP UPDATE
+     * Invalid ORIGIN Attribute Error: send NOTIFICATION and close the channel.
+     *
+     * @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 void actionsBgpUpdateInvalidOriginAttribute(
+                        ChannelHandlerContext ctx,
+                        int attrTypeCode,
+                        int attrLen,
+                        int attrFlags,
+                        ChannelBuffer message,
+                        short origin) {
+        log.debug("BGP RX UPDATE Error from {}: Invalid ORIGIN Attribute",
+                  remoteAddress);
+
+        //
+        // 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 =
+            prepareBgpNotification(errorCode, errorSubcode, data);
+        ctx.getChannel().write(txMessage);
+        closeChannel(ctx);
+    }
+
+    /**
+     * Applies the appropriate actions after detecting BGP UPDATE
+     * Attribute Flags Error: send NOTIFICATION and close the channel.
+     *
+     * @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 void actionsBgpUpdateAttributeFlagsError(
+                        ChannelHandlerContext ctx,
+                        int attrTypeCode,
+                        int attrLen,
+                        int attrFlags,
+                        ChannelBuffer message) {
+        log.debug("BGP RX UPDATE Error from {}: Attribute Flags Error",
+                  remoteAddress);
+
+        //
+        // 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 =
+            prepareBgpNotification(errorCode, errorSubcode, data);
+        ctx.getChannel().write(txMessage);
+        closeChannel(ctx);
+    }
+
+    /**
+     * Applies the appropriate actions after detecting BGP UPDATE
+     * Invalid NEXT_HOP Attribute Error: send NOTIFICATION and close the
+     * channel.
+     *
+     * @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 void actionsBgpUpdateInvalidNextHopAttribute(
+                        ChannelHandlerContext ctx,
+                        int attrTypeCode,
+                        int attrLen,
+                        int attrFlags,
+                        ChannelBuffer message,
+                        IpAddress nextHop) {
+        log.debug("BGP RX UPDATE Error from {}: Invalid NEXT_HOP Attribute {}",
+                  remoteAddress, nextHop);
+
+        //
+        // ERROR: Invalid ORIGIN 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 =
+            prepareBgpNotification(errorCode, errorSubcode, data);
+        ctx.getChannel().write(txMessage);
+        closeChannel(ctx);
+    }
+
+    /**
+     * Applies the appropriate actions after detecting BGP UPDATE
+     * Unrecognized Well-known Attribute Error: send NOTIFICATION and close
+     * the channel.
+     *
+     * @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 void actionsBgpUpdateUnrecognizedWellKnownAttribute(
+                        ChannelHandlerContext ctx,
+                        int attrTypeCode,
+                        int attrLen,
+                        int attrFlags,
+                        ChannelBuffer message) {
+        log.debug("BGP RX UPDATE Error from {}: " +
+                  "Unrecognized Well-known Attribute Error: {}",
+                  remoteAddress, 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 =
+            prepareBgpNotification(errorCode, errorSubcode, data);
+        ctx.getChannel().write(txMessage);
+        closeChannel(ctx);
+    }
+
+    /**
+     * Applies the appropriate actions after detecting BGP UPDATE
+     * Attribute Length Error: send NOTIFICATION and close the channel.
+     *
+     * @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 void actionsBgpUpdateAttributeLengthError(
+                        ChannelHandlerContext ctx,
+                        int attrTypeCode,
+                        int attrLen,
+                        int attrFlags,
+                        ChannelBuffer message) {
+        log.debug("BGP RX UPDATE Error from {}: Attribute Length Error",
+                  remoteAddress);
+
+        //
+        // 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 =
+            prepareBgpNotification(errorCode, errorSubcode, data);
+        ctx.getChannel().write(txMessage);
+        closeChannel(ctx);
+    }
+
+    /**
+     * Applies the appropriate actions after detecting BGP UPDATE
+     * Malformed AS_PATH Error: send NOTIFICATION and close the channel.
+     *
+     * @param ctx the Channel Handler Context
+     */
+    private void actionsBgpUpdateMalformedAsPath(
+                        ChannelHandlerContext ctx) {
+        log.debug("BGP RX UPDATE Error from {}: Malformed AS Path",
+                  remoteAddress);
+
+        //
+        // ERROR: Malformed AS_PATH
+        //
+        // Send NOTIFICATION and close the connection
+        int errorCode = UpdateMessageError.ERROR_CODE;
+        int errorSubcode = UpdateMessageError.MALFORMED_AS_PATH;
+        ChannelBuffer txMessage =
+            prepareBgpNotification(errorCode, errorSubcode, null);
+        ctx.getChannel().write(txMessage);
+        closeChannel(ctx);
+    }
+
+    /**
+     * Processes BGP NOTIFICATION message.
+     *
+     * @param ctx the Channel Handler Context
+     * @param message the message to process
+     */
+    void processBgpNotification(ChannelHandlerContext ctx,
+                                ChannelBuffer message) {
+        int minLength =
+            BgpConstants.BGP_NOTIFICATION_MIN_LENGTH - BgpConstants.BGP_HEADER_LENGTH;
+        if (message.readableBytes() < minLength) {
+            log.debug("BGP RX NOTIFICATION Error from {}: " +
+                      "Message length {} too short. Must be at least {}",
+                      remoteAddress, message.readableBytes(), minLength);
+            //
+            // ERROR: Bad Message Length
+            //
+            // NOTE: We do NOT send NOTIFICATION in response to a notification
+            return;
+        }
+
+        //
+        // Parse the NOTIFICATION message
+        //
+        int errorCode = message.readUnsignedByte();
+        int errorSubcode = message.readUnsignedByte();
+        int dataLength = message.readableBytes();
+
+        log.debug("BGP RX NOTIFICATION message from {}: Error Code {} " +
+                  "Error Subcode {} Data Length {}",
+                  remoteAddress, errorCode, errorSubcode, dataLength);
+
+        //
+        // NOTE: If the peer sent a NOTIFICATION, we leave it to the peer to
+        // close the connection.
+        //
+
+        // Start the Session Timeout timer
+        restartSessionTimeoutTimer(ctx);
+    }
+
+    /**
+     * Processes BGP KEEPALIVE message.
+     *
+     * @param ctx the Channel Handler Context
+     * @param message the message to process
+     */
+    void processBgpKeepalive(ChannelHandlerContext ctx,
+                             ChannelBuffer message) {
+        if (message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH !=
+            BgpConstants.BGP_KEEPALIVE_EXPECTED_LENGTH) {
+            log.debug("BGP RX KEEPALIVE Error from {}: " +
+                      "Invalid total message length {}. Expected {}",
+                      remoteAddress,
+                      message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH,
+                      BgpConstants.BGP_KEEPALIVE_EXPECTED_LENGTH);
+            //
+            // ERROR: Bad Message Length
+            //
+            // Send NOTIFICATION and close the connection
+            ChannelBuffer txMessage = prepareBgpNotificationBadMessageLength(
+                message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH);
+            ctx.getChannel().write(txMessage);
+            closeChannel(ctx);
+            return;
+        }
+
+        //
+        // Parse the KEEPALIVE message: nothing to do
+        //
+        log.debug("BGP RX KEEPALIVE message from {}", remoteAddress);
+
+        // Start the Session Timeout timer
+        restartSessionTimeoutTimer(ctx);
+    }
+
+    /**
+     * Prepares BGP OPEN message.
+     *
+     * @return the message to transmit (BGP header included)
+     */
+    private ChannelBuffer prepareBgpOpen() {
+        ChannelBuffer message =
+            ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
+
+        //
+        // Prepare the OPEN message payload
+        //
+        message.writeByte(localBgpVersion);
+        message.writeShort((int) localAs);
+        message.writeShort((int) localHoldtime);
+        message.writeInt(bgpSessionManager.getMyBgpId().toRealInt());
+        message.writeByte(0);               // No Optional Parameters
+        return prepareBgpMessage(BgpConstants.BGP_TYPE_OPEN, message);
+    }
+
+    /**
+     * Prepares BGP KEEPALIVE message.
+     *
+     * @return the message to transmit (BGP header included)
+     */
+    private ChannelBuffer prepareBgpKeepalive() {
+        ChannelBuffer message =
+            ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
+
+        //
+        // Prepare the KEEPALIVE message payload: nothing to do
+        //
+        return prepareBgpMessage(BgpConstants.BGP_TYPE_KEEPALIVE, message);
+    }
+
+    /**
+     * Prepares BGP NOTIFICATION message.
+     *
+     * @param errorCode the BGP NOTIFICATION Error Code
+     * @param errorSubcode the BGP NOTIFICATION Error Subcode if applicable,
+     * otherwise BgpConstants.Notifications.ERROR_SUBCODE_UNSPECIFIC
+     * @param payload the BGP NOTIFICATION Data if applicable, otherwise null
+     * @return the message to transmit (BGP header included)
+     */
+    ChannelBuffer prepareBgpNotification(int errorCode, int errorSubcode,
+                                         ChannelBuffer data) {
+        ChannelBuffer message =
+            ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
+
+        //
+        // Prepare the NOTIFICATION message payload
+        //
+        message.writeByte(errorCode);
+        message.writeByte(errorSubcode);
+        if (data != null) {
+            message.writeBytes(data);
+        }
+        return prepareBgpMessage(BgpConstants.BGP_TYPE_NOTIFICATION, message);
+    }
+
+    /**
+     * Prepares BGP NOTIFICATION message: Bad Message Length.
+     *
+     * @param length the erroneous Length field
+     * @return the message to transmit (BGP header included)
+     */
+    ChannelBuffer prepareBgpNotificationBadMessageLength(int length) {
+        int errorCode = MessageHeaderError.ERROR_CODE;
+        int errorSubcode = MessageHeaderError.BAD_MESSAGE_LENGTH;
+        ChannelBuffer data = ChannelBuffers.buffer(2);
+        data.writeShort(length);
+
+        return prepareBgpNotification(errorCode, errorSubcode, data);
+    }
+
+    /**
+     * 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 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;
+    }
+
+    /**
+     * Prepares BGP message.
+     *
+     * @param type the BGP message type
+     * @param payload the message payload to transmit (BGP header excluded)
+     * @return the message to transmit (BGP header included)
+     */
+    private ChannelBuffer prepareBgpMessage(int type, ChannelBuffer payload) {
+        ChannelBuffer message =
+            ChannelBuffers.buffer(BgpConstants.BGP_HEADER_LENGTH +
+                                  payload.readableBytes());
+
+        // Write the marker
+        for (int i = 0; i < BgpConstants.BGP_HEADER_MARKER_LENGTH; i++) {
+            message.writeByte(0xff);
+        }
+
+        // Write the rest of the BGP header
+        message.writeShort(BgpConstants.BGP_HEADER_LENGTH +
+                           payload.readableBytes());
+        message.writeByte(type);
+
+        // Write the payload
+        message.writeBytes(payload);
+        return message;
+    }
+
+    /**
+     * Restarts the BGP KeepaliveTimer.
+     */
+    private void restartKeepaliveTimer(ChannelHandlerContext ctx) {
+        if (localKeepaliveInterval == 0) {
+            return;                 // Nothing to do
+        }
+        keepaliveTimeout = timer.newTimeout(new TransmitKeepaliveTask(ctx),
+                                            localKeepaliveInterval,
+                                            TimeUnit.SECONDS);
+    }
+
+    /**
+     * Task class for transmitting KEEPALIVE messages.
+     */
+    private final class TransmitKeepaliveTask implements TimerTask {
+        private final ChannelHandlerContext ctx;
+
+        /**
+         * Constructor for given Channel Handler Context.
+         *
+         * @param ctx the Channel Handler Context to use
+         */
+        TransmitKeepaliveTask(ChannelHandlerContext ctx) {
+            this.ctx = ctx;
+        }
+
+        @Override
+        public void run(Timeout timeout) throws Exception {
+            if (timeout.isCancelled()) {
+                return;
+            }
+            if (!ctx.getChannel().isOpen()) {
+                return;
+            }
+
+            // Transmit the KEEPALIVE
+            ChannelBuffer txMessage = prepareBgpKeepalive();
+            ctx.getChannel().write(txMessage);
+
+            // Restart the KEEPALIVE timer
+            restartKeepaliveTimer(ctx);
+        }
+    }
+
+    /**
+     * Restarts the BGP Session Timeout Timer.
+     */
+    private void restartSessionTimeoutTimer(ChannelHandlerContext ctx) {
+        if (remoteHoldtime == 0) {
+            return;                 // Nothing to do
+        }
+        if (sessionTimeout != null) {
+            sessionTimeout.cancel();
+        }
+        sessionTimeout = timer.newTimeout(new SessionTimeoutTask(ctx),
+                                          remoteHoldtime,
+                                          TimeUnit.SECONDS);
+    }
+
+    /**
+     * Task class for BGP Session timeout.
+     */
+    private final class SessionTimeoutTask implements TimerTask {
+        private final ChannelHandlerContext ctx;
+
+        /**
+         * Constructor for given Channel Handler Context.
+         *
+         * @param ctx the Channel Handler Context to use
+         */
+        SessionTimeoutTask(ChannelHandlerContext ctx) {
+            this.ctx = ctx;
+        }
+
+        @Override
+        public void run(Timeout timeout) throws Exception {
+            if (timeout.isCancelled()) {
+                return;
+            }
+            if (!ctx.getChannel().isOpen()) {
+                return;
+            }
+
+            log.debug("BGP Session Timeout: peer {}", remoteAddress);
+            //
+            // ERROR: Invalid Optional Parameter Length field: Unspecific
+            //
+            // Send NOTIFICATION and close the connection
+            int errorCode = HoldTimerExpired.ERROR_CODE;
+            int errorSubcode = Notifications.ERROR_SUBCODE_UNSPECIFIC;
+            ChannelBuffer txMessage =
+                prepareBgpNotification(errorCode, errorSubcode, null);
+            ctx.getChannel().write(txMessage);
+            closeChannel(ctx);
+        }
+    }
+
+    /**
+     * An exception indicating a parsing error of the BGP message.
+     */
+    private static class BgpParseException extends Exception {
+        /**
+         * Default constructor.
+         */
+        public BgpParseException() {
+            super();
+        }
+
+        /**
+         * Constructor for a specific exception details message.
+         *
+         * @param message the message with the exception details
+         */
+         public BgpParseException(String message) {
+             super(message);
+         }
+    }
+}
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSessionManager.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSessionManager.java
new file mode 100644
index 0000000..097c002
--- /dev/null
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSessionManager.java
@@ -0,0 +1,355 @@
+package org.onlab.onos.sdnip.bgp;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.Collection;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executors;
+
+import org.jboss.netty.bootstrap.ServerBootstrap;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelException;
+import org.jboss.netty.channel.ChannelFactory;
+import org.jboss.netty.channel.ChannelPipeline;
+import org.jboss.netty.channel.ChannelPipelineFactory;
+import org.jboss.netty.channel.Channels;
+import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
+import org.onlab.onos.sdnip.RouteListener;
+import org.onlab.onos.sdnip.RouteUpdate;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * BGP Session Manager class.
+ */
+public class BgpSessionManager {
+    private static final Logger log =
+        LoggerFactory.getLogger(BgpSessionManager.class);
+    private Channel serverChannel;     // Listener for incoming BGP connections
+    private ConcurrentMap<SocketAddress, BgpSession> bgpSessions =
+        new ConcurrentHashMap<>();
+    private IpAddress myBgpId;         // Same BGP ID for all peers
+
+    private BgpRouteSelector bgpRouteSelector = new BgpRouteSelector();
+    private ConcurrentMap<IpPrefix, BgpRouteEntry> bgpRoutes =
+        new ConcurrentHashMap<>();
+
+    private final RouteListener routeListener;
+
+    /**
+     * Constructor for given route listener.
+     *
+     * @param routeListener the route listener to use
+     */
+    public BgpSessionManager(RouteListener routeListener) {
+        this.routeListener = checkNotNull(routeListener);
+    }
+
+    /**
+     * Gets the BGP sessions.
+     *
+     * @return the BGP sessions
+     */
+    public Collection<BgpSession> getBgpSessions() {
+        return bgpSessions.values();
+    }
+
+    /**
+     * Gets the BGP routes.
+     *
+     * @return the BGP routes
+     */
+    public Collection<BgpRouteEntry> getBgpRoutes() {
+        return bgpRoutes.values();
+    }
+
+    /**
+     * Processes the connection from a BGP peer.
+     *
+     * @param bgpSession the BGP session for the peer
+     * @return true if the connection can be established, otherwise false
+     */
+    boolean peerConnected(BgpSession bgpSession) {
+
+        // Test whether there is already a session from the same remote
+        if (bgpSessions.get(bgpSession.getRemoteAddress()) != null) {
+            return false;               // Duplicate BGP session
+        }
+        bgpSessions.put(bgpSession.getRemoteAddress(), bgpSession);
+
+        //
+        // If the first connection, set my BGP ID to the local address
+        // of the socket.
+        //
+        if (bgpSession.getLocalAddress() instanceof InetSocketAddress) {
+            InetAddress inetAddr =
+                ((InetSocketAddress) bgpSession.getLocalAddress()).getAddress();
+            IpAddress ip4Address = IpAddress.valueOf(inetAddr.getAddress());
+            updateMyBgpId(ip4Address);
+        }
+        return true;
+    }
+
+    /**
+     * Processes the disconnection from a BGP peer.
+     *
+     * @param bgpSession the BGP session for the peer
+     */
+    void peerDisconnected(BgpSession bgpSession) {
+        bgpSessions.remove(bgpSession.getRemoteAddress());
+    }
+
+    /**
+     * Conditionally updates the local BGP ID if it wasn't set already.
+     * <p/>
+     * NOTE: A BGP instance should use same BGP ID across all BGP sessions.
+     *
+     * @param ip4Address the IPv4 address to use as BGP ID
+     */
+    private synchronized void updateMyBgpId(IpAddress ip4Address) {
+        if (myBgpId == null) {
+            myBgpId = ip4Address;
+            log.debug("BGP: My BGP ID is {}", myBgpId);
+        }
+    }
+
+    /**
+     * Gets the local BGP Identifier as an IPv4 address.
+     *
+     * @return the local BGP Identifier as an IPv4 address
+     */
+    IpAddress getMyBgpId() {
+        return myBgpId;
+    }
+
+    /**
+     * Gets the BGP Route Selector.
+     *
+     * @return the BGP Route Selector
+     */
+    BgpRouteSelector getBgpRouteSelector() {
+        return bgpRouteSelector;
+    }
+
+    /**
+     * Starts up BGP Session Manager operation.
+     *
+     * @param listenPortNumber the port number to listen on. By default
+     * it should be BgpConstants.BGP_PORT (179)
+     */
+    public void startUp(int listenPortNumber) {
+        log.debug("BGP Session Manager startUp()");
+
+        ChannelFactory channelFactory =
+            new NioServerSocketChannelFactory(Executors.newCachedThreadPool(),
+                                              Executors.newCachedThreadPool());
+        ChannelPipelineFactory pipelineFactory = new ChannelPipelineFactory() {
+                @Override
+                public ChannelPipeline getPipeline() throws Exception {
+                    // Allocate a new session per connection
+                    BgpSession bgpSessionHandler =
+                        new BgpSession(BgpSessionManager.this);
+                    BgpFrameDecoder bgpFrameDecoder =
+                        new BgpFrameDecoder(bgpSessionHandler);
+
+                    // Setup the processing pipeline
+                    ChannelPipeline pipeline = Channels.pipeline();
+                    pipeline.addLast("BgpFrameDecoder", bgpFrameDecoder);
+                    pipeline.addLast("BgpSession", bgpSessionHandler);
+                    return pipeline;
+                }
+            };
+        InetSocketAddress listenAddress =
+            new InetSocketAddress(listenPortNumber);
+
+        ServerBootstrap serverBootstrap = new ServerBootstrap(channelFactory);
+        // serverBootstrap.setOptions("reuseAddr", true);
+        serverBootstrap.setOption("child.keepAlive", true);
+        serverBootstrap.setOption("child.tcpNoDelay", true);
+        serverBootstrap.setPipelineFactory(pipelineFactory);
+        try {
+            serverChannel = serverBootstrap.bind(listenAddress);
+        } catch (ChannelException e) {
+            log.debug("Exception binding to BGP port {}: ",
+                      listenAddress.getPort(), e);
+        }
+    }
+
+    /**
+     * Shuts down the BGP Session Manager operation.
+     */
+    public void shutDown() {
+        // TODO: Complete the implementation: remove routes, etc.
+        if (serverChannel != null) {
+            serverChannel.close();
+        }
+    }
+
+    /**
+     * Class to receive and process the BGP routes from each BGP Session/Peer.
+     */
+    class BgpRouteSelector {
+        /**
+         * Processes route entry updates: added/updated and deleted route
+         * entries.
+         *
+         * @param bgpSession the BGP session the route entry updates were
+         * received on
+         * @param addedBgpRouteEntries the added/updated route entries to
+         * process
+         * @param deletedBgpRouteEntries the deleted route entries to process
+         */
+        synchronized void routeUpdates(BgpSession bgpSession,
+                        Collection<BgpRouteEntry> addedBgpRouteEntries,
+                        Collection<BgpRouteEntry> deletedBgpRouteEntries) {
+            //
+            // TODO: Merge the updates from different BGP Peers,
+            // by choosing the best route.
+            //
+
+            // Process the deleted route entries
+            for (BgpRouteEntry bgpRouteEntry : deletedBgpRouteEntries) {
+                processDeletedRoute(bgpSession, bgpRouteEntry);
+            }
+
+            // Process the added/updated route entries
+            for (BgpRouteEntry bgpRouteEntry : addedBgpRouteEntries) {
+                processAddedRoute(bgpSession, bgpRouteEntry);
+            }
+        }
+
+        /**
+         * Processes an added/updated route entry.
+         *
+         * @param bgpSession the BGP session the route entry update was
+         * received on
+         * @param bgpRouteEntry the added/updated route entry
+         */
+        private void processAddedRoute(BgpSession bgpSession,
+                                       BgpRouteEntry bgpRouteEntry) {
+            RouteUpdate routeUpdate;
+            BgpRouteEntry bestBgpRouteEntry =
+                bgpRoutes.get(bgpRouteEntry.prefix());
+
+            //
+            // Install the new route entry if it is better than the
+            // current best route.
+            //
+            if ((bestBgpRouteEntry == null) ||
+                bgpRouteEntry.isBetterThan(bestBgpRouteEntry)) {
+                bgpRoutes.put(bgpRouteEntry.prefix(), bgpRouteEntry);
+                routeUpdate =
+                    new RouteUpdate(RouteUpdate.Type.UPDATE, bgpRouteEntry);
+                // Forward the result route updates to the Route Listener
+                routeListener.update(routeUpdate);
+                return;
+            }
+
+            //
+            // If the route entry arrived on the same BGP Session as
+            // the current best route, then elect the next best route
+            // and install it.
+            //
+            if (bestBgpRouteEntry.getBgpSession() !=
+                bgpRouteEntry.getBgpSession()) {
+                return;
+            }
+
+            // Find the next best route
+            bestBgpRouteEntry = findBestBgpRoute(bgpRouteEntry.prefix());
+            if (bestBgpRouteEntry == null) {
+                //
+                // TODO: Shouldn't happen. Install the new route as a
+                // pre-caution.
+                //
+                log.debug("BGP next best route for prefix {} is missing. " +
+                          "Adding the route that is currently processed.",
+                          bgpRouteEntry.prefix());
+                bestBgpRouteEntry = bgpRouteEntry;
+            }
+            // Install the next best route
+            bgpRoutes.put(bestBgpRouteEntry.prefix(), bestBgpRouteEntry);
+            routeUpdate = new RouteUpdate(RouteUpdate.Type.UPDATE,
+                                          bestBgpRouteEntry);
+            // Forward the result route updates to the Route Listener
+            routeListener.update(routeUpdate);
+        }
+
+        /**
+         * Processes a deleted route entry.
+         *
+         * @param bgpSession the BGP session the route entry update was
+         * received on
+         * @param bgpRouteEntry the deleted route entry
+         */
+        private void processDeletedRoute(BgpSession bgpSession,
+                                         BgpRouteEntry bgpRouteEntry) {
+            RouteUpdate routeUpdate;
+            BgpRouteEntry bestBgpRouteEntry =
+                bgpRoutes.get(bgpRouteEntry.prefix());
+
+            //
+            // Remove the route entry only if it was the best one.
+            // Install the the next best route if it exists.
+            //
+            // NOTE: We intentionally use "==" instead of method equals(),
+            // because we need to check whether this is same object.
+            //
+            if (bgpRouteEntry != bestBgpRouteEntry) {
+                return;         // Nothing to do
+            }
+
+            //
+            // Find the next best route
+            //
+            bestBgpRouteEntry = findBestBgpRoute(bgpRouteEntry.prefix());
+            if (bestBgpRouteEntry != null) {
+                // Install the next best route
+                bgpRoutes.put(bestBgpRouteEntry.prefix(),
+                              bestBgpRouteEntry);
+                routeUpdate = new RouteUpdate(RouteUpdate.Type.UPDATE,
+                                              bestBgpRouteEntry);
+                // Forward the result route updates to the Route Listener
+                routeListener.update(routeUpdate);
+                return;
+            }
+
+            //
+            // No route found. Remove the route entry
+            //
+            bgpRoutes.remove(bgpRouteEntry.prefix());
+            routeUpdate = new RouteUpdate(RouteUpdate.Type.DELETE,
+                                          bgpRouteEntry);
+            // Forward the result route updates to the Route Listener
+            routeListener.update(routeUpdate);
+        }
+
+        /**
+         * Finds the best route entry among all BGP Sessions.
+         *
+         * @param prefix the prefix of the route
+         * @return the best route if found, otherwise null
+         */
+        private BgpRouteEntry findBestBgpRoute(IpPrefix prefix) {
+            BgpRouteEntry bestRoute = null;
+
+            // Iterate across all BGP Sessions and select the best route
+            for (BgpSession bgpSession : bgpSessions.values()) {
+                BgpRouteEntry route = bgpSession.findBgpRouteEntry(prefix);
+                if (route == null) {
+                    continue;
+                }
+                if ((bestRoute == null) || route.isBetterThan(bestRoute)) {
+                    bestRoute = route;
+                }
+            }
+            return bestRoute;
+        }
+    }
+}
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/package-info.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/package-info.java
new file mode 100644
index 0000000..e39d7e0
--- /dev/null
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Implementation of the BGP protocol.
+ */
+package org.onlab.onos.sdnip.bgp;
\ No newline at end of file