Pavlin Radoslavov

Refactor the BGP code in the SDN-IP application:

 * Resolves ONOS-476
 * Moved the BGP message-specific processing from class BgpSession
   to per-message type classes: BgpKeepalive, BgpNotification,
   BgpOpen, BgpUpdate
 * Minor modifications in some of the methods or BGP-specific API
   to accomodate the above change.
   No functional changes.

Change-Id: I95df128fa31c60397a279aaca25a487b7991a6e1
......@@ -90,8 +90,9 @@ class BgpFrameDecoder extends FrameDecoder {
int errorSubcode =
MessageHeaderError.CONNECTION_NOT_SYNCHRONIZED;
ChannelBuffer txMessage =
bgpSession.prepareBgpNotification(errorCode, errorSubcode,
null);
BgpNotification.prepareBgpNotification(errorCode,
errorSubcode,
null);
ctx.getChannel().write(txMessage);
bgpSession.closeSession(ctx);
return null;
......@@ -114,7 +115,7 @@ class BgpFrameDecoder extends FrameDecoder {
//
// Send NOTIFICATION and close the connection
ChannelBuffer txMessage =
bgpSession.prepareBgpNotificationBadMessageLength(length);
BgpNotification.prepareBgpNotificationBadMessageLength(length);
ctx.getChannel().write(txMessage);
bgpSession.closeSession(ctx);
return null;
......@@ -145,16 +146,16 @@ class BgpFrameDecoder extends FrameDecoder {
//
switch (type) {
case BgpConstants.BGP_TYPE_OPEN:
bgpSession.processBgpOpen(ctx, message);
BgpOpen.processBgpOpen(bgpSession, ctx, message);
break;
case BgpConstants.BGP_TYPE_UPDATE:
bgpSession.processBgpUpdate(ctx, message);
BgpUpdate.processBgpUpdate(bgpSession, ctx, message);
break;
case BgpConstants.BGP_TYPE_NOTIFICATION:
bgpSession.processBgpNotification(ctx, message);
BgpNotification.processBgpNotification(bgpSession, ctx, message);
break;
case BgpConstants.BGP_TYPE_KEEPALIVE:
bgpSession.processBgpKeepalive(ctx, message);
BgpKeepalive.processBgpKeepalive(bgpSession, ctx, message);
break;
default:
//
......@@ -166,8 +167,8 @@ class BgpFrameDecoder extends FrameDecoder {
ChannelBuffer data = ChannelBuffers.buffer(1);
data.writeByte(type);
ChannelBuffer txMessage =
bgpSession.prepareBgpNotification(errorCode, errorSubcode,
data);
BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
data);
ctx.getChannel().write(txMessage);
bgpSession.closeSession(ctx);
return null;
......
/*
* Copyright 2014 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.sdnip.bgp;
import 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.getRemoteAddress(),
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.getRemoteAddress());
// 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);
}
}
/*
* Copyright 2014 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.sdnip.bgp;
import 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;
}
}
/*
* Copyright 2014 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.sdnip.bgp;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.onosproject.sdnip.bgp.BgpConstants.Notifications.MessageHeaderError;
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.getRemoteAddress(), 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.getRemoteAddress(), 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 = MessageHeaderError.ERROR_CODE;
int errorSubcode = MessageHeaderError.BAD_MESSAGE_LENGTH;
ChannelBuffer data = ChannelBuffers.buffer(2);
data.writeShort(length);
return prepareBgpNotification(errorCode, errorSubcode, data);
}
}
/*
* Copyright 2014 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.sdnip.bgp;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.onlab.packet.Ip4Address;
import org.onosproject.sdnip.bgp.BgpConstants.Notifications;
import org.onosproject.sdnip.bgp.BgpConstants.Notifications.OpenMessageError;
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.getRemoteAddress(), 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.getRemoteAddress(), remoteBgpVersion,
BgpConstants.BGP_VERSION);
//
// ERROR: Unsupported Version Number
//
// Send NOTIFICATION and close the connection
int errorCode = OpenMessageError.ERROR_CODE;
int errorSubcode = OpenMessageError.UNSUPPORTED_VERSION_NUMBER;
ChannelBuffer data = ChannelBuffers.buffer(2);
data.writeShort(BgpConstants.BGP_VERSION);
ChannelBuffer txMessage =
BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
data);
ctx.getChannel().write(txMessage);
bgpSession.closeSession(ctx);
return;
}
bgpSession.setRemoteBgpVersion(remoteBgpVersion);
// Remote AS number
long remoteAs = message.readUnsignedShort();
//
// Verify that the AS number is same for all other BGP Sessions
// NOTE: This check applies only for our use-case where all BGP
// sessions are iBGP.
//
for (BgpSession bs : bgpSession.getBgpSessionManager().getBgpSessions()) {
if ((bs.getRemoteAs() != 0) && (remoteAs != bs.getRemoteAs())) {
log.debug("BGP RX OPEN Error from {}: Bad Peer AS {}. " +
"Expected {}",
bgpSession.getRemoteAddress(), remoteAs,
bs.getRemoteAs());
//
// ERROR: Bad Peer AS
//
// Send NOTIFICATION and close the connection
int errorCode = OpenMessageError.ERROR_CODE;
int errorSubcode = OpenMessageError.BAD_PEER_AS;
ChannelBuffer txMessage =
BgpNotification.prepareBgpNotification(errorCode,
errorSubcode, null);
ctx.getChannel().write(txMessage);
bgpSession.closeSession(ctx);
return;
}
}
bgpSession.setRemoteAs(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.getRemoteAddress(), remoteHoldtime,
BgpConstants.BGP_KEEPALIVE_MIN_HOLDTIME);
//
// ERROR: Unacceptable Hold Time
//
// Send NOTIFICATION and close the connection
int errorCode = OpenMessageError.ERROR_CODE;
int errorSubcode = OpenMessageError.UNACCEPTABLE_HOLD_TIME;
ChannelBuffer txMessage =
BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
null);
ctx.getChannel().write(txMessage);
bgpSession.closeSession(ctx);
return;
}
bgpSession.setRemoteHoldtime(remoteHoldtime);
// Remote BGP Identifier
Ip4Address remoteBgpId =
Ip4Address.valueOf((int) message.readUnsignedInt());
bgpSession.setRemoteBgpId(remoteBgpId);
// Optional Parameters
int optParamLen = message.readUnsignedByte();
if (message.readableBytes() < optParamLen) {
log.debug("BGP RX OPEN Error from {}: " +
"Invalid Optional Parameter Length field {}. " +
"Remaining Optional Parameters {}",
bgpSession.getRemoteAddress(), optParamLen,
message.readableBytes());
//
// ERROR: Invalid Optional Parameter Length field: Unspecific
//
// Send NOTIFICATION and close the connection
int errorCode = OpenMessageError.ERROR_CODE;
int errorSubcode = Notifications.ERROR_SUBCODE_UNSPECIFIC;
ChannelBuffer txMessage =
BgpNotification.prepareBgpNotification(errorCode, errorSubcode,
null);
ctx.getChannel().write(txMessage);
bgpSession.closeSession(ctx);
return;
}
// NOTE: Parse the optional parameters (if needed)
message.readBytes(optParamLen); // NOTE: data ignored
log.debug("BGP RX OPEN message from {}: " +
"BGPv{} AS {} BGP-ID {} Holdtime {}",
bgpSession.getRemoteAddress(), remoteBgpVersion, remoteAs,
remoteBgpId, remoteHoldtime);
// Send my OPEN followed by KEEPALIVE
ChannelBuffer txMessage = prepareBgpOpen(bgpSession);
ctx.getChannel().write(txMessage);
//
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 bgpSession the BGP Session to use
* @return the message to transmit (BGP header included)
*/
private static ChannelBuffer prepareBgpOpen(BgpSession bgpSession) {
ChannelBuffer message =
ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
//
// Prepare the OPEN message payload
//
message.writeByte(bgpSession.getLocalBgpVersion());
message.writeShort((int) bgpSession.getLocalAs());
message.writeShort((int) bgpSession.getLocalHoldtime());
message.writeInt(bgpSession.getLocalBgpId().toInt());
message.writeByte(0); // No Optional Parameters
return BgpMessage.prepareBgpMessage(BgpConstants.BGP_TYPE_OPEN,
message);
}
}
......@@ -81,7 +81,7 @@ public class BgpRoutesListCommand extends AbstractShellCommand {
// Print the routes
if (foundBgpSession != null) {
printRoutes(foundBgpSession.getBgpRibIn());
printRoutes(foundBgpSession.bgpRibIn().values());
} else {
printRoutes(service.getBgpRoutes());
}
......
......@@ -303,7 +303,7 @@ public class BgpSessionManagerTest {
private Collection<BgpRouteEntry> waitForBgpRibIn(BgpSession bgpSession,
long expectedRoutes)
throws InterruptedException {
Collection<BgpRouteEntry> bgpRibIn = bgpSession.getBgpRibIn();
Collection<BgpRouteEntry> bgpRibIn = bgpSession.bgpRibIn().values();
final int maxChecks = 500; // Max wait of 5 seconds
for (int i = 0; i < maxChecks; i++) {
......@@ -311,7 +311,7 @@ public class BgpSessionManagerTest {
break;
}
Thread.sleep(10);
bgpRibIn = bgpSession.getBgpRibIn();
bgpRibIn = bgpSession.bgpRibIn().values();
}
return bgpRibIn;
......