BUG-8292 Fix BGP flowspec NLRI length read 47/55947/6
authorKevin Wang <kevixw@gmail.com>
Mon, 24 Apr 2017 23:28:10 +0000 (16:28 -0700)
committerClaudio D. Gasparini <claudio.gasparini@pantheon.tech>
Thu, 1 Jun 2017 19:55:10 +0000 (19:55 +0000)
In BGP Flowspec, NLRI length is set per NLRI. The
current implementation only read NLRI once. So
BGP parser will throw exception when multiple
NLRIs are batched in one BGP UPDATE message.

Change-Id: I6630bca4c222e68f4609134192550d6c0a7ea3ba
Signed-off-by: Kevin Wang <kevixw@gmail.com>
(cherry picked from commit 51646d108fc36adef7cffd99657386fd6f5f96bd)

bgp/flowspec/src/main/java/org/opendaylight/protocol/bgp/flowspec/AbstractFlowspecNlriParser.java
bgp/flowspec/src/main/java/org/opendaylight/protocol/bgp/flowspec/SimpleFlowspecTypeRegistry.java
bgp/flowspec/src/main/java/org/opendaylight/protocol/bgp/flowspec/l3vpn/AbstractFlowspecL3vpnNlriParser.java
bgp/flowspec/src/test/java/org/opendaylight/protocol/bgp/flowspec/AbstractFlowspecNlriParserTest.java [new file with mode: 0644]
bgp/flowspec/src/test/java/org/opendaylight/protocol/bgp/flowspec/SimpleFlowspecIpv4NlriParserTest.java

index 4eae0d2d07bdcf687c9e787a60220fab84ae76d8..03db6723a611beb8d26b72b16c6f1f723b1d99c2 100644 (file)
@@ -94,8 +94,12 @@ import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode;
 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public abstract class AbstractFlowspecNlriParser implements NlriParser, NlriSerializer {
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractFlowspecNlriParser.class);
+
     @VisibleForTesting
     static final NodeIdentifier FLOWSPEC_NID = new NodeIdentifier(Flowspec.QNAME);
     @VisibleForTesting
@@ -125,17 +129,14 @@ public abstract class AbstractFlowspecNlriParser implements NlriParser, NlriSeri
     @VisibleForTesting
     public static final NodeIdentifier VALUE_NID = new NodeIdentifier(QName.create(Flowspec.QNAME.getNamespace(), Flowspec.QNAME.getRevision(), "value"));
 
-    protected static final int NLRI_LENGTH = 1;
-    protected static final int NLRI_LENGTH_EXTENDED = 2;
-
     protected SimpleFlowspecTypeRegistry flowspecTypeRegistry;
 
     /**
      * Add this constant to length value to achieve all ones in the leftmost nibble.
      */
     protected static final int LENGTH_MAGIC = 61440;
-    protected static final int MAX_NLRI_LENGTH = 4095;
-    protected static final int MAX_NLRI_LENGTH_ONE_BYTE = 240;
+    protected static final int MAX_NLRI_LENGTH = 0xFFF;
+    protected static final int MAX_NLRI_LENGTH_ONE_BYTE = 0xF0;
 
     @VisibleForTesting
     static final String DO_NOT_VALUE = "do-not";
@@ -565,6 +566,17 @@ public abstract class AbstractFlowspecNlriParser implements NlriParser, NlriSeri
         return buffer.toString();
     }
 
+    public static final int readNlriLength(@Nonnull final ByteBuf nlri) {
+        Preconditions.checkNotNull(nlri, "NLRI information cannot be null");
+        Preconditions.checkState(nlri.isReadable(), "NLRI Byte buffer is not readable.");
+        int length = nlri.readUnsignedByte();
+        if (length >= MAX_NLRI_LENGTH_ONE_BYTE) {
+            length = (length << Byte.SIZE | nlri.readUnsignedByte()) & MAX_NLRI_LENGTH;
+        }
+        Preconditions.checkState(length > 0 && length <= nlri.readableBytes(), "Invalid flowspec NLRI length %s", length);
+        return length;
+    }
+
     /**
      * Parses Flowspec NLRI into list of Flowspec.
      *
@@ -578,34 +590,22 @@ public abstract class AbstractFlowspecNlriParser implements NlriParser, NlriSeri
         final List<Flowspec> fss = new ArrayList<>();
 
         while (nlri.isReadable()) {
-            final FlowspecBuilder builder = new FlowspecBuilder();
-            builder.setFlowspecType(this.flowspecTypeRegistry.parseFlowspecType(nlri));
-            fss.add(builder.build());
+            int nlriLength = readNlriLength(nlri);
+            Preconditions.checkState(nlriLength > 0 && nlriLength <= nlri.readableBytes(), "Invalid flowspec NLRI length %s", nlriLength);
+            LOG.trace("Flowspec NLRI length is {}", nlriLength);
+
+            while (nlriLength > 0) {
+                final int readableLength = nlri.readableBytes();
+                final FlowspecBuilder builder = new FlowspecBuilder();
+                builder.setFlowspecType(this.flowspecTypeRegistry.parseFlowspecType(nlri));
+                fss.add(builder.build());
+                final int flowspecTypeLength = readableLength - nlri.readableBytes();
+                nlriLength -= flowspecTypeLength;
+            }
+            Preconditions.checkState(nlriLength == 0, "Remain NLRI length should be 0 instead of %s", nlriLength);
         }
-        return fss;
-    }
 
-    /**
-     * This step is used to verify the NLRI length we read from BGP message
-     *
-     * @param nlri
-     */
-    private static final void verifyNlriLength(@Nonnull final ByteBuf nlri) {
-        // length field can be one or two bytes (if needed)
-        // check the length of nlri to see how many bytes we can skip
-        int readableLength = nlri.readableBytes();
-        // read the length from field
-        final int expectedLength;
-        if (readableLength > MAX_NLRI_LENGTH_ONE_BYTE) {
-            expectedLength = nlri.readUnsignedShort();
-            // deduct the two bytes of the NLRI length field
-            readableLength -= NLRI_LENGTH_EXTENDED;
-        } else {
-            expectedLength = nlri.readUnsignedByte();
-            // deduct the one byte of the NLRI length field
-            readableLength -= NLRI_LENGTH;
-        }
-        Preconditions.checkState(readableLength == expectedLength, "NLRI length read from message doesn't match. Length expected (read from NLRI) is %s, length readable is %s", expectedLength, readableLength);
+        return fss;
     }
 
     /**
@@ -626,7 +626,6 @@ public abstract class AbstractFlowspecNlriParser implements NlriParser, NlriSeri
             return;
         }
         final PathId pathId = readPathId(nlri, builder.getAfi(), builder.getSafi(), constraint);
-        verifyNlriLength(nlri);
         final Object[] nlriFields = parseNlri(nlri);
         builder.setAdvertizedRoutes(
             new AdvertizedRoutesBuilder()
@@ -652,7 +651,6 @@ public abstract class AbstractFlowspecNlriParser implements NlriParser, NlriSeri
             return;
         }
         final PathId pathId = readPathId(nlri, builder.getAfi(), builder.getSafi(), constraint);
-        verifyNlriLength(nlri);
         final Object[] nlriFields = parseNlri(nlri);
         builder.setWithdrawnRoutes(
             new WithdrawnRoutesBuilder()
index 46642924f5d3ef39d8b7014ab9e0420cedddd93d..bd752afc722f6f7dff029713099fd7d566b53197 100644 (file)
@@ -32,7 +32,7 @@ public class SimpleFlowspecTypeRegistry {
         serializer.serializeType(fsType, output);
     }
 
-    public FlowspecType parseFlowspecType(ByteBuf buffer) {
+    public FlowspecType parseFlowspecType(final ByteBuf buffer) {
         final short type = buffer.readUnsignedByte();
         final FlowspecTypeParser parser = getFlowspecTypeParser(type);
         Preconditions.checkNotNull(parser, "parser for flowspec type %s is not registered", type);
index 7627d30cbe6be25bc85ab41180e5d20f85034f5c..b538f405d6c28a033bd173a84d0e6f793541948a 100644 (file)
@@ -11,13 +11,14 @@ import static org.opendaylight.bgp.concepts.RouteDistinguisherUtil.extractRouteD
 
 import com.google.common.base.Preconditions;
 import io.netty.buffer.ByteBuf;
+import java.util.ArrayList;
 import java.util.List;
-import javax.annotation.Nonnull;
 import org.opendaylight.bgp.concepts.RouteDistinguisherUtil;
 import org.opendaylight.protocol.bgp.flowspec.AbstractFlowspecNlriParser;
 import org.opendaylight.protocol.bgp.flowspec.SimpleFlowspecTypeRegistry;
 import org.opendaylight.protocol.bgp.parser.BGPParsingException;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.flowspec.rev150807.flowspec.destination.Flowspec;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.flowspec.rev150807.flowspec.destination.FlowspecBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev130919.RouteDistinguisher;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
@@ -57,7 +58,7 @@ public abstract class AbstractFlowspecL3vpnNlriParser extends AbstractFlowspecNl
     }
 
     @Override
-    protected void serializeNlri(@Nonnull final Object[] nlriFields, @Nonnull final ByteBuf buffer) {
+    protected void serializeNlri(final Object[] nlriFields, final ByteBuf buffer) {
         final RouteDistinguisher rd = Preconditions.checkNotNull((RouteDistinguisher) nlriFields[0]);
         RouteDistinguisherUtil.serializeRouteDistinquisher(rd, buffer);
         final List<Flowspec> flowspecList = (List<Flowspec>) nlriFields[1];
@@ -65,12 +66,27 @@ public abstract class AbstractFlowspecL3vpnNlriParser extends AbstractFlowspecNl
     }
 
     @Override
-    @Nonnull
-    protected Object[] parseNlri(@Nonnull final ByteBuf nlri) throws BGPParsingException {
+    protected Object[] parseNlri(final ByteBuf nlri) throws BGPParsingException {
+        readNlriLength(nlri);
         return new Object[] {
             Preconditions.checkNotNull(readRouteDistinguisher(nlri)),
-            parseNlriFlowspecList(nlri)
+            parseL3vpnNlriFlowspecList(nlri)
         };
     }
+
+    protected final List<Flowspec> parseL3vpnNlriFlowspecList(final ByteBuf nlri) throws BGPParsingException {
+        if (!nlri.isReadable()) {
+            return null;
+        }
+        final List<Flowspec> fss = new ArrayList<>();
+
+        while (nlri.isReadable()) {
+            final FlowspecBuilder builder = new FlowspecBuilder();
+            builder.setFlowspecType(this.flowspecTypeRegistry.parseFlowspecType(nlri));
+            fss.add(builder.build());
+        }
+
+        return fss;
+    }
 }
 
diff --git a/bgp/flowspec/src/test/java/org/opendaylight/protocol/bgp/flowspec/AbstractFlowspecNlriParserTest.java b/bgp/flowspec/src/test/java/org/opendaylight/protocol/bgp/flowspec/AbstractFlowspecNlriParserTest.java
new file mode 100644 (file)
index 0000000..2636196
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2017 Brocade Communications Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.protocol.bgp.flowspec;
+
+import static org.junit.Assert.assertEquals;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import java.nio.ByteBuffer;
+import org.junit.Test;
+
+public class AbstractFlowspecNlriParserTest {
+
+    @Test(expected = IllegalStateException.class)
+    public void zeroNlriLengthTest() throws Exception {
+        // invalid zero NRLI length
+        AbstractFlowspecNlriParser.readNlriLength(Unpooled.wrappedBuffer(new byte[]{0x00, 0x0}));
+    }
+
+    @Test
+    public void readNlriLength() throws Exception {
+        final ByteBuffer byteBuffer = ByteBuffer.allocate(5003);
+        byteBuffer.put(new byte[]{(byte) 0x01});   // length = 1
+        byteBuffer.put(new byte[]{(byte) 0xf0, (byte) 0xf0});   // length = 240
+        byteBuffer.put(new byte[]{(byte) 0xf0, (byte) 0xf1});   // length = 241
+        byteBuffer.put(new byte[]{(byte) 0xff, (byte) 0xff});   // length = 4095
+        byteBuffer.rewind();
+        final ByteBuf byteBuf = Unpooled.wrappedBuffer(byteBuffer);
+        assertEquals(1, AbstractFlowspecNlriParser.readNlriLength(byteBuf));
+        assertEquals(240, AbstractFlowspecNlriParser.readNlriLength(byteBuf));
+        assertEquals(241, AbstractFlowspecNlriParser.readNlriLength(byteBuf));
+        assertEquals(4095, AbstractFlowspecNlriParser.readNlriLength(byteBuf));
+    }
+
+}
index 06c0d3789009f0d13d47dddcbe7d833b627868e1..2f21b80c3a8962fa1fd8ab9ed3c134e0bfceda7b 100644 (file)
@@ -9,6 +9,7 @@ package org.opendaylight.protocol.bgp.flowspec;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
@@ -77,12 +78,14 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.flow
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.flowspec.rev150807.flowspec.destination.group.ipv4.flowspec.flowspec.type.protocol.ip._case.ProtocolIpsBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.flowspec.rev150807.flowspec.destination.ipv4.DestinationFlowspecBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.flowspec.rev150807.flowspec.ipv4.route.FlowspecRoute;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.flowspec.rev150807.update.attributes.mp.reach.nlri.advertized.routes.destination.type.DestinationFlowspecCase;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.PathId;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.path.attributes.AttributesBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev130919.Attributes1;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev130919.Attributes1Builder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev130919.Attributes2;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev130919.Attributes2Builder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev130919.update.attributes.MpReachNlri;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev130919.update.attributes.MpReachNlriBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev130919.update.attributes.MpUnreachNlriBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev130919.update.attributes.mp.reach.nlri.AdvertizedRoutesBuilder;
@@ -125,6 +128,12 @@ public class SimpleFlowspecIpv4NlriParserTest {
         06, (byte) 0x91, 0x1f, (byte) 0x90
     };
 
+    private static final byte[] REACHED_NLRI_BATCHED = new byte[] {
+        0x06, 0x01, 0x20, (byte) 0xd8, 0x3a, (byte) 0xf5, 0x65,
+        0x06, 0x01, 0x20, (byte) 0xd8, 0x3a, (byte) 0xda, (byte) 0xc4,
+        0x06, 0x01, 0x20, (byte) 0xd8, 0x3a, (byte) 0xd8, (byte) 0xc3
+    };
+
     private static final byte[] UNREACHED_NLRI = new byte[] {
         0x1B,   // NLRI length: 27
         07, 4, 2, (byte) 0x84, 3,
@@ -753,5 +762,25 @@ public class SimpleFlowspecIpv4NlriParserTest {
         expected.add(expectedFS.build());
         assertEquals(expected, this.FS_PARSER.extractFlowspec(entry.build()));
     }
+
+    @Test
+    public void testBatchedFlowspecNlri() throws Exception {
+        final SimpleFlowspecIpv4NlriParser parser = new SimpleFlowspecIpv4NlriParser(flowspecContext.getFlowspecTypeRegistry(SimpleFlowspecExtensionProviderContext.AFI.IPV4, SimpleFlowspecExtensionProviderContext.SAFI.FLOWSPEC));
+
+        final MpReachNlriBuilder result = new MpReachNlriBuilder();
+        result.setAfi(Ipv4AddressFamily.class);
+        result.setSafi(FlowspecSubsequentAddressFamily.class);
+
+        parser.parseNlri(Unpooled.wrappedBuffer(REACHED_NLRI_BATCHED), result);
+        final MpReachNlri nlri = result.build();
+
+        final List<Flowspec> flowspecList = ((DestinationFlowspecCase) nlri.getAdvertizedRoutes().getDestinationType())
+                .getDestinationFlowspec().getFlowspec();
+        assertEquals(3, flowspecList.size());
+        assertEquals("216.58.245.101/32", ((DestinationPrefixCase) flowspecList.get(0).getFlowspecType()).getDestinationPrefix().getValue());
+        assertEquals("216.58.218.196/32", ((DestinationPrefixCase) flowspecList.get(1).getFlowspecType()).getDestinationPrefix().getValue());
+        assertEquals("216.58.216.195/32", ((DestinationPrefixCase) flowspecList.get(2).getFlowspecType()).getDestinationPrefix().getValue());
+    }
+
 }