aboutsummaryrefslogtreecommitdiffstats
path: root/framework/src/onos/apps/routing
diff options
context:
space:
mode:
Diffstat (limited to 'framework/src/onos/apps/routing')
-rw-r--r--framework/src/onos/apps/routing/pom.xml111
-rw-r--r--framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpConstants.java556
-rw-r--r--framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpFrameDecoder.java177
-rw-r--r--framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpInfoService.java45
-rw-r--r--framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpKeepalive.java94
-rw-r--r--framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpMessage.java86
-rw-r--r--framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpNotification.java126
-rw-r--r--framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpOpen.java497
-rw-r--r--framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpRouteEntry.java494
-rw-r--r--framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpRouteSelector.java202
-rw-r--r--framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpSession.java473
-rw-r--r--framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpSessionInfo.java272
-rw-r--r--framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpSessionManager.java342
-rw-r--r--framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpUpdate.java1707
-rw-r--r--framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/package-info.java20
-rw-r--r--framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/cli/AddRouteCommand.java57
-rw-r--r--framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/cli/BgpNeighborsListCommand.java183
-rw-r--r--framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/cli/BgpRoutesListCommand.java283
-rw-r--r--framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/cli/BgpSpeakersListCommand.java74
-rw-r--r--framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/cli/RemoveRouteCommand.java56
-rw-r--r--framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/cli/RoutesListCommand.java157
-rw-r--r--framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/cli/package-info.java20
-rw-r--r--framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/config/impl/Configuration.java158
-rw-r--r--framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/config/impl/HostToInterfaceAdaptor.java94
-rw-r--r--framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/config/impl/RoutingConfigurationImpl.java261
-rw-r--r--framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/config/impl/package-info.java20
-rw-r--r--framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/impl/Router.java733
-rw-r--r--framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/impl/StaticRouter.java97
-rw-r--r--framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/impl/package-info.java20
-rw-r--r--framework/src/onos/apps/routing/src/main/resources/OSGI-INF/blueprint/shell-config.xml38
-rw-r--r--framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/bgp/AsPathTest.java233
-rw-r--r--framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/bgp/BgpRouteEntryTest.java520
-rw-r--r--framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/bgp/BgpSessionManagerTest.java895
-rw-r--r--framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/bgp/PathSegmentTest.java131
-rw-r--r--framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/bgp/TestBgpPeerChannelHandler.java207
-rw-r--r--framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/bgp/TestBgpPeerFrameDecoder.java175
-rw-r--r--framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/config/impl/HostToInterfaceAdaptorTest.java210
-rw-r--r--framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/impl/RouterAsyncArpTest.java232
-rw-r--r--framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/impl/RouterTest.java287
39 files changed, 10343 insertions, 0 deletions
diff --git a/framework/src/onos/apps/routing/pom.xml b/framework/src/onos/apps/routing/pom.xml
new file mode 100644
index 00000000..1d9f19b7
--- /dev/null
+++ b/framework/src/onos/apps/routing/pom.xml
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2015 Open Networking Laboratory
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>onos-apps</artifactId>
+ <groupId>org.onosproject</groupId>
+ <version>1.3.0-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>onos-app-routing</artifactId>
+
+ <packaging>bundle</packaging>
+ <description>Libraries for routing applications</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-app-routing-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-cli</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onlab-thirdparty</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onlab-misc</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-annotations</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>io.netty</groupId>
+ <artifactId>netty</artifactId>
+ <version>3.9.0.Final</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.karaf.shell</groupId>
+ <artifactId>org.apache.karaf.shell.console</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onlab-junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.easymock</groupId>
+ <artifactId>easymock</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ </dependencies>
+
+</project>
diff --git a/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpConstants.java b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpConstants.java
new file mode 100644
index 00000000..89060ad5
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpConstants.java
@@ -0,0 +1,556 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.routing.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 OPEN related constants.
+ */
+ public static final class Open {
+ /**
+ * Default constructor.
+ * <p>
+ * The constructor is private to prevent creating an instance of
+ * this utility class.
+ */
+ private Open() {
+ }
+
+ /**
+ * BGP OPEN: Optional Parameters related constants.
+ */
+ public static final class OptionalParameters {
+ }
+
+ /**
+ * BGP OPEN: Capabilities related constants (RFC 5492).
+ */
+ public static final class Capabilities {
+ /** BGP OPEN Optional Parameter Type: Capabilities. */
+ public static final int TYPE = 2;
+
+ /** BGP OPEN Optional Parameter minimum length. */
+ public static final int MIN_LENGTH = 2;
+
+ /**
+ * BGP OPEN: Multiprotocol Extensions Capabilities (RFC 4760).
+ */
+ public static final class MultiprotocolExtensions {
+ /** BGP OPEN Multiprotocol Extensions code. */
+ public static final int CODE = 1;
+
+ /** BGP OPEN Multiprotocol Extensions length. */
+ public static final int LENGTH = 4;
+
+ /** BGP OPEN Multiprotocol Extensions AFI: IPv4. */
+ public static final int AFI_IPV4 = 1;
+
+ /** BGP OPEN Multiprotocol Extensions AFI: IPv6. */
+ public static final int AFI_IPV6 = 2;
+
+ /** BGP OPEN Multiprotocol Extensions SAFI: unicast. */
+ public static final int SAFI_UNICAST = 1;
+
+ /** BGP OPEN Multiprotocol Extensions SAFI: multicast. */
+ public static final int SAFI_MULTICAST = 2;
+ }
+
+ /**
+ * BGP OPEN: Support for 4-octet AS Number Capability (RFC 6793).
+ */
+ public static final class As4Octet {
+ /** BGP OPEN Support for 4-octet AS Number Capability code. */
+ public static final int CODE = 65;
+
+ /** BGP OPEN 4-octet AS Number Capability length. */
+ public static final int LENGTH = 4;
+ }
+ }
+ }
+
+ /**
+ * 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 AS length. */
+ public static final int AS_LENGTH = 2;
+
+ /** BGP 4 Octet AS length (RFC 6793). */
+ public static final int AS_4OCTET_LENGTH = 4;
+
+ /**
+ * 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;
+
+ /**
+ * Gets the BGP UPDATE origin type as a string.
+ *
+ * @param type the BGP UPDATE origin type
+ * @return the BGP UPDATE origin type as a string
+ */
+ public static String typeToString(int type) {
+ String typeString = "UNKNOWN";
+
+ switch (type) {
+ case IGP:
+ typeString = "IGP";
+ break;
+ case EGP:
+ typeString = "EGP";
+ break;
+ case INCOMPLETE:
+ typeString = "INCOMPLETE";
+ break;
+ default:
+ break;
+ }
+ return typeString;
+ }
+ }
+
+ /**
+ * 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 AS_PATH Type: AS_CONFED_SEQUENCE. */
+ public static final int AS_CONFED_SEQUENCE = 3;
+
+ /** BGP UPDATE AS_PATH Type: AS_CONFED_SET. */
+ public static final int AS_CONFED_SET = 4;
+
+ /**
+ * Gets the BGP AS_PATH type as a string.
+ *
+ * @param type the BGP AS_PATH type
+ * @return the BGP AS_PATH type as a string
+ */
+ public static String typeToString(int type) {
+ String typeString = "UNKNOWN";
+
+ switch (type) {
+ case AS_SET:
+ typeString = "AS_SET";
+ break;
+ case AS_SEQUENCE:
+ typeString = "AS_SEQUENCE";
+ break;
+ case AS_CONFED_SEQUENCE:
+ typeString = "AS_CONFED_SEQUENCE";
+ break;
+ case AS_CONFED_SET:
+ typeString = "AS_CONFED_SET";
+ break;
+ default:
+ break;
+ }
+ return typeString;
+ }
+ }
+
+ /**
+ * 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: 2 octet AS. */
+ public static final int AS2_LENGTH = 6;
+
+ /** BGP UPDATE Attributes Type Code AGGREGATOR length: 4 octet AS. */
+ public static final int AS4_LENGTH = 8;
+ }
+
+ /**
+ * BGP UPDATE: MP_REACH_NLRI related constants.
+ */
+ public static final class MpReachNlri {
+ /**
+ * Default constructor.
+ * <p>
+ * The constructor is private to prevent creating an instance of
+ * this utility class.
+ */
+ private MpReachNlri() {
+ }
+
+ /** BGP UPDATE Attributes Type Code MP_REACH_NLRI. */
+ public static final int TYPE = 14;
+
+ /** BGP UPDATE Attributes Type Code MP_REACH_NLRI min length. */
+ public static final int MIN_LENGTH = 5;
+ }
+
+ /**
+ * BGP UPDATE: MP_UNREACH_NLRI related constants.
+ */
+ public static final class MpUnreachNlri {
+ /**
+ * Default constructor.
+ * <p>
+ * The constructor is private to prevent creating an instance of
+ * this utility class.
+ */
+ private MpUnreachNlri() {
+ }
+
+ /** BGP UPDATE Attributes Type Code MP_UNREACH_NLRI. */
+ public static final int TYPE = 15;
+
+ /** BGP UPDATE Attributes Type Code MP_UNREACH_NLRI min length. */
+ public static final int MIN_LENGTH = 3;
+ }
+ }
+
+ /**
+ * 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/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpFrameDecoder.java b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpFrameDecoder.java
new file mode 100644
index 00000000..98ec4a45
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpFrameDecoder.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.routing.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.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 = BgpConstants.Notifications.MessageHeaderError.ERROR_CODE;
+ int errorSubcode =
+ BgpConstants.Notifications.MessageHeaderError.CONNECTION_NOT_SYNCHRONIZED;
+ ChannelBuffer txMessage =
+ BgpNotification.prepareBgpNotification(errorCode,
+ errorSubcode,
+ null);
+ ctx.getChannel().write(txMessage);
+ bgpSession.closeSession(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 =
+ BgpNotification.prepareBgpNotificationBadMessageLength(length);
+ ctx.getChannel().write(txMessage);
+ bgpSession.closeSession(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:
+ BgpOpen.processBgpOpen(bgpSession, ctx, message);
+ break;
+ case BgpConstants.BGP_TYPE_UPDATE:
+ BgpUpdate.processBgpUpdate(bgpSession, ctx, message);
+ break;
+ case BgpConstants.BGP_TYPE_NOTIFICATION:
+ BgpNotification.processBgpNotification(bgpSession, ctx, message);
+ break;
+ case BgpConstants.BGP_TYPE_KEEPALIVE:
+ BgpKeepalive.processBgpKeepalive(bgpSession, ctx, message);
+ break;
+ default:
+ //
+ // ERROR: Bad Message Type
+ //
+ // Send NOTIFICATION and close the connection
+ int errorCode = BgpConstants.Notifications.MessageHeaderError.ERROR_CODE;
+ int errorSubcode = BgpConstants.Notifications.MessageHeaderError.BAD_MESSAGE_TYPE;
+ ChannelBuffer data = ChannelBuffers.buffer(1);
+ data.writeByte(type);
+ ChannelBuffer txMessage =
+ BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
+ data);
+ ctx.getChannel().write(txMessage);
+ bgpSession.closeSession(ctx);
+ return null;
+ }
+ return null;
+ }
+}
diff --git a/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpInfoService.java b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpInfoService.java
new file mode 100644
index 00000000..ee8fef16
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpInfoService.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.routing.bgp;
+
+import java.util.Collection;
+
+/**
+ * Provides information about BGP peering and routes.
+ */
+public interface BgpInfoService {
+
+ /**
+ * Gets the BGP sessions.
+ *
+ * @return the BGP sessions
+ */
+ Collection<BgpSession> getBgpSessions();
+
+ /**
+ * Gets the selected IPv4 BGP routes among all BGP sessions.
+ *
+ * @return the selected IPv4 BGP routes among all BGP sessions
+ */
+ Collection<BgpRouteEntry> getBgpRoutes4();
+
+ /**
+ * Gets the selected IPv6 BGP routes among all BGP sessions.
+ *
+ * @return the selected IPv6 BGP routes among all BGP sessions
+ */
+ Collection<BgpRouteEntry> getBgpRoutes6();
+}
diff --git a/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpKeepalive.java b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpKeepalive.java
new file mode 100644
index 00000000..1d7f5b6d
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpKeepalive.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.routing.bgp;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A class for handling BGP KEEPALIVE messages.
+ */
+final class BgpKeepalive {
+ private static final Logger log =
+ LoggerFactory.getLogger(BgpKeepalive.class);
+
+ /**
+ * Default constructor.
+ * <p>
+ * The constructor is private to prevent creating an instance of
+ * this utility class.
+ */
+ private BgpKeepalive() {
+ }
+
+ /**
+ * Processes BGP KEEPALIVE message.
+ *
+ * @param bgpSession the BGP Session to use
+ * @param ctx the Channel Handler Context
+ * @param message the message to process
+ */
+ static void processBgpKeepalive(BgpSession bgpSession,
+ 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 {}",
+ bgpSession.remoteInfo().address(),
+ message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH,
+ BgpConstants.BGP_KEEPALIVE_EXPECTED_LENGTH);
+ //
+ // ERROR: Bad Message Length
+ //
+ // Send NOTIFICATION and close the connection
+ ChannelBuffer txMessage =
+ BgpNotification.prepareBgpNotificationBadMessageLength(
+ message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH);
+ ctx.getChannel().write(txMessage);
+ bgpSession.closeSession(ctx);
+ return;
+ }
+
+ //
+ // Parse the KEEPALIVE message: nothing to do
+ //
+ log.trace("BGP RX KEEPALIVE message from {}",
+ bgpSession.remoteInfo().address());
+
+ // Start the Session Timeout timer
+ bgpSession.restartSessionTimeoutTimer(ctx);
+ }
+
+ /**
+ * Prepares BGP KEEPALIVE message.
+ *
+ * @return the message to transmit (BGP header included)
+ */
+ static ChannelBuffer prepareBgpKeepalive() {
+ ChannelBuffer message =
+ ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
+
+ //
+ // Prepare the KEEPALIVE message payload: nothing to do
+ //
+ return BgpMessage.prepareBgpMessage(BgpConstants.BGP_TYPE_KEEPALIVE,
+ message);
+ }
+}
diff --git a/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpMessage.java b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpMessage.java
new file mode 100644
index 00000000..e93362e5
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpMessage.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.routing.bgp;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A class for preparing BGP messages.
+ */
+final class BgpMessage {
+ private static final Logger log =
+ LoggerFactory.getLogger(BgpMessage.class);
+
+ /**
+ * Default constructor.
+ * <p>
+ * The constructor is private to prevent creating an instance of
+ * this utility class.
+ */
+ private BgpMessage() {
+ }
+
+ /**
+ * 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)
+ */
+ static 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;
+ }
+
+ /**
+ * An exception indicating a parsing error of the BGP message.
+ */
+ static final class BgpParseException extends Exception {
+ /**
+ * Default constructor.
+ */
+ private BgpParseException() {
+ super();
+ }
+
+ /**
+ * Constructor for a specific exception details message.
+ *
+ * @param message the message with the exception details
+ */
+ BgpParseException(String message) {
+ super(message);
+ }
+ }
+}
diff --git a/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpNotification.java b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpNotification.java
new file mode 100644
index 00000000..233f771b
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpNotification.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.routing.bgp;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A class for handling BGP NOTIFICATION messages.
+ */
+final class BgpNotification {
+ private static final Logger log =
+ LoggerFactory.getLogger(BgpNotification.class);
+
+ /**
+ * Default constructor.
+ * <p>
+ * The constructor is private to prevent creating an instance of
+ * this utility class.
+ */
+ private BgpNotification() {
+ }
+
+ /**
+ * Processes BGP NOTIFICATION message.
+ *
+ * @param bgpSession the BGP Session to use
+ * @param ctx the Channel Handler Context
+ * @param message the message to process
+ */
+ static void processBgpNotification(BgpSession bgpSession,
+ 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 {}",
+ bgpSession.remoteInfo().address(),
+ 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 {}",
+ bgpSession.remoteInfo().address(), 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
+ bgpSession.restartSessionTimeoutTimer(ctx);
+ }
+
+ /**
+ * 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 data the BGP NOTIFICATION Data if applicable, otherwise null
+ * @return the message to transmit (BGP header included)
+ */
+ static 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 BgpMessage.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)
+ */
+ static ChannelBuffer prepareBgpNotificationBadMessageLength(int length) {
+ int errorCode = BgpConstants.Notifications.MessageHeaderError.ERROR_CODE;
+ int errorSubcode = BgpConstants.Notifications.MessageHeaderError.BAD_MESSAGE_LENGTH;
+ ChannelBuffer data = ChannelBuffers.buffer(2);
+ data.writeShort(length);
+
+ return prepareBgpNotification(errorCode, errorSubcode, data);
+ }
+}
diff --git a/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpOpen.java b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpOpen.java
new file mode 100644
index 00000000..5b5a259e
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpOpen.java
@@ -0,0 +1,497 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.routing.bgp;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.onlab.packet.Ip4Address;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A class for handling BGP OPEN messages.
+ */
+final class BgpOpen {
+ private static final Logger log = LoggerFactory.getLogger(BgpOpen.class);
+
+ /**
+ * Default constructor.
+ * <p>
+ * The constructor is private to prevent creating an instance of
+ * this utility class.
+ */
+ private BgpOpen() {
+ }
+
+ /**
+ * Processes BGP OPEN message.
+ *
+ * @param bgpSession the BGP Session to use
+ * @param ctx the Channel Handler Context
+ * @param message the message to process
+ */
+ static void processBgpOpen(BgpSession bgpSession,
+ 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 {}",
+ bgpSession.remoteInfo().address(),
+ message.readableBytes(), minLength);
+ //
+ // ERROR: Bad Message Length
+ //
+ // Send NOTIFICATION and close the connection
+ ChannelBuffer txMessage =
+ BgpNotification.prepareBgpNotificationBadMessageLength(
+ message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH);
+ ctx.getChannel().write(txMessage);
+ bgpSession.closeSession(ctx);
+ return;
+ }
+
+ //
+ // Parse the OPEN message
+ //
+ // Remote BGP version
+ int remoteBgpVersion = message.readUnsignedByte();
+ if (remoteBgpVersion != BgpConstants.BGP_VERSION) {
+ log.debug("BGP RX OPEN Error from {}: " +
+ "Unsupported BGP version {}. Should be {}",
+ bgpSession.remoteInfo().address(), remoteBgpVersion,
+ BgpConstants.BGP_VERSION);
+ //
+ // ERROR: Unsupported Version Number
+ //
+ // Send NOTIFICATION and close the connection
+ int errorCode = BgpConstants.Notifications.OpenMessageError.ERROR_CODE;
+ int errorSubcode = BgpConstants.Notifications.OpenMessageError.UNSUPPORTED_VERSION_NUMBER;
+ ChannelBuffer data = ChannelBuffers.buffer(2);
+ data.writeShort(BgpConstants.BGP_VERSION);
+ ChannelBuffer txMessage =
+ BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
+ data);
+ ctx.getChannel().write(txMessage);
+ bgpSession.closeSession(ctx);
+ return;
+ }
+ bgpSession.remoteInfo().setBgpVersion(remoteBgpVersion);
+
+ // Remote AS number
+ long remoteAs = message.readUnsignedShort();
+ bgpSession.remoteInfo().setAsNumber(remoteAs);
+ //
+ // NOTE: Currently, the local AS number is always set to the remote AS.
+ // This is done, because the peer setup is always iBGP.
+ // In the future, the local AS number should be configured as part
+ // of an explicit BGP peering configuration.
+ //
+ bgpSession.localInfo().setAsNumber(remoteAs);
+
+ // Remote Hold Time
+ long 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 {}",
+ bgpSession.remoteInfo().address(), remoteHoldtime,
+ BgpConstants.BGP_KEEPALIVE_MIN_HOLDTIME);
+ //
+ // ERROR: Unacceptable Hold Time
+ //
+ // Send NOTIFICATION and close the connection
+ int errorCode = BgpConstants.Notifications.OpenMessageError.ERROR_CODE;
+ int errorSubcode = BgpConstants.Notifications.OpenMessageError.UNACCEPTABLE_HOLD_TIME;
+ ChannelBuffer txMessage =
+ BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
+ null);
+ ctx.getChannel().write(txMessage);
+ bgpSession.closeSession(ctx);
+ return;
+ }
+ bgpSession.remoteInfo().setHoldtime(remoteHoldtime);
+ //
+ // NOTE: Currently. the local BGP Holdtime is always set to the remote
+ // BGP holdtime.
+ // In the future, the local BGP Holdtime should be configured as part
+ // of an explicit BGP peering configuration.
+ //
+ bgpSession.localInfo().setHoldtime(remoteHoldtime);
+
+ // Remote BGP Identifier
+ Ip4Address remoteBgpId =
+ Ip4Address.valueOf((int) message.readUnsignedInt());
+ bgpSession.remoteInfo().setBgpId(remoteBgpId);
+
+ // Parse the Optional Parameters
+ try {
+ parseOptionalParameters(bgpSession, ctx, message);
+ } catch (BgpMessage.BgpParseException e) {
+ // ERROR: Error parsing optional parameters
+ log.debug("BGP RX OPEN Error from {}: " +
+ "Exception parsing Optional Parameters: {}",
+ bgpSession.remoteInfo().address(), e);
+ //
+ // ERROR: Invalid Optional Parameters: Unspecific
+ //
+ // Send NOTIFICATION and close the connection
+ int errorCode = BgpConstants.Notifications.OpenMessageError.ERROR_CODE;
+ int errorSubcode = BgpConstants.Notifications.ERROR_SUBCODE_UNSPECIFIC;
+ ChannelBuffer txMessage =
+ BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
+ null);
+ ctx.getChannel().write(txMessage);
+ bgpSession.closeSession(ctx);
+ return;
+ }
+
+ //
+ // NOTE: Prepare the BGP OPEN message before the original local AS
+ // is overwritten by the 4-octet AS number
+ //
+ ChannelBuffer txOpenMessage = prepareBgpOpen(bgpSession.localInfo());
+
+ //
+ // Use the 4-octet AS number in lieu of the "My AS" field
+ // See RFC 6793, Section 4.1, second paragraph.
+ //
+ if (bgpSession.remoteInfo().as4OctetCapability()) {
+ long as4Number = bgpSession.remoteInfo().as4Number();
+ bgpSession.remoteInfo().setAsNumber(as4Number);
+ bgpSession.localInfo().setAsNumber(as4Number);
+ }
+
+ //
+ // 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 bs : bgpSession.getBgpSessionManager().getBgpSessions()) {
+ if ((bs.remoteInfo().asNumber() != 0) &&
+ (bgpSession.remoteInfo().asNumber() !=
+ bs.remoteInfo().asNumber())) {
+ log.debug("BGP RX OPEN Error from {}: Bad Peer AS {}. " +
+ "Expected {}",
+ bgpSession.remoteInfo().address(),
+ bgpSession.remoteInfo().asNumber(),
+ bs.remoteInfo().asNumber());
+ //
+ // ERROR: Bad Peer AS
+ //
+ // Send NOTIFICATION and close the connection
+ int errorCode = BgpConstants.Notifications.OpenMessageError.ERROR_CODE;
+ int errorSubcode = BgpConstants.Notifications.OpenMessageError.BAD_PEER_AS;
+ ChannelBuffer txMessage =
+ BgpNotification.prepareBgpNotification(errorCode,
+ errorSubcode, null);
+ ctx.getChannel().write(txMessage);
+ bgpSession.closeSession(ctx);
+ return;
+ }
+ }
+
+ log.debug("BGP RX OPEN message from {}: " +
+ "BGPv{} AS {} BGP-ID {} Holdtime {}",
+ bgpSession.remoteInfo().address(),
+ bgpSession.remoteInfo().bgpVersion(),
+ bgpSession.remoteInfo().asNumber(),
+ bgpSession.remoteInfo().bgpId(),
+ bgpSession.remoteInfo().holdtime());
+
+ // Send my OPEN followed by KEEPALIVE
+ ctx.getChannel().write(txOpenMessage);
+ //
+ ChannelBuffer txMessage = BgpKeepalive.prepareBgpKeepalive();
+ ctx.getChannel().write(txMessage);
+
+ // Start the KEEPALIVE timer
+ bgpSession.restartKeepaliveTimer(ctx);
+
+ // Start the Session Timeout timer
+ bgpSession.restartSessionTimeoutTimer(ctx);
+ }
+
+ /**
+ * Prepares BGP OPEN message.
+ *
+ * @param localInfo the BGP Session local information to use
+ * @return the message to transmit (BGP header included)
+ */
+ static ChannelBuffer prepareBgpOpen(BgpSessionInfo localInfo) {
+ ChannelBuffer message =
+ ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
+
+ //
+ // Prepare the OPEN message payload
+ //
+ message.writeByte(localInfo.bgpVersion());
+ message.writeShort((int) localInfo.asNumber());
+ message.writeShort((int) localInfo.holdtime());
+ message.writeInt(localInfo.bgpId().toInt());
+
+ // Prepare the optional BGP Capabilities
+ ChannelBuffer capabilitiesMessage =
+ prepareBgpOpenCapabilities(localInfo);
+ message.writeByte(capabilitiesMessage.readableBytes());
+ message.writeBytes(capabilitiesMessage);
+
+ return BgpMessage.prepareBgpMessage(BgpConstants.BGP_TYPE_OPEN,
+ message);
+ }
+
+ /**
+ * Parses BGP OPEN Optional Parameters.
+ *
+ * @param bgpSession the BGP Session to use
+ * @param ctx the Channel Handler Context
+ * @param message the message to process
+ * @throws BgpMessage.BgpParseException
+ */
+ private static void parseOptionalParameters(BgpSession bgpSession,
+ ChannelHandlerContext ctx,
+ ChannelBuffer message)
+ throws BgpMessage.BgpParseException {
+
+ //
+ // Get and verify the Optional Parameters Length
+ //
+ int optParamLength = message.readUnsignedByte();
+ if (optParamLength > message.readableBytes()) {
+ // ERROR: Invalid Optional Parameter Length
+ String errorMsg = "Invalid Optional Parameter Length field " +
+ optParamLength + ". Remaining Optional Parameters " +
+ message.readableBytes();
+ throw new BgpMessage.BgpParseException(errorMsg);
+ }
+ if (optParamLength == 0) {
+ return; // No Optional Parameters
+ }
+
+ //
+ // Parse the Optional Parameters
+ //
+ int optParamEnd = message.readerIndex() + optParamLength;
+ while (message.readerIndex() < optParamEnd) {
+ int paramType = message.readUnsignedByte();
+ if (message.readerIndex() >= optParamEnd) {
+ // ERROR: Malformed Optional Parameters
+ String errorMsg = "Malformed Optional Parameters";
+ throw new BgpMessage.BgpParseException(errorMsg);
+ }
+ int paramLen = message.readUnsignedByte();
+ if (message.readerIndex() + paramLen > optParamEnd) {
+ // ERROR: Malformed Optional Parameters
+ String errorMsg = "Malformed Optional Parameters";
+ throw new BgpMessage.BgpParseException(errorMsg);
+ }
+
+ //
+ // Extract the Optional Parameter Value based on the Parameter Type
+ //
+ switch (paramType) {
+ case BgpConstants.Open.Capabilities.TYPE:
+ // Optional Parameter Type: Capabilities
+ if (paramLen < BgpConstants.Open.Capabilities.MIN_LENGTH) {
+ // ERROR: Malformed Capability
+ String errorMsg = "Malformed Capability Type " + paramType;
+ throw new BgpMessage.BgpParseException(errorMsg);
+ }
+ int capabEnd = message.readerIndex() + paramLen;
+ int capabCode = message.readUnsignedByte();
+ int capabLen = message.readUnsignedByte();
+ if (message.readerIndex() + capabLen > capabEnd) {
+ // ERROR: Malformed Capability
+ String errorMsg = "Malformed Capability Type " + paramType;
+ throw new BgpMessage.BgpParseException(errorMsg);
+ }
+
+ switch (capabCode) {
+ case BgpConstants.Open.Capabilities.MultiprotocolExtensions.CODE:
+ // Multiprotocol Extensions Capabilities (RFC 4760)
+ if (capabLen != BgpConstants.Open.Capabilities.MultiprotocolExtensions.LENGTH) {
+ // ERROR: Multiprotocol Extension Length Error
+ String errorMsg = "Multiprotocol Extension Length Error";
+ throw new BgpMessage.BgpParseException(errorMsg);
+ }
+ // Decode the AFI (2 octets) and SAFI (1 octet)
+ int afi = message.readUnsignedShort();
+ int reserved = message.readUnsignedByte();
+ int safi = message.readUnsignedByte();
+ log.debug("BGP RX OPEN Capability: AFI = {} SAFI = {}",
+ afi, safi);
+ //
+ // Setup the AFI/SAFI in the BgpSession
+ //
+ // NOTE: For now we just copy the remote AFI/SAFI setting
+ // to the local configuration.
+ //
+ if (afi == BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV4 &&
+ safi == BgpConstants.Open.Capabilities.MultiprotocolExtensions.SAFI_UNICAST) {
+ bgpSession.remoteInfo().setIpv4Unicast();
+ bgpSession.localInfo().setIpv4Unicast();
+ } else if (afi == BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV4 &&
+ safi == BgpConstants.Open.Capabilities.MultiprotocolExtensions.SAFI_MULTICAST) {
+ bgpSession.remoteInfo().setIpv4Multicast();
+ bgpSession.localInfo().setIpv4Multicast();
+ } else if (afi == BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV6 &&
+ safi == BgpConstants.Open.Capabilities.MultiprotocolExtensions.SAFI_UNICAST) {
+ bgpSession.remoteInfo().setIpv6Unicast();
+ bgpSession.localInfo().setIpv6Unicast();
+ } else if (afi == BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV6 &&
+ safi == BgpConstants.Open.Capabilities.MultiprotocolExtensions.SAFI_MULTICAST) {
+ bgpSession.remoteInfo().setIpv6Multicast();
+ bgpSession.localInfo().setIpv6Multicast();
+ } else {
+ log.debug("BGP RX OPEN Capability: Unknown AFI = {} SAFI = {}",
+ afi, safi);
+ }
+ break;
+
+ case BgpConstants.Open.Capabilities.As4Octet.CODE:
+ // Support for 4-octet AS Number Capabilities (RFC 6793)
+ if (capabLen != BgpConstants.Open.Capabilities.As4Octet.LENGTH) {
+ // ERROR: 4-octet AS Number Capability Length Error
+ String errorMsg = "4-octet AS Number Capability Length Error";
+ throw new BgpMessage.BgpParseException(errorMsg);
+ }
+ long as4Number = message.readUnsignedInt();
+
+ bgpSession.remoteInfo().setAs4OctetCapability();
+ bgpSession.remoteInfo().setAs4Number(as4Number);
+
+ //
+ // Copy remote 4-octet AS Number Capabilities and AS
+ // Number. This is a temporary setting until local AS
+ // number configuration is supported.
+ //
+ bgpSession.localInfo().setAs4OctetCapability();
+ bgpSession.localInfo().setAs4Number(as4Number);
+ log.debug("BGP RX OPEN Capability: AS4 Number = {}",
+ as4Number);
+ break;
+
+ default:
+ // Unknown Capability: ignore it
+ log.debug("BGP RX OPEN Capability Code = {} Length = {}",
+ capabCode, capabLen);
+ message.readBytes(capabLen);
+ break;
+ }
+
+ break;
+
+ default:
+ // Unknown Parameter Type: ignore it
+ log.debug("BGP RX OPEN Parameter Type = {} Length = {}",
+ paramType, paramLen);
+ message.readBytes(paramLen);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Prepares the Capabilities for the BGP OPEN message.
+ *
+ * @param localInfo the BGP Session local information to use
+ * @return the buffer with the BGP Capabilities to transmit
+ */
+ private static ChannelBuffer prepareBgpOpenCapabilities(
+ BgpSessionInfo localInfo) {
+ ChannelBuffer message =
+ ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
+
+ //
+ // Write the Multiprotocol Extensions Capabilities
+ //
+
+ // IPv4 unicast
+ if (localInfo.ipv4Unicast()) {
+ message.writeByte(BgpConstants.Open.Capabilities.TYPE); // Param type
+ message.writeByte(BgpConstants.Open.Capabilities.MIN_LENGTH +
+ BgpConstants.Open.Capabilities.MultiprotocolExtensions.LENGTH); // Param len
+ message.writeByte(
+ BgpConstants.Open.Capabilities.MultiprotocolExtensions.CODE); // Capab. code
+ message.writeByte(
+ BgpConstants.Open.Capabilities.MultiprotocolExtensions.LENGTH); // Capab. len
+ message.writeShort(
+ BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV4);
+ message.writeByte(0); // Reserved field
+ message.writeByte(
+ BgpConstants.Open.Capabilities.MultiprotocolExtensions.SAFI_UNICAST);
+ }
+ // IPv4 multicast
+ if (localInfo.ipv4Multicast()) {
+ message.writeByte(BgpConstants.Open.Capabilities.TYPE); // Param type
+ message.writeByte(BgpConstants.Open.Capabilities.MIN_LENGTH +
+ BgpConstants.Open.Capabilities.MultiprotocolExtensions.LENGTH); // Param len
+ message.writeByte(
+ BgpConstants.Open.Capabilities.MultiprotocolExtensions.CODE); // Capab. code
+ message.writeByte(
+ BgpConstants.Open.Capabilities.MultiprotocolExtensions.LENGTH); // Capab. len
+ message.writeShort(
+ BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV4);
+ message.writeByte(0); // Reserved field
+ message.writeByte(
+ BgpConstants.Open.Capabilities.MultiprotocolExtensions.SAFI_MULTICAST);
+ }
+ // IPv6 unicast
+ if (localInfo.ipv6Unicast()) {
+ message.writeByte(BgpConstants.Open.Capabilities.TYPE); // Param type
+ message.writeByte(BgpConstants.Open.Capabilities.MIN_LENGTH +
+ BgpConstants.Open.Capabilities.MultiprotocolExtensions.LENGTH); // Param len
+ message.writeByte(
+ BgpConstants.Open.Capabilities.MultiprotocolExtensions.CODE); // Capab. code
+ message.writeByte(
+ BgpConstants.Open.Capabilities.MultiprotocolExtensions.LENGTH); // Capab. len
+ message.writeShort(
+ BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV6);
+ message.writeByte(0); // Reserved field
+ message.writeByte(
+ BgpConstants.Open.Capabilities.MultiprotocolExtensions.SAFI_UNICAST);
+ }
+ // IPv6 multicast
+ if (localInfo.ipv6Multicast()) {
+ message.writeByte(BgpConstants.Open.Capabilities.TYPE); // Param type
+ message.writeByte(BgpConstants.Open.Capabilities.MIN_LENGTH +
+ BgpConstants.Open.Capabilities.MultiprotocolExtensions.LENGTH); // Param len
+ message.writeByte(
+ BgpConstants.Open.Capabilities.MultiprotocolExtensions.CODE); // Capab. code
+ message.writeByte(
+ BgpConstants.Open.Capabilities.MultiprotocolExtensions.LENGTH); // Capab. len
+ message.writeShort(
+ BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV6);
+ message.writeByte(0); // Reserved field
+ message.writeByte(
+ BgpConstants.Open.Capabilities.MultiprotocolExtensions.SAFI_MULTICAST);
+ }
+
+ // 4 octet AS path capability
+ if (localInfo.as4OctetCapability()) {
+ message.writeByte(BgpConstants.Open.Capabilities.TYPE); // Param type
+ message.writeByte(BgpConstants.Open.Capabilities.MIN_LENGTH +
+ BgpConstants.Open.Capabilities.As4Octet.LENGTH); // Param len
+ message.writeByte(BgpConstants.Open.Capabilities.As4Octet.CODE); // Capab. code
+ message.writeByte(BgpConstants.Open.Capabilities.As4Octet.LENGTH); // Capab. len
+ message.writeInt((int) localInfo.as4Number());
+ }
+ return message;
+ }
+}
diff --git a/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpRouteEntry.java b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpRouteEntry.java
new file mode 100644
index 00000000..974da808
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpRouteEntry.java
@@ -0,0 +1,494 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.routing.bgp;
+
+import com.google.common.base.MoreObjects;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onosproject.routing.RouteEntry;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * 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 (after skipping
+ * AS_CONFED_SEQUENCE and AS_CONFED_SET).
+ * </p>
+ *
+ * @return true if the route is originated from the local AS, otherwise
+ * false
+ */
+ boolean isLocalRoute() {
+ PathSegment firstPathSegment = null;
+
+ // Find the first Path Segment by ignoring the AS_CONFED_* segments
+ for (PathSegment pathSegment : asPath.getPathSegments()) {
+ if ((pathSegment.getType() == BgpConstants.Update.AsPath.AS_SET) ||
+ (pathSegment.getType() == BgpConstants.Update.AsPath.AS_SEQUENCE)) {
+ firstPathSegment = pathSegment;
+ break;
+ }
+ }
+ if (firstPathSegment == null) {
+ return true; // Local route: no path segments
+ }
+ // If the first path segment is AS_SET, the route is considered local
+ if (firstPathSegment.getType() == BgpConstants.Update.AsPath.AS_SET) {
+ return true;
+ }
+
+ return false; // The route is not local
+ }
+
+ /**
+ * 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).
+ * </p>
+ *
+ * @return the BGP Neighbor AS number the route was received from.
+ */
+ long getNeighborAs() {
+ PathSegment firstPathSegment = null;
+
+ if (isLocalRoute()) {
+ return BgpConstants.BGP_AS_0;
+ }
+
+ // Find the first Path Segment by ignoring the AS_CONFED_* segments
+ for (PathSegment pathSegment : asPath.getPathSegments()) {
+ if ((pathSegment.getType() == BgpConstants.Update.AsPath.AS_SET) ||
+ (pathSegment.getType() == BgpConstants.Update.AsPath.AS_SEQUENCE)) {
+ firstPathSegment = pathSegment;
+ break;
+ }
+ }
+ if (firstPathSegment == null) {
+ // NOTE: Shouldn't happen - should be captured by isLocalRoute()
+ return BgpConstants.BGP_AS_0;
+ }
+
+ if (firstPathSegment.getSegmentAsNumbers().isEmpty()) {
+ // NOTE: 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.
+ * </p>
+ *
+ * @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.
+ * </p>
+ *
+ * @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: {
+ if (isLocalRoute() || other.isLocalRoute()) {
+ // Compare MEDs for non-local routes only
+ break medLabel;
+ }
+ 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
+ Ip4Address peerBgpId = getBgpSession().remoteInfo().bgpId();
+ Ip4Address otherPeerBgpId = other.getBgpSession().remoteInfo().bgpId();
+ if (!peerBgpId.equals(otherPeerBgpId)) {
+ return (peerBgpId.compareTo(otherPeerBgpId) < 0);
+ }
+
+ // Compare the peer BGP address: lower is better
+ Ip4Address peerAddress = getBgpSession().remoteInfo().ip4Address();
+ Ip4Address otherPeerAddress =
+ other.getBgpSession().remoteInfo().ip4Address();
+ 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 {
+ // Segment type: AS_SET(1), AS_SEQUENCE(2), AS_CONFED_SEQUENCE(3),
+ // AS_CONFED_SET(4)
+ private final byte type;
+ private final ArrayList<Long> segmentAsNumbers; // Segment AS numbers
+
+ /**
+ * Constructor.
+ *
+ * @param type the Path Segment Type: AS_SET(1), AS_SEQUENCE(2),
+ * AS_CONFED_SEQUENCE(3), AS_CONFED_SET(4)
+ * @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(1), AS_SEQUENCE(2),
+ * AS_CONFED_SEQUENCE(3), AS_CONFED_SET(4).
+ *
+ * @return the Path Segment Type: AS_SET(1), AS_SEQUENCE(2),
+ * AS_CONFED_SEQUENCE(3), AS_CONFED_SET(4)
+ */
+ 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", BgpConstants.Update.AsPath.typeToString(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
+ // - AS_SEQUENCE counts how many AS numbers are included
+ // - AS_CONFED_SEQUENCE and AS_CONFED_SET are ignored
+ //
+ int pl = 0;
+ for (PathSegment pathSegment : pathSegments) {
+ switch (pathSegment.getType()) {
+ case BgpConstants.Update.AsPath.AS_SET:
+ pl++; // AS_SET counts as 1
+ break;
+ case BgpConstants.Update.AsPath.AS_SEQUENCE:
+ // Count each AS number
+ pl += pathSegment.getSegmentAsNumbers().size();
+ break;
+ case BgpConstants.Update.AsPath.AS_CONFED_SEQUENCE:
+ break; // Ignore
+ case BgpConstants.Update.AsPath.AS_CONFED_SET:
+ break; // Ignore
+ default:
+ // NOTE: What to do if the Path Segment type is unknown?
+ break;
+ }
+ }
+ 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.
+ * </p>
+ *
+ * @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
+ * </p>
+ *
+ * @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.remoteInfo().bgpId())
+ .add("origin", BgpConstants.Update.Origin.typeToString(origin))
+ .add("asPath", asPath)
+ .add("localPref", localPref)
+ .add("multiExitDisc", multiExitDisc)
+ .toString();
+ }
+}
diff --git a/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpRouteSelector.java b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpRouteSelector.java
new file mode 100644
index 00000000..ec020230
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpRouteSelector.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.routing.bgp;
+
+import org.onlab.packet.IpPrefix;
+import org.onosproject.routing.RouteUpdate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.LinkedList;
+
+/**
+ * Class to receive and process the BGP routes from each BGP Session/Peer.
+ */
+class BgpRouteSelector {
+ private static final Logger log =
+ LoggerFactory.getLogger(BgpRouteSelector.class);
+
+ private BgpSessionManager bgpSessionManager;
+
+ /**
+ * Constructor.
+ *
+ * @param bgpSessionManager the BGP Session Manager to use
+ */
+ BgpRouteSelector(BgpSessionManager bgpSessionManager) {
+ this.bgpSessionManager = bgpSessionManager;
+ }
+
+ /**
+ * 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) {
+ Collection<RouteUpdate> routeUpdates = new LinkedList<>();
+ RouteUpdate routeUpdate;
+
+ if (bgpSessionManager.isShutdown()) {
+ return; // Ignore any leftover updates if shutdown
+ }
+ // Process the deleted route entries
+ for (BgpRouteEntry bgpRouteEntry : deletedBgpRouteEntries) {
+ routeUpdate = processDeletedRoute(bgpSession, bgpRouteEntry);
+ if (routeUpdate != null) {
+ routeUpdates.add(routeUpdate);
+ }
+ }
+
+ // Process the added/updated route entries
+ for (BgpRouteEntry bgpRouteEntry : addedBgpRouteEntries) {
+ routeUpdate = processAddedRoute(bgpSession, bgpRouteEntry);
+ if (routeUpdate != null) {
+ routeUpdates.add(routeUpdate);
+ }
+ }
+ bgpSessionManager.getRouteListener().update(routeUpdates);
+ }
+
+ /**
+ * 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
+ * @return the result route update that should be forwarded to the
+ * Route Listener, or null if no route update should be forwarded
+ */
+ private RouteUpdate processAddedRoute(BgpSession bgpSession,
+ BgpRouteEntry bgpRouteEntry) {
+ RouteUpdate routeUpdate;
+ BgpRouteEntry bestBgpRouteEntry =
+ bgpSessionManager.findBgpRoute(bgpRouteEntry.prefix());
+
+ //
+ // Install the new route entry if it is better than the
+ // current best route.
+ //
+ if ((bestBgpRouteEntry == null) ||
+ bgpRouteEntry.isBetterThan(bestBgpRouteEntry)) {
+ bgpSessionManager.addBgpRoute(bgpRouteEntry);
+ routeUpdate =
+ new RouteUpdate(RouteUpdate.Type.UPDATE, bgpRouteEntry);
+ return routeUpdate;
+ }
+
+ //
+ // 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 null; // Nothing to do
+ }
+
+ // 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
+ bgpSessionManager.addBgpRoute(bestBgpRouteEntry);
+ routeUpdate = new RouteUpdate(RouteUpdate.Type.UPDATE,
+ bestBgpRouteEntry);
+ return routeUpdate;
+ }
+
+ /**
+ * Processes a deleted route entry.
+ *
+ * @param bgpSession the BGP session the route entry update was received on
+ * @param bgpRouteEntry the deleted route entry
+ * @return the result route update that should be forwarded to the
+ * Route Listener, or null if no route update should be forwarded
+ */
+ private RouteUpdate processDeletedRoute(BgpSession bgpSession,
+ BgpRouteEntry bgpRouteEntry) {
+ RouteUpdate routeUpdate;
+ BgpRouteEntry bestBgpRouteEntry =
+ bgpSessionManager.findBgpRoute(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 null; // Nothing to do
+ }
+
+ //
+ // Find the next best route
+ //
+ bestBgpRouteEntry = findBestBgpRoute(bgpRouteEntry.prefix());
+ if (bestBgpRouteEntry != null) {
+ // Install the next best route
+ bgpSessionManager.addBgpRoute(bestBgpRouteEntry);
+ routeUpdate = new RouteUpdate(RouteUpdate.Type.UPDATE,
+ bestBgpRouteEntry);
+ return routeUpdate;
+ }
+
+ //
+ // No route found. Remove the route entry
+ //
+ bgpSessionManager.removeBgpRoute(bgpRouteEntry.prefix());
+ routeUpdate = new RouteUpdate(RouteUpdate.Type.DELETE, bgpRouteEntry);
+ return 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 : bgpSessionManager.getBgpSessions()) {
+ BgpRouteEntry route = bgpSession.findBgpRoute(prefix);
+ if (route == null) {
+ continue;
+ }
+ if ((bestRoute == null) || route.isBetterThan(bestRoute)) {
+ bestRoute = route;
+ }
+ }
+ return bestRoute;
+ }
+}
diff --git a/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpSession.java b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpSession.java
new file mode 100644
index 00000000..dedf6ac3
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpSession.java
@@ -0,0 +1,473 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.routing.bgp;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.ChannelStateEvent;
+import org.jboss.netty.channel.ExceptionEvent;
+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.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.Ip6Prefix;
+import org.onlab.packet.IpPrefix;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 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;
+
+ // BGP session info: local and remote
+ private final BgpSessionInfo localInfo; // BGP session local info
+ private final BgpSessionInfo remoteInfo; // BGP session remote info
+
+ // 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<Ip4Prefix, BgpRouteEntry> bgpRibIn4 =
+ new ConcurrentHashMap<>();
+ private ConcurrentMap<Ip6Prefix, BgpRouteEntry> bgpRibIn6 =
+ new ConcurrentHashMap<>();
+
+ /**
+ * Constructor for a given BGP Session Manager.
+ *
+ * @param bgpSessionManager the BGP Session Manager to use
+ */
+ BgpSession(BgpSessionManager bgpSessionManager) {
+ this.bgpSessionManager = bgpSessionManager;
+ this.localInfo = new BgpSessionInfo();
+ this.remoteInfo = new BgpSessionInfo();
+
+ // NOTE: We support only BGP4
+ this.localInfo.setBgpVersion(BgpConstants.BGP_VERSION);
+ }
+
+ /**
+ * Gets the BGP Session Manager.
+ *
+ * @return the BGP Session Manager
+ */
+ BgpSessionManager getBgpSessionManager() {
+ return bgpSessionManager;
+ }
+
+ /**
+ * Gets the BGP Session local information.
+ *
+ * @return the BGP Session local information.
+ */
+ public BgpSessionInfo localInfo() {
+ return localInfo;
+ }
+
+ /**
+ * Gets the BGP Session remote information.
+ *
+ * @return the BGP Session remote information.
+ */
+ public BgpSessionInfo remoteInfo() {
+ return remoteInfo;
+ }
+
+ /**
+ * Gets the BGP Multiprotocol Extensions for the session.
+ *
+ * @return true if the BGP Multiprotocol Extensions are enabled for the
+ * session, otherwise false
+ */
+ public boolean mpExtensions() {
+ return remoteInfo.mpExtensions() && localInfo.mpExtensions();
+ }
+
+ /**
+ * Gets the BGP session 4 octet AS path capability.
+ *
+ * @return true when the BGP session is 4 octet AS path capable
+ */
+ public boolean isAs4OctetCapable() {
+ return remoteInfo.as4OctetCapability() &&
+ localInfo.as4OctetCapability();
+ }
+
+ /**
+ * Gets the IPv4 BGP RIB-IN routing entries.
+ *
+ * @return the IPv4 BGP RIB-IN routing entries
+ */
+ public Collection<BgpRouteEntry> getBgpRibIn4() {
+ return bgpRibIn4.values();
+ }
+
+ /**
+ * Gets the IPv6 BGP RIB-IN routing entries.
+ *
+ * @return the IPv6 BGP RIB-IN routing entries
+ */
+ public Collection<BgpRouteEntry> getBgpRibIn6() {
+ return bgpRibIn6.values();
+ }
+
+ /**
+ * Finds an IPv4 BGP routing entry for a prefix in the IPv4 BGP RIB-IN.
+ *
+ * @param prefix the IPv4 prefix of the route to search for
+ * @return the IPv4 BGP routing entry if found, otherwise null
+ */
+ public BgpRouteEntry findBgpRoute(Ip4Prefix prefix) {
+ return bgpRibIn4.get(prefix);
+ }
+
+ /**
+ * Finds an IPv6 BGP routing entry for a prefix in the IPv6 BGP RIB-IN.
+ *
+ * @param prefix the IPv6 prefix of the route to search for
+ * @return the IPv6 BGP routing entry if found, otherwise null
+ */
+ public BgpRouteEntry findBgpRoute(Ip6Prefix prefix) {
+ return bgpRibIn6.get(prefix);
+ }
+
+ /**
+ * Finds a BGP routing entry for a prefix in the BGP RIB-IN. The prefix
+ * can be either IPv4 or IPv6.
+ *
+ * @param prefix the IP prefix of the route to search for
+ * @return the BGP routing entry if found, otherwise null
+ */
+ public BgpRouteEntry findBgpRoute(IpPrefix prefix) {
+ if (prefix.isIp4()) {
+ // IPv4 prefix
+ Ip4Prefix ip4Prefix = prefix.getIp4Prefix();
+ return bgpRibIn4.get(ip4Prefix);
+ }
+
+ // IPv6 prefix
+ Ip6Prefix ip6Prefix = prefix.getIp6Prefix();
+ return bgpRibIn6.get(ip6Prefix);
+ }
+
+ /**
+ * Adds a BGP route. The route can be either IPv4 or IPv6.
+ *
+ * @param bgpRouteEntry the BGP route entry to use
+ */
+ void addBgpRoute(BgpRouteEntry bgpRouteEntry) {
+ if (bgpRouteEntry.isIp4()) {
+ // IPv4 route
+ Ip4Prefix ip4Prefix = bgpRouteEntry.prefix().getIp4Prefix();
+ bgpRibIn4.put(ip4Prefix, bgpRouteEntry);
+ } else {
+ // IPv6 route
+ Ip6Prefix ip6Prefix = bgpRouteEntry.prefix().getIp6Prefix();
+ bgpRibIn6.put(ip6Prefix, bgpRouteEntry);
+ }
+ }
+
+ /**
+ * Removes an IPv4 BGP route for a prefix.
+ *
+ * @param prefix the prefix to use
+ * @return true if the route was found and removed, otherwise false
+ */
+ boolean removeBgpRoute(Ip4Prefix prefix) {
+ return (bgpRibIn4.remove(prefix) != null);
+ }
+
+ /**
+ * Removes an IPv6 BGP route for a prefix.
+ *
+ * @param prefix the prefix to use
+ * @return true if the route was found and removed, otherwise false
+ */
+ boolean removeBgpRoute(Ip6Prefix prefix) {
+ return (bgpRibIn6.remove(prefix) != null);
+ }
+
+ /**
+ * Removes a BGP route for a prefix. The prefix can be either IPv4 or IPv6.
+ *
+ * @param prefix the prefix to use
+ * @return true if the route was found and removed, otherwise false
+ */
+ boolean removeBgpRoute(IpPrefix prefix) {
+ if (prefix.isIp4()) {
+ return (bgpRibIn4.remove(prefix.getIp4Prefix()) != null); // IPv4
+ }
+ return (bgpRibIn6.remove(prefix.getIp6Prefix()) != null); // IPv6
+ }
+
+ /**
+ * Tests whether the session is closed.
+ * <p>
+ * NOTE: We use this method to avoid the Netty's asynchronous closing
+ * of a channel.
+ * </p>
+ * @return true if the session is closed
+ */
+ boolean isClosed() {
+ return isClosed;
+ }
+
+ /**
+ * Closes the session.
+ *
+ * @param ctx the Channel Handler Context
+ */
+ void closeSession(ChannelHandlerContext ctx) {
+ timer.stop();
+ closeChannel(ctx);
+ }
+
+ /**
+ * Closes the Netty channel.
+ *
+ * @param ctx the Channel Handler Context
+ */
+ void closeChannel(ChannelHandlerContext ctx) {
+ isClosed = true;
+ ctx.getChannel().close();
+ }
+
+ @Override
+ public void channelOpen(ChannelHandlerContext ctx,
+ ChannelStateEvent channelEvent) {
+ bgpSessionManager.addSessionChannel(channelEvent.getChannel());
+ }
+
+ @Override
+ public void channelClosed(ChannelHandlerContext ctx,
+ ChannelStateEvent channelEvent) {
+ bgpSessionManager.removeSessionChannel(channelEvent.getChannel());
+ }
+
+ @Override
+ public void channelConnected(ChannelHandlerContext ctx,
+ ChannelStateEvent channelEvent) {
+ localInfo.setAddress(ctx.getChannel().getLocalAddress());
+ remoteInfo.setAddress(ctx.getChannel().getRemoteAddress());
+
+ // Assign the local and remote IPv4 addresses
+ InetAddress inetAddr;
+ if (localInfo.address() instanceof InetSocketAddress) {
+ inetAddr = ((InetSocketAddress) localInfo.address()).getAddress();
+ localInfo.setIp4Address(Ip4Address.valueOf(inetAddr.getAddress()));
+ }
+ if (remoteInfo.address() instanceof InetSocketAddress) {
+ inetAddr = ((InetSocketAddress) remoteInfo.address()).getAddress();
+ remoteInfo.setIp4Address(Ip4Address.valueOf(inetAddr.getAddress()));
+ }
+
+ log.debug("BGP Session Connected from {} on {}",
+ remoteInfo.address(), localInfo.address());
+ if (!bgpSessionManager.peerConnected(this)) {
+ log.debug("Cannot setup BGP Session Connection from {}. Closing...",
+ remoteInfo.address());
+ ctx.getChannel().close();
+ }
+
+ //
+ // Assign the local BGP ID
+ // NOTE: This should be configuration-based
+ //
+ localInfo.setBgpId(bgpSessionManager.getMyBgpId());
+ }
+
+ @Override
+ public void channelDisconnected(ChannelHandlerContext ctx,
+ ChannelStateEvent channelEvent) {
+ log.debug("BGP Session Disconnected from {} on {}",
+ ctx.getChannel().getRemoteAddress(),
+ ctx.getChannel().getLocalAddress());
+ processChannelDisconnected();
+ }
+
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
+ log.debug("BGP Session Exception Caught from {} on {}: {}",
+ ctx.getChannel().getRemoteAddress(),
+ ctx.getChannel().getLocalAddress(),
+ e);
+ processChannelDisconnected();
+ }
+
+ /**
+ * Processes the channel being disconnected.
+ */
+ private void processChannelDisconnected() {
+ //
+ // 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> deletedRoutes4 = bgpRibIn4.values();
+ Collection<BgpRouteEntry> deletedRoutes6 = bgpRibIn6.values();
+ bgpRibIn4 = new ConcurrentHashMap<>();
+ bgpRibIn6 = new ConcurrentHashMap<>();
+
+ // Push the updates to the BGP Merged RIB
+ BgpRouteSelector bgpRouteSelector =
+ bgpSessionManager.getBgpRouteSelector();
+ Collection<BgpRouteEntry> addedRoutes = Collections.emptyList();
+ bgpRouteSelector.routeUpdates(this, addedRoutes, deletedRoutes4);
+ bgpRouteSelector.routeUpdates(this, addedRoutes, deletedRoutes6);
+
+ bgpSessionManager.peerDisconnected(this);
+ }
+
+ /**
+ * Restarts the BGP KeepaliveTimer.
+ *
+ * @param ctx the Channel Handler Context to use
+ */
+ void restartKeepaliveTimer(ChannelHandlerContext ctx) {
+ long localKeepaliveInterval = 0;
+
+ //
+ // Compute the local Keepalive interval
+ //
+ if (localInfo.holdtime() != 0) {
+ localKeepaliveInterval = Math.max(localInfo.holdtime() /
+ BgpConstants.BGP_KEEPALIVE_PER_HOLD_INTERVAL,
+ BgpConstants.BGP_KEEPALIVE_MIN_INTERVAL);
+ }
+
+ // Restart the Keepalive timer
+ 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 = BgpKeepalive.prepareBgpKeepalive();
+ ctx.getChannel().write(txMessage);
+
+ // Restart the KEEPALIVE timer
+ restartKeepaliveTimer(ctx);
+ }
+ }
+
+ /**
+ * Restarts the BGP Session Timeout Timer.
+ *
+ * @param ctx the Channel Handler Context to use
+ */
+ void restartSessionTimeoutTimer(ChannelHandlerContext ctx) {
+ if (remoteInfo.holdtime() == 0) {
+ return; // Nothing to do
+ }
+ if (sessionTimeout != null) {
+ sessionTimeout.cancel();
+ }
+ sessionTimeout = timer.newTimeout(new SessionTimeoutTask(ctx),
+ remoteInfo.holdtime(),
+ 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 {}", remoteInfo.address());
+ //
+ // ERROR: Invalid Optional Parameter Length field: Unspecific
+ //
+ // Send NOTIFICATION and close the connection
+ int errorCode = BgpConstants.Notifications.HoldTimerExpired.ERROR_CODE;
+ int errorSubcode = BgpConstants.Notifications.ERROR_SUBCODE_UNSPECIFIC;
+ ChannelBuffer txMessage =
+ BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
+ null);
+ ctx.getChannel().write(txMessage);
+ closeChannel(ctx);
+ }
+ }
+}
diff --git a/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpSessionInfo.java b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpSessionInfo.java
new file mode 100644
index 00000000..a1274556
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpSessionInfo.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.routing.bgp;
+
+import org.onlab.packet.Ip4Address;
+
+import java.net.SocketAddress;
+
+/**
+ * Class for keeping information about a BGP session.
+ *
+ * There are two instances per each BGP peer session: one to keep the local
+ * information about the BGP session, and another to keep information about
+ * the remote BGP peer.
+ */
+public class BgpSessionInfo {
+ private SocketAddress address; // IP addr/port
+ private Ip4Address ip4Address; // IPv4 address
+ private int bgpVersion; // 1 octet
+ private long asNumber; // AS number: 2 octets
+ private long as4Number; // AS4 number: 4 octets
+ private long holdtime; // 2 octets
+ private Ip4Address bgpId; // 4 octets -> IPv4 address
+ private boolean mpExtensions; // Multiprotocol Extensions
+ // enabled: RFC 4760
+ private boolean ipv4Unicast; // IPv4/UNICAST AFI/SAFI
+ private boolean ipv4Multicast; // IPv4/MULTICAST AFI/SAFI
+ private boolean ipv6Unicast; // IPv6/UNICAST AFI/SAFI
+ private boolean ipv6Multicast; // IPv6/MULTICAST AFI/SAFI
+ private boolean as4OctetCapability; // AS 4 octet path capability
+
+ /**
+ * Gets the BGP session address: local or remote.
+ *
+ * @return the BGP session address
+ */
+ public SocketAddress address() {
+ return this.address;
+ }
+
+ /**
+ * Sets the BGP session address: local or remote.
+ *
+ * @param address the BGP session address to set
+ */
+ public void setAddress(SocketAddress address) {
+ this.address = address;
+ }
+
+ /**
+ * Gets the BGP session IPv4 address: local or remote.
+ *
+ * @return the BGP session IPv4 address
+ */
+ public Ip4Address ip4Address() {
+ return this.ip4Address;
+ }
+
+ /**
+ * Sets the BGP session IPv4 address: local or remote.
+ *
+ * @param ip4Address the BGP session IPv4 address to set
+ */
+ public void setIp4Address(Ip4Address ip4Address) {
+ this.ip4Address = ip4Address;
+ }
+
+ /**
+ * Gets the BGP session BGP version: local or remote.
+ *
+ * @return the BGP session BGP version
+ */
+ public int bgpVersion() {
+ return this.bgpVersion;
+ }
+
+ /**
+ * Sets the BGP session BGP version: local or remote.
+ *
+ * @param bgpVersion the BGP session BGP version to set
+ */
+ public void setBgpVersion(int bgpVersion) {
+ this.bgpVersion = bgpVersion;
+ }
+
+ /**
+ * Gets the BGP session AS number: local or remote.
+ *
+ * @return the BGP session AS number
+ */
+ public long asNumber() {
+ return this.asNumber;
+ }
+
+ /**
+ * Sets the BGP session AS number: local or remote.
+ *
+ * @param asNumber the BGP session AS number to set
+ */
+ public void setAsNumber(long asNumber) {
+ this.asNumber = asNumber;
+ }
+
+ /**
+ * Gets the BGP session AS4 number: local or remote.
+ *
+ * @return the BGP session AS4 number
+ */
+ public long as4Number() {
+ return this.as4Number;
+ }
+
+ /**
+ * Sets the BGP session AS4 number: local or remote.
+ *
+ * @param as4Number the BGP session AS4 number to set
+ */
+ public void setAs4Number(long as4Number) {
+ this.as4Number = as4Number;
+ }
+
+ /**
+ * Gets the BGP session holdtime: local or remote.
+ *
+ * @return the BGP session holdtime
+ */
+ public long holdtime() {
+ return this.holdtime;
+ }
+
+ /**
+ * Sets the BGP session holdtime: local or remote.
+ *
+ * @param holdtime the BGP session holdtime to set
+ */
+ public void setHoldtime(long holdtime) {
+ this.holdtime = holdtime;
+ }
+
+ /**
+ * Gets the BGP session BGP Identifier as an IPv4 address: local or remote.
+ *
+ * @return the BGP session BGP Identifier as an IPv4 address
+ */
+ public Ip4Address bgpId() {
+ return this.bgpId;
+ }
+
+ /**
+ * Sets the BGP session BGP Identifier as an IPv4 address: local or remote.
+ *
+ * @param bgpId the BGP session BGP Identifier to set
+ */
+ public void setBgpId(Ip4Address bgpId) {
+ this.bgpId = bgpId;
+ }
+
+ /**
+ * Gets the BGP Multiprotocol Extensions: local or remote.
+ *
+ * @return true if the BGP Multiprotocol Extensions are enabled, otherwise
+ * false
+ */
+ public boolean mpExtensions() {
+ return this.mpExtensions;
+ }
+
+ /**
+ * Gets the BGP session AFI/SAFI configuration for IPv4 unicast: local or
+ * remote.
+ *
+ * @return the BGP session AFI/SAFI configuration for IPv4 unicast
+ */
+ public boolean ipv4Unicast() {
+ return ipv4Unicast;
+ }
+
+ /**
+ * Sets the BGP session AFI/SAFI configuration for IPv4 unicast: local or
+ * remote.
+ */
+ public void setIpv4Unicast() {
+ this.mpExtensions = true;
+ this.ipv4Unicast = true;
+ }
+
+ /**
+ * Gets the BGP session AFI/SAFI configuration for IPv4 multicast: local or
+ * remote.
+ *
+ * @return the BGP session AFI/SAFI configuration for IPv4 multicast
+ */
+ public boolean ipv4Multicast() {
+ return ipv4Multicast;
+ }
+
+ /**
+ * Sets the BGP session AFI/SAFI configuration for IPv4 multicast: local or
+ * remote.
+ */
+ public void setIpv4Multicast() {
+ this.mpExtensions = true;
+ this.ipv4Multicast = true;
+ }
+
+ /**
+ * Gets the BGP session AFI/SAFI configuration for IPv6 unicast: local or
+ * remote.
+ *
+ * @return the BGP session AFI/SAFI configuration for IPv6 unicast
+ */
+ public boolean ipv6Unicast() {
+ return ipv6Unicast;
+ }
+
+ /**
+ * Sets the BGP session AFI/SAFI configuration for IPv6 unicast: local or
+ * remote.
+ */
+ void setIpv6Unicast() {
+ this.mpExtensions = true;
+ this.ipv6Unicast = true;
+ }
+
+ /**
+ * Gets the BGP session AFI/SAFI configuration for IPv6 multicast: local or
+ * remote.
+ *
+ * @return the BGP session AFI/SAFI configuration for IPv6 multicast
+ */
+ public boolean ipv6Multicast() {
+ return ipv6Multicast;
+ }
+
+ /**
+ * Sets the BGP session AFI/SAFI configuration for IPv6 multicast: local or
+ * remote.
+ */
+ public void setIpv6Multicast() {
+ this.mpExtensions = true;
+ this.ipv6Multicast = true;
+ }
+
+ /**
+ * Gets the BGP session 4 octet AS path capability: local or remote.
+ *
+ * @return true when the BGP session has 4 octet AS path capability
+ */
+ public boolean as4OctetCapability() {
+ return this.as4OctetCapability;
+ }
+
+ /**
+ * Sets the BGP session 4 octet AS path capability.
+ */
+ public void setAs4OctetCapability() {
+ this.as4OctetCapability = true;
+ }
+}
diff --git a/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpSessionManager.java b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpSessionManager.java
new file mode 100644
index 00000000..57481c20
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpSessionManager.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.routing.bgp;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Modified;
+import org.apache.felix.scr.annotations.Service;
+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.group.ChannelGroup;
+import org.jboss.netty.channel.group.DefaultChannelGroup;
+import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.Ip6Prefix;
+import org.onlab.packet.IpPrefix;
+import org.onosproject.routing.BgpService;
+import org.onosproject.routing.RouteListener;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.concurrent.Executors.newCachedThreadPool;
+import static org.onlab.util.Tools.groupedThreads;
+
+/**
+ * BGP Session Manager class.
+ */
+@Component(immediate = true)
+@Service
+public class BgpSessionManager implements BgpInfoService, BgpService {
+ private static final Logger log =
+ LoggerFactory.getLogger(BgpSessionManager.class);
+
+ boolean isShutdown = true;
+ private Channel serverChannel; // Listener for incoming BGP connections
+ private ServerBootstrap serverBootstrap;
+ private ChannelGroup allChannels = new DefaultChannelGroup();
+ private ConcurrentMap<SocketAddress, BgpSession> bgpSessions =
+ new ConcurrentHashMap<>();
+ private Ip4Address myBgpId; // Same BGP ID for all peers
+
+ private BgpRouteSelector bgpRouteSelector = new BgpRouteSelector(this);
+ private ConcurrentMap<Ip4Prefix, BgpRouteEntry> bgpRoutes4 =
+ new ConcurrentHashMap<>();
+ private ConcurrentMap<Ip6Prefix, BgpRouteEntry> bgpRoutes6 =
+ new ConcurrentHashMap<>();
+
+ private RouteListener routeListener;
+
+ private static final int DEFAULT_BGP_PORT = 2000;
+ private int bgpPort;
+
+ @Activate
+ protected void activate(ComponentContext context) {
+ readComponentConfiguration(context);
+ log.info("BgpSessionManager started");
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ log.info("BgpSessionManager stopped");
+ }
+
+ /**
+ * Extracts properties from the component configuration context.
+ *
+ * @param context the component context
+ */
+ private void readComponentConfiguration(ComponentContext context) {
+ Dictionary<?, ?> properties = context.getProperties();
+ try {
+ String strPort = (String) properties.get("bgpPort");
+ if (strPort != null) {
+ bgpPort = Integer.parseInt(strPort);
+ } else {
+ bgpPort = DEFAULT_BGP_PORT;
+ }
+ } catch (NumberFormatException | ClassCastException e) {
+ bgpPort = DEFAULT_BGP_PORT;
+ }
+ log.debug("BGP port is set to {}", bgpPort);
+ }
+
+ @Modified
+ public void modified(ComponentContext context) {
+ // Blank @Modified method to catch modifications to the context.
+ // If no @Modified method exists, it seems @Activate is called again
+ // when the context is modified.
+ }
+
+ /**
+ * Checks whether the BGP Session Manager is shutdown.
+ *
+ * @return true if the BGP Session Manager is shutdown, otherwise false
+ */
+ boolean isShutdown() {
+ return this.isShutdown;
+ }
+
+ /**
+ * Gets the route listener.
+ *
+ * @return the route listener to use
+ */
+ RouteListener getRouteListener() {
+ return routeListener;
+ }
+
+ /**
+ * Gets the BGP sessions.
+ *
+ * @return the BGP sessions
+ */
+ public Collection<BgpSession> getBgpSessions() {
+ return bgpSessions.values();
+ }
+
+ /**
+ * Gets the selected IPv4 BGP routes among all BGP sessions.
+ *
+ * @return the selected IPv4 BGP routes among all BGP sessions
+ */
+ public Collection<BgpRouteEntry> getBgpRoutes4() {
+ return bgpRoutes4.values();
+ }
+
+ /**
+ * Gets the selected IPv6 BGP routes among all BGP sessions.
+ *
+ * @return the selected IPv6 BGP routes among all BGP sessions
+ */
+ public Collection<BgpRouteEntry> getBgpRoutes6() {
+ return bgpRoutes6.values();
+ }
+
+ /**
+ * Finds a BGP route for a prefix. The prefix can be either IPv4 or IPv6.
+ *
+ * @param prefix the prefix to use
+ * @return the BGP route if found, otherwise null
+ */
+ BgpRouteEntry findBgpRoute(IpPrefix prefix) {
+ if (prefix.isIp4()) {
+ return bgpRoutes4.get(prefix.getIp4Prefix()); // IPv4
+ }
+ return bgpRoutes6.get(prefix.getIp6Prefix()); // IPv6
+ }
+
+ /**
+ * Adds a BGP route. The route can be either IPv4 or IPv6.
+ *
+ * @param bgpRouteEntry the BGP route entry to use
+ */
+ void addBgpRoute(BgpRouteEntry bgpRouteEntry) {
+ if (bgpRouteEntry.isIp4()) {
+ bgpRoutes4.put(bgpRouteEntry.prefix().getIp4Prefix(), // IPv4
+ bgpRouteEntry);
+ } else {
+ bgpRoutes6.put(bgpRouteEntry.prefix().getIp6Prefix(), // IPv6
+ bgpRouteEntry);
+ }
+ }
+
+ /**
+ * Removes a BGP route for a prefix. The prefix can be either IPv4 or IPv6.
+ *
+ * @param prefix the prefix to use
+ * @return true if the route was found and removed, otherwise false
+ */
+ boolean removeBgpRoute(IpPrefix prefix) {
+ if (prefix.isIp4()) {
+ return (bgpRoutes4.remove(prefix.getIp4Prefix()) != null); // IPv4
+ }
+ return (bgpRoutes6.remove(prefix.getIp6Prefix()) != null); // IPv6
+ }
+
+ /**
+ * Adds the channel for a BGP session.
+ *
+ * @param channel the channel to add
+ */
+ void addSessionChannel(Channel channel) {
+ allChannels.add(channel);
+ }
+
+ /**
+ * Removes the channel for a BGP session.
+ *
+ * @param channel the channel to remove
+ */
+ void removeSessionChannel(Channel channel) {
+ allChannels.remove(channel);
+ }
+
+ /**
+ * 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.remoteInfo().address()) != null) {
+ return false; // Duplicate BGP session
+ }
+ bgpSessions.put(bgpSession.remoteInfo().address(), bgpSession);
+
+ //
+ // If the first connection, set my BGP ID to the local address
+ // of the socket.
+ //
+ if (bgpSession.localInfo().address() instanceof InetSocketAddress) {
+ InetAddress inetAddr =
+ ((InetSocketAddress) bgpSession.localInfo().address()).getAddress();
+ Ip4Address ip4Address = Ip4Address.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.remoteInfo().address());
+ }
+
+ /**
+ * 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(Ip4Address 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
+ */
+ Ip4Address getMyBgpId() {
+ return myBgpId;
+ }
+
+ /**
+ * Gets the BGP Route Selector.
+ *
+ * @return the BGP Route Selector
+ */
+ BgpRouteSelector getBgpRouteSelector() {
+ return bgpRouteSelector;
+ }
+
+ @Override
+ public void start(RouteListener routeListener) {
+ log.debug("BGP Session Manager start.");
+ isShutdown = false;
+
+ this.routeListener = checkNotNull(routeListener);
+
+ ChannelFactory channelFactory = new NioServerSocketChannelFactory(
+ newCachedThreadPool(groupedThreads("onos/bgp", "sm-boss-%d")),
+ newCachedThreadPool(groupedThreads("onos/bgp", "sm-worker-%d")));
+ 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(bgpPort);
+
+ 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);
+ allChannels.add(serverChannel);
+ } catch (ChannelException e) {
+ log.debug("Exception binding to BGP port {}: ",
+ listenAddress.getPort(), e);
+ }
+ }
+
+ @Override
+ public void stop() {
+ isShutdown = true;
+ allChannels.close().awaitUninterruptibly();
+ serverBootstrap.releaseExternalResources();
+ }
+}
diff --git a/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpUpdate.java b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpUpdate.java
new file mode 100644
index 00000000..babc8507
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/BgpUpdate.java
@@ -0,0 +1,1707 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.routing.bgp;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.Ip6Prefix;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A class for handling BGP UPDATE messages.
+ */
+final class BgpUpdate {
+ private static final Logger log = LoggerFactory.getLogger(BgpUpdate.class);
+
+ /**
+ * Default constructor.
+ * <p>
+ * The constructor is private to prevent creating an instance of
+ * this utility class.
+ */
+ private BgpUpdate() {
+ }
+
+ /**
+ * Processes BGP UPDATE message.
+ *
+ * @param bgpSession the BGP Session to use
+ * @param ctx the Channel Handler Context
+ * @param message the message to process
+ */
+ static void processBgpUpdate(BgpSession bgpSession,
+ ChannelHandlerContext ctx,
+ ChannelBuffer message) {
+ DecodedBgpRoutes decodedBgpRoutes = new DecodedBgpRoutes();
+
+ int minLength =
+ BgpConstants.BGP_UPDATE_MIN_LENGTH - BgpConstants.BGP_HEADER_LENGTH;
+ if (message.readableBytes() < minLength) {
+ log.debug("BGP RX UPDATE Error from {}: " +
+ "Message length {} too short. Must be at least {}",
+ bgpSession.remoteInfo().address(),
+ message.readableBytes(), minLength);
+ //
+ // ERROR: Bad Message Length
+ //
+ // Send NOTIFICATION and close the connection
+ ChannelBuffer txMessage =
+ BgpNotification.prepareBgpNotificationBadMessageLength(
+ message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH);
+ ctx.getChannel().write(txMessage);
+ bgpSession.closeSession(ctx);
+ return;
+ }
+
+ log.debug("BGP RX UPDATE message from {}",
+ bgpSession.remoteInfo().address());
+
+ //
+ // Parse the UPDATE message
+ //
+
+ //
+ // Parse the Withdrawn Routes
+ //
+ int withdrawnRoutesLength = message.readUnsignedShort();
+ if (withdrawnRoutesLength > message.readableBytes()) {
+ // ERROR: Malformed Attribute List
+ actionsBgpUpdateMalformedAttributeList(bgpSession, ctx);
+ return;
+ }
+ Collection<Ip4Prefix> withdrawnPrefixes = null;
+ try {
+ withdrawnPrefixes = parsePackedIp4Prefixes(withdrawnRoutesLength,
+ message);
+ } catch (BgpMessage.BgpParseException e) {
+ // ERROR: Invalid Network Field
+ log.debug("Exception parsing Withdrawn Prefixes from BGP peer {}: ",
+ bgpSession.remoteInfo().bgpId(), e);
+ actionsBgpUpdateInvalidNetworkField(bgpSession, ctx);
+ return;
+ }
+ for (Ip4Prefix prefix : withdrawnPrefixes) {
+ log.debug("BGP RX UPDATE message WITHDRAWN from {}: {}",
+ bgpSession.remoteInfo().address(), prefix);
+ BgpRouteEntry bgpRouteEntry = bgpSession.findBgpRoute(prefix);
+ if (bgpRouteEntry != null) {
+ decodedBgpRoutes.deletedUnicastRoutes4.put(prefix,
+ bgpRouteEntry);
+ }
+ }
+
+ //
+ // Parse the Path Attributes
+ //
+ try {
+ parsePathAttributes(bgpSession, ctx, message, decodedBgpRoutes);
+ } catch (BgpMessage.BgpParseException e) {
+ log.debug("Exception parsing Path Attributes from BGP peer {}: ",
+ bgpSession.remoteInfo().bgpId(), e);
+ // NOTE: The session was already closed, so nothing else to do
+ return;
+ }
+
+ //
+ // Update the BGP RIB-IN
+ //
+ for (Ip4Prefix ip4Prefix :
+ decodedBgpRoutes.deletedUnicastRoutes4.keySet()) {
+ bgpSession.removeBgpRoute(ip4Prefix);
+ }
+ //
+ for (BgpRouteEntry bgpRouteEntry :
+ decodedBgpRoutes.addedUnicastRoutes4.values()) {
+ bgpSession.addBgpRoute(bgpRouteEntry);
+ }
+ //
+ for (Ip6Prefix ip6Prefix :
+ decodedBgpRoutes.deletedUnicastRoutes6.keySet()) {
+ bgpSession.removeBgpRoute(ip6Prefix);
+ }
+ //
+ for (BgpRouteEntry bgpRouteEntry :
+ decodedBgpRoutes.addedUnicastRoutes6.values()) {
+ bgpSession.addBgpRoute(bgpRouteEntry);
+ }
+
+ //
+ // Push the updates to the BGP Merged RIB
+ //
+ BgpRouteSelector bgpRouteSelector =
+ bgpSession.getBgpSessionManager().getBgpRouteSelector();
+ bgpRouteSelector.routeUpdates(bgpSession,
+ decodedBgpRoutes.addedUnicastRoutes4.values(),
+ decodedBgpRoutes.deletedUnicastRoutes4.values());
+ bgpRouteSelector.routeUpdates(bgpSession,
+ decodedBgpRoutes.addedUnicastRoutes6.values(),
+ decodedBgpRoutes.deletedUnicastRoutes6.values());
+
+ // Start the Session Timeout timer
+ bgpSession.restartSessionTimeoutTimer(ctx);
+ }
+
+ /**
+ * Parse BGP Path Attributes from the BGP UPDATE message.
+ *
+ * @param bgpSession the BGP Session to use
+ * @param ctx the Channel Handler Context
+ * @param message the message to parse
+ * @param decodedBgpRoutes the container to store the decoded BGP Route
+ * Entries. It might already contain some route entries such as withdrawn
+ * IPv4 prefixes
+ * @throws BgpMessage.BgpParseException
+ */
+ // CHECKSTYLE IGNORE MethodLength FOR NEXT 300 LINES
+ private static void parsePathAttributes(
+ BgpSession bgpSession,
+ ChannelHandlerContext ctx,
+ ChannelBuffer message,
+ DecodedBgpRoutes decodedBgpRoutes)
+ throws BgpMessage.BgpParseException {
+
+ //
+ // Parsed values
+ //
+ Short origin = -1; // Mandatory
+ BgpRouteEntry.AsPath asPath = null; // Mandatory
+ // Legacy NLRI (RFC 4271). Mandatory NEXT_HOP if legacy NLRI is used
+ MpNlri legacyNlri = new MpNlri(
+ BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV4,
+ BgpConstants.Open.Capabilities.MultiprotocolExtensions.SAFI_UNICAST);
+ long multiExitDisc = // Optional
+ BgpConstants.Update.MultiExitDisc.LOWEST_MULTI_EXIT_DISC;
+ Long localPref = null; // Mandatory
+ Long aggregatorAsNumber = null; // Optional: unused
+ Ip4Address aggregatorIpAddress = null; // Optional: unused
+ Collection<MpNlri> mpNlriReachList = new ArrayList<>(); // Optional
+ Collection<MpNlri> mpNlriUnreachList = new ArrayList<>(); // Optional
+
+ //
+ // Get and verify the Path Attributes Length
+ //
+ int pathAttributeLength = message.readUnsignedShort();
+ if (pathAttributeLength > message.readableBytes()) {
+ // ERROR: Malformed Attribute List
+ actionsBgpUpdateMalformedAttributeList(bgpSession, ctx);
+ String errorMsg = "Malformed Attribute List";
+ throw new BgpMessage.BgpParseException(errorMsg);
+ }
+ if (pathAttributeLength == 0) {
+ return;
+ }
+
+ //
+ // Parse the Path Attributes
+ //
+ int pathAttributeEnd = message.readerIndex() + pathAttributeLength;
+ while (message.readerIndex() < pathAttributeEnd) {
+ int attrFlags = message.readUnsignedByte();
+ if (message.readerIndex() >= pathAttributeEnd) {
+ // ERROR: Malformed Attribute List
+ actionsBgpUpdateMalformedAttributeList(bgpSession, ctx);
+ String errorMsg = "Malformed Attribute List";
+ throw new BgpMessage.BgpParseException(errorMsg);
+ }
+ int attrTypeCode = message.readUnsignedByte();
+
+ // The Attribute Flags
+ boolean optionalBit = ((0x80 & attrFlags) != 0);
+ boolean transitiveBit = ((0x40 & attrFlags) != 0);
+ boolean partialBit = ((0x20 & attrFlags) != 0);
+ boolean extendedLengthBit = ((0x10 & attrFlags) != 0);
+
+ // The Attribute Length
+ int attrLen = 0;
+ int attrLenOctets = 1;
+ if (extendedLengthBit) {
+ attrLenOctets = 2;
+ }
+ if (message.readerIndex() + attrLenOctets > pathAttributeEnd) {
+ // ERROR: Malformed Attribute List
+ actionsBgpUpdateMalformedAttributeList(bgpSession, ctx);
+ String errorMsg = "Malformed Attribute List";
+ throw new BgpMessage.BgpParseException(errorMsg);
+ }
+ if (extendedLengthBit) {
+ attrLen = message.readUnsignedShort();
+ } else {
+ attrLen = message.readUnsignedByte();
+ }
+ if (message.readerIndex() + attrLen > pathAttributeEnd) {
+ // ERROR: Malformed Attribute List
+ actionsBgpUpdateMalformedAttributeList(bgpSession, ctx);
+ String errorMsg = "Malformed Attribute List";
+ throw new BgpMessage.BgpParseException(errorMsg);
+ }
+
+ // Verify the Attribute Flags
+ verifyBgpUpdateAttributeFlags(bgpSession, ctx, attrTypeCode,
+ attrLen, attrFlags, message);
+
+ //
+ // Extract the Attribute Value based on the Attribute Type Code
+ //
+ switch (attrTypeCode) {
+
+ case BgpConstants.Update.Origin.TYPE:
+ // Attribute Type Code ORIGIN
+ origin = parseAttributeTypeOrigin(bgpSession, ctx,
+ attrTypeCode, attrLen,
+ attrFlags, message);
+ break;
+
+ case BgpConstants.Update.AsPath.TYPE:
+ // Attribute Type Code AS_PATH
+ asPath = parseAttributeTypeAsPath(bgpSession, ctx,
+ attrTypeCode, attrLen,
+ attrFlags, message);
+ break;
+
+ case BgpConstants.Update.NextHop.TYPE:
+ // Attribute Type Code NEXT_HOP
+ legacyNlri.nextHop4 =
+ parseAttributeTypeNextHop(bgpSession, ctx,
+ attrTypeCode, attrLen,
+ attrFlags, message);
+ break;
+
+ case BgpConstants.Update.MultiExitDisc.TYPE:
+ // Attribute Type Code MULTI_EXIT_DISC
+ multiExitDisc =
+ parseAttributeTypeMultiExitDisc(bgpSession, ctx,
+ attrTypeCode, attrLen,
+ attrFlags, message);
+ break;
+
+ case BgpConstants.Update.LocalPref.TYPE:
+ // Attribute Type Code LOCAL_PREF
+ localPref =
+ parseAttributeTypeLocalPref(bgpSession, ctx,
+ attrTypeCode, attrLen,
+ attrFlags, message);
+ break;
+
+ case BgpConstants.Update.AtomicAggregate.TYPE:
+ // Attribute Type Code ATOMIC_AGGREGATE
+ parseAttributeTypeAtomicAggregate(bgpSession, ctx,
+ attrTypeCode, attrLen,
+ attrFlags, message);
+ // Nothing to do: this attribute is primarily informational
+ break;
+
+ case BgpConstants.Update.Aggregator.TYPE:
+ // Attribute Type Code AGGREGATOR
+ Pair<Long, Ip4Address> aggregator =
+ parseAttributeTypeAggregator(bgpSession, ctx,
+ attrTypeCode, attrLen,
+ attrFlags, message);
+ aggregatorAsNumber = aggregator.getLeft();
+ aggregatorIpAddress = aggregator.getRight();
+ break;
+
+ case BgpConstants.Update.MpReachNlri.TYPE:
+ // Attribute Type Code MP_REACH_NLRI
+ MpNlri mpNlriReach =
+ parseAttributeTypeMpReachNlri(bgpSession, ctx,
+ attrTypeCode,
+ attrLen,
+ attrFlags, message);
+ if (mpNlriReach != null) {
+ mpNlriReachList.add(mpNlriReach);
+ }
+ break;
+
+ case BgpConstants.Update.MpUnreachNlri.TYPE:
+ // Attribute Type Code MP_UNREACH_NLRI
+ MpNlri mpNlriUnreach =
+ parseAttributeTypeMpUnreachNlri(bgpSession, ctx,
+ attrTypeCode, attrLen,
+ attrFlags, message);
+ if (mpNlriUnreach != null) {
+ mpNlriUnreachList.add(mpNlriUnreach);
+ }
+ break;
+
+ default:
+ // NOTE: Parse any new Attribute Types if needed
+ if (!optionalBit) {
+ // ERROR: Unrecognized Well-known Attribute
+ actionsBgpUpdateUnrecognizedWellKnownAttribute(
+ bgpSession, ctx, attrTypeCode, attrLen, attrFlags,
+ message);
+ String errorMsg = "Unrecognized Well-known Attribute: " +
+ attrTypeCode;
+ throw new BgpMessage.BgpParseException(errorMsg);
+ }
+
+ // Skip the data from the unrecognized attribute
+ log.debug("BGP RX UPDATE message from {}: " +
+ "Unrecognized Attribute Type {}",
+ bgpSession.remoteInfo().address(), attrTypeCode);
+ message.skipBytes(attrLen);
+ break;
+ }
+ }
+
+ //
+ // Parse the NLRI (Network Layer Reachability Information)
+ //
+ int nlriLength = message.readableBytes();
+ try {
+ Collection<Ip4Prefix> addedPrefixes4 =
+ parsePackedIp4Prefixes(nlriLength, message);
+ // Store it inside the legacy NLRI wrapper
+ legacyNlri.nlri4 = addedPrefixes4;
+ } catch (BgpMessage.BgpParseException e) {
+ // ERROR: Invalid Network Field
+ log.debug("Exception parsing NLRI from BGP peer {}: ",
+ bgpSession.remoteInfo().bgpId(), e);
+ actionsBgpUpdateInvalidNetworkField(bgpSession, ctx);
+ // Rethrow the exception
+ throw e;
+ }
+
+ // Verify the Well-known Attributes
+ verifyBgpUpdateWellKnownAttributes(bgpSession, ctx, origin, asPath,
+ localPref, legacyNlri,
+ mpNlriReachList);
+
+ //
+ // Generate the deleted routes
+ //
+ for (MpNlri mpNlri : mpNlriUnreachList) {
+ BgpRouteEntry bgpRouteEntry;
+
+ // The deleted IPv4 routes
+ for (Ip4Prefix prefix : mpNlri.nlri4) {
+ bgpRouteEntry = bgpSession.findBgpRoute(prefix);
+ if (bgpRouteEntry != null) {
+ decodedBgpRoutes.deletedUnicastRoutes4.put(prefix,
+ bgpRouteEntry);
+ }
+ }
+
+ // The deleted IPv6 routes
+ for (Ip6Prefix prefix : mpNlri.nlri6) {
+ bgpRouteEntry = bgpSession.findBgpRoute(prefix);
+ if (bgpRouteEntry != null) {
+ decodedBgpRoutes.deletedUnicastRoutes6.put(prefix,
+ bgpRouteEntry);
+ }
+ }
+ }
+
+ //
+ // Generate the added routes
+ //
+ mpNlriReachList.add(legacyNlri);
+ for (MpNlri mpNlri : mpNlriReachList) {
+ BgpRouteEntry bgpRouteEntry;
+
+ // The added IPv4 routes
+ for (Ip4Prefix prefix : mpNlri.nlri4) {
+ bgpRouteEntry =
+ new BgpRouteEntry(bgpSession, prefix, mpNlri.nextHop4,
+ origin.byteValue(), asPath, localPref);
+ bgpRouteEntry.setMultiExitDisc(multiExitDisc);
+ if (bgpRouteEntry.hasAsPathLoop(bgpSession.localInfo().asNumber())) {
+ log.debug("BGP RX UPDATE message IGNORED from {}: {} " +
+ "nextHop {}: contains AS Path loop",
+ bgpSession.remoteInfo().address(), prefix,
+ mpNlri.nextHop4);
+ continue;
+ } else {
+ log.debug("BGP RX UPDATE message ADDED from {}: {} nextHop {}",
+ bgpSession.remoteInfo().address(), prefix,
+ mpNlri.nextHop4);
+ }
+ // Remove from the collection of deleted routes
+ decodedBgpRoutes.deletedUnicastRoutes4.remove(prefix);
+ decodedBgpRoutes.addedUnicastRoutes4.put(prefix,
+ bgpRouteEntry);
+ }
+
+ // The added IPv6 routes
+ for (Ip6Prefix prefix : mpNlri.nlri6) {
+ bgpRouteEntry =
+ new BgpRouteEntry(bgpSession, prefix, mpNlri.nextHop6,
+ origin.byteValue(), asPath, localPref);
+ bgpRouteEntry.setMultiExitDisc(multiExitDisc);
+ if (bgpRouteEntry.hasAsPathLoop(bgpSession.localInfo().asNumber())) {
+ log.debug("BGP RX UPDATE message IGNORED from {}: {} " +
+ "nextHop {}: contains AS Path loop",
+ bgpSession.remoteInfo().address(), prefix,
+ mpNlri.nextHop6);
+ continue;
+ } else {
+ log.debug("BGP RX UPDATE message ADDED from {}: {} nextHop {}",
+ bgpSession.remoteInfo().address(), prefix,
+ mpNlri.nextHop6);
+ }
+ // Remove from the collection of deleted routes
+ decodedBgpRoutes.deletedUnicastRoutes6.remove(prefix);
+ decodedBgpRoutes.addedUnicastRoutes6.put(prefix,
+ bgpRouteEntry);
+ }
+ }
+ }
+
+ /**
+ * Verifies BGP UPDATE Well-known Attributes.
+ *
+ * @param bgpSession the BGP Session to use
+ * @param ctx the Channel Handler Context
+ * @param origin the ORIGIN well-known mandatory attribute
+ * @param asPath the AS_PATH well-known mandatory attribute
+ * @param localPref the LOCAL_PREF required attribute
+ * @param legacyNlri the legacy NLRI. Encapsulates the NEXT_HOP well-known
+ * mandatory attribute (mandatory if legacy NLRI is used).
+ * @param mpNlriReachList the Multiprotocol NLRI attributes
+ * @throws BgpMessage.BgpParseException
+ */
+ private static void verifyBgpUpdateWellKnownAttributes(
+ BgpSession bgpSession,
+ ChannelHandlerContext ctx,
+ Short origin,
+ BgpRouteEntry.AsPath asPath,
+ Long localPref,
+ MpNlri legacyNlri,
+ Collection<MpNlri> mpNlriReachList)
+ throws BgpMessage.BgpParseException {
+ boolean hasNlri = false;
+ boolean hasLegacyNlri = false;
+
+ //
+ // Convenience flags that are used to check for missing attributes.
+ //
+ // NOTE: The hasLegacyNlri flag is always set to true if the
+ // Multiprotocol Extensions are not enabled, even if the UPDATE
+ // message doesn't contain the legacy NLRI (per RFC 4271).
+ //
+ if (!bgpSession.mpExtensions()) {
+ hasNlri = true;
+ hasLegacyNlri = true;
+ } else {
+ if (!legacyNlri.nlri4.isEmpty()) {
+ hasNlri = true;
+ hasLegacyNlri = true;
+ }
+ if (!mpNlriReachList.isEmpty()) {
+ hasNlri = true;
+ }
+ }
+
+ //
+ // Check for Missing Well-known Attributes
+ //
+ if (hasNlri && ((origin == null) || (origin == -1))) {
+ // Missing Attribute Type Code ORIGIN
+ int type = BgpConstants.Update.Origin.TYPE;
+ actionsBgpUpdateMissingWellKnownAttribute(bgpSession, ctx, type);
+ String errorMsg = "Missing Well-known Attribute: ORIGIN";
+ throw new BgpMessage.BgpParseException(errorMsg);
+ }
+ if (hasNlri && (asPath == null)) {
+ // Missing Attribute Type Code AS_PATH
+ int type = BgpConstants.Update.AsPath.TYPE;
+ actionsBgpUpdateMissingWellKnownAttribute(bgpSession, ctx, type);
+ String errorMsg = "Missing Well-known Attribute: AS_PATH";
+ throw new BgpMessage.BgpParseException(errorMsg);
+ }
+ if (hasNlri && (localPref == null)) {
+ // Missing Attribute Type Code LOCAL_PREF
+ // NOTE: Required for iBGP
+ int type = BgpConstants.Update.LocalPref.TYPE;
+ actionsBgpUpdateMissingWellKnownAttribute(bgpSession, ctx, type);
+ String errorMsg = "Missing Well-known Attribute: LOCAL_PREF";
+ throw new BgpMessage.BgpParseException(errorMsg);
+ }
+ if (hasLegacyNlri && (legacyNlri.nextHop4 == null)) {
+ // Missing Attribute Type Code NEXT_HOP
+ int type = BgpConstants.Update.NextHop.TYPE;
+ actionsBgpUpdateMissingWellKnownAttribute(bgpSession, ctx, type);
+ String errorMsg = "Missing Well-known Attribute: NEXT_HOP";
+ throw new BgpMessage.BgpParseException(errorMsg);
+ }
+ }
+
+ /**
+ * Verifies the BGP UPDATE Attribute Flags.
+ *
+ * @param bgpSession the BGP Session to use
+ * @param ctx the Channel Handler Context
+ * @param attrTypeCode the attribute type code
+ * @param attrLen the attribute length (in octets)
+ * @param attrFlags the attribute flags
+ * @param message the message to parse
+ * @throws BgpMessage.BgpParseException
+ */
+ private static void verifyBgpUpdateAttributeFlags(
+ BgpSession bgpSession,
+ ChannelHandlerContext ctx,
+ int attrTypeCode,
+ int attrLen,
+ int attrFlags,
+ ChannelBuffer message)
+ throws BgpMessage.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;
+ case BgpConstants.Update.MpReachNlri.TYPE:
+ isWellKnown = false;
+ typeName = "MP_REACH_NLRI";
+ break;
+ case BgpConstants.Update.MpUnreachNlri.TYPE:
+ isWellKnown = false;
+ typeName = "MP_UNREACH_NLRI";
+ break;
+ default:
+ isWellKnown = false;
+ typeName = "UNKNOWN(" + attrTypeCode + ")";
+ break;
+ }
+
+ //
+ // Verify the Attribute Flags
+ //
+ boolean optionalBit = ((0x80 & attrFlags) != 0);
+ boolean transitiveBit = ((0x40 & attrFlags) != 0);
+ boolean partialBit = ((0x20 & attrFlags) != 0);
+ if ((isWellKnown && optionalBit) ||
+ (isWellKnown && (!transitiveBit)) ||
+ (isWellKnown && partialBit) ||
+ (optionalBit && (!transitiveBit) && partialBit)) {
+ //
+ // ERROR: The Optional bit cannot be set for Well-known attributes
+ // ERROR: The Transtive bit MUST be 1 for well-known attributes
+ // ERROR: The Partial bit MUST be 0 for well-known attributes
+ // ERROR: The Partial bit MUST be 0 for optional non-transitive
+ // attributes
+ //
+ actionsBgpUpdateAttributeFlagsError(
+ bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
+ String errorMsg = "Attribute Flags Error for " + typeName + ": " +
+ attrFlags;
+ throw new BgpMessage.BgpParseException(errorMsg);
+ }
+ }
+
+ /**
+ * Parses BGP UPDATE Attribute Type ORIGIN.
+ *
+ * @param bgpSession the BGP Session to use
+ * @param ctx the Channel Handler Context
+ * @param attrTypeCode the attribute type code
+ * @param attrLen the attribute length (in octets)
+ * @param attrFlags the attribute flags
+ * @param message the message to parse
+ * @return the parsed ORIGIN value
+ * @throws BgpMessage.BgpParseException
+ */
+ private static short parseAttributeTypeOrigin(
+ BgpSession bgpSession,
+ ChannelHandlerContext ctx,
+ int attrTypeCode,
+ int attrLen,
+ int attrFlags,
+ ChannelBuffer message)
+ throws BgpMessage.BgpParseException {
+
+ // Check the Attribute Length
+ if (attrLen != BgpConstants.Update.Origin.LENGTH) {
+ // ERROR: Attribute Length Error
+ actionsBgpUpdateAttributeLengthError(
+ bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
+ String errorMsg = "Attribute Length Error";
+ throw new BgpMessage.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(
+ bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message,
+ origin);
+ String errorMsg = "Invalid ORIGIN Attribute: " + origin;
+ throw new BgpMessage.BgpParseException(errorMsg);
+ }
+
+ return origin;
+ }
+
+ /**
+ * Parses BGP UPDATE Attribute AS Path.
+ *
+ * @param bgpSession the BGP Session to use
+ * @param ctx the Channel Handler Context
+ * @param attrTypeCode the attribute type code
+ * @param attrLen the attribute length (in octets)
+ * @param attrFlags the attribute flags
+ * @param message the message to parse
+ * @return the parsed AS Path
+ * @throws BgpMessage.BgpParseException
+ */
+ private static BgpRouteEntry.AsPath parseAttributeTypeAsPath(
+ BgpSession bgpSession,
+ ChannelHandlerContext ctx,
+ int attrTypeCode,
+ int attrLen,
+ int attrFlags,
+ ChannelBuffer message)
+ throws BgpMessage.BgpParseException {
+ ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
+
+ //
+ // Parse the message
+ //
+ while (attrLen > 0) {
+ if (attrLen < 2) {
+ // ERROR: Malformed AS_PATH
+ actionsBgpUpdateMalformedAsPath(bgpSession, ctx);
+ String errorMsg = "Malformed AS Path";
+ throw new BgpMessage.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:
+ // FALLTHROUGH
+ case BgpConstants.Update.AsPath.AS_CONFED_SEQUENCE:
+ // FALLTHROUGH
+ case BgpConstants.Update.AsPath.AS_CONFED_SET:
+ break;
+ default:
+ // ERROR: Invalid Path Segment Type
+ //
+ // NOTE: The BGP Spec (RFC 4271) doesn't contain Error Subcode
+ // for "Invalid Path Segment Type", hence we return
+ // the error as "Malformed AS_PATH".
+ //
+ actionsBgpUpdateMalformedAsPath(bgpSession, ctx);
+ String errorMsg =
+ "Invalid AS Path Segment Type: " + pathSegmentType;
+ throw new BgpMessage.BgpParseException(errorMsg);
+ }
+
+ // 4-octet AS number handling.
+ int asPathLen;
+ if (bgpSession.isAs4OctetCapable()) {
+ asPathLen = BgpConstants.Update.AS_4OCTET_LENGTH;
+ } else {
+ asPathLen = BgpConstants.Update.AS_LENGTH;
+ }
+
+ // Parse the AS numbers
+ if (asPathLen * pathSegmentLength > attrLen) {
+ // ERROR: Malformed AS_PATH
+ actionsBgpUpdateMalformedAsPath(bgpSession, ctx);
+ String errorMsg = "Malformed AS Path";
+ throw new BgpMessage.BgpParseException(errorMsg);
+ }
+ attrLen -= (asPathLen * pathSegmentLength);
+ ArrayList<Long> segmentAsNumbers = new ArrayList<>();
+ while (pathSegmentLength-- > 0) {
+ long asNumber;
+ if (asPathLen == BgpConstants.Update.AS_4OCTET_LENGTH) {
+ asNumber = message.readUnsignedInt();
+ } else {
+ asNumber = message.readUnsignedShort();
+ }
+ segmentAsNumbers.add(asNumber);
+ }
+
+ BgpRouteEntry.PathSegment pathSegment =
+ new BgpRouteEntry.PathSegment((byte) pathSegmentType,
+ segmentAsNumbers);
+ pathSegments.add(pathSegment);
+ }
+
+ return new BgpRouteEntry.AsPath(pathSegments);
+ }
+
+ /**
+ * Parses BGP UPDATE Attribute Type NEXT_HOP.
+ *
+ * @param bgpSession the BGP Session to use
+ * @param ctx the Channel Handler Context
+ * @param attrTypeCode the attribute type code
+ * @param attrLen the attribute length (in octets)
+ * @param attrFlags the attribute flags
+ * @param message the message to parse
+ * @return the parsed NEXT_HOP value
+ * @throws BgpMessage.BgpParseException
+ */
+ private static Ip4Address parseAttributeTypeNextHop(
+ BgpSession bgpSession,
+ ChannelHandlerContext ctx,
+ int attrTypeCode,
+ int attrLen,
+ int attrFlags,
+ ChannelBuffer message)
+ throws BgpMessage.BgpParseException {
+
+ // Check the Attribute Length
+ if (attrLen != BgpConstants.Update.NextHop.LENGTH) {
+ // ERROR: Attribute Length Error
+ actionsBgpUpdateAttributeLengthError(
+ bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
+ String errorMsg = "Attribute Length Error";
+ throw new BgpMessage.BgpParseException(errorMsg);
+ }
+
+ message.markReaderIndex();
+ Ip4Address nextHopAddress =
+ Ip4Address.valueOf((int) message.readUnsignedInt());
+ //
+ // Check whether the NEXT_HOP IP address is semantically correct.
+ // As per RFC 4271, Section 6.3:
+ //
+ // a) It MUST NOT be the IP address of the receiving speaker
+ // b) In the case of an EBGP ....
+ //
+ // Here we check only (a), because (b) doesn't apply for us: all our
+ // peers are iBGP.
+ //
+ if (nextHopAddress.equals(bgpSession.localInfo().ip4Address())) {
+ // ERROR: Invalid NEXT_HOP Attribute
+ message.resetReaderIndex();
+ actionsBgpUpdateInvalidNextHopAttribute(
+ bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message,
+ nextHopAddress);
+ String errorMsg = "Invalid NEXT_HOP Attribute: " + nextHopAddress;
+ throw new BgpMessage.BgpParseException(errorMsg);
+ }
+
+ return nextHopAddress;
+ }
+
+ /**
+ * Parses BGP UPDATE Attribute Type MULTI_EXIT_DISC.
+ *
+ * @param bgpSession the BGP Session to use
+ * @param ctx the Channel Handler Context
+ * @param attrTypeCode the attribute type code
+ * @param attrLen the attribute length (in octets)
+ * @param attrFlags the attribute flags
+ * @param message the message to parse
+ * @return the parsed MULTI_EXIT_DISC value
+ * @throws BgpMessage.BgpParseException
+ */
+ private static long parseAttributeTypeMultiExitDisc(
+ BgpSession bgpSession,
+ ChannelHandlerContext ctx,
+ int attrTypeCode,
+ int attrLen,
+ int attrFlags,
+ ChannelBuffer message)
+ throws BgpMessage.BgpParseException {
+
+ // Check the Attribute Length
+ if (attrLen != BgpConstants.Update.MultiExitDisc.LENGTH) {
+ // ERROR: Attribute Length Error
+ actionsBgpUpdateAttributeLengthError(
+ bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
+ String errorMsg = "Attribute Length Error";
+ throw new BgpMessage.BgpParseException(errorMsg);
+ }
+
+ long multiExitDisc = message.readUnsignedInt();
+ return multiExitDisc;
+ }
+
+ /**
+ * Parses BGP UPDATE Attribute Type LOCAL_PREF.
+ *
+ * @param bgpSession the BGP Session to use
+ * @param ctx the Channel Handler Context
+ * @param attrTypeCode the attribute type code
+ * @param attrLen the attribute length (in octets)
+ * @param attrFlags the attribute flags
+ * @param message the message to parse
+ * @return the parsed LOCAL_PREF value
+ * @throws BgpMessage.BgpParseException
+ */
+ private static long parseAttributeTypeLocalPref(
+ BgpSession bgpSession,
+ ChannelHandlerContext ctx,
+ int attrTypeCode,
+ int attrLen,
+ int attrFlags,
+ ChannelBuffer message)
+ throws BgpMessage.BgpParseException {
+
+ // Check the Attribute Length
+ if (attrLen != BgpConstants.Update.LocalPref.LENGTH) {
+ // ERROR: Attribute Length Error
+ actionsBgpUpdateAttributeLengthError(
+ bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
+ String errorMsg = "Attribute Length Error";
+ throw new BgpMessage.BgpParseException(errorMsg);
+ }
+
+ long localPref = message.readUnsignedInt();
+ return localPref;
+ }
+
+ /**
+ * Parses BGP UPDATE Attribute Type ATOMIC_AGGREGATE.
+ *
+ * @param bgpSession the BGP Session to use
+ * @param ctx the Channel Handler Context
+ * @param attrTypeCode the attribute type code
+ * @param attrLen the attribute length (in octets)
+ * @param attrFlags the attribute flags
+ * @param message the message to parse
+ * @throws BgpMessage.BgpParseException
+ */
+ private static void parseAttributeTypeAtomicAggregate(
+ BgpSession bgpSession,
+ ChannelHandlerContext ctx,
+ int attrTypeCode,
+ int attrLen,
+ int attrFlags,
+ ChannelBuffer message)
+ throws BgpMessage.BgpParseException {
+
+ // Check the Attribute Length
+ if (attrLen != BgpConstants.Update.AtomicAggregate.LENGTH) {
+ // ERROR: Attribute Length Error
+ actionsBgpUpdateAttributeLengthError(
+ bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
+ String errorMsg = "Attribute Length Error";
+ throw new BgpMessage.BgpParseException(errorMsg);
+ }
+
+ // Nothing to do: this attribute is primarily informational
+ }
+
+ /**
+ * Parses BGP UPDATE Attribute Type AGGREGATOR.
+ *
+ * @param bgpSession the BGP Session to use
+ * @param ctx the Channel Handler Context
+ * @param attrTypeCode the attribute type code
+ * @param attrLen the attribute length (in octets)
+ * @param attrFlags the attribute flags
+ * @param message the message to parse
+ * @return the parsed AGGREGATOR value: a tuple of <AS-Number, IP-Address>
+ * @throws BgpMessage.BgpParseException
+ */
+ private static Pair<Long, Ip4Address> parseAttributeTypeAggregator(
+ BgpSession bgpSession,
+ ChannelHandlerContext ctx,
+ int attrTypeCode,
+ int attrLen,
+ int attrFlags,
+ ChannelBuffer message)
+ throws BgpMessage.BgpParseException {
+ int expectedAttrLen;
+
+ if (bgpSession.isAs4OctetCapable()) {
+ expectedAttrLen = BgpConstants.Update.Aggregator.AS4_LENGTH;
+ } else {
+ expectedAttrLen = BgpConstants.Update.Aggregator.AS2_LENGTH;
+ }
+
+ // Check the Attribute Length
+ if (attrLen != expectedAttrLen) {
+ // ERROR: Attribute Length Error
+ actionsBgpUpdateAttributeLengthError(
+ bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
+ String errorMsg = "Attribute Length Error";
+ throw new BgpMessage.BgpParseException(errorMsg);
+ }
+
+ // The AGGREGATOR AS number
+ long aggregatorAsNumber;
+ if (bgpSession.isAs4OctetCapable()) {
+ aggregatorAsNumber = message.readUnsignedInt();
+ } else {
+ aggregatorAsNumber = message.readUnsignedShort();
+ }
+ // The AGGREGATOR IP address
+ Ip4Address aggregatorIpAddress =
+ Ip4Address.valueOf((int) message.readUnsignedInt());
+
+ Pair<Long, Ip4Address> aggregator = Pair.of(aggregatorAsNumber,
+ aggregatorIpAddress);
+ return aggregator;
+ }
+
+ /**
+ * Parses BGP UPDATE Attribute Type MP_REACH_NLRI.
+ *
+ * @param bgpSession the BGP Session to use
+ * @param ctx the Channel Handler Context
+ * @param attrTypeCode the attribute type code
+ * @param attrLen the attribute length (in octets)
+ * @param attrFlags the attribute flags
+ * @param message the message to parse
+ * @return the parsed MP_REACH_NLRI information if recognized, otherwise
+ * null
+ * @throws BgpMessage.BgpParseException
+ */
+ private static MpNlri parseAttributeTypeMpReachNlri(
+ BgpSession bgpSession,
+ ChannelHandlerContext ctx,
+ int attrTypeCode,
+ int attrLen,
+ int attrFlags,
+ ChannelBuffer message)
+ throws BgpMessage.BgpParseException {
+ int attributeEnd = message.readerIndex() + attrLen;
+
+ // Check the Attribute Length
+ if (attrLen < BgpConstants.Update.MpReachNlri.MIN_LENGTH) {
+ // ERROR: Attribute Length Error
+ actionsBgpUpdateAttributeLengthError(
+ bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
+ String errorMsg = "Attribute Length Error";
+ throw new BgpMessage.BgpParseException(errorMsg);
+ }
+
+ message.markReaderIndex();
+ int afi = message.readUnsignedShort();
+ int safi = message.readUnsignedByte();
+ int nextHopLen = message.readUnsignedByte();
+
+ //
+ // Verify the AFI/SAFI, and skip the attribute if not recognized.
+ // NOTE: Currently, we support only IPv4/IPv6 UNICAST
+ //
+ if (((afi != BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV4) &&
+ (afi != BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV6)) ||
+ (safi != BgpConstants.Open.Capabilities.MultiprotocolExtensions.SAFI_UNICAST)) {
+ // Skip the attribute
+ message.resetReaderIndex();
+ message.skipBytes(attrLen);
+ return null;
+ }
+
+ //
+ // Verify the next-hop length
+ //
+ int expectedNextHopLen = 0;
+ switch (afi) {
+ case BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV4:
+ expectedNextHopLen = Ip4Address.BYTE_LENGTH;
+ break;
+ case BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV6:
+ expectedNextHopLen = Ip6Address.BYTE_LENGTH;
+ break;
+ default:
+ // UNREACHABLE
+ break;
+ }
+ if (nextHopLen != expectedNextHopLen) {
+ // ERROR: Optional Attribute Error
+ message.resetReaderIndex();
+ actionsBgpUpdateOptionalAttributeError(
+ bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
+ String errorMsg = "Invalid next-hop network address length. " +
+ "Received " + nextHopLen + " expected " + expectedNextHopLen;
+ throw new BgpMessage.BgpParseException(errorMsg);
+ }
+ // NOTE: We use "+ 1" to take into account the Reserved field (1 octet)
+ if (message.readerIndex() + nextHopLen + 1 >= attributeEnd) {
+ // ERROR: Optional Attribute Error
+ message.resetReaderIndex();
+ actionsBgpUpdateOptionalAttributeError(
+ bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
+ String errorMsg = "Malformed next-hop network address";
+ throw new BgpMessage.BgpParseException(errorMsg);
+ }
+
+ //
+ // Get the Next-hop address, skip the Reserved field, and get the NLRI
+ //
+ byte[] nextHopBuffer = new byte[nextHopLen];
+ message.readBytes(nextHopBuffer, 0, nextHopLen);
+ int reserved = message.readUnsignedByte();
+ MpNlri mpNlri = new MpNlri(afi, safi);
+ try {
+ switch (afi) {
+ case BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV4:
+ // The next-hop address
+ mpNlri.nextHop4 = Ip4Address.valueOf(nextHopBuffer);
+ // The NLRI
+ mpNlri.nlri4 = parsePackedIp4Prefixes(
+ attributeEnd - message.readerIndex(),
+ message);
+ break;
+ case BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV6:
+ // The next-hop address
+ mpNlri.nextHop6 = Ip6Address.valueOf(nextHopBuffer);
+ // The NLRI
+ mpNlri.nlri6 = parsePackedIp6Prefixes(
+ attributeEnd - message.readerIndex(),
+ message);
+ break;
+ default:
+ // UNREACHABLE
+ break;
+ }
+ } catch (BgpMessage.BgpParseException e) {
+ // ERROR: Optional Attribute Error
+ message.resetReaderIndex();
+ actionsBgpUpdateOptionalAttributeError(
+ bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
+ String errorMsg = "Malformed network layer reachability information";
+ throw new BgpMessage.BgpParseException(errorMsg);
+ }
+
+ return mpNlri;
+ }
+
+ /**
+ * Parses BGP UPDATE Attribute Type MP_UNREACH_NLRI.
+ *
+ * @param bgpSession the BGP Session to use
+ * @param ctx the Channel Handler Context
+ * @param attrTypeCode the attribute type code
+ * @param attrLen the attribute length (in octets)
+ * @param attrFlags the attribute flags
+ * @param message the message to parse
+ * @return the parsed MP_UNREACH_NLRI information if recognized, otherwise
+ * null
+ * @throws BgpMessage.BgpParseException
+ */
+ private static MpNlri parseAttributeTypeMpUnreachNlri(
+ BgpSession bgpSession,
+ ChannelHandlerContext ctx,
+ int attrTypeCode,
+ int attrLen,
+ int attrFlags,
+ ChannelBuffer message)
+ throws BgpMessage.BgpParseException {
+ int attributeEnd = message.readerIndex() + attrLen;
+
+ // Check the Attribute Length
+ if (attrLen < BgpConstants.Update.MpUnreachNlri.MIN_LENGTH) {
+ // ERROR: Attribute Length Error
+ actionsBgpUpdateAttributeLengthError(
+ bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
+ String errorMsg = "Attribute Length Error";
+ throw new BgpMessage.BgpParseException(errorMsg);
+ }
+
+ message.markReaderIndex();
+ int afi = message.readUnsignedShort();
+ int safi = message.readUnsignedByte();
+
+ //
+ // Verify the AFI/SAFI, and skip the attribute if not recognized.
+ // NOTE: Currently, we support only IPv4/IPv6 UNICAST
+ //
+ if (((afi != BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV4) &&
+ (afi != BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV6)) ||
+ (safi != BgpConstants.Open.Capabilities.MultiprotocolExtensions.SAFI_UNICAST)) {
+ // Skip the attribute
+ message.resetReaderIndex();
+ message.skipBytes(attrLen);
+ return null;
+ }
+
+ //
+ // Get the Withdrawn Routes
+ //
+ MpNlri mpNlri = new MpNlri(afi, safi);
+ try {
+ switch (afi) {
+ case BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV4:
+ // The Withdrawn Routes
+ mpNlri.nlri4 = parsePackedIp4Prefixes(
+ attributeEnd - message.readerIndex(),
+ message);
+ break;
+ case BgpConstants.Open.Capabilities.MultiprotocolExtensions.AFI_IPV6:
+ // The Withdrawn Routes
+ mpNlri.nlri6 = parsePackedIp6Prefixes(
+ attributeEnd - message.readerIndex(),
+ message);
+ break;
+ default:
+ // UNREACHABLE
+ break;
+ }
+ } catch (BgpMessage.BgpParseException e) {
+ // ERROR: Optional Attribute Error
+ message.resetReaderIndex();
+ actionsBgpUpdateOptionalAttributeError(
+ bgpSession, ctx, attrTypeCode, attrLen, attrFlags, message);
+ String errorMsg = "Malformed withdrawn routes";
+ throw new BgpMessage.BgpParseException(errorMsg);
+ }
+
+ return mpNlri;
+ }
+
+ /**
+ * Parses a message that contains encoded IPv4 network prefixes.
+ * <p>
+ * The IPv4 prefixes are encoded in the form:
+ * <Length, Prefix> where Length is the length in bits of the IPv4 prefix,
+ * and Prefix is the IPv4 prefix (padded with trailing bits to the end
+ * of an octet).
+ *
+ * @param totalLength the total length of the data to parse
+ * @param message the message with data to parse
+ * @return a collection of parsed IPv4 network prefixes
+ * @throws BgpMessage.BgpParseException
+ */
+ private static Collection<Ip4Prefix> parsePackedIp4Prefixes(
+ int totalLength,
+ ChannelBuffer message)
+ throws BgpMessage.BgpParseException {
+ Collection<Ip4Prefix> result = new ArrayList<>();
+
+ if (totalLength == 0) {
+ return result;
+ }
+
+ // Parse the data
+ byte[] buffer = new byte[Ip4Address.BYTE_LENGTH];
+ int dataEnd = message.readerIndex() + totalLength;
+ while (message.readerIndex() < dataEnd) {
+ int prefixBitlen = message.readUnsignedByte();
+ int prefixBytelen = (prefixBitlen + 7) / 8; // Round-up
+ if (message.readerIndex() + prefixBytelen > dataEnd) {
+ String errorMsg = "Malformed Network Prefixes";
+ throw new BgpMessage.BgpParseException(errorMsg);
+ }
+
+ message.readBytes(buffer, 0, prefixBytelen);
+ Ip4Prefix prefix = Ip4Prefix.valueOf(Ip4Address.valueOf(buffer),
+ prefixBitlen);
+ result.add(prefix);
+ }
+
+ return result;
+ }
+
+ /**
+ * Parses a message that contains encoded IPv6 network prefixes.
+ * <p>
+ * The IPv6 prefixes are encoded in the form:
+ * <Length, Prefix> where Length is the length in bits of the IPv6 prefix,
+ * and Prefix is the IPv6 prefix (padded with trailing bits to the end
+ * of an octet).
+ *
+ * @param totalLength the total length of the data to parse
+ * @param message the message with data to parse
+ * @return a collection of parsed IPv6 network prefixes
+ * @throws BgpMessage.BgpParseException
+ */
+ private static Collection<Ip6Prefix> parsePackedIp6Prefixes(
+ int totalLength,
+ ChannelBuffer message)
+ throws BgpMessage.BgpParseException {
+ Collection<Ip6Prefix> result = new ArrayList<>();
+
+ if (totalLength == 0) {
+ return result;
+ }
+
+ // Parse the data
+ byte[] buffer = new byte[Ip6Address.BYTE_LENGTH];
+ int dataEnd = message.readerIndex() + totalLength;
+ while (message.readerIndex() < dataEnd) {
+ int prefixBitlen = message.readUnsignedByte();
+ int prefixBytelen = (prefixBitlen + 7) / 8; // Round-up
+ if (message.readerIndex() + prefixBytelen > dataEnd) {
+ String errorMsg = "Malformed Network Prefixes";
+ throw new BgpMessage.BgpParseException(errorMsg);
+ }
+
+ message.readBytes(buffer, 0, prefixBytelen);
+ Ip6Prefix prefix = Ip6Prefix.valueOf(Ip6Address.valueOf(buffer),
+ prefixBitlen);
+ result.add(prefix);
+ }
+
+ return result;
+ }
+
+ /**
+ * Applies the appropriate actions after detecting BGP UPDATE
+ * Invalid Network Field Error: send NOTIFICATION and close the channel.
+ *
+ * @param bgpSession the BGP Session to use
+ * @param ctx the Channel Handler Context
+ */
+ private static void actionsBgpUpdateInvalidNetworkField(
+ BgpSession bgpSession,
+ ChannelHandlerContext ctx) {
+ log.debug("BGP RX UPDATE Error from {}: Invalid Network Field",
+ bgpSession.remoteInfo().address());
+
+ //
+ // ERROR: Invalid Network Field
+ //
+ // Send NOTIFICATION and close the connection
+ int errorCode = BgpConstants.Notifications.UpdateMessageError.ERROR_CODE;
+ int errorSubcode = BgpConstants.Notifications.UpdateMessageError.INVALID_NETWORK_FIELD;
+ ChannelBuffer txMessage =
+ BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
+ null);
+ ctx.getChannel().write(txMessage);
+ bgpSession.closeSession(ctx);
+ }
+
+ /**
+ * Applies the appropriate actions after detecting BGP UPDATE
+ * Malformed Attribute List Error: send NOTIFICATION and close the channel.
+ *
+ * @param bgpSession the BGP Session to use
+ * @param ctx the Channel Handler Context
+ */
+ private static void actionsBgpUpdateMalformedAttributeList(
+ BgpSession bgpSession,
+ ChannelHandlerContext ctx) {
+ log.debug("BGP RX UPDATE Error from {}: Malformed Attribute List",
+ bgpSession.remoteInfo().address());
+
+ //
+ // ERROR: Malformed Attribute List
+ //
+ // Send NOTIFICATION and close the connection
+ int errorCode = BgpConstants.Notifications.UpdateMessageError.ERROR_CODE;
+ int errorSubcode = BgpConstants.Notifications.UpdateMessageError.MALFORMED_ATTRIBUTE_LIST;
+ ChannelBuffer txMessage =
+ BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
+ null);
+ ctx.getChannel().write(txMessage);
+ bgpSession.closeSession(ctx);
+ }
+
+ /**
+ * Applies the appropriate actions after detecting BGP UPDATE
+ * Missing Well-known Attribute Error: send NOTIFICATION and close the
+ * channel.
+ *
+ * @param bgpSession the BGP Session to use
+ * @param ctx the Channel Handler Context
+ * @param missingAttrTypeCode the missing attribute type code
+ */
+ private static void actionsBgpUpdateMissingWellKnownAttribute(
+ BgpSession bgpSession,
+ ChannelHandlerContext ctx,
+ int missingAttrTypeCode) {
+ log.debug("BGP RX UPDATE Error from {}: Missing Well-known Attribute: {}",
+ bgpSession.remoteInfo().address(), missingAttrTypeCode);
+
+ //
+ // ERROR: Missing Well-known Attribute
+ //
+ // Send NOTIFICATION and close the connection
+ int errorCode = BgpConstants.Notifications.UpdateMessageError.ERROR_CODE;
+ int errorSubcode = BgpConstants.Notifications.UpdateMessageError.MISSING_WELL_KNOWN_ATTRIBUTE;
+ ChannelBuffer data = ChannelBuffers.buffer(1);
+ data.writeByte(missingAttrTypeCode);
+ ChannelBuffer txMessage =
+ BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
+ data);
+ ctx.getChannel().write(txMessage);
+ bgpSession.closeSession(ctx);
+ }
+
+ /**
+ * Applies the appropriate actions after detecting BGP UPDATE
+ * Invalid ORIGIN Attribute Error: send NOTIFICATION and close the channel.
+ *
+ * @param bgpSession the BGP Session to use
+ * @param ctx the Channel Handler Context
+ * @param attrTypeCode the attribute type code
+ * @param attrLen the attribute length (in octets)
+ * @param attrFlags the attribute flags
+ * @param message the message with the data
+ * @param origin the ORIGIN attribute value
+ */
+ private static void actionsBgpUpdateInvalidOriginAttribute(
+ BgpSession bgpSession,
+ ChannelHandlerContext ctx,
+ int attrTypeCode,
+ int attrLen,
+ int attrFlags,
+ ChannelBuffer message,
+ short origin) {
+ log.debug("BGP RX UPDATE Error from {}: Invalid ORIGIN Attribute",
+ bgpSession.remoteInfo().address());
+
+ //
+ // ERROR: Invalid ORIGIN Attribute
+ //
+ // Send NOTIFICATION and close the connection
+ int errorCode = BgpConstants.Notifications.UpdateMessageError.ERROR_CODE;
+ int errorSubcode = BgpConstants.Notifications.UpdateMessageError.INVALID_ORIGIN_ATTRIBUTE;
+ ChannelBuffer data =
+ prepareBgpUpdateNotificationDataPayload(attrTypeCode, attrLen,
+ attrFlags, message);
+ ChannelBuffer txMessage =
+ BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
+ data);
+ ctx.getChannel().write(txMessage);
+ bgpSession.closeSession(ctx);
+ }
+
+ /**
+ * Applies the appropriate actions after detecting BGP UPDATE
+ * Attribute Flags Error: send NOTIFICATION and close the channel.
+ *
+ * @param bgpSession the BGP Session to use
+ * @param ctx the Channel Handler Context
+ * @param attrTypeCode the attribute type code
+ * @param attrLen the attribute length (in octets)
+ * @param attrFlags the attribute flags
+ * @param message the message with the data
+ */
+ private static void actionsBgpUpdateAttributeFlagsError(
+ BgpSession bgpSession,
+ ChannelHandlerContext ctx,
+ int attrTypeCode,
+ int attrLen,
+ int attrFlags,
+ ChannelBuffer message) {
+ log.debug("BGP RX UPDATE Error from {}: Attribute Flags Error",
+ bgpSession.remoteInfo().address());
+
+ //
+ // ERROR: Attribute Flags Error
+ //
+ // Send NOTIFICATION and close the connection
+ int errorCode = BgpConstants.Notifications.UpdateMessageError.ERROR_CODE;
+ int errorSubcode = BgpConstants.Notifications.UpdateMessageError.ATTRIBUTE_FLAGS_ERROR;
+ ChannelBuffer data =
+ prepareBgpUpdateNotificationDataPayload(attrTypeCode, attrLen,
+ attrFlags, message);
+ ChannelBuffer txMessage =
+ BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
+ data);
+ ctx.getChannel().write(txMessage);
+ bgpSession.closeSession(ctx);
+ }
+
+ /**
+ * Applies the appropriate actions after detecting BGP UPDATE
+ * Invalid NEXT_HOP Attribute Error: send NOTIFICATION and close the
+ * channel.
+ *
+ * @param bgpSession the BGP Session to use
+ * @param ctx the Channel Handler Context
+ * @param attrTypeCode the attribute type code
+ * @param attrLen the attribute length (in octets)
+ * @param attrFlags the attribute flags
+ * @param message the message with the data
+ * @param nextHop the NEXT_HOP attribute value
+ */
+ private static void actionsBgpUpdateInvalidNextHopAttribute(
+ BgpSession bgpSession,
+ ChannelHandlerContext ctx,
+ int attrTypeCode,
+ int attrLen,
+ int attrFlags,
+ ChannelBuffer message,
+ Ip4Address nextHop) {
+ log.debug("BGP RX UPDATE Error from {}: Invalid NEXT_HOP Attribute {}",
+ bgpSession.remoteInfo().address(), nextHop);
+
+ //
+ // ERROR: Invalid NEXT_HOP Attribute
+ //
+ // Send NOTIFICATION and close the connection
+ int errorCode = BgpConstants.Notifications.UpdateMessageError.ERROR_CODE;
+ int errorSubcode = BgpConstants.Notifications.UpdateMessageError.INVALID_NEXT_HOP_ATTRIBUTE;
+ ChannelBuffer data =
+ prepareBgpUpdateNotificationDataPayload(attrTypeCode, attrLen,
+ attrFlags, message);
+ ChannelBuffer txMessage =
+ BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
+ data);
+ ctx.getChannel().write(txMessage);
+ bgpSession.closeSession(ctx);
+ }
+
+ /**
+ * Applies the appropriate actions after detecting BGP UPDATE
+ * Unrecognized Well-known Attribute Error: send NOTIFICATION and close
+ * the channel.
+ *
+ * @param bgpSession the BGP Session to use
+ * @param ctx the Channel Handler Context
+ * @param attrTypeCode the attribute type code
+ * @param attrLen the attribute length (in octets)
+ * @param attrFlags the attribute flags
+ * @param message the message with the data
+ */
+ private static void actionsBgpUpdateUnrecognizedWellKnownAttribute(
+ BgpSession bgpSession,
+ ChannelHandlerContext ctx,
+ int attrTypeCode,
+ int attrLen,
+ int attrFlags,
+ ChannelBuffer message) {
+ log.debug("BGP RX UPDATE Error from {}: " +
+ "Unrecognized Well-known Attribute Error: {}",
+ bgpSession.remoteInfo().address(), attrTypeCode);
+
+ //
+ // ERROR: Unrecognized Well-known Attribute
+ //
+ // Send NOTIFICATION and close the connection
+ int errorCode = BgpConstants.Notifications.UpdateMessageError.ERROR_CODE;
+ int errorSubcode =
+ BgpConstants.Notifications.UpdateMessageError.UNRECOGNIZED_WELL_KNOWN_ATTRIBUTE;
+ ChannelBuffer data =
+ prepareBgpUpdateNotificationDataPayload(attrTypeCode, attrLen,
+ attrFlags, message);
+ ChannelBuffer txMessage =
+ BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
+ data);
+ ctx.getChannel().write(txMessage);
+ bgpSession.closeSession(ctx);
+ }
+
+ /**
+ * Applies the appropriate actions after detecting BGP UPDATE
+ * Optional Attribute Error: send NOTIFICATION and close
+ * the channel.
+ *
+ * @param bgpSession the BGP Session to use
+ * @param ctx the Channel Handler Context
+ * @param attrTypeCode the attribute type code
+ * @param attrLen the attribute length (in octets)
+ * @param attrFlags the attribute flags
+ * @param message the message with the data
+ */
+ private static void actionsBgpUpdateOptionalAttributeError(
+ BgpSession bgpSession,
+ ChannelHandlerContext ctx,
+ int attrTypeCode,
+ int attrLen,
+ int attrFlags,
+ ChannelBuffer message) {
+ log.debug("BGP RX UPDATE Error from {}: Optional Attribute Error: {}",
+ bgpSession.remoteInfo().address(), attrTypeCode);
+
+ //
+ // ERROR: Optional Attribute Error
+ //
+ // Send NOTIFICATION and close the connection
+ int errorCode = BgpConstants.Notifications.UpdateMessageError.ERROR_CODE;
+ int errorSubcode =
+ BgpConstants.Notifications.UpdateMessageError.OPTIONAL_ATTRIBUTE_ERROR;
+ ChannelBuffer data =
+ prepareBgpUpdateNotificationDataPayload(attrTypeCode, attrLen,
+ attrFlags, message);
+ ChannelBuffer txMessage =
+ BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
+ data);
+ ctx.getChannel().write(txMessage);
+ bgpSession.closeSession(ctx);
+ }
+
+ /**
+ * Applies the appropriate actions after detecting BGP UPDATE
+ * Attribute Length Error: send NOTIFICATION and close the channel.
+ *
+ * @param bgpSession the BGP Session to use
+ * @param ctx the Channel Handler Context
+ * @param attrTypeCode the attribute type code
+ * @param attrLen the attribute length (in octets)
+ * @param attrFlags the attribute flags
+ * @param message the message with the data
+ */
+ private static void actionsBgpUpdateAttributeLengthError(
+ BgpSession bgpSession,
+ ChannelHandlerContext ctx,
+ int attrTypeCode,
+ int attrLen,
+ int attrFlags,
+ ChannelBuffer message) {
+ log.debug("BGP RX UPDATE Error from {}: Attribute Length Error",
+ bgpSession.remoteInfo().address());
+
+ //
+ // ERROR: Attribute Length Error
+ //
+ // Send NOTIFICATION and close the connection
+ int errorCode = BgpConstants.Notifications.UpdateMessageError.ERROR_CODE;
+ int errorSubcode = BgpConstants.Notifications.UpdateMessageError.ATTRIBUTE_LENGTH_ERROR;
+ ChannelBuffer data =
+ prepareBgpUpdateNotificationDataPayload(attrTypeCode, attrLen,
+ attrFlags, message);
+ ChannelBuffer txMessage =
+ BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
+ data);
+ ctx.getChannel().write(txMessage);
+ bgpSession.closeSession(ctx);
+ }
+
+ /**
+ * Applies the appropriate actions after detecting BGP UPDATE
+ * Malformed AS_PATH Error: send NOTIFICATION and close the channel.
+ *
+ * @param bgpSession the BGP Session to use
+ * @param ctx the Channel Handler Context
+ */
+ private static void actionsBgpUpdateMalformedAsPath(
+ BgpSession bgpSession,
+ ChannelHandlerContext ctx) {
+ log.debug("BGP RX UPDATE Error from {}: Malformed AS Path",
+ bgpSession.remoteInfo().address());
+
+ //
+ // ERROR: Malformed AS_PATH
+ //
+ // Send NOTIFICATION and close the connection
+ int errorCode = BgpConstants.Notifications.UpdateMessageError.ERROR_CODE;
+ int errorSubcode = BgpConstants.Notifications.UpdateMessageError.MALFORMED_AS_PATH;
+ ChannelBuffer txMessage =
+ BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
+ null);
+ ctx.getChannel().write(txMessage);
+ bgpSession.closeSession(ctx);
+ }
+
+ /**
+ * Prepares BGP UPDATE Notification data payload.
+ *
+ * @param attrTypeCode the attribute type code
+ * @param attrLen the attribute length (in octets)
+ * @param attrFlags the attribute flags
+ * @param message the message with the data
+ * @return the buffer with the data payload for the BGP UPDATE Notification
+ */
+ private static ChannelBuffer prepareBgpUpdateNotificationDataPayload(
+ int attrTypeCode,
+ int attrLen,
+ int attrFlags,
+ ChannelBuffer message) {
+ // Compute the attribute length field octets
+ boolean extendedLengthBit = ((0x10 & attrFlags) != 0);
+ int attrLenOctets = 1;
+ if (extendedLengthBit) {
+ attrLenOctets = 2;
+ }
+ ChannelBuffer data =
+ ChannelBuffers.buffer(attrLen + attrLenOctets + 1);
+ data.writeByte(attrTypeCode);
+ if (extendedLengthBit) {
+ data.writeShort(attrLen);
+ } else {
+ data.writeByte(attrLen);
+ }
+ data.writeBytes(message, attrLen);
+ return data;
+ }
+
+ /**
+ * Helper class for storing Multiprotocol Network Layer Reachability
+ * information.
+ */
+ private static final class MpNlri {
+ private final int afi;
+ private final int safi;
+ private Ip4Address nextHop4;
+ private Ip6Address nextHop6;
+ private Collection<Ip4Prefix> nlri4 = new ArrayList<>();
+ private Collection<Ip6Prefix> nlri6 = new ArrayList<>();
+
+ /**
+ * Constructor.
+ *
+ * @param afi the Address Family Identifier
+ * @param safi the Subsequent Address Family Identifier
+ */
+ private MpNlri(int afi, int safi) {
+ this.afi = afi;
+ this.safi = safi;
+ }
+ }
+
+ /**
+ * Helper class for storing decoded BGP routing information.
+ */
+ private static final class DecodedBgpRoutes {
+ private final Map<Ip4Prefix, BgpRouteEntry> addedUnicastRoutes4 =
+ new HashMap<>();
+ private final Map<Ip6Prefix, BgpRouteEntry> addedUnicastRoutes6 =
+ new HashMap<>();
+ private final Map<Ip4Prefix, BgpRouteEntry> deletedUnicastRoutes4 =
+ new HashMap<>();
+ private final Map<Ip6Prefix, BgpRouteEntry> deletedUnicastRoutes6 =
+ new HashMap<>();
+ }
+}
diff --git a/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/package-info.java b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/package-info.java
new file mode 100644
index 00000000..031111b2
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/bgp/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Implementation of the BGP protocol.
+ */
+package org.onosproject.routing.bgp;
diff --git a/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/cli/AddRouteCommand.java b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/cli/AddRouteCommand.java
new file mode 100644
index 00000000..452a145e
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/cli/AddRouteCommand.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.routing.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.routing.FibEntry;
+import org.onosproject.routing.FibListener;
+import org.onosproject.routing.FibUpdate;
+import org.onosproject.routing.StaticRoutingService;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+@Command(scope = "onos", name = "add-route", description = "Installs static route")
+public class AddRouteCommand extends AbstractShellCommand {
+
+ @Argument(index = 0, name = "prefix IP MAC",
+ description = "prefix nexthopIP nexthopMAC",
+ required = true, multiValued = true)
+ String[] fibEntryString = null;
+
+ @Override
+ protected void execute() {
+ StaticRoutingService routingService = get(StaticRoutingService.class);
+
+ if (fibEntryString.length < 3) {
+ return;
+ }
+
+ IpPrefix prefix = IpPrefix.valueOf(fibEntryString[0]);
+ IpAddress nextHopIp = IpAddress.valueOf(fibEntryString[1]);
+ MacAddress nextHopMac = MacAddress.valueOf(fibEntryString[2]);
+ FibEntry fibEntry = new FibEntry(prefix, nextHopIp, nextHopMac);
+ FibUpdate fibUpdate = new FibUpdate(FibUpdate.Type.UPDATE, fibEntry);
+
+ FibListener fibListener = routingService.getFibListener();
+ fibListener.update(Arrays.asList(fibUpdate), Collections.emptyList());
+ }
+}
diff --git a/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/cli/BgpNeighborsListCommand.java b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/cli/BgpNeighborsListCommand.java
new file mode 100644
index 00000000..875a339a
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/cli/BgpNeighborsListCommand.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.routing.cli;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.Option;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.routing.bgp.BgpInfoService;
+import org.onosproject.routing.bgp.BgpSession;
+
+import java.util.Collection;
+
+/**
+ * Command to show the BGP neighbors.
+ */
+@Command(scope = "onos", name = "bgp-neighbors",
+ description = "Lists the BGP neighbors")
+public class BgpNeighborsListCommand extends AbstractShellCommand {
+ @Option(name = "-n", aliases = "--neighbor",
+ description = "BGP neighbor to display information about",
+ required = false, multiValued = false)
+ private String bgpNeighbor;
+
+ private static final String FORMAT_NEIGHBOR_LINE1 =
+ "BGP neighbor is %s, remote AS %d, local AS %d";
+ private static final String FORMAT_NEIGHBOR_LINE2 =
+ " Remote router ID %s, IP %s, BGP version %d, Hold time %d";
+ private static final String FORMAT_NEIGHBOR_LINE3 =
+ " Remote AFI/SAFI IPv4 Unicast %s Multicast %s, IPv6 Unicast %s Multicast %s";
+ private static final String FORMAT_NEIGHBOR_LINE4 =
+ " Local router ID %s, IP %s, BGP version %d, Hold time %d";
+ private static final String FORMAT_NEIGHBOR_LINE5 =
+ " Local AFI/SAFI IPv4 Unicast %s Multicast %s, IPv6 Unicast %s Multicast %s";
+ private static final String FORMAT_NEIGHBOR_LINE6 =
+ " 4 Octet AS Capability: %s %s";
+
+ @Override
+ protected void execute() {
+ BgpInfoService service = AbstractShellCommand.get(BgpInfoService.class);
+ Collection<BgpSession> bgpSessions = service.getBgpSessions();
+
+ if (bgpNeighbor != null) {
+ // Print a single neighbor (if found)
+ BgpSession foundBgpSession = null;
+ for (BgpSession bgpSession : bgpSessions) {
+ if (bgpSession.remoteInfo().bgpId().toString().equals(bgpNeighbor)) {
+ foundBgpSession = bgpSession;
+ break;
+ }
+ }
+ if (foundBgpSession != null) {
+ printNeighbor(foundBgpSession);
+ } else {
+ print("BGP neighbor %s not found", bgpNeighbor);
+ }
+ return;
+ }
+
+ // Print all neighbors
+ printNeighbors(bgpSessions);
+ }
+
+ /**
+ * Prints all BGP neighbors.
+ *
+ * @param bgpSessions the BGP sessions for the neighbors to print
+ */
+ private void printNeighbors(Collection<BgpSession> bgpSessions) {
+ if (outputJson()) {
+ print("%s", json(bgpSessions));
+ } else {
+ for (BgpSession bgpSession : bgpSessions) {
+ printNeighbor(bgpSession);
+ }
+ }
+ }
+
+ /**
+ * Prints a BGP neighbor.
+ *
+ * @param bgpSession the BGP session for the neighbor to print
+ */
+ private void printNeighbor(BgpSession bgpSession) {
+ print(FORMAT_NEIGHBOR_LINE1,
+ bgpSession.remoteInfo().bgpId().toString(),
+ bgpSession.remoteInfo().asNumber(),
+ bgpSession.localInfo().asNumber());
+ print(FORMAT_NEIGHBOR_LINE2,
+ bgpSession.remoteInfo().bgpId().toString(),
+ bgpSession.remoteInfo().address().toString(),
+ bgpSession.remoteInfo().bgpVersion(),
+ bgpSession.remoteInfo().holdtime());
+ print(FORMAT_NEIGHBOR_LINE3,
+ bgpSession.remoteInfo().ipv4Unicast() ? "YES" : "NO",
+ bgpSession.remoteInfo().ipv4Multicast() ? "YES" : "NO",
+ bgpSession.remoteInfo().ipv6Unicast() ? "YES" : "NO",
+ bgpSession.remoteInfo().ipv6Multicast() ? "YES" : "NO");
+ print(FORMAT_NEIGHBOR_LINE4,
+ bgpSession.localInfo().bgpId().toString(),
+ bgpSession.localInfo().address().toString(),
+ bgpSession.localInfo().bgpVersion(),
+ bgpSession.localInfo().holdtime());
+ print(FORMAT_NEIGHBOR_LINE5,
+ bgpSession.localInfo().ipv4Unicast() ? "YES" : "NO",
+ bgpSession.localInfo().ipv4Multicast() ? "YES" : "NO",
+ bgpSession.localInfo().ipv6Unicast() ? "YES" : "NO",
+ bgpSession.localInfo().ipv6Multicast() ? "YES" : "NO");
+ if (bgpSession.localInfo().as4OctetCapability() ||
+ bgpSession.remoteInfo().as4OctetCapability()) {
+ print(FORMAT_NEIGHBOR_LINE6,
+ bgpSession.localInfo().as4OctetCapability() ? "Advertised" : "",
+ bgpSession.remoteInfo().as4OctetCapability() ? "Received" : "");
+ }
+ }
+
+ /**
+ * Produces a JSON array of BGP neighbors.
+ *
+ * @param bgpSessions the BGP sessions with the data
+ * @return JSON array with the neighbors
+ */
+ private JsonNode json(Collection<BgpSession> bgpSessions) {
+ ObjectMapper mapper = new ObjectMapper();
+ ArrayNode result = mapper.createArrayNode();
+
+ for (BgpSession bgpSession : bgpSessions) {
+ result.add(json(mapper, bgpSession));
+ }
+ return result;
+ }
+
+ /**
+ * Produces JSON object for a BGP neighbor.
+ *
+ * @param mapper the JSON object mapper to use
+ * @param bgpSession the BGP session with the data
+ * @return JSON object for the route
+ */
+ private ObjectNode json(ObjectMapper mapper, BgpSession bgpSession) {
+ ObjectNode result = mapper.createObjectNode();
+
+ result.put("remoteAddress", bgpSession.remoteInfo().address().toString());
+ result.put("remoteBgpVersion", bgpSession.remoteInfo().bgpVersion());
+ result.put("remoteAs", bgpSession.remoteInfo().asNumber());
+ result.put("remoteAs4", bgpSession.remoteInfo().as4Number());
+ result.put("remoteHoldtime", bgpSession.remoteInfo().holdtime());
+ result.put("remoteBgpId", bgpSession.remoteInfo().bgpId().toString());
+ result.put("remoteIpv4Unicast", bgpSession.remoteInfo().ipv4Unicast());
+ result.put("remoteIpv4Multicast", bgpSession.remoteInfo().ipv4Multicast());
+ result.put("remoteIpv6Unicast", bgpSession.remoteInfo().ipv6Unicast());
+ result.put("remoteIpv6Multicast", bgpSession.remoteInfo().ipv6Multicast());
+ //
+ result.put("localAddress", bgpSession.localInfo().address().toString());
+ result.put("localBgpVersion", bgpSession.localInfo().bgpVersion());
+ result.put("localAs", bgpSession.localInfo().asNumber());
+ result.put("localAs4", bgpSession.localInfo().as4Number());
+ result.put("localHoldtime", bgpSession.localInfo().holdtime());
+ result.put("localBgpId", bgpSession.localInfo().bgpId().toString());
+ result.put("localIpv4Unicast", bgpSession.localInfo().ipv4Unicast());
+ result.put("localIpv4Multicast", bgpSession.localInfo().ipv4Multicast());
+ result.put("localIpv6Unicast", bgpSession.localInfo().ipv6Unicast());
+ result.put("localIpv6Multicast", bgpSession.localInfo().ipv6Multicast());
+
+ return result;
+ }
+}
diff --git a/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/cli/BgpRoutesListCommand.java b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/cli/BgpRoutesListCommand.java
new file mode 100644
index 00000000..ab8e975a
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/cli/BgpRoutesListCommand.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.routing.cli;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.Option;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.routing.bgp.BgpInfoService;
+import org.onosproject.routing.bgp.BgpRouteEntry;
+import org.onosproject.routing.bgp.BgpSession;
+import org.onosproject.routing.bgp.BgpConstants;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Command to show the routes learned through BGP.
+ */
+@Command(scope = "onos", name = "bgp-routes",
+ description = "Lists all BGP best routes")
+public class BgpRoutesListCommand extends AbstractShellCommand {
+ @Option(name = "-s", aliases = "--summary",
+ description = "BGP routes summary",
+ required = false, multiValued = false)
+ private boolean routesSummary = false;
+
+ @Option(name = "-n", aliases = "--neighbor",
+ description = "Routes from a BGP neighbor",
+ required = false, multiValued = false)
+ private String bgpNeighbor;
+
+ private static final String FORMAT_SUMMARY_V4 =
+ "Total BGP IPv4 routes = %d";
+ private static final String FORMAT_SUMMARY_V6 =
+ "Total BGP IPv6 routes = %d";
+ private static final String FORMAT_HEADER =
+ " Network Next Hop Origin LocalPref MED BGP-ID";
+ private static final String FORMAT_ROUTE_LINE1 =
+ " %-18s %-15s %6s %9s %9s %-15s";
+ private static final String FORMAT_ROUTE_LINE2 =
+ " AsPath %s";
+
+ @Override
+ protected void execute() {
+ BgpInfoService service = AbstractShellCommand.get(BgpInfoService.class);
+
+ // Print summary of the routes
+ if (routesSummary) {
+ printSummary(service.getBgpRoutes4(), service.getBgpRoutes6());
+ return;
+ }
+
+ BgpSession foundBgpSession = null;
+ if (bgpNeighbor != null) {
+ // Print the routes from a single neighbor (if found)
+ for (BgpSession bgpSession : service.getBgpSessions()) {
+ if (bgpSession.remoteInfo().bgpId().toString().equals(bgpNeighbor)) {
+ foundBgpSession = bgpSession;
+ break;
+ }
+ }
+ if (foundBgpSession == null) {
+ print("BGP neighbor %s not found", bgpNeighbor);
+ return;
+ }
+ }
+
+ // Print the routes
+ if (foundBgpSession != null) {
+ printRoutes(foundBgpSession.getBgpRibIn4(),
+ foundBgpSession.getBgpRibIn6());
+ } else {
+ printRoutes(service.getBgpRoutes4(), service.getBgpRoutes6());
+ }
+ }
+
+ /**
+ * Prints summary of the routes.
+ *
+ * @param routes4 the IPv4 routes
+ * @param routes6 the IPv6 routes
+ */
+ private void printSummary(Collection<BgpRouteEntry> routes4,
+ Collection<BgpRouteEntry> routes6) {
+ if (outputJson()) {
+ ObjectMapper mapper = new ObjectMapper();
+ ObjectNode result = mapper.createObjectNode();
+ result.put("totalRoutes4", routes4.size());
+ result.put("totalRoutes6", routes6.size());
+ print("%s", result);
+ } else {
+ print(FORMAT_SUMMARY_V4, routes4.size());
+ print(FORMAT_SUMMARY_V6, routes6.size());
+ }
+ }
+
+ /**
+ * Prints all routes.
+ *
+ * @param routes4 the IPv4 routes to print
+ * @param routes6 the IPv6 routes to print
+ */
+ private void printRoutes(Collection<BgpRouteEntry> routes4,
+ Collection<BgpRouteEntry> routes6) {
+ if (outputJson()) {
+ ObjectMapper mapper = new ObjectMapper();
+ ObjectNode result = mapper.createObjectNode();
+ result.set("routes4", json(routes4));
+ result.set("routes6", json(routes6));
+ print("%s", result);
+ } else {
+ // The IPv4 routes
+ print(FORMAT_HEADER);
+ for (BgpRouteEntry route : routes4) {
+ printRoute(route);
+ }
+ print(FORMAT_SUMMARY_V4, routes4.size());
+ print(""); // Empty separator line
+ // The IPv6 routes
+ print(FORMAT_HEADER);
+ for (BgpRouteEntry route : routes6) {
+ printRoute(route);
+ }
+ print(FORMAT_SUMMARY_V6, routes6.size());
+ }
+ }
+
+ /**
+ * Prints a BGP route.
+ *
+ * @param route the route to print
+ */
+ private void printRoute(BgpRouteEntry route) {
+ if (route != null) {
+ print(FORMAT_ROUTE_LINE1, route.prefix(), route.nextHop(),
+ BgpConstants.Update.Origin.typeToString(route.getOrigin()),
+ route.getLocalPref(), route.getMultiExitDisc(),
+ route.getBgpSession().remoteInfo().bgpId());
+ print(FORMAT_ROUTE_LINE2, asPath4Cli(route.getAsPath()));
+ }
+ }
+
+ /**
+ * Formats the AS Path as a string that can be shown on the CLI.
+ *
+ * @param asPath the AS Path to format
+ * @return the AS Path as a string
+ */
+ private String asPath4Cli(BgpRouteEntry.AsPath asPath) {
+ ArrayList<BgpRouteEntry.PathSegment> pathSegments =
+ asPath.getPathSegments();
+
+ if (pathSegments.isEmpty()) {
+ return "[none]";
+ }
+
+ final StringBuilder builder = new StringBuilder();
+ for (BgpRouteEntry.PathSegment pathSegment : pathSegments) {
+ String prefix = null;
+ String suffix = null;
+ switch (pathSegment.getType()) {
+ case BgpConstants.Update.AsPath.AS_SET:
+ prefix = "[AS-Set";
+ suffix = "]";
+ break;
+ case BgpConstants.Update.AsPath.AS_SEQUENCE:
+ break;
+ case BgpConstants.Update.AsPath.AS_CONFED_SEQUENCE:
+ prefix = "[AS-Confed-Seq";
+ suffix = "]";
+ break;
+ case BgpConstants.Update.AsPath.AS_CONFED_SET:
+ prefix = "[AS-Confed-Set";
+ suffix = "]";
+ break;
+ default:
+ builder.append(String.format("(type = %s)",
+ BgpConstants.Update.AsPath.typeToString(pathSegment.getType())));
+ break;
+ }
+
+ if (prefix != null) {
+ if (builder.length() > 0) {
+ builder.append(" "); // Separator
+ }
+ builder.append(prefix);
+ }
+ // Print the AS numbers
+ for (Long asn : pathSegment.getSegmentAsNumbers()) {
+ if (builder.length() > 0) {
+ builder.append(" "); // Separator
+ }
+ builder.append(String.format("%d", asn));
+ }
+ if (suffix != null) {
+ // No need for separator
+ builder.append(prefix);
+ }
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Produces a JSON array of routes.
+ *
+ * @param routes the routes with the data
+ * @return JSON array with the routes
+ */
+ private JsonNode json(Collection<BgpRouteEntry> routes) {
+ ObjectMapper mapper = new ObjectMapper();
+ ArrayNode result = mapper.createArrayNode();
+
+ for (BgpRouteEntry route : routes) {
+ result.add(json(mapper, route));
+ }
+ return result;
+ }
+
+ /**
+ * Produces JSON object for a route.
+ *
+ * @param mapper the JSON object mapper to use
+ * @param route the route with the data
+ * @return JSON object for the route
+ */
+ private ObjectNode json(ObjectMapper mapper, BgpRouteEntry route) {
+ ObjectNode result = mapper.createObjectNode();
+
+ result.put("prefix", route.prefix().toString());
+ result.put("nextHop", route.nextHop().toString());
+ result.put("bgpId",
+ route.getBgpSession().remoteInfo().bgpId().toString());
+ result.put("origin", BgpConstants.Update.Origin.typeToString(route.getOrigin()));
+ result.set("asPath", json(mapper, route.getAsPath()));
+ result.put("localPref", route.getLocalPref());
+ result.put("multiExitDisc", route.getMultiExitDisc());
+
+ return result;
+ }
+
+ /**
+ * Produces JSON object for an AS path.
+ *
+ * @param mapper the JSON object mapper to use
+ * @param asPath the AS path with the data
+ * @return JSON object for the AS path
+ */
+ private ObjectNode json(ObjectMapper mapper, BgpRouteEntry.AsPath asPath) {
+ ObjectNode result = mapper.createObjectNode();
+ ArrayNode pathSegmentsJson = mapper.createArrayNode();
+ for (BgpRouteEntry.PathSegment pathSegment : asPath.getPathSegments()) {
+ ObjectNode pathSegmentJson = mapper.createObjectNode();
+ pathSegmentJson.put("type",
+ BgpConstants.Update.AsPath.typeToString(pathSegment.getType()));
+ ArrayNode segmentAsNumbersJson = mapper.createArrayNode();
+ for (Long asNumber : pathSegment.getSegmentAsNumbers()) {
+ segmentAsNumbersJson.add(asNumber);
+ }
+ pathSegmentJson.set("segmentAsNumbers", segmentAsNumbersJson);
+ pathSegmentsJson.add(pathSegmentJson);
+ }
+ result.set("pathSegments", pathSegmentsJson);
+
+ return result;
+ }
+}
diff --git a/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/cli/BgpSpeakersListCommand.java b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/cli/BgpSpeakersListCommand.java
new file mode 100644
index 00000000..23d7086c
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/cli/BgpSpeakersListCommand.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.routing.cli;
+
+import com.google.common.collect.Lists;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.cli.Comparators;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.config.NetworkConfigService;
+import org.onosproject.routing.RoutingService;
+import org.onosproject.routing.config.BgpConfig;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Lists the BGP speakers configured in the system.
+ */
+@Command(scope = "onos", name = "bgp-speakers",
+ description = "Lists all BGP speakers")
+public class BgpSpeakersListCommand extends AbstractShellCommand {
+
+ private static final String FORMAT = "port=%s/%s, peers=%s";
+ private static final String NAME_FORMAT = "%s: " + FORMAT;
+
+ private static final Comparator<BgpConfig.BgpSpeakerConfig> SPEAKERS_COMPARATOR = (s1, s2) ->
+ Comparators.CONNECT_POINT_COMPARATOR.compare(s1.connectPoint(), s2.connectPoint());
+
+ @Override
+ protected void execute() {
+ NetworkConfigService configService = get(NetworkConfigService.class);
+ CoreService coreService = get(CoreService.class);
+ ApplicationId appId = coreService.getAppId(RoutingService.ROUTER_APP_ID);
+
+ BgpConfig config = configService.getConfig(appId, BgpConfig.class);
+
+ List<BgpConfig.BgpSpeakerConfig> bgpSpeakers =
+ Lists.newArrayList(config.bgpSpeakers());
+
+ Collections.sort(bgpSpeakers, SPEAKERS_COMPARATOR);
+
+ if (config == null || config.bgpSpeakers().isEmpty()) {
+ print("No speakers configured");
+ } else {
+ bgpSpeakers.forEach(
+ s -> {
+ if (s.name().isPresent()) {
+ print(NAME_FORMAT, s.name().get(), s.connectPoint().deviceId(),
+ s.connectPoint().port(), s.peers());
+ } else {
+ print(FORMAT, s.connectPoint().deviceId(),
+ s.connectPoint().port(), s.peers());
+ }
+ });
+ }
+ }
+}
diff --git a/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/cli/RemoveRouteCommand.java b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/cli/RemoveRouteCommand.java
new file mode 100644
index 00000000..3e216f95
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/cli/RemoveRouteCommand.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.routing.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.routing.FibEntry;
+import org.onosproject.routing.FibListener;
+import org.onosproject.routing.FibUpdate;
+import org.onosproject.routing.StaticRoutingService;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+@Command(scope = "onos", name = "remove-route", description = "Removes static route")
+public class RemoveRouteCommand extends AbstractShellCommand {
+ @Argument(index = 0, name = "prefix IP MAC",
+ description = "prefix nexthopIP nexthopMAC",
+ required = true, multiValued = true)
+ String[] fibEntryString = null;
+
+ @Override
+ protected void execute() {
+ StaticRoutingService routingService = get(StaticRoutingService.class);
+
+ if (fibEntryString.length < 3) {
+ return;
+ }
+
+ IpPrefix prefix = IpPrefix.valueOf(fibEntryString[0]);
+ IpAddress nextHopIp = IpAddress.valueOf(fibEntryString[1]);
+ MacAddress nextHopMac = MacAddress.valueOf(fibEntryString[2]);
+ FibEntry fibEntry = new FibEntry(prefix, nextHopIp, nextHopMac);
+ FibUpdate fibUpdate = new FibUpdate(FibUpdate.Type.DELETE, fibEntry);
+
+ FibListener fibListener = routingService.getFibListener();
+ fibListener.update(Collections.emptyList(), Arrays.asList(fibUpdate));
+ }
+}
diff --git a/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/cli/RoutesListCommand.java b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/cli/RoutesListCommand.java
new file mode 100644
index 00000000..04fcb8ff
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/cli/RoutesListCommand.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.routing.cli;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.Option;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.routing.RouteEntry;
+import org.onosproject.routing.RoutingService;
+
+import java.util.Collection;
+
+/**
+ * Command to show the list of routes in SDN-IP's routing table.
+ */
+@Command(scope = "onos", name = "routes",
+ description = "Lists all SDN-IP best routes")
+public class RoutesListCommand extends AbstractShellCommand {
+ @Option(name = "-s", aliases = "--summary",
+ description = "SDN-IP routes summary",
+ required = false, multiValued = false)
+ private boolean routesSummary = false;
+
+ private static final String FORMAT_SUMMARY_V4 =
+ "Total SDN-IP IPv4 routes = %d";
+ private static final String FORMAT_SUMMARY_V6 =
+ "Total SDN-IP IPv6 routes = %d";
+ private static final String FORMAT_HEADER =
+ " Network Next Hop";
+ private static final String FORMAT_ROUTE =
+ " %-18s %-15s";
+
+ @Override
+ protected void execute() {
+ RoutingService service = AbstractShellCommand.get(RoutingService.class);
+
+ // Print summary of the routes
+ if (routesSummary) {
+ printSummary(service.getRoutes4(), service.getRoutes6());
+ return;
+ }
+
+ // Print all routes
+ printRoutes(service.getRoutes4(), service.getRoutes6());
+ }
+
+ /**
+ * Prints summary of the routes.
+ *
+ * @param routes4 the IPv4 routes
+ * @param routes6 the IPv6 routes
+ */
+ private void printSummary(Collection<RouteEntry> routes4,
+ Collection<RouteEntry> routes6) {
+ if (outputJson()) {
+ ObjectMapper mapper = new ObjectMapper();
+ ObjectNode result = mapper.createObjectNode();
+ result.put("totalRoutes4", routes4.size());
+ result.put("totalRoutes6", routes6.size());
+ print("%s", result);
+ } else {
+ print(FORMAT_SUMMARY_V4, routes4.size());
+ print(FORMAT_SUMMARY_V6, routes6.size());
+ }
+ }
+
+ /**
+ * Prints all routes.
+ *
+ * @param routes4 the IPv4 routes to print
+ * @param routes6 the IPv6 routes to print
+ */
+ private void printRoutes(Collection<RouteEntry> routes4,
+ Collection<RouteEntry> routes6) {
+ if (outputJson()) {
+ ObjectMapper mapper = new ObjectMapper();
+ ObjectNode result = mapper.createObjectNode();
+ result.set("routes4", json(routes4));
+ result.set("routes6", json(routes6));
+ print("%s", result);
+ } else {
+ // The IPv4 routes
+ print(FORMAT_HEADER);
+ for (RouteEntry route : routes4) {
+ printRoute(route);
+ }
+ print(FORMAT_SUMMARY_V4, routes4.size());
+ print(""); // Empty separator line
+ // The IPv6 routes
+ print(FORMAT_HEADER);
+ for (RouteEntry route : routes6) {
+ printRoute(route);
+ }
+ print(FORMAT_SUMMARY_V6, routes6.size());
+ }
+ }
+
+ /**
+ * Prints a route.
+ *
+ * @param route the route to print
+ */
+ private void printRoute(RouteEntry route) {
+ if (route != null) {
+ print(FORMAT_ROUTE, route.prefix(), route.nextHop());
+ }
+ }
+
+ /**
+ * Produces a JSON array of routes.
+ *
+ * @param routes the routes with the data
+ * @return JSON array with the routes
+ */
+ private JsonNode json(Collection<RouteEntry> routes) {
+ ObjectMapper mapper = new ObjectMapper();
+ ArrayNode result = mapper.createArrayNode();
+
+ for (RouteEntry route : routes) {
+ result.add(json(mapper, route));
+ }
+ return result;
+ }
+
+ /**
+ * Produces JSON object for a route.
+ *
+ * @param mapper the JSON object mapper to use
+ * @param route the route with the data
+ * @return JSON object for the route
+ */
+ private ObjectNode json(ObjectMapper mapper, RouteEntry route) {
+ ObjectNode result = mapper.createObjectNode();
+
+ result.put("prefix", route.prefix().toString());
+ result.put("nextHop", route.nextHop().toString());
+
+ return result;
+ }
+}
diff --git a/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/cli/package-info.java b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/cli/package-info.java
new file mode 100644
index 00000000..cb6781e3
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/cli/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * CLI handlers for routing commands.
+ */
+package org.onosproject.routing.cli;
diff --git a/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/config/impl/Configuration.java b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/config/impl/Configuration.java
new file mode 100644
index 00000000..45206903
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/config/impl/Configuration.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.routing.config.impl;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import org.onlab.packet.MacAddress;
+import org.onosproject.routing.config.BgpPeer;
+import org.onosproject.routing.config.BgpSpeaker;
+import org.onosproject.routing.config.LocalIpPrefixEntry;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Contains the configuration data for SDN-IP that has been read from a
+ * JSON-formatted configuration file.
+ */
+public class Configuration {
+ // We call the BGP routers in our SDN network the BGP speakers, and call
+ // the BGP routers outside our SDN network the BGP peers.
+ private List<BgpSpeaker> bgpSpeakers;
+ private List<BgpPeer> peers;
+ private MacAddress virtualGatewayMacAddress;
+
+ // All IP prefixes from the configuration are local
+ private List<LocalIpPrefixEntry> localIp4PrefixEntries =
+ Collections.emptyList();
+ private List<LocalIpPrefixEntry> localIp6PrefixEntries =
+ Collections.emptyList();
+
+ /**
+ * Default constructor.
+ */
+ public Configuration() {
+ }
+
+ /**
+ * Gets a list of bgpSpeakers in the system, represented by
+ * {@link BgpSpeaker} objects.
+ *
+ * @return the list of BGP speakers
+ */
+ public List<BgpSpeaker> getBgpSpeakers() {
+ return Collections.unmodifiableList(bgpSpeakers);
+ }
+
+ /**
+ * Sets a list of bgpSpeakers in the system.
+ *
+ * @param bgpSpeakers the list of BGP speakers
+ */
+ @JsonProperty("bgpSpeakers")
+ public void setBgpSpeakers(List<BgpSpeaker> bgpSpeakers) {
+ this.bgpSpeakers = bgpSpeakers;
+ }
+
+ /**
+ * Gets a list of BGP peers we are configured to peer with. Peers are
+ * represented by {@link BgpPeer} objects.
+ *
+ * @return the list of BGP peers
+ */
+ public List<BgpPeer> getPeers() {
+ return Collections.unmodifiableList(peers);
+ }
+
+ /**
+ * Sets a list of BGP peers we configured to peer with.
+ *
+ * @param peers the list of BGP peers
+ */
+ @JsonProperty("bgpPeers")
+ public void setPeers(List<BgpPeer> peers) {
+ this.peers = peers;
+ }
+
+ /**
+ * Gets the MAC address we configured for virtual gateway
+ * in SDN network.
+ *
+ * @return the MAC address of virtual gateway
+ */
+ public MacAddress getVirtualGatewayMacAddress() {
+ return virtualGatewayMacAddress;
+ }
+
+ /**
+ * Sets the MAC address for virtual gateway in SDN network.
+ *
+ * @param virtualGatewayMacAddress the MAC address of virtual gateway
+ */
+ @JsonProperty("virtualGatewayMacAddress")
+ public void setVirtualGatewayMacAddress(MacAddress virtualGatewayMacAddress) {
+ this.virtualGatewayMacAddress = virtualGatewayMacAddress;
+ }
+
+ /**
+ * Gets a list of local IPv4 prefix entries configured for local
+ * SDN network.
+ * <p>
+ * IP prefix entries are represented by {@link LocalIpPrefixEntry}
+ * objects.
+ * </p>
+ *
+ * @return the list of local IPv4 prefix entries
+ */
+ public List<LocalIpPrefixEntry> getLocalIp4PrefixEntries() {
+ return Collections.unmodifiableList(localIp4PrefixEntries);
+ }
+
+ /**
+ * Sets a list of IPv4 prefix entries configured for local SDN network.
+ *
+ * @param ip4PrefixEntries the list of Ipv4 prefix entries
+ */
+ @JsonProperty("ip4LocalPrefixes")
+ public void setLocalIp4PrefixEntries(List<LocalIpPrefixEntry> ip4PrefixEntries) {
+ this.localIp4PrefixEntries = ip4PrefixEntries;
+ }
+
+ /**
+ * Gets a list of IPv6 prefix entries configured for local SDN network.
+ * <p>
+ * IP prefix entries are represented by {@link LocalIpPrefixEntry}
+ * objects.
+ * </p>
+ *
+ * @return the list of IPv6 prefix entries
+ */
+ public List<LocalIpPrefixEntry> getLocalIp6PrefixEntries() {
+ return Collections.unmodifiableList(localIp6PrefixEntries);
+ }
+
+ /**
+ * Sets a list of IPv6 prefix entries configured for local SDN network.
+ *
+ * @param ip6PrefixEntries the list of Ipv6 prefix entries
+ */
+ @JsonProperty("ip6LocalPrefixes")
+ public void setLocalIp6PrefixEntries(List<LocalIpPrefixEntry> ip6PrefixEntries) {
+ this.localIp6PrefixEntries = ip6PrefixEntries;
+ }
+
+}
diff --git a/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/config/impl/HostToInterfaceAdaptor.java b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/config/impl/HostToInterfaceAdaptor.java
new file mode 100644
index 00000000..78eecb86
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/config/impl/HostToInterfaceAdaptor.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.routing.config.impl;
+
+import com.google.common.collect.Sets;
+import org.onlab.packet.IpAddress;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.host.InterfaceIpAddress;
+import org.onosproject.net.host.PortAddresses;
+import org.onosproject.routing.config.Interface;
+
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Adapts PortAddresses data from the HostService into Interface data used by
+ * the routing module.
+ */
+public class HostToInterfaceAdaptor {
+
+ private final HostService hostService;
+
+ public HostToInterfaceAdaptor(HostService hostService) {
+ this.hostService = checkNotNull(hostService);
+ }
+
+ public Set<Interface> getInterfaces() {
+ Set<PortAddresses> addresses = hostService.getAddressBindings();
+ Set<Interface> interfaces = Sets.newHashSetWithExpectedSize(addresses.size());
+ for (PortAddresses a : addresses) {
+ interfaces.add(new Interface(a));
+ }
+ return interfaces;
+ }
+
+ public Interface getInterface(ConnectPoint connectPoint) {
+ checkNotNull(connectPoint);
+
+ Set<PortAddresses> portAddresses =
+ hostService.getAddressBindingsForPort(connectPoint);
+
+ for (PortAddresses addresses : portAddresses) {
+ if (addresses.connectPoint().equals(connectPoint)) {
+ return new Interface(addresses);
+ }
+ }
+
+ return null;
+ }
+
+ public Interface getInterface(IpAddress ip) {
+ Set<PortAddresses> portAddresses = hostService.getAddressBindings();
+
+ for (PortAddresses portAddress : portAddresses) {
+ for (InterfaceIpAddress portIp : portAddress.ipAddresses()) {
+ if (portIp.ipAddress().equals(ip)) {
+ return new Interface(portAddress);
+ }
+ }
+ }
+
+ return null;
+ }
+
+ public Interface getMatchingInterface(IpAddress ipAddress) {
+ checkNotNull(ipAddress);
+
+ for (PortAddresses portAddresses : hostService.getAddressBindings()) {
+ for (InterfaceIpAddress ia : portAddresses.ipAddresses()) {
+ if (ia.subnetAddress().contains(ipAddress)) {
+ return new Interface(portAddresses);
+ }
+ }
+ }
+
+ return null;
+ }
+
+}
diff --git a/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/config/impl/RoutingConfigurationImpl.java b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/config/impl/RoutingConfigurationImpl.java
new file mode 100644
index 00000000..1078319d
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/config/impl/RoutingConfigurationImpl.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.routing.config.impl;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.googlecode.concurrenttrees.radix.node.concrete.DefaultByteArrayNodeFactory;
+import com.googlecode.concurrenttrees.radixinverted.ConcurrentInvertedRadixTree;
+import com.googlecode.concurrenttrees.radixinverted.InvertedRadixTree;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.incubator.net.intf.InterfaceService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.config.ConfigFactory;
+import org.onosproject.net.config.NetworkConfigRegistry;
+import org.onosproject.net.config.NetworkConfigService;
+import org.onosproject.net.config.basics.SubjectFactories;
+import org.onosproject.net.host.HostService;
+import org.onosproject.routing.config.BgpConfig;
+import org.onosproject.routing.config.BgpPeer;
+import org.onosproject.routing.config.BgpSpeaker;
+import org.onosproject.routing.config.Interface;
+import org.onosproject.routing.config.LocalIpPrefixEntry;
+import org.onosproject.routing.config.RoutingConfigurationService;
+import org.onosproject.routing.impl.Router;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+import static org.onosproject.routing.RouteEntry.createBinaryString;
+
+/**
+ * Implementation of RoutingConfigurationService which reads routing
+ * configuration from a file.
+ */
+@Component(immediate = true)
+@Service
+public class RoutingConfigurationImpl implements RoutingConfigurationService {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private static final String CONFIG_DIR = "../config";
+ private static final String DEFAULT_CONFIG_FILE = "sdnip.json";
+ private String configFileName = DEFAULT_CONFIG_FILE;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected HostService hostService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected NetworkConfigRegistry registry;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected NetworkConfigService configService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected InterfaceService interfaceService;
+
+ private Map<String, BgpSpeaker> bgpSpeakers = new ConcurrentHashMap<>();
+ private Map<IpAddress, BgpPeer> bgpPeers = new ConcurrentHashMap<>();
+ private Set<IpAddress> gatewayIpAddresses = new HashSet<>();
+ private Set<ConnectPoint> bgpPeerConnectPoints = new HashSet<>();
+
+ private InvertedRadixTree<LocalIpPrefixEntry>
+ localPrefixTable4 = new ConcurrentInvertedRadixTree<>(
+ new DefaultByteArrayNodeFactory());
+ private InvertedRadixTree<LocalIpPrefixEntry>
+ localPrefixTable6 = new ConcurrentInvertedRadixTree<>(
+ new DefaultByteArrayNodeFactory());
+
+ private MacAddress virtualGatewayMacAddress;
+ private HostToInterfaceAdaptor hostAdaptor;
+
+ private ConfigFactory configFactory =
+ new ConfigFactory(SubjectFactories.APP_SUBJECT_FACTORY, BgpConfig.class, "bgp") {
+ @Override
+ public BgpConfig createConfig() {
+ return new BgpConfig();
+ }
+ };
+
+ @Activate
+ public void activate() {
+ registry.registerConfigFactory(configFactory);
+ readConfiguration();
+ hostAdaptor = new HostToInterfaceAdaptor(hostService);
+ log.info("Routing configuration service started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ registry.unregisterConfigFactory(configFactory);
+ log.info("Routing configuration service stopped");
+ }
+
+ /**
+ * Reads SDN-IP related information contained in the configuration file.
+ *
+ * @param configFilename the name of the configuration file for the SDN-IP
+ * application
+ */
+ private void readConfiguration(String configFilename) {
+ File configFile = new File(CONFIG_DIR, configFilename);
+ ObjectMapper mapper = new ObjectMapper();
+
+ try {
+ log.info("Loading config: {}", configFile.getAbsolutePath());
+ Configuration config = mapper.readValue(configFile,
+ Configuration.class);
+ for (BgpSpeaker speaker : config.getBgpSpeakers()) {
+ bgpSpeakers.put(speaker.name(), speaker);
+ }
+ for (BgpPeer peer : config.getPeers()) {
+ bgpPeers.put(peer.ipAddress(), peer);
+ bgpPeerConnectPoints.add(peer.connectPoint());
+ }
+
+ for (LocalIpPrefixEntry entry : config.getLocalIp4PrefixEntries()) {
+ localPrefixTable4.put(createBinaryString(entry.ipPrefix()),
+ entry);
+ gatewayIpAddresses.add(entry.getGatewayIpAddress());
+ }
+ for (LocalIpPrefixEntry entry : config.getLocalIp6PrefixEntries()) {
+ localPrefixTable6.put(createBinaryString(entry.ipPrefix()),
+ entry);
+ gatewayIpAddresses.add(entry.getGatewayIpAddress());
+ }
+
+ virtualGatewayMacAddress = config.getVirtualGatewayMacAddress();
+
+ } catch (FileNotFoundException e) {
+ log.warn("Configuration file not found: {}", configFileName);
+ } catch (IOException e) {
+ log.error("Error loading configuration", e);
+ }
+ }
+
+ /**
+ * Instructs the configuration reader to read the configuration from the
+ * file.
+ */
+ public void readConfiguration() {
+ readConfiguration(configFileName);
+ }
+
+ @Override
+ public Map<String, BgpSpeaker> getBgpSpeakers() {
+ return Collections.unmodifiableMap(bgpSpeakers);
+ }
+
+ @Override
+ public Map<IpAddress, BgpPeer> getBgpPeers() {
+ return Collections.unmodifiableMap(bgpPeers);
+ }
+
+ @Override
+ public Set<Interface> getInterfaces() {
+ return hostAdaptor.getInterfaces();
+ }
+
+ @Override
+ public Set<ConnectPoint> getBgpPeerConnectPoints() {
+ // TODO perhaps cache this result in future
+ ApplicationId routerAppId = coreService.getAppId(Router.ROUTER_APP_ID);
+ if (routerAppId == null) {
+ return Collections.emptySet();
+ }
+
+ BgpConfig bgpConfig = configService.getConfig(routerAppId, BgpConfig.class);
+
+ return bgpConfig.bgpSpeakers().stream()
+ .flatMap(speaker -> speaker.peers().stream())
+ .map(peer -> interfaceService.getMatchingInterface(peer))
+ .filter(intf -> intf != null)
+ .map(intf -> intf.connectPoint())
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public Interface getInterface(ConnectPoint connectPoint) {
+ return hostAdaptor.getInterface(connectPoint);
+ }
+
+ @Override
+ public Interface getInterface(IpAddress ip) {
+ return hostAdaptor.getInterface(ip);
+ }
+
+ @Override
+ public Interface getMatchingInterface(IpAddress ipAddress) {
+ return hostAdaptor.getMatchingInterface(ipAddress);
+ }
+
+ @Override
+ public boolean isIpAddressLocal(IpAddress ipAddress) {
+ if (ipAddress.isIp4()) {
+ return localPrefixTable4.getValuesForKeysPrefixing(
+ createBinaryString(
+ IpPrefix.valueOf(ipAddress, Ip4Address.BIT_LENGTH)))
+ .iterator().hasNext();
+ } else {
+ return localPrefixTable6.getValuesForKeysPrefixing(
+ createBinaryString(
+ IpPrefix.valueOf(ipAddress, Ip6Address.BIT_LENGTH)))
+ .iterator().hasNext();
+ }
+ }
+
+ @Override
+ public boolean isIpPrefixLocal(IpPrefix ipPrefix) {
+ return (localPrefixTable4.getValueForExactKey(
+ createBinaryString(ipPrefix)) != null ||
+ localPrefixTable6.getValueForExactKey(
+ createBinaryString(ipPrefix)) != null);
+ }
+
+ @Override
+ public boolean isVirtualGatewayIpAddress(IpAddress ipAddress) {
+ return gatewayIpAddresses.contains(ipAddress);
+ }
+
+ @Override
+ public MacAddress getVirtualGatewayMacAddress() {
+ return virtualGatewayMacAddress;
+ }
+
+}
diff --git a/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/config/impl/package-info.java b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/config/impl/package-info.java
new file mode 100644
index 00000000..83c4a86d
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/config/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Implementation of routing configuration APIs.
+ */
+package org.onosproject.routing.config.impl;
diff --git a/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/impl/Router.java b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/impl/Router.java
new file mode 100644
index 00000000..c4f291ba
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/impl/Router.java
@@ -0,0 +1,733 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.routing.impl;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.SetMultimap;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.googlecode.concurrenttrees.common.KeyValuePair;
+import com.googlecode.concurrenttrees.radix.node.concrete.DefaultByteArrayNodeFactory;
+import com.googlecode.concurrenttrees.radixinverted.ConcurrentInvertedRadixTree;
+import com.googlecode.concurrenttrees.radixinverted.InvertedRadixTree;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onosproject.core.CoreService;
+import org.onosproject.incubator.net.intf.Interface;
+import org.onosproject.incubator.net.intf.InterfaceService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Host;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostService;
+import org.onosproject.routing.BgpService;
+import org.onosproject.routing.FibEntry;
+import org.onosproject.routing.FibListener;
+import org.onosproject.routing.FibUpdate;
+import org.onosproject.routing.IntentRequestListener;
+import org.onosproject.routing.RouteEntry;
+import org.onosproject.routing.RouteListener;
+import org.onosproject.routing.RouteUpdate;
+import org.onosproject.routing.RoutingService;
+import org.onosproject.routing.config.RoutingConfigurationService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.routing.RouteEntry.createBinaryString;
+
+/**
+ * This class processes route updates and maintains a Routing Information Base
+ * (RIB). After route updates have been processed and next hops have been
+ * resolved, FIB updates are sent to any listening FIB components.
+ */
+@Component(immediate = true)
+@Service
+public class Router implements RoutingService {
+
+ private static final Logger log = LoggerFactory.getLogger(Router.class);
+
+ // Route entries are stored in a radix tree.
+ // The key in this tree is the binary string of prefix of the route.
+ private InvertedRadixTree<RouteEntry> ribTable4;
+ private InvertedRadixTree<RouteEntry> ribTable6;
+
+ // Stores all incoming route updates in a queue.
+ private final BlockingQueue<Collection<RouteUpdate>> routeUpdatesQueue =
+ new LinkedBlockingQueue<>();
+
+ // Next-hop IP address to route entry mapping for next hops pending MAC
+ // resolution
+ private SetMultimap<IpAddress, RouteEntry> routesWaitingOnArp;
+
+ // The IPv4 address to MAC address mapping
+ private final Map<IpAddress, MacAddress> ip2Mac = new ConcurrentHashMap<>();
+
+ private FibListener fibComponent;
+ private IntentRequestListener intentRequestListener;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected HostService hostService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected BgpService bgpService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected InterfaceService interfaceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected RoutingConfigurationService routingConfigurationService;
+
+ private ExecutorService bgpUpdatesExecutor;
+ private final HostListener hostListener = new InternalHostListener();
+
+ @Activate
+ public void activate() {
+ ribTable4 = new ConcurrentInvertedRadixTree<>(
+ new DefaultByteArrayNodeFactory());
+ ribTable6 = new ConcurrentInvertedRadixTree<>(
+ new DefaultByteArrayNodeFactory());
+
+ routesWaitingOnArp = Multimaps.synchronizedSetMultimap(
+ HashMultimap.<IpAddress, RouteEntry>create());
+
+ coreService.registerApplication(ROUTER_APP_ID);
+
+ bgpUpdatesExecutor = Executors.newSingleThreadExecutor(
+ new ThreadFactoryBuilder()
+ .setNameFormat("sdnip-bgp-updates-%d").build());
+ }
+
+ @Deactivate
+ public void deactivate() {
+ log.debug("Stopped");
+ }
+
+ @Override
+ public void addFibListener(FibListener fibListener) {
+ this.fibComponent = checkNotNull(fibListener);
+
+ }
+
+ @Override
+ public void addIntentRequestListener(IntentRequestListener intentRequestListener) {
+ this.intentRequestListener = checkNotNull(intentRequestListener);
+ }
+
+ @Override
+ public void start() {
+ this.hostService.addListener(hostListener);
+
+ bgpService.start(new InternalRouteListener());
+
+ bgpUpdatesExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ doUpdatesThread();
+ }
+ });
+ }
+
+ @Override
+ public void stop() {
+ bgpService.stop();
+
+ this.hostService.removeListener(hostListener);
+
+ // Stop the thread(s)
+ bgpUpdatesExecutor.shutdownNow();
+
+ synchronized (this) {
+ // Cleanup all local state
+ ribTable4 = new ConcurrentInvertedRadixTree<>(
+ new DefaultByteArrayNodeFactory());
+ ribTable6 = new ConcurrentInvertedRadixTree<>(
+ new DefaultByteArrayNodeFactory());
+ routeUpdatesQueue.clear();
+ routesWaitingOnArp.clear();
+ ip2Mac.clear();
+ }
+ }
+
+ /**
+ * Entry point for route updates.
+ *
+ * @param routeUpdates collection of route updates to process
+ */
+ private void update(Collection<RouteUpdate> routeUpdates) {
+ try {
+ routeUpdatesQueue.put(routeUpdates);
+ } catch (InterruptedException e) {
+ log.error("Interrupted while putting on routeUpdatesQueue", e);
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ /**
+ * Thread for handling route updates.
+ */
+ private void doUpdatesThread() {
+ boolean interrupted = false;
+ try {
+ while (!interrupted) {
+ try {
+ Collection<RouteUpdate> routeUpdates =
+ routeUpdatesQueue.take();
+ processRouteUpdates(routeUpdates);
+ } catch (InterruptedException e) {
+ log.error("Interrupted while taking from updates queue", e);
+ interrupted = true;
+ } catch (Exception e) {
+ log.error("exception", e);
+ }
+ }
+ } finally {
+ if (interrupted) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ /**
+ * Gets all IPv4 routes from the RIB.
+ *
+ * @return all IPv4 routes from the RIB
+ */
+ @Override
+ public Collection<RouteEntry> getRoutes4() {
+ Iterator<KeyValuePair<RouteEntry>> it =
+ ribTable4.getKeyValuePairsForKeysStartingWith("").iterator();
+
+ List<RouteEntry> routes = new LinkedList<>();
+
+ while (it.hasNext()) {
+ KeyValuePair<RouteEntry> entry = it.next();
+ routes.add(entry.getValue());
+ }
+
+ return routes;
+ }
+
+ /**
+ * Gets all IPv6 routes from the RIB.
+ *
+ * @return all IPv6 routes from the RIB
+ */
+ @Override
+ public Collection<RouteEntry> getRoutes6() {
+ Iterator<KeyValuePair<RouteEntry>> it =
+ ribTable6.getKeyValuePairsForKeysStartingWith("").iterator();
+
+ List<RouteEntry> routes = new LinkedList<>();
+
+ while (it.hasNext()) {
+ KeyValuePair<RouteEntry> entry = it.next();
+ routes.add(entry.getValue());
+ }
+
+ return routes;
+ }
+
+ /**
+ * Finds a route in the RIB for a prefix. The prefix can be either IPv4 or
+ * IPv6.
+ *
+ * @param prefix the prefix to use
+ * @return the route if found, otherwise null
+ */
+ RouteEntry findRibRoute(IpPrefix prefix) {
+ String binaryString = createBinaryString(prefix);
+ if (prefix.isIp4()) {
+ // IPv4
+ return ribTable4.getValueForExactKey(binaryString);
+ }
+ // IPv6
+ return ribTable6.getValueForExactKey(binaryString);
+ }
+
+ /**
+ * Adds a route to the RIB. The route can be either IPv4 or IPv6.
+ *
+ * @param routeEntry the route entry to use
+ */
+ void addRibRoute(RouteEntry routeEntry) {
+ if (routeEntry.isIp4()) {
+ // IPv4
+ ribTable4.put(createBinaryString(routeEntry.prefix()),
+ routeEntry);
+ } else {
+ // IPv6
+ ribTable6.put(createBinaryString(routeEntry.prefix()),
+ routeEntry);
+ }
+ }
+
+ /**
+ * Removes a route for a prefix from the RIB. The prefix can be either IPv4
+ * or IPv6.
+ *
+ * @param prefix the prefix to use
+ * @return true if the route was found and removed, otherwise false
+ */
+ boolean removeRibRoute(IpPrefix prefix) {
+ if (prefix.isIp4()) {
+ // IPv4
+ return ribTable4.remove(createBinaryString(prefix));
+ }
+ // IPv6
+ return ribTable6.remove(createBinaryString(prefix));
+ }
+
+ /**
+ * Processes route updates.
+ *
+ * @param routeUpdates the route updates to process
+ */
+ void processRouteUpdates(Collection<RouteUpdate> routeUpdates) {
+ synchronized (this) {
+ Collection<IpPrefix> withdrawPrefixes = new LinkedList<>();
+ Collection<FibUpdate> fibUpdates = new LinkedList<>();
+ Collection<FibUpdate> fibWithdraws = new LinkedList<>();
+
+ for (RouteUpdate update : routeUpdates) {
+ switch (update.type()) {
+ case UPDATE:
+
+ FibEntry fib = processRouteAdd(update.routeEntry(),
+ withdrawPrefixes);
+ if (fib != null) {
+ fibUpdates.add(new FibUpdate(FibUpdate.Type.UPDATE, fib));
+ }
+
+ break;
+ case DELETE:
+ processRouteDelete(update.routeEntry(), withdrawPrefixes);
+
+ break;
+ default:
+ log.error("Unknown update Type: {}", update.type());
+ break;
+ }
+ }
+
+ withdrawPrefixes.forEach(p -> fibWithdraws.add(new FibUpdate(
+ FibUpdate.Type.DELETE, new FibEntry(p, null, null))));
+
+ if (!fibUpdates.isEmpty() || !fibWithdraws.isEmpty()) {
+ fibComponent.update(fibUpdates, fibWithdraws);
+ }
+ }
+ }
+
+ /**
+ * Processes adding a route entry.
+ * <p>
+ * The route entry is added to the radix tree. If there was an existing
+ * next hop for this prefix, but the next hop was different, then the
+ * old route entry is deleted.
+ * </p>
+ * <p>
+ * NOTE: Currently, we don't handle routes if the next hop is within the
+ * SDN domain.
+ * </p>
+ *
+ * @param routeEntry the route entry to add
+ * @param withdrawPrefixes the collection of accumulated prefixes whose
+ * intents will be withdrawn
+ * @return the corresponding FIB entry change, or null
+ */
+ private FibEntry processRouteAdd(RouteEntry routeEntry,
+ Collection<IpPrefix> withdrawPrefixes) {
+ log.debug("Processing route add: {}", routeEntry);
+
+ // Find the old next-hop if we are updating an old route entry
+ IpAddress oldNextHop = null;
+ RouteEntry oldRouteEntry = findRibRoute(routeEntry.prefix());
+ if (oldRouteEntry != null) {
+ oldNextHop = oldRouteEntry.nextHop();
+ }
+
+ // Add the new route to the RIB
+ addRibRoute(routeEntry);
+
+ if (oldNextHop != null) {
+ if (oldNextHop.equals(routeEntry.nextHop())) {
+ return null; // No change
+ }
+ //
+ // Update an existing nexthop for the prefix.
+ // We need to remove the old flows for this prefix from the
+ // switches before the new flows are added.
+ //
+ withdrawPrefixes.add(routeEntry.prefix());
+ }
+
+ if (routingConfigurationService.isIpPrefixLocal(routeEntry.prefix())) {
+ // Route originated by local SDN domain
+ // We don't handle these here, reactive routing APP will handle
+ // these
+ log.debug("Own route {} to {}",
+ routeEntry.prefix(), routeEntry.nextHop());
+ return null;
+ }
+
+ //
+ // Find the MAC address of next hop router for this route entry.
+ // If the MAC address can not be found in ARP cache, then this prefix
+ // will be put in routesWaitingOnArp queue. Otherwise, generate
+ // a new route intent.
+ //
+
+ // Monitor the IP address for updates of the MAC address
+ hostService.startMonitoringIp(routeEntry.nextHop());
+
+ // Check if we know the MAC address of the next hop
+ MacAddress nextHopMacAddress = ip2Mac.get(routeEntry.nextHop());
+ if (nextHopMacAddress == null) {
+ Set<Host> hosts = hostService.getHostsByIp(routeEntry.nextHop());
+ if (!hosts.isEmpty()) {
+ nextHopMacAddress = hosts.iterator().next().mac();
+ }
+ if (nextHopMacAddress != null) {
+ ip2Mac.put(routeEntry.nextHop(), nextHopMacAddress);
+ }
+ }
+ if (nextHopMacAddress == null) {
+ routesWaitingOnArp.put(routeEntry.nextHop(), routeEntry);
+ return null;
+ }
+ return new FibEntry(routeEntry.prefix(), routeEntry.nextHop(),
+ nextHopMacAddress);
+ }
+
+ /**
+ * Processes the deletion of a route entry.
+ * <p>
+ * The prefix for the routing entry is removed from radix tree.
+ * If the operation is successful, the prefix is added to the collection
+ * of prefixes whose intents that will be withdrawn.
+ * </p>
+ *
+ * @param routeEntry the route entry to delete
+ * @param withdrawPrefixes the collection of accumulated prefixes whose
+ * intents will be withdrawn
+ */
+ private void processRouteDelete(RouteEntry routeEntry,
+ Collection<IpPrefix> withdrawPrefixes) {
+ log.debug("Processing route delete: {}", routeEntry);
+ boolean isRemoved = removeRibRoute(routeEntry.prefix());
+
+ if (isRemoved) {
+ //
+ // Only withdraw intents if an entry was actually removed from the
+ // tree. If no entry was removed, the <prefix, nexthop> wasn't
+ // there so it's probably already been removed and we don't
+ // need to do anything.
+ //
+ withdrawPrefixes.add(routeEntry.prefix());
+ }
+
+ routesWaitingOnArp.remove(routeEntry.nextHop(), routeEntry);
+ }
+
+ /**
+ * Signals the Router that the MAC to IP mapping has potentially been
+ * updated. This has the effect of updating the MAC address for any
+ * installed prefixes if it has changed, as well as installing any pending
+ * prefixes that were waiting for MAC resolution.
+ *
+ * @param ipAddress the IP address that an event was received for
+ * @param macAddress the most recently known MAC address for the IP address
+ */
+ private void updateMac(IpAddress ipAddress, MacAddress macAddress) {
+ log.debug("Received updated MAC info: {} => {}", ipAddress,
+ macAddress);
+
+ //
+ // We synchronize on "this" to prevent changes to the Radix tree
+ // while we're pushing intents. If the tree changes, the
+ // tree and the intents could get out of sync.
+ //
+ synchronized (this) {
+ Collection<FibUpdate> submitFibEntries = new LinkedList<>();
+
+ Set<RouteEntry> routesToPush =
+ routesWaitingOnArp.removeAll(ipAddress);
+
+ for (RouteEntry routeEntry : routesToPush) {
+ // These will always be adds
+ RouteEntry foundRouteEntry = findRibRoute(routeEntry.prefix());
+ if (foundRouteEntry != null &&
+ foundRouteEntry.nextHop().equals(routeEntry.nextHop())) {
+ // We only push FIB updates if the prefix is still in the
+ // radix tree and the next hop is the same as our entry.
+ // The prefix could have been removed while we were waiting
+ // for the ARP, or the next hop could have changed.
+ submitFibEntries.add(new FibUpdate(FibUpdate.Type.UPDATE,
+ new FibEntry(routeEntry.prefix(),
+ ipAddress, macAddress)));
+ } else {
+ log.debug("{} has been revoked before the MAC was resolved",
+ routeEntry);
+ }
+ }
+
+ if (!submitFibEntries.isEmpty()) {
+ fibComponent.update(submitFibEntries, Collections.emptyList());
+ }
+
+ ip2Mac.put(ipAddress, macAddress);
+ }
+ }
+
+ /**
+ * Listener for host events.
+ */
+ class InternalHostListener implements HostListener {
+ @Override
+ public void event(HostEvent event) {
+ log.debug("Received HostEvent {}", event);
+
+ Host host = event.subject();
+ switch (event.type()) {
+ case HOST_ADDED:
+ // FALLTHROUGH
+ case HOST_UPDATED:
+ for (IpAddress ipAddress : host.ipAddresses()) {
+ updateMac(ipAddress, host.mac());
+ }
+ break;
+ case HOST_REMOVED:
+ for (IpAddress ipAddress : host.ipAddresses()) {
+ ip2Mac.remove(ipAddress);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /**
+ * Listener for route events.
+ */
+ private class InternalRouteListener implements RouteListener {
+ @Override
+ public void update(Collection<RouteUpdate> routeUpdates) {
+ Router.this.update(routeUpdates);
+ }
+ }
+
+ @Override
+ public LocationType getLocationType(IpAddress ipAddress) {
+ if (routingConfigurationService.isIpAddressLocal(ipAddress)) {
+ return LocationType.LOCAL;
+ } else if (getLongestMatchableRouteEntry(ipAddress) != null) {
+ return LocationType.INTERNET;
+ } else {
+ return LocationType.NO_ROUTE;
+ }
+ }
+
+ @Override
+ public RouteEntry getLongestMatchableRouteEntry(IpAddress ipAddress) {
+ RouteEntry routeEntry = null;
+ Iterable<RouteEntry> routeEntries;
+
+ if (ipAddress.isIp4()) {
+ routeEntries = ribTable4.getValuesForKeysPrefixing(
+ createBinaryString(
+ IpPrefix.valueOf(ipAddress, Ip4Address.BIT_LENGTH)));
+ } else {
+ routeEntries = ribTable6.getValuesForKeysPrefixing(
+ createBinaryString(
+ IpPrefix.valueOf(ipAddress, Ip6Address.BIT_LENGTH)));
+ }
+ if (routeEntries == null) {
+ return null;
+ }
+ Iterator<RouteEntry> it = routeEntries.iterator();
+ while (it.hasNext()) {
+ routeEntry = it.next();
+ }
+ return routeEntry;
+ }
+
+ @Override
+ public ConnectPoint getEgressConnectPoint(IpAddress dstIpAddress) {
+ LocationType type = getLocationType(dstIpAddress);
+ if (type == LocationType.LOCAL) {
+ Set<Host> hosts = hostService.getHostsByIp(dstIpAddress);
+ if (!hosts.isEmpty()) {
+ return hosts.iterator().next().location();
+ } else {
+ hostService.startMonitoringIp(dstIpAddress);
+ return null;
+ }
+ } else if (type == LocationType.INTERNET) {
+ IpAddress nextHopIpAddress = null;
+ RouteEntry routeEntry = getLongestMatchableRouteEntry(dstIpAddress);
+ if (routeEntry != null) {
+ nextHopIpAddress = routeEntry.nextHop();
+ Interface it = interfaceService.getMatchingInterface(nextHopIpAddress);
+ if (it != null) {
+ return it.connectPoint();
+ } else {
+ return null;
+ }
+ } else {
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public void packetReactiveProcessor(IpAddress dstIpAddress,
+ IpAddress srcIpAddress,
+ ConnectPoint srcConnectPoint,
+ MacAddress srcMacAddress) {
+ checkNotNull(dstIpAddress);
+ checkNotNull(srcIpAddress);
+ checkNotNull(srcConnectPoint);
+ checkNotNull(srcMacAddress);
+
+ //
+ // Step1: Try to update the existing intent first if it exists.
+ //
+ IpPrefix ipPrefix = null;
+ if (routingConfigurationService.isIpAddressLocal(dstIpAddress)) {
+ if (dstIpAddress.isIp4()) {
+ ipPrefix = IpPrefix.valueOf(dstIpAddress,
+ Ip4Address.BIT_LENGTH);
+ } else {
+ ipPrefix = IpPrefix.valueOf(dstIpAddress,
+ Ip6Address.BIT_LENGTH);
+ }
+ } else {
+ // Get IP prefix from BGP route table
+ RouteEntry routeEntry = getLongestMatchableRouteEntry(dstIpAddress);
+ if (routeEntry != null) {
+ ipPrefix = routeEntry.prefix();
+ }
+ }
+ if (ipPrefix != null
+ && intentRequestListener.mp2pIntentExists(ipPrefix)) {
+ intentRequestListener.updateExistingMp2pIntent(ipPrefix,
+ srcConnectPoint);
+ return;
+ }
+
+ //
+ // Step2: There is no existing intent for the destination IP address.
+ // Check whether it is necessary to create a new one. If necessary then
+ // create a new one.
+ //
+ TrafficType trafficType =
+ trafficTypeClassifier(srcConnectPoint, dstIpAddress);
+
+ switch (trafficType) {
+ case HOST_TO_INTERNET:
+ // If the destination IP address is outside the local SDN network.
+ // The Step 1 has already handled it. We do not need to do anything here.
+ break;
+ case INTERNET_TO_HOST:
+ intentRequestListener.setUpConnectivityInternetToHost(dstIpAddress);
+ break;
+ case HOST_TO_HOST:
+ intentRequestListener.setUpConnectivityHostToHost(dstIpAddress,
+ srcIpAddress, srcMacAddress, srcConnectPoint);
+ break;
+ case INTERNET_TO_INTERNET:
+ log.trace("This is transit traffic, "
+ + "the intent should be preinstalled already");
+ break;
+ case DROP:
+ // TODO here should setUpDropPaccketIntent(...);
+ // We need a new type of intent here.
+ break;
+ case UNKNOWN:
+ log.trace("This is unknown traffic, so we do nothing");
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * Classifies the traffic and return the traffic type.
+ *
+ * @param srcConnectPoint the connect point where the packet comes from
+ * @param dstIp the destination IP address in packet
+ * @return the traffic type which this packet belongs to
+ */
+ private TrafficType trafficTypeClassifier(ConnectPoint srcConnectPoint,
+ IpAddress dstIp) {
+ LocationType dstIpLocationType = getLocationType(dstIp);
+ Optional<Interface> srcInterface =
+ interfaceService.getInterfacesByPort(srcConnectPoint).stream().findFirst();
+
+ switch (dstIpLocationType) {
+ case INTERNET:
+ if (!srcInterface.isPresent()) {
+ return TrafficType.HOST_TO_INTERNET;
+ } else {
+ return TrafficType.INTERNET_TO_INTERNET;
+ }
+ case LOCAL:
+ if (!srcInterface.isPresent()) {
+ return TrafficType.HOST_TO_HOST;
+ } else {
+ // TODO Currently we only consider local public prefixes.
+ // In the future, we will consider the local private prefixes.
+ // If dstIpLocationType is a local private, we should return
+ // TrafficType.DROP.
+ return TrafficType.INTERNET_TO_HOST;
+ }
+ case NO_ROUTE:
+ return TrafficType.DROP;
+ default:
+ return TrafficType.UNKNOWN;
+ }
+ }
+}
diff --git a/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/impl/StaticRouter.java b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/impl/StaticRouter.java
new file mode 100644
index 00000000..29526fff
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/impl/StaticRouter.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.routing.impl;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.routing.FibListener;
+import org.onosproject.routing.IntentRequestListener;
+import org.onosproject.routing.RouteEntry;
+import org.onosproject.routing.RoutingService;
+import org.onosproject.routing.StaticRoutingService;
+
+import java.util.Collection;
+
+/**
+ * Static router maintains handle to FIB listener.
+ *
+ * TODO: implement getRoutes methods
+ */
+@Component(immediate = true, enabled = false)
+@Service
+public class StaticRouter implements RoutingService, StaticRoutingService {
+ private FibListener fibListener;
+
+ @Override
+ public void start() {
+
+ }
+
+ @Override
+ public void addFibListener(FibListener fibListener) {
+ this.fibListener = fibListener;
+ }
+
+ @Override
+ public void addIntentRequestListener(IntentRequestListener intentRequestListener) {
+
+ }
+
+ @Override
+ public void stop() {
+
+ }
+
+ @Override
+ public Collection<RouteEntry> getRoutes4() {
+ return null;
+ }
+
+ @Override
+ public Collection<RouteEntry> getRoutes6() {
+ return null;
+ }
+
+ @Override
+ public LocationType getLocationType(IpAddress ipAddress) {
+ return null;
+ }
+
+ @Override
+ public RouteEntry getLongestMatchableRouteEntry(IpAddress ipAddress) {
+ return null;
+ }
+
+ @Override
+ public ConnectPoint getEgressConnectPoint(IpAddress dstIpAddress) {
+ return null;
+ }
+
+ @Override
+ public void packetReactiveProcessor(IpAddress dstIpAddress, IpAddress srcIpAddress,
+ ConnectPoint srcConnectPoint, MacAddress srcMacAddress) {
+
+ }
+
+ @Override
+ public FibListener getFibListener() {
+ return fibListener;
+ }
+
+}
diff --git a/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/impl/package-info.java b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/impl/package-info.java
new file mode 100644
index 00000000..bb64bf67
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/main/java/org/onosproject/routing/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Implementation of routing functionality.
+ */
+package org.onosproject.routing.impl;
diff --git a/framework/src/onos/apps/routing/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/framework/src/onos/apps/routing/src/main/resources/OSGI-INF/blueprint/shell-config.xml
new file mode 100644
index 00000000..866e1c4a
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -0,0 +1,38 @@
+<!--
+ ~ Copyright 2015 Open Networking Laboratory
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
+
+ <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
+ <command>
+ <action class="org.onosproject.routing.cli.BgpNeighborsListCommand"/>
+ </command>
+ <command>
+ <action class="org.onosproject.routing.cli.BgpRoutesListCommand"/>
+ </command>
+ <command>
+ <action class="org.onosproject.routing.cli.RoutesListCommand"/>
+ </command>
+ <command>
+ <action class="org.onosproject.routing.cli.AddRouteCommand"/>
+ </command>
+ <command>
+ <action class="org.onosproject.routing.cli.RemoveRouteCommand"/>
+ </command>
+ <command>
+ <action class="org.onosproject.routing.cli.BgpSpeakersListCommand"/>
+ </command>
+ </command-bundle>
+</blueprint>
diff --git a/framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/bgp/AsPathTest.java b/framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/bgp/AsPathTest.java
new file mode 100644
index 00000000..9213e4e0
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/bgp/AsPathTest.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.routing.bgp;
+
+import org.hamcrest.Matchers;
+import org.junit.Test;
+
+import java.util.ArrayList;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Unit tests for the BgpRouteEntry.AsPath class.
+ */
+public class AsPathTest {
+ /**
+ * Generates Path Segments.
+ *
+ * @return the generated Path Segments
+ */
+ private ArrayList<BgpRouteEntry.PathSegment> generatePathSegments() {
+ ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
+ byte pathSegmentType;
+ ArrayList<Long> segmentAsNumbers;
+ BgpRouteEntry.PathSegment pathSegment;
+
+ pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_CONFED_SEQUENCE;
+ segmentAsNumbers = new ArrayList<>();
+ segmentAsNumbers.add(1L);
+ segmentAsNumbers.add(2L);
+ segmentAsNumbers.add(3L);
+ pathSegment =
+ new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers);
+ pathSegments.add(pathSegment);
+ //
+ pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_CONFED_SET;
+ segmentAsNumbers = new ArrayList<>();
+ segmentAsNumbers.add(4L);
+ segmentAsNumbers.add(5L);
+ segmentAsNumbers.add(6L);
+ pathSegment =
+ new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers);
+ pathSegments.add(pathSegment);
+ //
+ pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
+ segmentAsNumbers = new ArrayList<>();
+ segmentAsNumbers.add(7L);
+ segmentAsNumbers.add(8L);
+ segmentAsNumbers.add(9L);
+ pathSegment =
+ new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers);
+ pathSegments.add(pathSegment);
+ //
+ pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_SET;
+ segmentAsNumbers = new ArrayList<>();
+ segmentAsNumbers.add(10L);
+ segmentAsNumbers.add(11L);
+ segmentAsNumbers.add(12L);
+ pathSegment =
+ new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers);
+ pathSegments.add(pathSegment);
+
+ return pathSegments;
+ }
+
+ /**
+ * Generates an AS Path.
+ *
+ * @return a generated AS Path
+ */
+ private BgpRouteEntry.AsPath generateAsPath() {
+ ArrayList<BgpRouteEntry.PathSegment> pathSegments =
+ generatePathSegments();
+ BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
+
+ return asPath;
+ }
+
+ /**
+ * Tests valid class constructor.
+ */
+ @Test
+ public void testConstructor() {
+ BgpRouteEntry.AsPath asPath = generateAsPath();
+
+ String expectedString =
+ "AsPath{pathSegments=[" +
+ "PathSegment{type=AS_CONFED_SEQUENCE, segmentAsNumbers=[1, 2, 3]}, " +
+ "PathSegment{type=AS_CONFED_SET, segmentAsNumbers=[4, 5, 6]}, " +
+ "PathSegment{type=AS_SEQUENCE, segmentAsNumbers=[7, 8, 9]}, " +
+ "PathSegment{type=AS_SET, segmentAsNumbers=[10, 11, 12]}]}";
+ assertThat(asPath.toString(), is(expectedString));
+ }
+
+ /**
+ * Tests invalid class constructor for null Path Segments.
+ */
+ @Test(expected = NullPointerException.class)
+ public void testInvalidConstructorNullPathSegments() {
+ ArrayList<BgpRouteEntry.PathSegment> pathSegments = null;
+ new BgpRouteEntry.AsPath(pathSegments);
+ }
+
+ /**
+ * Tests getting the fields of an AS Path.
+ */
+ @Test
+ public void testGetFields() {
+ // Create the fields to compare against
+ ArrayList<BgpRouteEntry.PathSegment> pathSegments =
+ generatePathSegments();
+
+ // Generate the entry to test
+ BgpRouteEntry.AsPath asPath = generateAsPath();
+
+ assertThat(asPath.getPathSegments(), is(pathSegments));
+ }
+
+ /**
+ * Tests getting the AS Path Length.
+ */
+ @Test
+ public void testGetAsPathLength() {
+ //
+ // NOTE:
+ // - AS_CONFED_SEQUENCE and AS_CONFED_SET are excluded
+ // - AS_SET counts as a single hop
+ //
+ BgpRouteEntry.AsPath asPath = generateAsPath();
+ assertThat(asPath.getAsPathLength(), is(4));
+
+ // Create an empty AS Path
+ ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
+ asPath = new BgpRouteEntry.AsPath(pathSegments);
+ assertThat(asPath.getAsPathLength(), is(0));
+ }
+
+ /**
+ * Tests equality of {@link BgpRouteEntry.AsPath}.
+ */
+ @Test
+ public void testEquality() {
+ BgpRouteEntry.AsPath asPath1 = generateAsPath();
+ BgpRouteEntry.AsPath asPath2 = generateAsPath();
+
+ assertThat(asPath1, is(asPath2));
+ }
+
+ /**
+ * Tests non-equality of {@link BgpRouteEntry.AsPath}.
+ */
+ @Test
+ public void testNonEquality() {
+ BgpRouteEntry.AsPath asPath1 = generateAsPath();
+
+ // Setup AS Path 2
+ ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
+ byte pathSegmentType;
+ ArrayList<Long> segmentAsNumbers;
+ BgpRouteEntry.PathSegment pathSegment;
+
+ pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_CONFED_SEQUENCE;
+ segmentAsNumbers = new ArrayList<>();
+ segmentAsNumbers.add(1L);
+ segmentAsNumbers.add(2L);
+ segmentAsNumbers.add(3L);
+ pathSegment =
+ new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers);
+ pathSegments.add(pathSegment);
+ //
+ pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_CONFED_SET;
+ segmentAsNumbers = new ArrayList<>();
+ segmentAsNumbers.add(4L);
+ segmentAsNumbers.add(5L);
+ segmentAsNumbers.add(6L);
+ pathSegment =
+ new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers);
+ pathSegments.add(pathSegment);
+ //
+ pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
+ segmentAsNumbers = new ArrayList<>();
+ segmentAsNumbers.add(7L);
+ segmentAsNumbers.add(8L);
+ segmentAsNumbers.add(9L);
+ pathSegment =
+ new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers);
+ pathSegments.add(pathSegment);
+ //
+ pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_SET;
+ segmentAsNumbers = new ArrayList<>();
+ segmentAsNumbers.add(10L);
+ segmentAsNumbers.add(111L); // Different
+ segmentAsNumbers.add(12L);
+ pathSegment =
+ new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers);
+ pathSegments.add(pathSegment);
+ //
+ BgpRouteEntry.AsPath asPath2 = new BgpRouteEntry.AsPath(pathSegments);
+
+ assertThat(asPath1, Matchers.is(not(asPath2)));
+ }
+
+ /**
+ * Tests object string representation.
+ */
+ @Test
+ public void testToString() {
+ BgpRouteEntry.AsPath asPath = generateAsPath();
+
+ String expectedString =
+ "AsPath{pathSegments=[" +
+ "PathSegment{type=AS_CONFED_SEQUENCE, segmentAsNumbers=[1, 2, 3]}, " +
+ "PathSegment{type=AS_CONFED_SET, segmentAsNumbers=[4, 5, 6]}, " +
+ "PathSegment{type=AS_SEQUENCE, segmentAsNumbers=[7, 8, 9]}, " +
+ "PathSegment{type=AS_SET, segmentAsNumbers=[10, 11, 12]}]}";
+ assertThat(asPath.toString(), is(expectedString));
+ }
+}
diff --git a/framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/bgp/BgpRouteEntryTest.java b/framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/bgp/BgpRouteEntryTest.java
new file mode 100644
index 00000000..446056e9
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/bgp/BgpRouteEntryTest.java
@@ -0,0 +1,520 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.routing.bgp;
+
+import org.easymock.EasyMock;
+import org.hamcrest.Matchers;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+
+import java.util.ArrayList;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Unit tests for the BgpRouteEntry class.
+ */
+public class BgpRouteEntryTest {
+ private BgpSession bgpSession;
+ private static final Ip4Address BGP_SESSION_BGP_ID =
+ Ip4Address.valueOf("10.0.0.1");
+ private static final Ip4Address BGP_SESSION_IP_ADDRESS =
+ Ip4Address.valueOf("20.0.0.1");
+
+ private BgpSession bgpSession2;
+ private static final Ip4Address BGP_SESSION_BGP_ID2 =
+ Ip4Address.valueOf("10.0.0.2");
+ private static final Ip4Address BGP_SESSION_IP_ADDRESS2 =
+ Ip4Address.valueOf("20.0.0.1");
+
+ private BgpSession bgpSession3;
+ private static final Ip4Address BGP_SESSION_BGP_ID3 =
+ Ip4Address.valueOf("10.0.0.1");
+ private static final Ip4Address BGP_SESSION_IP_ADDRESS3 =
+ Ip4Address.valueOf("20.0.0.2");
+
+ private final BgpSessionInfo localInfo = new BgpSessionInfo();
+ private final BgpSessionInfo remoteInfo = new BgpSessionInfo();
+
+ private final BgpSessionInfo localInfo2 = new BgpSessionInfo();
+ private final BgpSessionInfo remoteInfo2 = new BgpSessionInfo();
+
+ private final BgpSessionInfo localInfo3 = new BgpSessionInfo();
+ private final BgpSessionInfo remoteInfo3 = new BgpSessionInfo();
+
+ @Before
+ public void setUp() throws Exception {
+ // Mock objects for testing
+ bgpSession = EasyMock.createMock(BgpSession.class);
+ bgpSession2 = EasyMock.createMock(BgpSession.class);
+ bgpSession3 = EasyMock.createMock(BgpSession.class);
+
+ // Setup the BGP Sessions
+ remoteInfo.setIp4Address(BGP_SESSION_IP_ADDRESS);
+ remoteInfo2.setIp4Address(BGP_SESSION_IP_ADDRESS2);
+ remoteInfo3.setIp4Address(BGP_SESSION_IP_ADDRESS3);
+ remoteInfo.setBgpId(BGP_SESSION_BGP_ID);
+ remoteInfo2.setBgpId(BGP_SESSION_BGP_ID2);
+ remoteInfo3.setBgpId(BGP_SESSION_BGP_ID3);
+
+ EasyMock.expect(bgpSession.localInfo()).andReturn(localInfo).anyTimes();
+ EasyMock.expect(bgpSession.remoteInfo()).andReturn(remoteInfo).anyTimes();
+ EasyMock.expect(bgpSession2.localInfo()).andReturn(localInfo2).anyTimes();
+ EasyMock.expect(bgpSession2.remoteInfo()).andReturn(remoteInfo2).anyTimes();
+ EasyMock.expect(bgpSession3.localInfo()).andReturn(localInfo3).anyTimes();
+ EasyMock.expect(bgpSession3.remoteInfo()).andReturn(remoteInfo3).anyTimes();
+
+ EasyMock.replay(bgpSession);
+ EasyMock.replay(bgpSession2);
+ EasyMock.replay(bgpSession3);
+ }
+
+ /**
+ * Generates a BGP Route Entry.
+ *
+ * @return a generated BGP Route Entry
+ */
+ private BgpRouteEntry generateBgpRouteEntry() {
+ Ip4Prefix prefix = Ip4Prefix.valueOf("1.2.3.0/24");
+ Ip4Address nextHop = Ip4Address.valueOf("5.6.7.8");
+ byte origin = BgpConstants.Update.Origin.IGP;
+ // Setup the AS Path
+ ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
+ byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
+ ArrayList<Long> segmentAsNumbers1 = new ArrayList<>();
+ segmentAsNumbers1.add(1L);
+ segmentAsNumbers1.add(2L);
+ segmentAsNumbers1.add(3L);
+ BgpRouteEntry.PathSegment pathSegment1 =
+ new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1);
+ pathSegments.add(pathSegment1);
+ //
+ byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET;
+ ArrayList<Long> segmentAsNumbers2 = new ArrayList<>();
+ segmentAsNumbers2.add(4L);
+ segmentAsNumbers2.add(5L);
+ segmentAsNumbers2.add(6L);
+ BgpRouteEntry.PathSegment pathSegment2 =
+ new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2);
+ pathSegments.add(pathSegment2);
+ //
+ BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
+ //
+ long localPref = 100;
+ long multiExitDisc = 20;
+
+ BgpRouteEntry bgpRouteEntry =
+ new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
+ localPref);
+ bgpRouteEntry.setMultiExitDisc(multiExitDisc);
+
+ return bgpRouteEntry;
+ }
+
+ /**
+ * Tests valid class constructor.
+ */
+ @Test
+ public void testConstructor() {
+ BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry();
+
+ String expectedString =
+ "BgpRouteEntry{prefix=1.2.3.0/24, nextHop=5.6.7.8, " +
+ "bgpId=10.0.0.1, origin=IGP, asPath=AsPath{pathSegments=" +
+ "[PathSegment{type=AS_SEQUENCE, segmentAsNumbers=[1, 2, 3]}, " +
+ "PathSegment{type=AS_SET, segmentAsNumbers=[4, 5, 6]}]}, " +
+ "localPref=100, multiExitDisc=20}";
+ assertThat(bgpRouteEntry.toString(), is(expectedString));
+ }
+
+ /**
+ * Tests invalid class constructor for null BGP Session.
+ */
+ @Test(expected = NullPointerException.class)
+ public void testInvalidConstructorNullBgpSession() {
+ BgpSession bgpSessionNull = null;
+ Ip4Prefix prefix = Ip4Prefix.valueOf("1.2.3.0/24");
+ Ip4Address nextHop = Ip4Address.valueOf("5.6.7.8");
+ byte origin = BgpConstants.Update.Origin.IGP;
+ // Setup the AS Path
+ ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
+ BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
+ //
+ long localPref = 100;
+
+ new BgpRouteEntry(bgpSessionNull, prefix, nextHop, origin, asPath,
+ localPref);
+ }
+
+ /**
+ * Tests invalid class constructor for null AS Path.
+ */
+ @Test(expected = NullPointerException.class)
+ public void testInvalidConstructorNullAsPath() {
+ Ip4Prefix prefix = Ip4Prefix.valueOf("1.2.3.0/24");
+ Ip4Address nextHop = Ip4Address.valueOf("5.6.7.8");
+ byte origin = BgpConstants.Update.Origin.IGP;
+ BgpRouteEntry.AsPath asPath = null;
+ long localPref = 100;
+
+ new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
+ localPref);
+ }
+
+ /**
+ * Tests getting the fields of a BGP route entry.
+ */
+ @Test
+ public void testGetFields() {
+ // Create the fields to compare against
+ Ip4Prefix prefix = Ip4Prefix.valueOf("1.2.3.0/24");
+ Ip4Address nextHop = Ip4Address.valueOf("5.6.7.8");
+ byte origin = BgpConstants.Update.Origin.IGP;
+ // Setup the AS Path
+ ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
+ byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
+ ArrayList<Long> segmentAsNumbers1 = new ArrayList<>();
+ segmentAsNumbers1.add(1L);
+ segmentAsNumbers1.add(2L);
+ segmentAsNumbers1.add(3L);
+ BgpRouteEntry.PathSegment pathSegment1 =
+ new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1);
+ pathSegments.add(pathSegment1);
+ //
+ byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET;
+ ArrayList<Long> segmentAsNumbers2 = new ArrayList<>();
+ segmentAsNumbers2.add(4L);
+ segmentAsNumbers2.add(5L);
+ segmentAsNumbers2.add(6L);
+ BgpRouteEntry.PathSegment pathSegment2 =
+ new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2);
+ pathSegments.add(pathSegment2);
+ //
+ BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
+ //
+ long localPref = 100;
+ long multiExitDisc = 20;
+
+ // Generate the entry to test
+ BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry();
+
+ assertThat(bgpRouteEntry.prefix(), is(prefix));
+ assertThat(bgpRouteEntry.nextHop(), is(nextHop));
+ assertThat(bgpRouteEntry.getBgpSession(), is(bgpSession));
+ assertThat(bgpRouteEntry.getOrigin(), is(origin));
+ assertThat(bgpRouteEntry.getAsPath(), is(asPath));
+ assertThat(bgpRouteEntry.getLocalPref(), is(localPref));
+ assertThat(bgpRouteEntry.getMultiExitDisc(), is(multiExitDisc));
+ }
+
+ /**
+ * Tests whether a BGP route entry is a local route.
+ */
+ @Test
+ public void testIsLocalRoute() {
+ //
+ // Test non-local route
+ //
+ BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry();
+ assertThat(bgpRouteEntry.isLocalRoute(), is(false));
+
+ //
+ // Test local route with AS Path that begins with AS_SET
+ //
+ Ip4Prefix prefix = Ip4Prefix.valueOf("1.2.3.0/24");
+ Ip4Address nextHop = Ip4Address.valueOf("5.6.7.8");
+ byte origin = BgpConstants.Update.Origin.IGP;
+ // Setup the AS Path
+ ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
+ byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SET;
+ ArrayList<Long> segmentAsNumbers1 = new ArrayList<>();
+ segmentAsNumbers1.add(1L);
+ segmentAsNumbers1.add(2L);
+ segmentAsNumbers1.add(3L);
+ BgpRouteEntry.PathSegment pathSegment1 =
+ new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1);
+ pathSegments.add(pathSegment1);
+ //
+ byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
+ ArrayList<Long> segmentAsNumbers2 = new ArrayList<>();
+ segmentAsNumbers2.add(4L);
+ segmentAsNumbers2.add(5L);
+ segmentAsNumbers2.add(6L);
+ BgpRouteEntry.PathSegment pathSegment2 =
+ new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2);
+ pathSegments.add(pathSegment2);
+ //
+ BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
+ //
+ long localPref = 100;
+ long multiExitDisc = 20;
+ //
+ bgpRouteEntry =
+ new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
+ localPref);
+ bgpRouteEntry.setMultiExitDisc(multiExitDisc);
+ assertThat(bgpRouteEntry.isLocalRoute(), is(true));
+
+ //
+ // Test local route with empty AS Path
+ //
+ pathSegments = new ArrayList<>();
+ asPath = new BgpRouteEntry.AsPath(pathSegments);
+ bgpRouteEntry =
+ new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
+ localPref);
+ bgpRouteEntry.setMultiExitDisc(multiExitDisc);
+ assertThat(bgpRouteEntry.isLocalRoute(), is(true));
+ }
+
+ /**
+ * Tests getting the BGP Neighbor AS number for a route.
+ */
+ @Test
+ public void testGetNeighborAs() {
+ //
+ // Get neighbor AS for non-local route
+ //
+ BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry();
+ assertThat(bgpRouteEntry.getNeighborAs(), is(1L));
+
+ //
+ // Get neighbor AS for a local route
+ //
+ Ip4Prefix prefix = Ip4Prefix.valueOf("1.2.3.0/24");
+ Ip4Address nextHop = Ip4Address.valueOf("5.6.7.8");
+ byte origin = BgpConstants.Update.Origin.IGP;
+ // Setup the AS Path
+ ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
+ BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
+ //
+ long localPref = 100;
+ long multiExitDisc = 20;
+ //
+ bgpRouteEntry =
+ new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
+ localPref);
+ bgpRouteEntry.setMultiExitDisc(multiExitDisc);
+ assertThat(bgpRouteEntry.getNeighborAs(), is(BgpConstants.BGP_AS_0));
+ }
+
+ /**
+ * Tests whether a BGP route entry has AS Path loop.
+ */
+ @Test
+ public void testHasAsPathLoop() {
+ BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry();
+
+ // Test for loops: test each AS number in the interval [1, 6]
+ for (int i = 1; i <= 6; i++) {
+ assertThat(bgpRouteEntry.hasAsPathLoop(i), is(true));
+ }
+
+ // Test for non-loops
+ assertThat(bgpRouteEntry.hasAsPathLoop(500), is(false));
+ }
+
+ /**
+ * Tests the BGP Decision Process comparison of BGP routes.
+ */
+ @Test
+ public void testBgpDecisionProcessComparison() {
+ BgpRouteEntry bgpRouteEntry1 = generateBgpRouteEntry();
+ BgpRouteEntry bgpRouteEntry2 = generateBgpRouteEntry();
+
+ //
+ // Compare two routes that are same
+ //
+ assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true));
+ assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(true));
+
+ //
+ // Compare two routes with different LOCAL_PREF
+ //
+ Ip4Prefix prefix = Ip4Prefix.valueOf("1.2.3.0/24");
+ Ip4Address nextHop = Ip4Address.valueOf("5.6.7.8");
+ byte origin = BgpConstants.Update.Origin.IGP;
+ // Setup the AS Path
+ ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
+ byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
+ ArrayList<Long> segmentAsNumbers1 = new ArrayList<>();
+ segmentAsNumbers1.add(1L);
+ segmentAsNumbers1.add(2L);
+ segmentAsNumbers1.add(3L);
+ BgpRouteEntry.PathSegment pathSegment1 =
+ new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1);
+ pathSegments.add(pathSegment1);
+ //
+ byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET;
+ ArrayList<Long> segmentAsNumbers2 = new ArrayList<>();
+ segmentAsNumbers2.add(4L);
+ segmentAsNumbers2.add(5L);
+ segmentAsNumbers2.add(6L);
+ BgpRouteEntry.PathSegment pathSegment2 =
+ new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2);
+ pathSegments.add(pathSegment2);
+ //
+ BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
+ //
+ long localPref = 50; // Different
+ long multiExitDisc = 20;
+ bgpRouteEntry2 =
+ new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
+ localPref);
+ bgpRouteEntry2.setMultiExitDisc(multiExitDisc);
+ //
+ assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true));
+ assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(false));
+ localPref = bgpRouteEntry1.getLocalPref(); // Restore
+
+ //
+ // Compare two routes with different AS_PATH length
+ //
+ ArrayList<BgpRouteEntry.PathSegment> pathSegments2 = new ArrayList<>();
+ pathSegments2.add(pathSegment1);
+ // Different AS Path
+ BgpRouteEntry.AsPath asPath2 = new BgpRouteEntry.AsPath(pathSegments2);
+ bgpRouteEntry2 =
+ new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath2,
+ localPref);
+ bgpRouteEntry2.setMultiExitDisc(multiExitDisc);
+ //
+ assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(false));
+ assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(true));
+
+ //
+ // Compare two routes with different ORIGIN
+ //
+ origin = BgpConstants.Update.Origin.EGP; // Different
+ bgpRouteEntry2 =
+ new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
+ localPref);
+ bgpRouteEntry2.setMultiExitDisc(multiExitDisc);
+ //
+ assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true));
+ assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(false));
+ origin = bgpRouteEntry1.getOrigin(); // Restore
+
+ //
+ // Compare two routes with different MULTI_EXIT_DISC
+ //
+ multiExitDisc = 10; // Different
+ bgpRouteEntry2 =
+ new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
+ localPref);
+ bgpRouteEntry2.setMultiExitDisc(multiExitDisc);
+ //
+ assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true));
+ assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(false));
+ multiExitDisc = bgpRouteEntry1.getMultiExitDisc(); // Restore
+
+ //
+ // Compare two routes with different BGP ID
+ //
+ bgpRouteEntry2 =
+ new BgpRouteEntry(bgpSession2, prefix, nextHop, origin, asPath,
+ localPref);
+ bgpRouteEntry2.setMultiExitDisc(multiExitDisc);
+ //
+ assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true));
+ assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(false));
+
+ //
+ // Compare two routes with different BGP address
+ //
+ bgpRouteEntry2 =
+ new BgpRouteEntry(bgpSession3, prefix, nextHop, origin, asPath,
+ localPref);
+ bgpRouteEntry2.setMultiExitDisc(multiExitDisc);
+ //
+ assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true));
+ assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(false));
+ }
+
+ /**
+ * Tests equality of {@link BgpRouteEntry}.
+ */
+ @Test
+ public void testEquality() {
+ BgpRouteEntry bgpRouteEntry1 = generateBgpRouteEntry();
+ BgpRouteEntry bgpRouteEntry2 = generateBgpRouteEntry();
+
+ assertThat(bgpRouteEntry1, is(bgpRouteEntry2));
+ }
+
+ /**
+ * Tests non-equality of {@link BgpRouteEntry}.
+ */
+ @Test
+ public void testNonEquality() {
+ BgpRouteEntry bgpRouteEntry1 = generateBgpRouteEntry();
+
+ // Setup BGP Route 2
+ Ip4Prefix prefix = Ip4Prefix.valueOf("1.2.3.0/24");
+ Ip4Address nextHop = Ip4Address.valueOf("5.6.7.8");
+ byte origin = BgpConstants.Update.Origin.IGP;
+ // Setup the AS Path
+ ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
+ byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
+ ArrayList<Long> segmentAsNumbers1 = new ArrayList<>();
+ segmentAsNumbers1.add(1L);
+ segmentAsNumbers1.add(2L);
+ segmentAsNumbers1.add(3L);
+ BgpRouteEntry.PathSegment pathSegment1 =
+ new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1);
+ pathSegments.add(pathSegment1);
+ //
+ byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET;
+ ArrayList<Long> segmentAsNumbers2 = new ArrayList<>();
+ segmentAsNumbers2.add(4L);
+ segmentAsNumbers2.add(5L);
+ segmentAsNumbers2.add(6L);
+ BgpRouteEntry.PathSegment pathSegment2 =
+ new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2);
+ pathSegments.add(pathSegment2);
+ //
+ BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
+ //
+ long localPref = 500; // Different
+ long multiExitDisc = 20;
+ BgpRouteEntry bgpRouteEntry2 =
+ new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
+ localPref);
+ bgpRouteEntry2.setMultiExitDisc(multiExitDisc);
+
+ assertThat(bgpRouteEntry1, Matchers.is(not(bgpRouteEntry2)));
+ }
+
+ /**
+ * Tests object string representation.
+ */
+ @Test
+ public void testToString() {
+ BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry();
+
+ String expectedString =
+ "BgpRouteEntry{prefix=1.2.3.0/24, nextHop=5.6.7.8, " +
+ "bgpId=10.0.0.1, origin=IGP, asPath=AsPath{pathSegments=" +
+ "[PathSegment{type=AS_SEQUENCE, segmentAsNumbers=[1, 2, 3]}, " +
+ "PathSegment{type=AS_SET, segmentAsNumbers=[4, 5, 6]}]}, " +
+ "localPref=100, multiExitDisc=20}";
+ assertThat(bgpRouteEntry.toString(), is(expectedString));
+ }
+}
diff --git a/framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/bgp/BgpSessionManagerTest.java b/framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/bgp/BgpSessionManagerTest.java
new file mode 100644
index 00000000..634e87b9
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/bgp/BgpSessionManagerTest.java
@@ -0,0 +1,895 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.routing.bgp;
+
+import com.google.common.net.InetAddresses;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+import org.jboss.netty.bootstrap.ClientBootstrap;
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.channel.Channel;
+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.NioClientSocketChannelFactory;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.junit.TestUtils;
+import org.onlab.junit.TestUtils.TestUtilsException;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+import org.onosproject.routing.RouteListener;
+import org.onosproject.routing.RouteUpdate;
+import org.osgi.service.component.ComponentContext;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.LinkedList;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Unit tests for the BgpSessionManager class.
+ */
+public class BgpSessionManagerTest {
+ private static final Ip4Address IP_LOOPBACK_ID =
+ Ip4Address.valueOf("127.0.0.1");
+ private static final Ip4Address BGP_PEER1_ID =
+ Ip4Address.valueOf("10.0.0.1");
+ private static final Ip4Address BGP_PEER2_ID =
+ Ip4Address.valueOf("10.0.0.2");
+ private static final Ip4Address BGP_PEER3_ID =
+ Ip4Address.valueOf("10.0.0.3");
+ private static final Ip4Address NEXT_HOP1_ROUTER =
+ Ip4Address.valueOf("10.20.30.41");
+ private static final Ip4Address NEXT_HOP2_ROUTER =
+ Ip4Address.valueOf("10.20.30.42");
+ private static final Ip4Address NEXT_HOP3_ROUTER =
+ Ip4Address.valueOf("10.20.30.43");
+
+ private static final long DEFAULT_LOCAL_PREF = 10;
+ private static final long BETTER_LOCAL_PREF = 20;
+ private static final long DEFAULT_MULTI_EXIT_DISC = 20;
+ private static final long BETTER_MULTI_EXIT_DISC = 30;
+
+ BgpRouteEntry.AsPath asPathShort;
+ BgpRouteEntry.AsPath asPathLong;
+
+ // Timeout waiting for a message to be received
+ private static final int MESSAGE_TIMEOUT_MS = 5000; // 5s
+
+ // The BGP Session Manager to test
+ private BgpSessionManager bgpSessionManager;
+
+ // Remote Peer state
+ private final Collection<TestBgpPeer> peers = new LinkedList<>();
+ TestBgpPeer peer1;
+ TestBgpPeer peer2;
+ TestBgpPeer peer3;
+
+ // Local BGP per-peer session state
+ BgpSession bgpSession1;
+ BgpSession bgpSession2;
+ BgpSession bgpSession3;
+
+ // The socket that the remote peers should connect to
+ private InetSocketAddress connectToSocket;
+
+ private final DummyRouteListener dummyRouteListener =
+ new DummyRouteListener();
+
+ /**
+ * Dummy implementation for the RouteListener interface.
+ */
+ private class DummyRouteListener implements RouteListener {
+ @Override
+ public void update(Collection<RouteUpdate> routeUpdate) {
+ // Nothing to do
+ }
+ }
+
+ /**
+ * A class to capture the state for a BGP peer.
+ */
+ private final class TestBgpPeer {
+ private final Ip4Address peerId;
+ private ClientBootstrap peerBootstrap;
+ private TestBgpPeerChannelHandler peerChannelHandler;
+ private TestBgpPeerFrameDecoder peerFrameDecoder =
+ new TestBgpPeerFrameDecoder();
+
+ /**
+ * Constructor.
+ *
+ * @param peerId the peer ID
+ */
+ private TestBgpPeer(Ip4Address peerId) {
+ this.peerId = peerId;
+ peerChannelHandler = new TestBgpPeerChannelHandler(peerId);
+ }
+
+ /**
+ * Starts up the BGP peer and connects it to the tested SDN-IP
+ * instance.
+ *
+ * @param connectToSocket the socket to connect to
+ */
+ private void connect(InetSocketAddress connectToSocket)
+ throws InterruptedException {
+ //
+ // Setup the BGP Peer, i.e., the "remote" BGP router that will
+ // initiate the BGP connection, send BGP UPDATE messages, etc.
+ //
+ ChannelFactory channelFactory =
+ new NioClientSocketChannelFactory(
+ Executors.newCachedThreadPool(),
+ Executors.newCachedThreadPool());
+ ChannelPipelineFactory pipelineFactory =
+ new ChannelPipelineFactory() {
+ @Override
+ public ChannelPipeline getPipeline() throws Exception {
+ // Setup the transmitting pipeline
+ ChannelPipeline pipeline = Channels.pipeline();
+ pipeline.addLast("TestBgpPeerFrameDecoder",
+ peerFrameDecoder);
+ pipeline.addLast("TestBgpPeerChannelHandler",
+ peerChannelHandler);
+ return pipeline;
+ }
+ };
+
+ peerBootstrap = new ClientBootstrap(channelFactory);
+ peerBootstrap.setOption("child.keepAlive", true);
+ peerBootstrap.setOption("child.tcpNoDelay", true);
+ peerBootstrap.setPipelineFactory(pipelineFactory);
+ peerBootstrap.connect(connectToSocket);
+
+ boolean result;
+ // Wait until the OPEN message is received
+ result = peerFrameDecoder.receivedOpenMessageLatch.await(
+ MESSAGE_TIMEOUT_MS,
+ TimeUnit.MILLISECONDS);
+ assertThat(result, is(true));
+ // Wait until the KEEPALIVE message is received
+ result = peerFrameDecoder.receivedKeepaliveMessageLatch.await(
+ MESSAGE_TIMEOUT_MS,
+ TimeUnit.MILLISECONDS);
+ assertThat(result, is(true));
+
+ for (BgpSession bgpSession : bgpSessionManager.getBgpSessions()) {
+ if (bgpSession.remoteInfo().bgpId().equals(BGP_PEER1_ID)) {
+ bgpSession1 = bgpSession;
+ }
+ if (bgpSession.remoteInfo().bgpId().equals(BGP_PEER2_ID)) {
+ bgpSession2 = bgpSession;
+ }
+ if (bgpSession.remoteInfo().bgpId().equals(BGP_PEER3_ID)) {
+ bgpSession3 = bgpSession;
+ }
+ }
+ }
+ }
+
+ /**
+ * Class that implements a matcher for BgpRouteEntry by considering
+ * the BGP peer the entry was received from.
+ */
+ private static final class BgpRouteEntryAndPeerMatcher
+ extends TypeSafeMatcher<Collection<BgpRouteEntry>> {
+ private final BgpRouteEntry bgpRouteEntry;
+
+ private BgpRouteEntryAndPeerMatcher(BgpRouteEntry bgpRouteEntry) {
+ this.bgpRouteEntry = bgpRouteEntry;
+ }
+
+ @Override
+ public boolean matchesSafely(Collection<BgpRouteEntry> entries) {
+ for (BgpRouteEntry entry : entries) {
+ if (bgpRouteEntry.equals(entry) &&
+ bgpRouteEntry.getBgpSession() == entry.getBgpSession()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("BGP route entry lookup for entry \"").
+ appendText(bgpRouteEntry.toString()).
+ appendText("\"");
+ }
+ }
+
+ /**
+ * A helper method used for testing whether a collection of
+ * BGP route entries contains an entry from a specific BGP peer.
+ *
+ * @param bgpRouteEntry the BGP route entry to test
+ * @return an instance of BgpRouteEntryAndPeerMatcher that implements
+ * the matching logic
+ */
+ private static BgpRouteEntryAndPeerMatcher hasBgpRouteEntry(
+ BgpRouteEntry bgpRouteEntry) {
+ return new BgpRouteEntryAndPeerMatcher(bgpRouteEntry);
+ }
+
+ @SuppressWarnings("unchecked")
+ private Dictionary<String, String>
+ getDictionaryMock(ComponentContext componentContext) {
+ Dictionary<String, String> dictionary = createMock(Dictionary.class);
+ expect(dictionary.get("bgpPort")).andReturn("0");
+ replay(dictionary);
+ expect(componentContext.getProperties()).andReturn(dictionary);
+ return dictionary;
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ peer1 = new TestBgpPeer(BGP_PEER1_ID);
+ peer2 = new TestBgpPeer(BGP_PEER2_ID);
+ peer3 = new TestBgpPeer(BGP_PEER3_ID);
+ peers.clear();
+ peers.add(peer1);
+ peers.add(peer2);
+ peers.add(peer3);
+
+ //
+ // Setup the BGP Session Manager to test, and start listening for BGP
+ // connections.
+ //
+ bgpSessionManager = new BgpSessionManager();
+ // NOTE: We use port 0 to bind on any available port
+ ComponentContext componentContext = createMock(ComponentContext.class);
+ Dictionary<String, String> dictionary = getDictionaryMock(componentContext);
+ replay(componentContext);
+ bgpSessionManager.activate(componentContext);
+ bgpSessionManager.start(dummyRouteListener);
+
+ // Get the port number the BGP Session Manager is listening on
+ Channel serverChannel = TestUtils.getField(bgpSessionManager,
+ "serverChannel");
+ SocketAddress socketAddress = serverChannel.getLocalAddress();
+ InetSocketAddress inetSocketAddress =
+ (InetSocketAddress) socketAddress;
+ InetAddress connectToAddress = InetAddresses.forString("127.0.0.1");
+ connectToSocket = new InetSocketAddress(connectToAddress,
+ inetSocketAddress.getPort());
+
+ //
+ // Setup the AS Paths
+ //
+ ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
+ byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
+ ArrayList<Long> segmentAsNumbers1 = new ArrayList<>();
+ segmentAsNumbers1.add(65010L);
+ segmentAsNumbers1.add(65020L);
+ segmentAsNumbers1.add(65030L);
+ BgpRouteEntry.PathSegment pathSegment1 =
+ new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1);
+ pathSegments.add(pathSegment1);
+ asPathShort = new BgpRouteEntry.AsPath(new ArrayList<>(pathSegments));
+ //
+ byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET;
+ ArrayList<Long> segmentAsNumbers2 = new ArrayList<>();
+ segmentAsNumbers2.add(65041L);
+ segmentAsNumbers2.add(65042L);
+ segmentAsNumbers2.add(65043L);
+ BgpRouteEntry.PathSegment pathSegment2 =
+ new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2);
+ pathSegments.add(pathSegment2);
+ //
+ asPathLong = new BgpRouteEntry.AsPath(pathSegments);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ bgpSessionManager.stop();
+ bgpSessionManager = null;
+ }
+
+ /**
+ * Gets BGP RIB-IN routes by waiting until they are received.
+ * <p>
+ * NOTE: We keep checking once every 10ms the number of received routes,
+ * up to 5 seconds.
+ * </p>
+ *
+ * @param bgpSession the BGP session that is expected to receive the
+ * routes
+ * @param expectedRoutes the expected number of routes
+ * @return the BGP RIB-IN routes as received within the expected
+ * time interval
+ */
+ private Collection<BgpRouteEntry> waitForBgpRibIn(BgpSession bgpSession,
+ long expectedRoutes)
+ throws InterruptedException {
+ Collection<BgpRouteEntry> bgpRibIn = bgpSession.getBgpRibIn4();
+
+ final int maxChecks = 500; // Max wait of 5 seconds
+ for (int i = 0; i < maxChecks; i++) {
+ if (bgpRibIn.size() == expectedRoutes) {
+ break;
+ }
+ Thread.sleep(10);
+ bgpRibIn = bgpSession.getBgpRibIn4();
+ }
+
+ return bgpRibIn;
+ }
+
+ /**
+ * Gets BGP merged routes by waiting until they are received.
+ * <p>
+ * NOTE: We keep checking once every 10ms the number of received routes,
+ * up to 5 seconds.
+ * </p>
+ *
+ * @param expectedRoutes the expected number of routes
+ * @return the BGP Session Manager routes as received within the expected
+ * time interval
+ */
+ private Collection<BgpRouteEntry> waitForBgpRoutes(long expectedRoutes)
+ throws InterruptedException {
+ Collection<BgpRouteEntry> bgpRoutes =
+ bgpSessionManager.getBgpRoutes4();
+
+ final int maxChecks = 500; // Max wait of 5 seconds
+ for (int i = 0; i < maxChecks; i++) {
+ if (bgpRoutes.size() == expectedRoutes) {
+ break;
+ }
+ Thread.sleep(10);
+ bgpRoutes = bgpSessionManager.getBgpRoutes4();
+ }
+
+ return bgpRoutes;
+ }
+
+ /**
+ * Gets a merged BGP route by waiting until it is received.
+ * <p>
+ * NOTE: We keep checking once every 10ms whether the route is received,
+ * up to 5 seconds.
+ * </p>
+ *
+ * @param expectedRoute the expected route
+ * @return the merged BGP route if received within the expected time
+ * interval, otherwise null
+ */
+ private BgpRouteEntry waitForBgpRoute(BgpRouteEntry expectedRoute)
+ throws InterruptedException {
+ Collection<BgpRouteEntry> bgpRoutes =
+ bgpSessionManager.getBgpRoutes4();
+
+ final int maxChecks = 500; // Max wait of 5 seconds
+ for (int i = 0; i < maxChecks; i++) {
+ for (BgpRouteEntry bgpRouteEntry : bgpRoutes) {
+ if (bgpRouteEntry.equals(expectedRoute) &&
+ bgpRouteEntry.getBgpSession() ==
+ expectedRoute.getBgpSession()) {
+ return bgpRouteEntry;
+ }
+ }
+ Thread.sleep(10);
+ bgpRoutes = bgpSessionManager.getBgpRoutes4();
+ }
+
+ return null;
+ }
+
+ /**
+ * Tests that the BGP OPEN messages have been exchanged, followed by
+ * KEEPALIVE.
+ * <p>
+ * The BGP Peer opens the sessions and transmits OPEN Message, eventually
+ * followed by KEEPALIVE. The tested BGP listener should respond by
+ * OPEN Message, followed by KEEPALIVE.
+ * </p>
+ *
+ * @throws TestUtilsException TestUtils error
+ */
+ @Test
+ public void testExchangedBgpOpenMessages()
+ throws InterruptedException, TestUtilsException {
+ // Initiate the connections
+ peer1.connect(connectToSocket);
+ peer2.connect(connectToSocket);
+ peer3.connect(connectToSocket);
+
+ //
+ // Test the fields from the BGP OPEN message:
+ // BGP version, AS number, BGP ID
+ //
+ for (TestBgpPeer peer : peers) {
+ assertThat(peer.peerFrameDecoder.remoteInfo.bgpVersion(),
+ is(BgpConstants.BGP_VERSION));
+ assertThat(peer.peerFrameDecoder.remoteInfo.bgpId(),
+ is(IP_LOOPBACK_ID));
+ assertThat(peer.peerFrameDecoder.remoteInfo.asNumber(),
+ is(TestBgpPeerChannelHandler.PEER_AS));
+ }
+
+ //
+ // Test that the BgpSession instances have been created
+ //
+ assertThat(bgpSessionManager.getMyBgpId(), is(IP_LOOPBACK_ID));
+ assertThat(bgpSessionManager.getBgpSessions(), hasSize(3));
+ assertThat(bgpSession1, notNullValue());
+ assertThat(bgpSession2, notNullValue());
+ assertThat(bgpSession3, notNullValue());
+ for (BgpSession bgpSession : bgpSessionManager.getBgpSessions()) {
+ long sessionAs = bgpSession.localInfo().asNumber();
+ assertThat(sessionAs, is(TestBgpPeerChannelHandler.PEER_AS));
+ }
+ }
+
+
+ /**
+ * Tests that the BGP OPEN with Capability messages have been exchanged,
+ * followed by KEEPALIVE.
+ * <p>
+ * The BGP Peer opens the sessions and transmits OPEN Message, eventually
+ * followed by KEEPALIVE. The tested BGP listener should respond by
+ * OPEN Message, followed by KEEPALIVE.
+ * </p>
+ *
+ * @throws TestUtilsException TestUtils error
+ */
+ @Test
+ public void testExchangedBgpOpenCapabilityMessages()
+ throws InterruptedException, TestUtilsException {
+ //
+ // Setup the BGP Capabilities for all peers
+ //
+ for (TestBgpPeer peer : peers) {
+ peer.peerChannelHandler.localInfo.setIpv4Unicast();
+ peer.peerChannelHandler.localInfo.setIpv4Multicast();
+ peer.peerChannelHandler.localInfo.setIpv6Unicast();
+ peer.peerChannelHandler.localInfo.setIpv6Multicast();
+ peer.peerChannelHandler.localInfo.setAs4OctetCapability();
+ peer.peerChannelHandler.localInfo.setAs4Number(
+ TestBgpPeerChannelHandler.PEER_AS4);
+ }
+
+ // Initiate the connections
+ peer1.connect(connectToSocket);
+ peer2.connect(connectToSocket);
+ peer3.connect(connectToSocket);
+
+ //
+ // Test the fields from the BGP OPEN message:
+ // BGP version, BGP ID
+ //
+ for (TestBgpPeer peer : peers) {
+ assertThat(peer.peerFrameDecoder.remoteInfo.bgpVersion(),
+ is(BgpConstants.BGP_VERSION));
+ assertThat(peer.peerFrameDecoder.remoteInfo.bgpId(),
+ is(IP_LOOPBACK_ID));
+ }
+
+ //
+ // Test that the BgpSession instances have been created,
+ // and contain the appropriate BGP session information.
+ //
+ assertThat(bgpSessionManager.getMyBgpId(), is(IP_LOOPBACK_ID));
+ assertThat(bgpSessionManager.getBgpSessions(), hasSize(3));
+ assertThat(bgpSession1, notNullValue());
+ assertThat(bgpSession2, notNullValue());
+ assertThat(bgpSession3, notNullValue());
+ for (BgpSession bgpSession : bgpSessionManager.getBgpSessions()) {
+ BgpSessionInfo localInfo = bgpSession.localInfo();
+ assertThat(localInfo.ipv4Unicast(), is(true));
+ assertThat(localInfo.ipv4Multicast(), is(true));
+ assertThat(localInfo.ipv6Unicast(), is(true));
+ assertThat(localInfo.ipv6Multicast(), is(true));
+ assertThat(localInfo.as4OctetCapability(), is(true));
+ assertThat(localInfo.asNumber(),
+ is(TestBgpPeerChannelHandler.PEER_AS4));
+ assertThat(localInfo.as4Number(),
+ is(TestBgpPeerChannelHandler.PEER_AS4));
+ }
+ }
+
+ /**
+ * Tests that the BGP UPDATE messages have been received and processed.
+ */
+ @Test
+ public void testProcessedBgpUpdateMessages() throws InterruptedException {
+ ChannelBuffer message;
+ BgpRouteEntry bgpRouteEntry;
+ Collection<BgpRouteEntry> bgpRibIn1;
+ Collection<BgpRouteEntry> bgpRibIn2;
+ Collection<BgpRouteEntry> bgpRibIn3;
+ Collection<BgpRouteEntry> bgpRoutes;
+
+ // Initiate the connections
+ peer1.connect(connectToSocket);
+ peer2.connect(connectToSocket);
+ peer3.connect(connectToSocket);
+
+ // Prepare routes to add/delete
+ Collection<Ip4Prefix> addedRoutes = new LinkedList<>();
+ Collection<Ip4Prefix> withdrawnRoutes = new LinkedList<>();
+
+ //
+ // Add and delete some routes
+ //
+ addedRoutes.add(Ip4Prefix.valueOf("0.0.0.0/0"));
+ addedRoutes.add(Ip4Prefix.valueOf("20.0.0.0/8"));
+ addedRoutes.add(Ip4Prefix.valueOf("30.0.0.0/16"));
+ addedRoutes.add(Ip4Prefix.valueOf("40.0.0.0/24"));
+ addedRoutes.add(Ip4Prefix.valueOf("50.0.0.0/32"));
+ withdrawnRoutes.add(Ip4Prefix.valueOf("60.0.0.0/8"));
+ withdrawnRoutes.add(Ip4Prefix.valueOf("70.0.0.0/16"));
+ withdrawnRoutes.add(Ip4Prefix.valueOf("80.0.0.0/24"));
+ withdrawnRoutes.add(Ip4Prefix.valueOf("90.0.0.0/32"));
+ // Write the routes
+ message = peer1.peerChannelHandler.prepareBgpUpdate(
+ NEXT_HOP1_ROUTER,
+ DEFAULT_LOCAL_PREF,
+ DEFAULT_MULTI_EXIT_DISC,
+ asPathLong,
+ addedRoutes,
+ withdrawnRoutes);
+ peer1.peerChannelHandler.savedCtx.getChannel().write(message);
+ //
+ // Check that the routes have been received, processed and stored
+ //
+ bgpRibIn1 = waitForBgpRibIn(bgpSession1, 5);
+ assertThat(bgpRibIn1, hasSize(5));
+ bgpRoutes = waitForBgpRoutes(5);
+ assertThat(bgpRoutes, hasSize(5));
+ //
+ bgpRouteEntry =
+ new BgpRouteEntry(bgpSession1,
+ Ip4Prefix.valueOf("0.0.0.0/0"),
+ NEXT_HOP1_ROUTER,
+ (byte) BgpConstants.Update.Origin.IGP,
+ asPathLong,
+ DEFAULT_LOCAL_PREF);
+ bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
+ assertThat(bgpRibIn1, hasBgpRouteEntry(bgpRouteEntry));
+ assertThat(waitForBgpRoute(bgpRouteEntry), notNullValue());
+ //
+ bgpRouteEntry =
+ new BgpRouteEntry(bgpSession1,
+ Ip4Prefix.valueOf("20.0.0.0/8"),
+ NEXT_HOP1_ROUTER,
+ (byte) BgpConstants.Update.Origin.IGP,
+ asPathLong,
+ DEFAULT_LOCAL_PREF);
+ bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
+ assertThat(bgpRibIn1, hasBgpRouteEntry(bgpRouteEntry));
+ assertThat(waitForBgpRoute(bgpRouteEntry), notNullValue());
+ //
+ bgpRouteEntry =
+ new BgpRouteEntry(bgpSession1,
+ Ip4Prefix.valueOf("30.0.0.0/16"),
+ NEXT_HOP1_ROUTER,
+ (byte) BgpConstants.Update.Origin.IGP,
+ asPathLong,
+ DEFAULT_LOCAL_PREF);
+ bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
+ assertThat(bgpRibIn1, hasBgpRouteEntry(bgpRouteEntry));
+ assertThat(waitForBgpRoute(bgpRouteEntry), notNullValue());
+ //
+ bgpRouteEntry =
+ new BgpRouteEntry(bgpSession1,
+ Ip4Prefix.valueOf("40.0.0.0/24"),
+ NEXT_HOP1_ROUTER,
+ (byte) BgpConstants.Update.Origin.IGP,
+ asPathLong,
+ DEFAULT_LOCAL_PREF);
+ bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
+ assertThat(bgpRibIn1, hasBgpRouteEntry(bgpRouteEntry));
+ assertThat(waitForBgpRoute(bgpRouteEntry), notNullValue());
+ //
+ bgpRouteEntry =
+ new BgpRouteEntry(bgpSession1,
+ Ip4Prefix.valueOf("50.0.0.0/32"),
+ NEXT_HOP1_ROUTER,
+ (byte) BgpConstants.Update.Origin.IGP,
+ asPathLong,
+ DEFAULT_LOCAL_PREF);
+ bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
+ assertThat(bgpRibIn1, hasBgpRouteEntry(bgpRouteEntry));
+ assertThat(waitForBgpRoute(bgpRouteEntry), notNullValue());
+
+ //
+ // Delete some routes
+ //
+ addedRoutes = new LinkedList<>();
+ withdrawnRoutes = new LinkedList<>();
+ withdrawnRoutes.add(Ip4Prefix.valueOf("0.0.0.0/0"));
+ withdrawnRoutes.add(Ip4Prefix.valueOf("50.0.0.0/32"));
+ // Write the routes
+ message = peer1.peerChannelHandler.prepareBgpUpdate(
+ NEXT_HOP1_ROUTER,
+ DEFAULT_LOCAL_PREF,
+ DEFAULT_MULTI_EXIT_DISC,
+ asPathLong,
+ addedRoutes,
+ withdrawnRoutes);
+ peer1.peerChannelHandler.savedCtx.getChannel().write(message);
+ //
+ // Check that the routes have been received, processed and stored
+ //
+ bgpRibIn1 = waitForBgpRibIn(bgpSession1, 3);
+ assertThat(bgpRibIn1, hasSize(3));
+ bgpRoutes = waitForBgpRoutes(3);
+ assertThat(bgpRoutes, hasSize(3));
+ //
+ bgpRouteEntry =
+ new BgpRouteEntry(bgpSession1,
+ Ip4Prefix.valueOf("20.0.0.0/8"),
+ NEXT_HOP1_ROUTER,
+ (byte) BgpConstants.Update.Origin.IGP,
+ asPathLong,
+ DEFAULT_LOCAL_PREF);
+ bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
+ assertThat(bgpRibIn1, hasBgpRouteEntry(bgpRouteEntry));
+ assertThat(waitForBgpRoute(bgpRouteEntry), notNullValue());
+ //
+ bgpRouteEntry =
+ new BgpRouteEntry(bgpSession1,
+ Ip4Prefix.valueOf("30.0.0.0/16"),
+ NEXT_HOP1_ROUTER,
+ (byte) BgpConstants.Update.Origin.IGP,
+ asPathLong,
+ DEFAULT_LOCAL_PREF);
+ bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
+ assertThat(bgpRibIn1, hasBgpRouteEntry(bgpRouteEntry));
+ assertThat(waitForBgpRoute(bgpRouteEntry), notNullValue());
+ //
+ bgpRouteEntry =
+ new BgpRouteEntry(bgpSession1,
+ Ip4Prefix.valueOf("40.0.0.0/24"),
+ NEXT_HOP1_ROUTER,
+ (byte) BgpConstants.Update.Origin.IGP,
+ asPathLong,
+ DEFAULT_LOCAL_PREF);
+ bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
+ assertThat(bgpRibIn1, hasBgpRouteEntry(bgpRouteEntry));
+ assertThat(waitForBgpRoute(bgpRouteEntry), notNullValue());
+
+
+ // Close the channels and test there are no routes
+ peer1.peerChannelHandler.closeChannel();
+ peer2.peerChannelHandler.closeChannel();
+ peer3.peerChannelHandler.closeChannel();
+ bgpRoutes = waitForBgpRoutes(0);
+ assertThat(bgpRoutes, hasSize(0));
+ }
+
+ /**
+ * Tests the BGP route preference.
+ */
+ @Test
+ public void testBgpRoutePreference() throws InterruptedException {
+ ChannelBuffer message;
+ BgpRouteEntry bgpRouteEntry;
+ Collection<BgpRouteEntry> bgpRibIn1;
+ Collection<BgpRouteEntry> bgpRibIn2;
+ Collection<BgpRouteEntry> bgpRibIn3;
+ Collection<BgpRouteEntry> bgpRoutes;
+ Collection<Ip4Prefix> addedRoutes = new LinkedList<>();
+ Collection<Ip4Prefix> withdrawnRoutes = new LinkedList<>();
+
+ // Initiate the connections
+ peer1.connect(connectToSocket);
+ peer2.connect(connectToSocket);
+ peer3.connect(connectToSocket);
+
+ //
+ // Setup the initial set of routes to Peer1
+ //
+ addedRoutes.add(Ip4Prefix.valueOf("20.0.0.0/8"));
+ addedRoutes.add(Ip4Prefix.valueOf("30.0.0.0/16"));
+ // Write the routes
+ message = peer1.peerChannelHandler.prepareBgpUpdate(
+ NEXT_HOP1_ROUTER,
+ DEFAULT_LOCAL_PREF,
+ DEFAULT_MULTI_EXIT_DISC,
+ asPathLong,
+ addedRoutes,
+ withdrawnRoutes);
+ peer1.peerChannelHandler.savedCtx.getChannel().write(message);
+ bgpRoutes = waitForBgpRoutes(2);
+ assertThat(bgpRoutes, hasSize(2));
+
+ //
+ // Add a route entry to Peer2 with a better LOCAL_PREF
+ //
+ addedRoutes = new LinkedList<>();
+ withdrawnRoutes = new LinkedList<>();
+ addedRoutes.add(Ip4Prefix.valueOf("20.0.0.0/8"));
+ // Write the routes
+ message = peer2.peerChannelHandler.prepareBgpUpdate(
+ NEXT_HOP2_ROUTER,
+ BETTER_LOCAL_PREF,
+ DEFAULT_MULTI_EXIT_DISC,
+ asPathLong,
+ addedRoutes,
+ withdrawnRoutes);
+ peer2.peerChannelHandler.savedCtx.getChannel().write(message);
+ //
+ // Check that the routes have been received, processed and stored
+ //
+ bgpRibIn2 = waitForBgpRibIn(bgpSession2, 1);
+ assertThat(bgpRibIn2, hasSize(1));
+ bgpRoutes = waitForBgpRoutes(2);
+ assertThat(bgpRoutes, hasSize(2));
+ //
+ bgpRouteEntry =
+ new BgpRouteEntry(bgpSession2,
+ Ip4Prefix.valueOf("20.0.0.0/8"),
+ NEXT_HOP2_ROUTER,
+ (byte) BgpConstants.Update.Origin.IGP,
+ asPathLong,
+ BETTER_LOCAL_PREF);
+ bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
+ assertThat(bgpRibIn2, hasBgpRouteEntry(bgpRouteEntry));
+ assertThat(waitForBgpRoute(bgpRouteEntry), notNullValue());
+
+ //
+ // Add a route entry to Peer3 with a shorter AS path
+ //
+ addedRoutes = new LinkedList<>();
+ withdrawnRoutes = new LinkedList<>();
+ addedRoutes.add(Ip4Prefix.valueOf("20.0.0.0/8"));
+ // Write the routes
+ message = peer3.peerChannelHandler.prepareBgpUpdate(
+ NEXT_HOP3_ROUTER,
+ BETTER_LOCAL_PREF,
+ DEFAULT_MULTI_EXIT_DISC,
+ asPathShort,
+ addedRoutes,
+ withdrawnRoutes);
+ peer3.peerChannelHandler.savedCtx.getChannel().write(message);
+ //
+ // Check that the routes have been received, processed and stored
+ //
+ bgpRibIn3 = waitForBgpRibIn(bgpSession3, 1);
+ assertThat(bgpRibIn3, hasSize(1));
+ bgpRoutes = waitForBgpRoutes(2);
+ assertThat(bgpRoutes, hasSize(2));
+ //
+ bgpRouteEntry =
+ new BgpRouteEntry(bgpSession3,
+ Ip4Prefix.valueOf("20.0.0.0/8"),
+ NEXT_HOP3_ROUTER,
+ (byte) BgpConstants.Update.Origin.IGP,
+ asPathShort,
+ BETTER_LOCAL_PREF);
+ bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
+ assertThat(bgpRibIn3, hasBgpRouteEntry(bgpRouteEntry));
+ assertThat(waitForBgpRoute(bgpRouteEntry), notNullValue());
+
+ //
+ // Cleanup in preparation for next test: delete old route entry from
+ // Peer2
+ //
+ addedRoutes = new LinkedList<>();
+ withdrawnRoutes = new LinkedList<>();
+ withdrawnRoutes.add(Ip4Prefix.valueOf("20.0.0.0/8"));
+ // Write the routes
+ message = peer2.peerChannelHandler.prepareBgpUpdate(
+ NEXT_HOP2_ROUTER,
+ BETTER_LOCAL_PREF,
+ BETTER_MULTI_EXIT_DISC,
+ asPathShort,
+ addedRoutes,
+ withdrawnRoutes);
+ peer2.peerChannelHandler.savedCtx.getChannel().write(message);
+ //
+ // Check that the routes have been received, processed and stored
+ //
+ bgpRibIn2 = waitForBgpRibIn(bgpSession2, 0);
+ assertThat(bgpRibIn2, hasSize(0));
+
+ //
+ // Add a route entry to Peer2 with a better MED
+ //
+ addedRoutes = new LinkedList<>();
+ withdrawnRoutes = new LinkedList<>();
+ addedRoutes.add(Ip4Prefix.valueOf("20.0.0.0/8"));
+ // Write the routes
+ message = peer2.peerChannelHandler.prepareBgpUpdate(
+ NEXT_HOP2_ROUTER,
+ BETTER_LOCAL_PREF,
+ BETTER_MULTI_EXIT_DISC,
+ asPathShort,
+ addedRoutes,
+ withdrawnRoutes);
+ peer2.peerChannelHandler.savedCtx.getChannel().write(message);
+ //
+ // Check that the routes have been received, processed and stored
+ //
+ bgpRibIn2 = waitForBgpRibIn(bgpSession2, 1);
+ assertThat(bgpRibIn2, hasSize(1));
+ bgpRoutes = waitForBgpRoutes(2);
+ assertThat(bgpRoutes, hasSize(2));
+ //
+ bgpRouteEntry =
+ new BgpRouteEntry(bgpSession2,
+ Ip4Prefix.valueOf("20.0.0.0/8"),
+ NEXT_HOP2_ROUTER,
+ (byte) BgpConstants.Update.Origin.IGP,
+ asPathShort,
+ BETTER_LOCAL_PREF);
+ bgpRouteEntry.setMultiExitDisc(BETTER_MULTI_EXIT_DISC);
+ assertThat(bgpRibIn2, hasBgpRouteEntry(bgpRouteEntry));
+ assertThat(waitForBgpRoute(bgpRouteEntry), notNullValue());
+
+ //
+ // Add a route entry to Peer1 with a better (lower) BGP ID
+ //
+ addedRoutes = new LinkedList<>();
+ withdrawnRoutes = new LinkedList<>();
+ addedRoutes.add(Ip4Prefix.valueOf("20.0.0.0/8"));
+ withdrawnRoutes.add(Ip4Prefix.valueOf("30.0.0.0/16"));
+ // Write the routes
+ message = peer1.peerChannelHandler.prepareBgpUpdate(
+ NEXT_HOP1_ROUTER,
+ BETTER_LOCAL_PREF,
+ BETTER_MULTI_EXIT_DISC,
+ asPathShort,
+ addedRoutes,
+ withdrawnRoutes);
+ peer1.peerChannelHandler.savedCtx.getChannel().write(message);
+ //
+ // Check that the routes have been received, processed and stored
+ //
+ bgpRibIn1 = waitForBgpRibIn(bgpSession1, 1);
+ assertThat(bgpRibIn1, hasSize(1));
+ bgpRoutes = waitForBgpRoutes(1);
+ assertThat(bgpRoutes, hasSize(1));
+ //
+ bgpRouteEntry =
+ new BgpRouteEntry(bgpSession1,
+ Ip4Prefix.valueOf("20.0.0.0/8"),
+ NEXT_HOP1_ROUTER,
+ (byte) BgpConstants.Update.Origin.IGP,
+ asPathShort,
+ BETTER_LOCAL_PREF);
+ bgpRouteEntry.setMultiExitDisc(BETTER_MULTI_EXIT_DISC);
+ assertThat(bgpRibIn1, hasBgpRouteEntry(bgpRouteEntry));
+ assertThat(waitForBgpRoute(bgpRouteEntry), notNullValue());
+
+
+ // Close the channels and test there are no routes
+ peer1.peerChannelHandler.closeChannel();
+ peer2.peerChannelHandler.closeChannel();
+ peer3.peerChannelHandler.closeChannel();
+ bgpRoutes = waitForBgpRoutes(0);
+ assertThat(bgpRoutes, hasSize(0));
+ }
+}
diff --git a/framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/bgp/PathSegmentTest.java b/framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/bgp/PathSegmentTest.java
new file mode 100644
index 00000000..516bf578
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/bgp/PathSegmentTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.routing.bgp;
+
+import org.hamcrest.Matchers;
+import org.junit.Test;
+
+import java.util.ArrayList;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Unit tests for the BgpRouteEntry.PathSegment class.
+ */
+public class PathSegmentTest {
+ /**
+ * Generates a Path Segment.
+ *
+ * @return a generated PathSegment
+ */
+ private BgpRouteEntry.PathSegment generatePathSegment() {
+ byte pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
+ ArrayList<Long> segmentAsNumbers = new ArrayList<>();
+ segmentAsNumbers.add(1L);
+ segmentAsNumbers.add(2L);
+ segmentAsNumbers.add(3L);
+ BgpRouteEntry.PathSegment pathSegment =
+ new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers);
+
+ return pathSegment;
+ }
+
+ /**
+ * Tests valid class constructor.
+ */
+ @Test
+ public void testConstructor() {
+ BgpRouteEntry.PathSegment pathSegment = generatePathSegment();
+
+ String expectedString =
+ "PathSegment{type=AS_SEQUENCE, segmentAsNumbers=[1, 2, 3]}";
+ assertThat(pathSegment.toString(), is(expectedString));
+ }
+
+ /**
+ * Tests invalid class constructor for null Segment AS Numbers.
+ */
+ @Test(expected = NullPointerException.class)
+ public void testInvalidConstructorNullSegmentAsNumbers() {
+ byte pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
+ ArrayList<Long> segmentAsNumbers = null;
+ new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers);
+ }
+
+ /**
+ * Tests getting the fields of a Path Segment.
+ */
+ @Test
+ public void testGetFields() {
+ // Create the fields to compare against
+ byte pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
+ ArrayList<Long> segmentAsNumbers = new ArrayList<>();
+ segmentAsNumbers.add(1L);
+ segmentAsNumbers.add(2L);
+ segmentAsNumbers.add(3L);
+
+ // Generate the entry to test
+ BgpRouteEntry.PathSegment pathSegment = generatePathSegment();
+
+ assertThat(pathSegment.getType(), is(pathSegmentType));
+ assertThat(pathSegment.getSegmentAsNumbers(), is(segmentAsNumbers));
+ }
+
+ /**
+ * Tests equality of {@link BgpRouteEntry.PathSegment}.
+ */
+ @Test
+ public void testEquality() {
+ BgpRouteEntry.PathSegment pathSegment1 = generatePathSegment();
+ BgpRouteEntry.PathSegment pathSegment2 = generatePathSegment();
+
+ assertThat(pathSegment1, is(pathSegment2));
+ }
+
+ /**
+ * Tests non-equality of {@link BgpRouteEntry.PathSegment}.
+ */
+ @Test
+ public void testNonEquality() {
+ BgpRouteEntry.PathSegment pathSegment1 = generatePathSegment();
+
+ // Setup Path Segment 2
+ byte pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
+ ArrayList<Long> segmentAsNumbers = new ArrayList<>();
+ segmentAsNumbers.add(1L);
+ segmentAsNumbers.add(22L); // Different
+ segmentAsNumbers.add(3L);
+ //
+ BgpRouteEntry.PathSegment pathSegment2 =
+ new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers);
+
+ assertThat(pathSegment1, Matchers.is(not(pathSegment2)));
+ }
+
+ /**
+ * Tests object string representation.
+ */
+ @Test
+ public void testToString() {
+ BgpRouteEntry.PathSegment pathSegment = generatePathSegment();
+
+ String expectedString =
+ "PathSegment{type=AS_SEQUENCE, segmentAsNumbers=[1, 2, 3]}";
+ assertThat(pathSegment.toString(), is(expectedString));
+ }
+}
diff --git a/framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/bgp/TestBgpPeerChannelHandler.java b/framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/bgp/TestBgpPeerChannelHandler.java
new file mode 100644
index 00000000..41485e95
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/bgp/TestBgpPeerChannelHandler.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.routing.bgp;
+
+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.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+
+import java.util.Collection;
+
+/**
+ * Class for handling the remote BGP Peer session.
+ */
+class TestBgpPeerChannelHandler extends SimpleChannelHandler {
+ static final long PEER_AS = 65001;
+ static final long PEER_AS4 = 0x12345678;
+ static final int PEER_HOLDTIME = 120; // 120 seconds
+
+ final BgpSessionInfo localInfo = new BgpSessionInfo();
+ ChannelHandlerContext savedCtx;
+
+ /**
+ * Constructor for given BGP ID.
+ *
+ * @param bgpId the BGP ID to use
+ */
+ TestBgpPeerChannelHandler(Ip4Address bgpId) {
+ this.localInfo.setBgpVersion(BgpConstants.BGP_VERSION);
+ this.localInfo.setBgpId(bgpId);
+ this.localInfo.setAsNumber(PEER_AS);
+ this.localInfo.setHoldtime(PEER_HOLDTIME);
+ }
+
+ /**
+ * Closes the channel.
+ */
+ void closeChannel() {
+ savedCtx.getChannel().close();
+ }
+
+ @Override
+ public void channelConnected(ChannelHandlerContext ctx,
+ ChannelStateEvent channelEvent) {
+ this.savedCtx = ctx;
+ // Prepare and transmit BGP OPEN message
+ ChannelBuffer message = BgpOpen.prepareBgpOpen(localInfo);
+ ctx.getChannel().write(message);
+
+ // Prepare and transmit BGP KEEPALIVE message
+ message = BgpKeepalive.prepareBgpKeepalive();
+ ctx.getChannel().write(message);
+ }
+
+ @Override
+ public void channelDisconnected(ChannelHandlerContext ctx,
+ ChannelStateEvent channelEvent) {
+ // Nothing to do
+ }
+
+ /**
+ * Prepares BGP UPDATE message.
+ *
+ * @param nextHopRouter the next-hop router address for the routes to add
+ * @param localPref the local preference for the routes to use
+ * @param multiExitDisc the MED value
+ * @param asPath the AS path for the routes to add
+ * @param addedRoutes the routes to add
+ * @param withdrawnRoutes the routes to withdraw
+ * @return the message to transmit (BGP header included)
+ */
+ ChannelBuffer prepareBgpUpdate(Ip4Address nextHopRouter,
+ long localPref,
+ long multiExitDisc,
+ BgpRouteEntry.AsPath asPath,
+ Collection<Ip4Prefix> addedRoutes,
+ Collection<Ip4Prefix> withdrawnRoutes) {
+ int attrFlags;
+ ChannelBuffer message =
+ ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
+ ChannelBuffer pathAttributes =
+ ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
+
+ // Encode the Withdrawn Routes
+ ChannelBuffer encodedPrefixes = encodePackedPrefixes(withdrawnRoutes);
+ message.writeShort(encodedPrefixes.readableBytes());
+ message.writeBytes(encodedPrefixes);
+
+ // Encode the Path Attributes
+ // ORIGIN: IGP
+ attrFlags = 0x40; // Transitive flag
+ pathAttributes.writeByte(attrFlags);
+ pathAttributes.writeByte(BgpConstants.Update.Origin.TYPE);
+ pathAttributes.writeByte(1); // Data length
+ pathAttributes.writeByte(BgpConstants.Update.Origin.IGP);
+
+ // AS_PATH: asPath
+ attrFlags = 0x40; // Transitive flag
+ pathAttributes.writeByte(attrFlags);
+ pathAttributes.writeByte(BgpConstants.Update.AsPath.TYPE);
+ ChannelBuffer encodedAsPath = encodeAsPath(asPath);
+ pathAttributes.writeByte(encodedAsPath.readableBytes()); // Data length
+ pathAttributes.writeBytes(encodedAsPath);
+ // NEXT_HOP: nextHopRouter
+ attrFlags = 0x40; // Transitive flag
+ pathAttributes.writeByte(attrFlags);
+ pathAttributes.writeByte(BgpConstants.Update.NextHop.TYPE);
+ pathAttributes.writeByte(4); // Data length
+ pathAttributes.writeInt(nextHopRouter.toInt()); // Next-hop router
+ // LOCAL_PREF: localPref
+ attrFlags = 0x40; // Transitive flag
+ pathAttributes.writeByte(attrFlags);
+ pathAttributes.writeByte(BgpConstants.Update.LocalPref.TYPE);
+ pathAttributes.writeByte(4); // Data length
+ pathAttributes.writeInt((int) localPref); // Preference value
+ // MULTI_EXIT_DISC: multiExitDisc
+ attrFlags = 0x80; // Optional
+ // Non-Transitive flag
+ pathAttributes.writeByte(attrFlags);
+ pathAttributes.writeByte(BgpConstants.Update.MultiExitDisc.TYPE);
+ pathAttributes.writeByte(4); // Data length
+ pathAttributes.writeInt((int) multiExitDisc); // Preference value
+ // The NLRI prefixes
+ encodedPrefixes = encodePackedPrefixes(addedRoutes);
+
+ // Write the Path Attributes, beginning with its length
+ message.writeShort(pathAttributes.readableBytes());
+ message.writeBytes(pathAttributes);
+ message.writeBytes(encodedPrefixes);
+
+ return BgpMessage.prepareBgpMessage(BgpConstants.BGP_TYPE_UPDATE,
+ message);
+ }
+
+ /**
+ * Encodes a collection of IPv4 network prefixes in a packed format.
+ * <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 prefixes the prefixes to encode
+ * @return the buffer with the encoded prefixes
+ */
+ private ChannelBuffer encodePackedPrefixes(Collection<Ip4Prefix> prefixes) {
+ ChannelBuffer message =
+ ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
+
+ // Write each of the prefixes
+ for (Ip4Prefix prefix : prefixes) {
+ int prefixBitlen = prefix.prefixLength();
+ int prefixBytelen = (prefixBitlen + 7) / 8; // Round-up
+ message.writeByte(prefixBitlen);
+
+ Ip4Address address = prefix.address();
+ long value = address.toInt() & 0xffffffffL;
+ for (int i = 0; i < Ip4Address.BYTE_LENGTH; i++) {
+ if (prefixBytelen-- == 0) {
+ break;
+ }
+ long nextByte =
+ (value >> ((Ip4Address.BYTE_LENGTH - i - 1) * 8)) & 0xff;
+ message.writeByte((int) nextByte);
+ }
+ }
+
+ return message;
+ }
+
+ /**
+ * Encodes an AS path.
+ *
+ * @param asPath the AS path to encode
+ * @return the buffer with the encoded AS path
+ */
+ private ChannelBuffer encodeAsPath(BgpRouteEntry.AsPath asPath) {
+ ChannelBuffer message =
+ ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
+
+ for (BgpRouteEntry.PathSegment pathSegment : asPath.getPathSegments()) {
+ message.writeByte(pathSegment.getType());
+ message.writeByte(pathSegment.getSegmentAsNumbers().size());
+ for (Long asNumber : pathSegment.getSegmentAsNumbers()) {
+ message.writeShort(asNumber.intValue());
+ }
+ }
+
+ return message;
+ }
+}
diff --git a/framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/bgp/TestBgpPeerFrameDecoder.java b/framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/bgp/TestBgpPeerFrameDecoder.java
new file mode 100644
index 00000000..b4ff40d2
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/bgp/TestBgpPeerFrameDecoder.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.routing.bgp;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.handler.codec.frame.FrameDecoder;
+import org.onlab.packet.Ip4Address;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Class for handling the decoding of the BGP messages at the remote
+ * BGP peer session.
+ */
+class TestBgpPeerFrameDecoder extends FrameDecoder {
+ final BgpSessionInfo remoteInfo = new BgpSessionInfo();
+
+ final CountDownLatch receivedOpenMessageLatch = new CountDownLatch(1);
+ final CountDownLatch receivedKeepaliveMessageLatch = new CountDownLatch(1);
+
+ @Override
+ protected Object decode(ChannelHandlerContext ctx,
+ Channel channel,
+ ChannelBuffer buf) throws Exception {
+ // 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
+ //
+ 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) {
+ // ERROR: Connection Not Synchronized. Close the channel.
+ ctx.getChannel().close();
+ 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)) {
+ // ERROR: Bad Message Length. Close the channel.
+ ctx.getChannel().close();
+ 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:
+ processBgpOpen(ctx, message);
+ break;
+ case BgpConstants.BGP_TYPE_UPDATE:
+ // NOTE: Not used as part of the test, because ONOS does not
+ // originate UPDATE messages.
+ break;
+ case BgpConstants.BGP_TYPE_NOTIFICATION:
+ // NOTE: Not used as part of the testing (yet)
+ break;
+ case BgpConstants.BGP_TYPE_KEEPALIVE:
+ processBgpKeepalive(ctx, message);
+ break;
+ default:
+ // ERROR: Bad Message Type. Close the channel.
+ ctx.getChannel().close();
+ return null;
+ }
+
+ return null;
+ }
+
+ /**
+ * Processes BGP OPEN message.
+ *
+ * @param ctx the Channel Handler Context.
+ * @param message the message to process.
+ */
+ private void processBgpOpen(ChannelHandlerContext ctx,
+ ChannelBuffer message) {
+ int minLength =
+ BgpConstants.BGP_OPEN_MIN_LENGTH - BgpConstants.BGP_HEADER_LENGTH;
+ if (message.readableBytes() < minLength) {
+ // ERROR: Bad Message Length. Close the channel.
+ ctx.getChannel().close();
+ return;
+ }
+
+ //
+ // Parse the OPEN message
+ //
+ remoteInfo.setBgpVersion(message.readUnsignedByte());
+ remoteInfo.setAsNumber(message.readUnsignedShort());
+ remoteInfo.setHoldtime(message.readUnsignedShort());
+ remoteInfo.setBgpId(Ip4Address.valueOf((int) message.readUnsignedInt()));
+ // Optional Parameters
+ int optParamLen = message.readUnsignedByte();
+ if (message.readableBytes() < optParamLen) {
+ // ERROR: Bad Message Length. Close the channel.
+ ctx.getChannel().close();
+ return;
+ }
+ message.readBytes(optParamLen); // NOTE: data ignored
+
+ // BGP OPEN message successfully received
+ receivedOpenMessageLatch.countDown();
+ }
+
+ /**
+ * Processes BGP KEEPALIVE message.
+ *
+ * @param ctx the Channel Handler Context.
+ * @param message the message to process.
+ */
+ private void processBgpKeepalive(ChannelHandlerContext ctx,
+ ChannelBuffer message) {
+ if (message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH !=
+ BgpConstants.BGP_KEEPALIVE_EXPECTED_LENGTH) {
+ // ERROR: Bad Message Length. Close the channel.
+ ctx.getChannel().close();
+ return;
+ }
+ // BGP KEEPALIVE message successfully received
+ receivedKeepaliveMessageLatch.countDown();
+ }
+}
diff --git a/framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/config/impl/HostToInterfaceAdaptorTest.java b/framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/config/impl/HostToInterfaceAdaptorTest.java
new file mode 100644
index 00000000..0347fc5f
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/config/impl/HostToInterfaceAdaptorTest.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.routing.config.impl;
+
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.host.InterfaceIpAddress;
+import org.onosproject.net.host.PortAddresses;
+import org.onosproject.routing.config.Interface;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.reset;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Unit tests for the HostToInterfaceAdaptor class.
+ */
+public class HostToInterfaceAdaptorTest {
+
+ private HostService hostService;
+ private HostToInterfaceAdaptor adaptor;
+
+ private Set<PortAddresses> portAddresses;
+ private Map<ConnectPoint, Interface> interfaces;
+
+ private static final ConnectPoint CP1 = new ConnectPoint(
+ DeviceId.deviceId("of:1"), PortNumber.portNumber(1));
+ private static final ConnectPoint CP2 = new ConnectPoint(
+ DeviceId.deviceId("of:1"), PortNumber.portNumber(2));
+ private static final ConnectPoint CP3 = new ConnectPoint(
+ DeviceId.deviceId("of:2"), PortNumber.portNumber(1));
+
+ private static final ConnectPoint NON_EXISTENT_CP = new ConnectPoint(
+ DeviceId.deviceId("doesnotexist"), PortNumber.portNumber(1));
+
+ @Before
+ public void setUp() throws Exception {
+ hostService = createMock(HostService.class);
+
+ portAddresses = Sets.newHashSet();
+ interfaces = Maps.newHashMap();
+
+ InterfaceIpAddress ia11 =
+ new InterfaceIpAddress(IpAddress.valueOf("192.168.1.1"),
+ IpPrefix.valueOf("192.168.1.0/24"));
+ createPortAddressesAndInterface(CP1,
+ Sets.newHashSet(ia11),
+ MacAddress.valueOf("00:00:00:00:00:01"),
+ VlanId.NONE);
+
+ // Two addresses in the same subnet
+ InterfaceIpAddress ia21 =
+ new InterfaceIpAddress(IpAddress.valueOf("192.168.2.1"),
+ IpPrefix.valueOf("192.168.2.0/24"));
+ InterfaceIpAddress ia22 =
+ new InterfaceIpAddress(IpAddress.valueOf("192.168.2.2"),
+ IpPrefix.valueOf("192.168.2.0/24"));
+ createPortAddressesAndInterface(CP2,
+ Sets.newHashSet(ia21, ia22),
+ MacAddress.valueOf("00:00:00:00:00:02"),
+ VlanId.vlanId((short) 4));
+
+ // Two addresses in different subnets
+ InterfaceIpAddress ia31 =
+ new InterfaceIpAddress(IpAddress.valueOf("192.168.3.1"),
+ IpPrefix.valueOf("192.168.3.0/24"));
+ InterfaceIpAddress ia41 =
+ new InterfaceIpAddress(IpAddress.valueOf("192.168.4.1"),
+ IpPrefix.valueOf("192.168.4.0/24"));
+ createPortAddressesAndInterface(CP3,
+ Sets.newHashSet(ia31, ia41),
+ MacAddress.valueOf("00:00:00:00:00:03"),
+ VlanId.NONE);
+
+ expect(hostService.getAddressBindings()).andReturn(portAddresses).anyTimes();
+
+ replay(hostService);
+
+ adaptor = new HostToInterfaceAdaptor(hostService);
+ }
+
+ /**
+ * Creates both a PortAddresses and an Interface for the given inputs and
+ * places them in the correct global data stores.
+ *
+ * @param cp the connect point
+ * @param ipAddresses the set of interface IP addresses
+ * @param mac the MAC address
+ * @param vlan the VLAN ID
+ */
+ private void createPortAddressesAndInterface(
+ ConnectPoint cp, Set<InterfaceIpAddress> ipAddresses,
+ MacAddress mac, VlanId vlan) {
+ PortAddresses pa = new PortAddresses(cp, ipAddresses, mac, vlan);
+ portAddresses.add(pa);
+ expect(hostService.getAddressBindingsForPort(cp)).andReturn(
+ Collections.singleton(pa)).anyTimes();
+
+ Interface intf = new Interface(cp, ipAddresses, mac, vlan);
+ interfaces.put(cp, intf);
+ }
+
+ /**
+ * Tests {@link HostToInterfaceAdaptor#getInterfaces()}.
+ * Verifies that the set of interfaces returned matches what is expected
+ * based on the input PortAddresses data.
+ */
+ @Test
+ public void testGetInterfaces() {
+ Set<Interface> adaptorIntfs = adaptor.getInterfaces();
+
+ assertEquals(3, adaptorIntfs.size());
+ assertTrue(adaptorIntfs.contains(this.interfaces.get(CP1)));
+ assertTrue(adaptorIntfs.contains(this.interfaces.get(CP2)));
+ assertTrue(adaptorIntfs.contains(this.interfaces.get(CP3)));
+ }
+
+ /**
+ * Tests {@link HostToInterfaceAdaptor#getInterface(ConnectPoint)}.
+ * Verifies that the correct interface is returned for a given connect
+ * point.
+ */
+ @Test
+ public void testGetInterface() {
+ assertEquals(this.interfaces.get(CP1), adaptor.getInterface(CP1));
+ assertEquals(this.interfaces.get(CP2), adaptor.getInterface(CP2));
+ assertEquals(this.interfaces.get(CP3), adaptor.getInterface(CP3));
+
+ // Try and get an interface for a connect point with no addresses
+ reset(hostService);
+ expect(hostService.getAddressBindingsForPort(NON_EXISTENT_CP))
+ .andReturn(Collections.<PortAddresses>emptySet()).anyTimes();
+ replay(hostService);
+
+ assertNull(adaptor.getInterface(NON_EXISTENT_CP));
+ }
+
+ /**
+ * Tests {@link HostToInterfaceAdaptor#getInterface(ConnectPoint)} in the
+ * case that the input connect point is null.
+ * Verifies that a NullPointerException is thrown.
+ */
+ @Test(expected = NullPointerException.class)
+ public void testGetInterfaceNull() {
+ ConnectPoint c = null;
+ adaptor.getInterface(c);
+ }
+
+ /**
+ * Tests {@link HostToInterfaceAdaptor#getMatchingInterface(IpAddress)}.
+ * Verifies that the correct interface is returned based on the given IP
+ * address.
+ */
+ @Test
+ public void testGetMatchingInterface() {
+ assertEquals(this.interfaces.get(CP1),
+ adaptor.getMatchingInterface(IpAddress.valueOf("192.168.1.100")));
+ assertEquals(this.interfaces.get(CP2),
+ adaptor.getMatchingInterface(IpAddress.valueOf("192.168.2.100")));
+ assertEquals(this.interfaces.get(CP3),
+ adaptor.getMatchingInterface(IpAddress.valueOf("192.168.3.100")));
+ assertEquals(this.interfaces.get(CP3),
+ adaptor.getMatchingInterface(IpAddress.valueOf("192.168.4.100")));
+
+ // Try and match an address we don't have subnet configured for
+ assertNull(adaptor.getMatchingInterface(IpAddress.valueOf("1.1.1.1")));
+ }
+
+ /**
+ * Tests {@link HostToInterfaceAdaptor#getMatchingInterface(IpAddress)} in the
+ * case that the input IP address is null.
+ * Verifies that a NullPointerException is thrown.
+ */
+ @Test(expected = NullPointerException.class)
+ public void testGetMatchingInterfaceNull() {
+ adaptor.getMatchingInterface(null);
+ }
+
+}
diff --git a/framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/impl/RouterAsyncArpTest.java b/framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/impl/RouterAsyncArpTest.java
new file mode 100644
index 00000000..100c13d4
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/impl/RouterAsyncArpTest.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.routing.impl;
+
+import com.google.common.collect.Sets;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultHost;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.routing.config.RoutingConfigurationService;
+import org.onosproject.routing.impl.Router.InternalHostListener;
+import org.onosproject.routing.BgpService;
+import org.onosproject.routing.FibEntry;
+import org.onosproject.routing.FibListener;
+import org.onosproject.routing.FibUpdate;
+import org.onosproject.routing.RouteEntry;
+import org.onosproject.routing.RouteListener;
+import org.onosproject.routing.RouteUpdate;
+
+import java.util.Collections;
+
+import static org.easymock.EasyMock.*;
+
+/**
+* This class tests adding a route and updating a route.
+* The HostService module answers the MAC address asynchronously.
+*/
+public class RouterAsyncArpTest {
+
+ private HostService hostService;
+ private FibListener fibListener;
+ private RoutingConfigurationService routingConfigurationService;
+
+ private static final ConnectPoint SW1_ETH1 = new ConnectPoint(
+ DeviceId.deviceId("of:0000000000000001"),
+ PortNumber.portNumber(1));
+
+ private static final ConnectPoint SW2_ETH1 = new ConnectPoint(
+ DeviceId.deviceId("of:0000000000000002"),
+ PortNumber.portNumber(1));
+
+ private static final ConnectPoint SW3_ETH1 = new ConnectPoint(
+ DeviceId.deviceId("of:0000000000000003"),
+ PortNumber.portNumber(1));
+
+ private Router router;
+ private InternalHostListener internalHostListener;
+
+ @Before
+ public void setUp() throws Exception {
+ hostService = createMock(HostService.class);
+ routingConfigurationService =
+ createMock(RoutingConfigurationService.class);
+
+ BgpService bgpService = createMock(BgpService.class);
+ bgpService.start(anyObject(RouteListener.class));
+ bgpService.stop();
+ replay(bgpService);
+
+ fibListener = createMock(FibListener.class);
+
+ router = new Router();
+ router.coreService = createNiceMock(CoreService.class);
+ router.hostService = hostService;
+ router.routingConfigurationService = routingConfigurationService;
+ router.bgpService = bgpService;
+ router.activate();
+
+ router.addFibListener(fibListener);
+ router.start();
+
+ internalHostListener = router.new InternalHostListener();
+ }
+
+ @After
+ public void tearDown() {
+ // Called during shutdown
+ reset(hostService);
+ hostService.removeListener(anyObject(HostListener.class));
+
+ router.stop();
+ }
+
+ /**
+ * Tests adding a route entry with asynchronous HostService replies.
+ */
+ @Test
+ public void testRouteAdd() {
+ // Construct a route entry
+ IpPrefix prefix = Ip4Prefix.valueOf("1.1.1.0/24");
+ IpAddress nextHopIp = Ip4Address.valueOf("192.168.10.1");
+
+ RouteEntry routeEntry = new RouteEntry(prefix, nextHopIp);
+
+ // Host service will reply with no hosts when asked
+ reset(hostService);
+ expect(hostService.getHostsByIp(anyObject(IpAddress.class))).andReturn(
+ Collections.emptySet()).anyTimes();
+ hostService.startMonitoringIp(IpAddress.valueOf("192.168.10.1"));
+ replay(hostService);
+
+ reset(routingConfigurationService);
+ expect(routingConfigurationService.isIpPrefixLocal(
+ anyObject(IpPrefix.class))).andReturn(false);
+ replay(routingConfigurationService);
+
+ // Initially when we add the route, no FIB update will be sent
+ replay(fibListener);
+
+ router.processRouteUpdates(Collections.singletonList(
+ new RouteUpdate(RouteUpdate.Type.UPDATE, routeEntry)));
+
+ verify(fibListener);
+
+
+ // Now when we send the event, we expect the FIB update to be sent
+ reset(fibListener);
+ FibEntry fibEntry = new FibEntry(prefix, nextHopIp,
+ MacAddress.valueOf("00:00:00:00:00:01"));
+
+ fibListener.update(Collections.singletonList(new FibUpdate(
+ FibUpdate.Type.UPDATE, fibEntry)), Collections.emptyList());
+ replay(fibListener);
+
+ Host host = new DefaultHost(ProviderId.NONE, HostId.NONE,
+ MacAddress.valueOf("00:00:00:00:00:01"), VlanId.NONE,
+ new HostLocation(
+ SW1_ETH1.deviceId(),
+ SW1_ETH1.port(), 1),
+ Sets.newHashSet(IpAddress.valueOf("192.168.10.1")));
+
+ // Send in the host event
+ internalHostListener.event(
+ new HostEvent(HostEvent.Type.HOST_ADDED, host));
+
+ verify(fibListener);
+ }
+
+ /**
+ * Tests updating a route entry with asynchronous HostService replies.
+ */
+ @Test
+ public void testRouteUpdate() {
+ // Add a route
+ testRouteAdd();
+
+ // Construct a route entry
+ IpPrefix prefix = Ip4Prefix.valueOf("1.1.1.0/24");
+ IpAddress nextHopIp = Ip4Address.valueOf("192.168.20.1");
+
+ RouteEntry routeEntry = new RouteEntry(prefix, nextHopIp);
+
+ // Host service will reply with no hosts when asked
+ reset(hostService);
+ expect(hostService.getHostsByIp(anyObject(IpAddress.class))).andReturn(
+ Collections.emptySet()).anyTimes();
+ hostService.startMonitoringIp(IpAddress.valueOf("192.168.20.1"));
+ replay(hostService);
+
+ reset(routingConfigurationService);
+ expect(routingConfigurationService.isIpPrefixLocal(
+ anyObject(IpPrefix.class))).andReturn(false);
+ replay(routingConfigurationService);
+
+ // Initially when we add the route, the DELETE FIB update will be sent
+ // but the UPDATE FIB update will come later when the MAC is resolved
+ reset(fibListener);
+
+ fibListener.update(Collections.emptyList(), Collections.singletonList(new FibUpdate(
+ FibUpdate.Type.DELETE, new FibEntry(prefix, null, null))));
+ replay(fibListener);
+
+ router.processRouteUpdates(Collections.singletonList(
+ new RouteUpdate(RouteUpdate.Type.UPDATE, routeEntry)));
+
+ verify(fibListener);
+
+
+ // Now when we send the event, we expect the FIB update to be sent
+ reset(fibListener);
+ FibEntry fibEntry = new FibEntry(prefix, nextHopIp,
+ MacAddress.valueOf("00:00:00:00:00:02"));
+
+ fibListener.update(Collections.singletonList(new FibUpdate(
+ FibUpdate.Type.UPDATE, fibEntry)), Collections.emptyList());
+ replay(fibListener);
+
+ Host host = new DefaultHost(ProviderId.NONE, HostId.NONE,
+ MacAddress.valueOf("00:00:00:00:00:02"), VlanId.NONE,
+ new HostLocation(
+ SW1_ETH1.deviceId(),
+ SW1_ETH1.port(), 1),
+ Sets.newHashSet(IpAddress.valueOf("192.168.20.1")));
+
+ // Send in the host event
+ internalHostListener.event(
+ new HostEvent(HostEvent.Type.HOST_ADDED, host));
+
+ verify(fibListener);
+ }
+}
diff --git a/framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/impl/RouterTest.java b/framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/impl/RouterTest.java
new file mode 100644
index 00000000..45bc309f
--- /dev/null
+++ b/framework/src/onos/apps/routing/src/test/java/org/onosproject/routing/impl/RouterTest.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.routing.impl;
+
+import com.google.common.collect.Sets;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultHost;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.routing.BgpService;
+import org.onosproject.routing.FibEntry;
+import org.onosproject.routing.FibListener;
+import org.onosproject.routing.FibUpdate;
+import org.onosproject.routing.RouteEntry;
+import org.onosproject.routing.RouteListener;
+import org.onosproject.routing.RouteUpdate;
+import org.onosproject.routing.config.RoutingConfigurationService;
+
+import java.util.Collections;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * This class tests adding a route, updating a route, deleting a route,
+ * and adding a route whose next hop is the local BGP speaker.
+ * <p/>
+ * The HostService answers requests synchronously.
+ */
+public class RouterTest {
+
+ private HostService hostService;
+ private RoutingConfigurationService routingConfigurationService;
+
+ private FibListener fibListener;
+
+ private static final ConnectPoint SW1_ETH1 = new ConnectPoint(
+ DeviceId.deviceId("of:0000000000000001"),
+ PortNumber.portNumber(1));
+
+ private static final ConnectPoint SW2_ETH1 = new ConnectPoint(
+ DeviceId.deviceId("of:0000000000000002"),
+ PortNumber.portNumber(1));
+
+ private static final ConnectPoint SW3_ETH1 = new ConnectPoint(
+ DeviceId.deviceId("of:0000000000000003"),
+ PortNumber.portNumber(1));
+
+ private static final ConnectPoint SW4_ETH1 = new ConnectPoint(
+ DeviceId.deviceId("of:0000000000000004"),
+ PortNumber.portNumber(1));
+
+ private Router router;
+
+ @Before
+ public void setUp() throws Exception {
+ setUpHostService();
+ routingConfigurationService =
+ createMock(RoutingConfigurationService.class);
+
+ BgpService bgpService = createMock(BgpService.class);
+ bgpService.start(anyObject(RouteListener.class));
+ bgpService.stop();
+ replay(bgpService);
+
+ fibListener = createMock(FibListener.class);
+
+ router = new Router();
+ router.coreService = createNiceMock(CoreService.class);
+ router.hostService = hostService;
+ router.routingConfigurationService = routingConfigurationService;
+ router.bgpService = bgpService;
+ router.activate();
+
+ router.addFibListener(fibListener);
+ router.start();
+ }
+
+ @After
+ public void tearDown() {
+ router.stop();
+ }
+
+ /**
+ * Sets up the host service with details of some hosts.
+ */
+ private void setUpHostService() {
+ hostService = createMock(HostService.class);
+
+ hostService.addListener(anyObject(HostListener.class));
+ expectLastCall().anyTimes();
+
+ IpAddress host1Address = IpAddress.valueOf("192.168.10.1");
+ Host host1 = new DefaultHost(ProviderId.NONE, HostId.NONE,
+ MacAddress.valueOf("00:00:00:00:00:01"), VlanId.NONE,
+ new HostLocation(SW1_ETH1, 1),
+ Sets.newHashSet(host1Address));
+
+ expect(hostService.getHostsByIp(host1Address))
+ .andReturn(Sets.newHashSet(host1)).anyTimes();
+ hostService.startMonitoringIp(host1Address);
+ expectLastCall().anyTimes();
+
+
+ IpAddress host2Address = IpAddress.valueOf("192.168.20.1");
+ Host host2 = new DefaultHost(ProviderId.NONE, HostId.NONE,
+ MacAddress.valueOf("00:00:00:00:00:02"), VlanId.NONE,
+ new HostLocation(SW2_ETH1, 1),
+ Sets.newHashSet(host2Address));
+
+ expect(hostService.getHostsByIp(host2Address))
+ .andReturn(Sets.newHashSet(host2)).anyTimes();
+ hostService.startMonitoringIp(host2Address);
+ expectLastCall().anyTimes();
+
+ // Next hop on a VLAN
+ IpAddress host3Address = IpAddress.valueOf("192.168.40.1");
+ Host host3 = new DefaultHost(ProviderId.NONE, HostId.NONE,
+ MacAddress.valueOf("00:00:00:00:00:03"), VlanId.vlanId((short) 1),
+ new HostLocation(SW4_ETH1, 1),
+ Sets.newHashSet(host3Address));
+
+ expect(hostService.getHostsByIp(host3Address))
+ .andReturn(Sets.newHashSet(host3)).anyTimes();
+ hostService.startMonitoringIp(host3Address);
+ expectLastCall().anyTimes();
+
+ // Called during shutdown
+ hostService.removeListener(anyObject(HostListener.class));
+
+ replay(hostService);
+ }
+
+ /**
+ * Tests adding a route entry.
+ */
+ @Test
+ public void testRouteAdd() {
+ // Construct a route entry
+ IpPrefix prefix = Ip4Prefix.valueOf("1.1.1.0/24");
+ IpAddress nextHopIp = Ip4Address.valueOf("192.168.10.1");
+
+ RouteEntry routeEntry = new RouteEntry(prefix, nextHopIp);
+
+ // Expected FIB entry
+ FibEntry fibEntry = new FibEntry(prefix, nextHopIp,
+ MacAddress.valueOf("00:00:00:00:00:01"));
+
+ fibListener.update(Collections.singletonList(new FibUpdate(
+ FibUpdate.Type.UPDATE, fibEntry)), Collections.emptyList());
+
+ replay(fibListener);
+
+ router.processRouteUpdates(Collections.singletonList(
+ new RouteUpdate(RouteUpdate.Type.UPDATE, routeEntry)));
+
+ verify(fibListener);
+ }
+
+ /**
+ * Tests updating a route entry.
+ */
+ @Test
+ public void testRouteUpdate() {
+ // Firstly add a route
+ testRouteAdd();
+
+ // Route entry with updated next hop for the original prefix
+ RouteEntry routeEntryUpdate = new RouteEntry(
+ Ip4Prefix.valueOf("1.1.1.0/24"),
+ Ip4Address.valueOf("192.168.20.1"));
+
+ // The old FIB entry will be withdrawn
+ FibEntry withdrawFibEntry = new FibEntry(
+ Ip4Prefix.valueOf("1.1.1.0/24"), null, null);
+
+ // A new FIB entry will be added
+ FibEntry updateFibEntry = new FibEntry(
+ Ip4Prefix.valueOf("1.1.1.0/24"),
+ Ip4Address.valueOf("192.168.20.1"),
+ MacAddress.valueOf("00:00:00:00:00:02"));
+
+ reset(fibListener);
+ fibListener.update(Collections.singletonList(new FibUpdate(
+ FibUpdate.Type.UPDATE, updateFibEntry)),
+ Collections.singletonList(new FibUpdate(
+ FibUpdate.Type.DELETE, withdrawFibEntry)));
+ replay(fibListener);
+
+ reset(routingConfigurationService);
+ expect(routingConfigurationService.isIpPrefixLocal(
+ anyObject(IpPrefix.class))).andReturn(false);
+ replay(routingConfigurationService);
+
+ router.processRouteUpdates(Collections.singletonList(new RouteUpdate(
+ RouteUpdate.Type.UPDATE, routeEntryUpdate)));
+
+ verify(fibListener);
+ }
+
+ /**
+ * Tests deleting a route entry.
+ */
+ @Test
+ public void testRouteDelete() {
+ // Firstly add a route
+ testRouteAdd();
+
+ RouteEntry deleteRouteEntry = new RouteEntry(
+ Ip4Prefix.valueOf("1.1.1.0/24"),
+ Ip4Address.valueOf("192.168.10.1"));
+
+ FibEntry deleteFibEntry = new FibEntry(
+ Ip4Prefix.valueOf("1.1.1.0/24"), null, null);
+
+ reset(fibListener);
+ fibListener.update(Collections.emptyList(), Collections.singletonList(
+ new FibUpdate(FibUpdate.Type.DELETE, deleteFibEntry)));
+
+ replay(fibListener);
+
+ router.processRouteUpdates(Collections.singletonList(
+ new RouteUpdate(RouteUpdate.Type.DELETE, deleteRouteEntry)));
+
+ verify(fibListener);
+ }
+
+ /**
+ * Tests adding a route whose next hop is the local BGP speaker.
+ */
+ @Test
+ public void testLocalRouteAdd() {
+ // Construct a route entry, the next hop is the local BGP speaker
+ RouteEntry routeEntry = new RouteEntry(
+ Ip4Prefix.valueOf("1.1.1.0/24"),
+ Ip4Address.valueOf("0.0.0.0"));
+
+ // No methods on the FIB listener should be called
+ replay(fibListener);
+
+ reset(routingConfigurationService);
+ expect(routingConfigurationService.isIpPrefixLocal(
+ anyObject(IpPrefix.class))).andReturn(true);
+ replay(routingConfigurationService);
+
+ // Call the processRouteUpdates() method in Router class
+ RouteUpdate routeUpdate = new RouteUpdate(RouteUpdate.Type.UPDATE,
+ routeEntry);
+ router.processRouteUpdates(Collections.singletonList(routeUpdate));
+
+ // Verify
+ assertEquals(1, router.getRoutes4().size());
+ assertTrue(router.getRoutes4().contains(routeEntry));
+ verify(fibListener);
+ }
+}