diff options
Diffstat (limited to 'framework/src/onos/apps/routing')
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); + } +} |