Pavlin Radoslavov

ONOS-734 Add unit tests for 4 Octets AS numbers in SDN-IP

 * Fix a bug in the storing, handling and verification of the AS numbers
   with 4 octet AS capability is used.

 * Add an unit test to test the decoding and parsing of supported
   BGP Capabilities: Multiprotocol Extensions AFI/SAFI, and 4 octet AS.

 * Minor refactoring of the BGP unit test framework.

Change-Id: I474b356bc00369c307ac0c5c214b065c1cc0c52c
......@@ -101,32 +101,6 @@ final class BgpOpen {
// 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.remoteInfo().asNumber() != 0) &&
(remoteAs != bs.remoteInfo().asNumber())) {
log.debug("BGP RX OPEN Error from {}: Bad Peer AS {}. " +
"Expected {}",
bgpSession.remoteInfo().address(), remoteAs,
bs.remoteInfo().asNumber());
//
// 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.remoteInfo().setAsNumber(remoteAs);
//
// NOTE: Currently, the local AS number is always set to the remote AS.
......@@ -194,16 +168,63 @@ final class BgpOpen {
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 = OpenMessageError.ERROR_CODE;
int errorSubcode = 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(), remoteBgpVersion,
remoteAs, remoteBgpId, remoteHoldtime);
bgpSession.remoteInfo().address(),
bgpSession.remoteInfo().bgpVersion(),
bgpSession.remoteInfo().asNumber(),
bgpSession.remoteInfo().bgpId(),
bgpSession.remoteInfo().holdtime());
// Send my OPEN followed by KEEPALIVE
ChannelBuffer txMessage = prepareBgpOpen(bgpSession.localInfo());
ctx.getChannel().write(txMessage);
ctx.getChannel().write(txOpenMessage);
//
txMessage = BgpKeepalive.prepareBgpKeepalive();
ChannelBuffer txMessage = BgpKeepalive.prepareBgpKeepalive();
ctx.getChannel().write(txMessage);
// Start the KEEPALIVE timer
......@@ -219,7 +240,7 @@ final class BgpOpen {
* @param localInfo the BGP Session local information to use
* @return the message to transmit (BGP header included)
*/
private static ChannelBuffer prepareBgpOpen(BgpSessionInfo localInfo) {
static ChannelBuffer prepareBgpOpen(BgpSessionInfo localInfo) {
ChannelBuffer message =
ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
......@@ -360,9 +381,6 @@ final class BgpOpen {
bgpSession.remoteInfo().setAs4OctetCapability();
bgpSession.remoteInfo().setAs4Number(as4Number);
// Use the 4-octet AS number in lieu of the "My AS" field
// See RFC 6793, Section 4.1, second paragraph.
bgpSession.remoteInfo().setAsNumber(as4Number);
//
// Copy remote 4-octet AS Number Capabilities and AS
......@@ -371,7 +389,6 @@ final class BgpOpen {
//
bgpSession.localInfo().setAs4OctetCapability();
bgpSession.localInfo().setAs4Number(as4Number);
bgpSession.localInfo().setAsNumber(as4Number);
log.debug("BGP RX OPEN Capability: AS4 Number = {}",
as4Number);
break;
......@@ -461,8 +478,8 @@ final class BgpOpen {
message.writeByte(Capabilities.TYPE); // Param type
message.writeByte(Capabilities.MIN_LENGTH +
As4Octet.LENGTH); // Param len
message.writeByte(As4Octet.CODE); // Capab, code
message.writeByte(As4Octet.LENGTH); // Capab, len
message.writeByte(As4Octet.CODE); // Capab. code
message.writeByte(As4Octet.LENGTH); // Capab. len
message.writeInt((int) localInfo.as4Number());
}
return message;
......
......@@ -85,9 +85,10 @@ public class BgpSessionManagerTest {
private BgpSessionManager bgpSessionManager;
// Remote Peer state
TestBgpPeer peer1 = new TestBgpPeer(BGP_PEER1_ID);
TestBgpPeer peer2 = new TestBgpPeer(BGP_PEER2_ID);
TestBgpPeer peer3 = new TestBgpPeer(BGP_PEER3_ID);
private final Collection<TestBgpPeer> peers = new LinkedList<>();
TestBgpPeer peer1;
TestBgpPeer peer2;
TestBgpPeer peer3;
// Local BGP per-peer session state
BgpSession bgpSession1;
......@@ -238,6 +239,14 @@ public class BgpSessionManagerTest {
@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.
......@@ -366,24 +375,14 @@ public class BgpSessionManagerTest {
// Test the fields from the BGP OPEN message:
// BGP version, AS number, BGP ID
//
assertThat(peer1.peerFrameDecoder.remoteBgpVersion,
is(BgpConstants.BGP_VERSION));
assertThat(peer1.peerFrameDecoder.remoteAs,
is(TestBgpPeerChannelHandler.PEER_AS));
assertThat(peer1.peerFrameDecoder.remoteBgpIdentifier,
is(IP_LOOPBACK_ID));
assertThat(peer2.peerFrameDecoder.remoteBgpVersion,
is(BgpConstants.BGP_VERSION));
assertThat(peer2.peerFrameDecoder.remoteAs,
is(TestBgpPeerChannelHandler.PEER_AS));
assertThat(peer2.peerFrameDecoder.remoteBgpIdentifier,
is(IP_LOOPBACK_ID));
assertThat(peer3.peerFrameDecoder.remoteBgpVersion,
is(BgpConstants.BGP_VERSION));
assertThat(peer3.peerFrameDecoder.remoteAs,
is(TestBgpPeerChannelHandler.PEER_AS));
assertThat(peer3.peerFrameDecoder.remoteBgpIdentifier,
is(IP_LOOPBACK_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
......@@ -399,6 +398,72 @@ public class BgpSessionManagerTest {
}
}
/**
* 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.
*
* @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.
*/
......
......@@ -30,8 +30,10 @@ import org.onlab.packet.Ip4Prefix;
*/
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 Ip4Address bgpId; // The BGP ID
final BgpSessionInfo localInfo = new BgpSessionInfo();
ChannelHandlerContext savedCtx;
/**
......@@ -40,7 +42,10 @@ class TestBgpPeerChannelHandler extends SimpleChannelHandler {
* @param bgpId the BGP ID to use
*/
TestBgpPeerChannelHandler(Ip4Address bgpId) {
this.bgpId = bgpId;
this.localInfo.setBgpVersion(BgpConstants.BGP_VERSION);
this.localInfo.setBgpId(bgpId);
this.localInfo.setAsNumber(PEER_AS);
this.localInfo.setHoldtime(PEER_HOLDTIME);
}
/**
......@@ -55,7 +60,7 @@ class TestBgpPeerChannelHandler extends SimpleChannelHandler {
ChannelStateEvent channelEvent) {
this.savedCtx = ctx;
// Prepare and transmit BGP OPEN message
ChannelBuffer message = prepareBgpOpen();
ChannelBuffer message = BgpOpen.prepareBgpOpen(localInfo);
ctx.getChannel().write(message);
// Prepare and transmit BGP KEEPALIVE message
......@@ -70,23 +75,6 @@ class TestBgpPeerChannelHandler extends SimpleChannelHandler {
}
/**
* Prepares BGP OPEN message.
*
* @return the message to transmit (BGP header included)
*/
ChannelBuffer prepareBgpOpen() {
ChannelBuffer message =
ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
message.writeByte(BgpConstants.BGP_VERSION);
message.writeShort((int) PEER_AS);
message.writeShort(PEER_HOLDTIME);
message.writeInt(bgpId.toInt());
message.writeByte(0); // No Optional Parameters
return BgpMessage.prepareBgpMessage(BgpConstants.BGP_TYPE_OPEN,
message);
}
/**
* Prepares BGP UPDATE message.
*
* @param nextHopRouter the next-hop router address for the routes to add
......
......@@ -28,10 +28,7 @@ import org.onlab.packet.Ip4Address;
* BGP peer session.
*/
class TestBgpPeerFrameDecoder extends FrameDecoder {
int remoteBgpVersion; // 1 octet
long remoteAs; // 2 octets
long remoteHoldtime; // 2 octets
Ip4Address remoteBgpIdentifier; // 4 octets -> IPv4 address
final BgpSessionInfo remoteInfo = new BgpSessionInfo();
final CountDownLatch receivedOpenMessageLatch = new CountDownLatch(1);
final CountDownLatch receivedKeepaliveMessageLatch = new CountDownLatch(1);
......@@ -141,11 +138,10 @@ class TestBgpPeerFrameDecoder extends FrameDecoder {
//
// Parse the OPEN message
//
remoteBgpVersion = message.readUnsignedByte();
remoteAs = message.readUnsignedShort();
remoteHoldtime = message.readUnsignedShort();
remoteBgpIdentifier =
Ip4Address.valueOf((int) message.readUnsignedInt());
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) {
......