summaryrefslogtreecommitdiffstats
path: root/v3po/v3po2vpp/src/main/java/io
diff options
context:
space:
mode:
authorMarek Gradzki <mgradzki@cisco.com>2016-08-26 12:37:45 +0200
committerMarek Gradzki <mgradzki@cisco.com>2016-08-26 14:28:28 +0200
commita0a1b0e2af851e1a15286e5d5eb576eae5769a59 (patch)
tree4dfd3a54eef8bfc6e3024cf623d20df5daece852 /v3po/v3po2vpp/src/main/java/io
parenta92df9b4a2c5488621f8e95a6b88fb126a375649 (diff)
HONEYCOMB-139: ietf-acl translation layer. IP6 L3 ACL support
Other changes: - documentation update - eth + ip4 writer rafactoring + tests Change-Id: I1ac6a4e99dd4f12c870cbd749af6b98018294dd4 Signed-off-by: Marek Gradzki <mgradzki@cisco.com>
Diffstat (limited to 'v3po/v3po2vpp/src/main/java/io')
-rw-r--r--v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/AclWriterFactory.java2
-rw-r--r--v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AbstractAceWriter.java104
-rw-r--r--v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceEthWriter.java50
-rw-r--r--v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceIp4Writer.java113
-rw-r--r--v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceIp6Writer.java225
-rw-r--r--v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceWriter.java13
-rw-r--r--v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AclWriter.java (renamed from v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/acl/AclWriter.java)2
-rw-r--r--v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/IetfAClWriter.java43
-rw-r--r--v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/Readme.adoc32
9 files changed, 436 insertions, 148 deletions
diff --git a/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/AclWriterFactory.java b/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/AclWriterFactory.java
index d0050ab62..21bc2e6cb 100644
--- a/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/AclWriterFactory.java
+++ b/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/AclWriterFactory.java
@@ -21,7 +21,7 @@ import static io.fd.honeycomb.translate.v3po.SubinterfaceAugmentationWriterFacto
import com.google.common.collect.Sets;
import io.fd.honeycomb.translate.impl.write.GenericListWriter;
-import io.fd.honeycomb.translate.v3po.acl.AclWriter;
+import io.fd.honeycomb.translate.v3po.interfaces.acl.AclWriter;
import io.fd.honeycomb.translate.write.WriterFactory;
import io.fd.honeycomb.translate.write.registry.ModifiableWriterRegistryBuilder;
import javax.annotation.Nonnull;
diff --git a/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AbstractAceWriter.java b/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AbstractAceWriter.java
index 9f27271b5..21a710701 100644
--- a/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AbstractAceWriter.java
+++ b/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AbstractAceWriter.java
@@ -27,6 +27,7 @@ import java.util.stream.Collector;
import javax.annotation.Nonnull;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.Ace;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.actions.PacketHandling;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.actions.packet.handling.Permit;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.matches.AceType;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
import org.openvpp.jvpp.VppBaseCallException;
@@ -37,7 +38,21 @@ import org.openvpp.jvpp.core.dto.ClassifyAddDelTableReply;
import org.openvpp.jvpp.core.dto.InputAclSetInterface;
import org.openvpp.jvpp.core.future.FutureJVppCore;
+/**
+ * Base writer for translation of ietf-acl model ACEs to VPP's classify tables and sessions.
+ *
+ * Creates one classify table with single session per ACE.
+ *
+ * @param <T> type of access control list entry
+ */
abstract class AbstractAceWriter<T extends AceType> implements AceWriter {
+
+ // TODO: minimise memory used by classify tables (we create a lot of them to make ietf-acl model
+ // mapping more convenient):
+ // according to https://wiki.fd.io/view/VPP/Introduction_To_N-tuple_Classifiers#Creating_a_classifier_table,
+ // classify table needs 16*(1 + match_n_vectors) bytes, but this does not quite work, so setting 8K for now
+ protected static final int TABLE_MEM_SIZE = 8 * 1024;
+
private static final Collector<PacketHandling, ?, PacketHandling> SINGLE_ITEM_COLLECTOR =
RWUtils.singleItemCollector();
@@ -47,19 +62,36 @@ abstract class AbstractAceWriter<T extends AceType> implements AceWriter {
this.futureJVppCore = checkNotNull(futureJVppCore, "futureJVppCore should not be null");
}
- @Nonnull
- public FutureJVppCore getFutureJVppCore() {
- return futureJVppCore;
- }
-
- protected abstract ClassifyAddDelTable getClassifyAddDelTableRequest(@Nonnull final PacketHandling action,
- @Nonnull final T ace,
- final int nextTableIndex);
-
- protected abstract ClassifyAddDelSession getClassifyAddDelSessionRequest(@Nonnull final PacketHandling action,
- @Nonnull final T ace,
- final int nextTableIndex);
-
+ /**
+ * Creates classify table for given ACE.
+ *
+ * @param action packet handling action (permit/deny)
+ * @param ace ACE to be translated
+ * @param nextTableIndex classify table index
+ * @return classify table that represents given ACE
+ */
+ protected abstract ClassifyAddDelTable createClassifyTable(@Nonnull final PacketHandling action,
+ @Nonnull final T ace,
+ final int nextTableIndex);
+
+ /**
+ * Creates classify session for given ACE.
+ *
+ * @param action packet handling action (permit/deny)
+ * @param ace ACE to be translated
+ * @param tableIndex classify table index for the given session
+ * @return classify session that represents given ACE
+ */
+ protected abstract ClassifyAddDelSession createClassifySession(@Nonnull final PacketHandling action,
+ @Nonnull final T ace,
+ final int tableIndex);
+
+ /**
+ * Sets classify table index for input_acl_set_interface request.
+ *
+ * @param request request DTO
+ * @param tableIndex pointer to a chain of classify tables
+ */
protected abstract void setClassifyTable(@Nonnull final InputAclSetInterface request, final int tableIndex);
public final void write(@Nonnull final InstanceIdentifier<?> id, @Nonnull final List<Ace> aces,
@@ -68,24 +100,17 @@ abstract class AbstractAceWriter<T extends AceType> implements AceWriter {
final PacketHandling action = aces.stream().map(ace -> ace.getActions().getPacketHandling()).distinct()
.collect(SINGLE_ITEM_COLLECTOR);
- int firstTableIndex = -1;
int nextTableIndex = -1;
for (final Ace ace : aces) {
- // Create table + session per entry. We actually need one table for each nonempty subset of params,
- // so we could decrease number of tables to 109 = 15 (eth) + 31 (ip4) + 63 (ip6) for general case.
- // TODO: For special cases like many ACEs of similar kind, it could be significant optimization.
+ // Create table + session per entry
final ClassifyAddDelTable ctRequest =
- getClassifyAddDelTableRequest(action, (T) ace.getMatches().getAceType(), nextTableIndex);
+ createClassifyTable(action, (T) ace.getMatches().getAceType(), nextTableIndex);
nextTableIndex = createClassifyTable(id, ctRequest);
createClassifySession(id,
- getClassifyAddDelSessionRequest(action, (T) ace.getMatches().getAceType(), nextTableIndex));
- if (firstTableIndex == -1) {
- firstTableIndex = nextTableIndex;
- }
+ createClassifySession(action, (T) ace.getMatches().getAceType(), nextTableIndex));
}
-
- setClassifyTable(request, firstTableIndex);
+ setClassifyTable(request, nextTableIndex);
}
private int createClassifyTable(@Nonnull final InstanceIdentifier<?> id,
@@ -104,4 +129,35 @@ abstract class AbstractAceWriter<T extends AceType> implements AceWriter {
TranslateUtils.getReplyForWrite(cs.toCompletableFuture(), id);
}
+
+ protected ClassifyAddDelTable createClassifyTable(@Nonnull final PacketHandling action, final int nextTableIndex) {
+ final ClassifyAddDelTable request = new ClassifyAddDelTable();
+ request.isAdd = 1;
+ request.tableIndex = -1; // value not present
+
+ request.nbuckets = 1; // we expect exactly one session per table
+
+ if (action instanceof Permit) {
+ request.missNextIndex = 0; // for list of permit rules, deny (0) should be default action
+ } else { // deny is default value
+ request.missNextIndex = -1; // for list of deny rules, permit (-1) should be default action
+ }
+
+ request.nextTableIndex = nextTableIndex;
+ request.memorySize = TABLE_MEM_SIZE;
+
+ return request;
+ }
+
+ protected ClassifyAddDelSession createClassifySession(@Nonnull final PacketHandling action, final int tableIndex) {
+ final ClassifyAddDelSession request = new ClassifyAddDelSession();
+ request.isAdd = 1;
+ request.tableIndex = tableIndex;
+
+ if (action instanceof Permit) {
+ request.hitNextIndex = -1;
+ } // deny (0) is default value
+
+ return request;
+ }
}
diff --git a/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceEthWriter.java b/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceEthWriter.java
index 4744de1fe..1240a2986 100644
--- a/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceEthWriter.java
+++ b/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceEthWriter.java
@@ -16,12 +16,12 @@
package io.fd.honeycomb.translate.v3po.interfaces.acl;
+import com.google.common.annotations.VisibleForTesting;
import io.fd.honeycomb.translate.v3po.util.TranslateUtils;
import java.util.List;
import javax.annotation.Nonnull;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.actions.PacketHandling;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.actions.packet.handling.Permit;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.matches.ace.type.AceEth;
import org.openvpp.jvpp.core.dto.ClassifyAddDelSession;
import org.openvpp.jvpp.core.dto.ClassifyAddDelTable;
@@ -30,8 +30,10 @@ import org.openvpp.jvpp.core.future.FutureJVppCore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-class AceEthWriter extends AbstractAceWriter<AceEth> {
+final class AceEthWriter extends AbstractAceWriter<AceEth> {
+ @VisibleForTesting
+ static final int MATCH_N_VECTORS = 1;
private static final Logger LOG = LoggerFactory.getLogger(AceEthWriter.class);
public AceEthWriter(@Nonnull final FutureJVppCore futureJVppCore) {
@@ -39,22 +41,10 @@ class AceEthWriter extends AbstractAceWriter<AceEth> {
}
@Override
- public ClassifyAddDelTable getClassifyAddDelTableRequest(@Nonnull final PacketHandling action,
- @Nonnull final AceEth aceEth,
- @Nonnull final int nextTableIndex) {
- final ClassifyAddDelTable request = new ClassifyAddDelTable();
- request.isAdd = 1;
- request.tableIndex = -1; // value not present
-
- request.nbuckets = 1; // we expect exactly one session per table
-
- if (action instanceof Permit) {
- request.missNextIndex = 0; // for list of permit rules, deny (0) should be default action
- } else { // deny is default value
- request.missNextIndex = -1; // for list of deny rules, permit (-1) should be default action
- }
-
- request.nextTableIndex = nextTableIndex;
+ public ClassifyAddDelTable createClassifyTable(@Nonnull final PacketHandling action,
+ @Nonnull final AceEth aceEth,
+ @Nonnull final int nextTableIndex) {
+ final ClassifyAddDelTable request = createClassifyTable(action, nextTableIndex);
request.mask = new byte[16];
boolean aceIsEmpty = true;
@@ -95,17 +85,11 @@ class AceEthWriter extends AbstractAceWriter<AceEth> {
if (aceIsEmpty) {
throw new IllegalArgumentException(
- String.format("Ace %s does not define packet field matches", aceEth.toString()));
+ String.format("Ace %s does not define packet field match values", aceEth.toString()));
}
request.skipNVectors = 0;
- request.matchNVectors = request.mask.length / 16;
-
- // TODO: minimise memory used by classify tables (we create a lot of them to make ietf-acl model
- // mapping more convenient):
- // according to https://wiki.fd.io/view/VPP/Introduction_To_N-tuple_Classifiers#Creating_a_classifier_table,
- // classify table needs 16*(1 + match_n_vectors) bytes, but this does not quite work, so setting 8K for now
- request.memorySize = 8 * 1024;
+ request.matchNVectors = MATCH_N_VECTORS;
if (LOG.isDebugEnabled()) {
LOG.debug("ACE action={}, rule={} translated to table={}.", action, aceEth,
@@ -115,16 +99,10 @@ class AceEthWriter extends AbstractAceWriter<AceEth> {
}
@Override
- public ClassifyAddDelSession getClassifyAddDelSessionRequest(@Nonnull final PacketHandling action,
- @Nonnull final AceEth aceEth,
- @Nonnull final int tableIndex) {
- final ClassifyAddDelSession request = new ClassifyAddDelSession();
- request.isAdd = 1;
- request.tableIndex = tableIndex;
-
- if (action instanceof Permit) {
- request.hitNextIndex = -1;
- } // deny (0) is default value
+ public ClassifyAddDelSession createClassifySession(@Nonnull final PacketHandling action,
+ @Nonnull final AceEth aceEth,
+ @Nonnull final int tableIndex) {
+ final ClassifyAddDelSession request = createClassifySession(action, tableIndex);
request.match = new byte[16];
boolean noMatch = true;
diff --git a/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceIp4Writer.java b/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceIp4Writer.java
index ac110f98b..b2a9613fa 100644
--- a/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceIp4Writer.java
+++ b/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceIp4Writer.java
@@ -19,11 +19,11 @@ package io.fd.honeycomb.translate.v3po.interfaces.acl;
import static com.google.common.base.Preconditions.checkArgument;
import static io.fd.honeycomb.translate.v3po.util.TranslateUtils.ipv4AddressNoZoneToArray;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.primitives.Ints;
import javax.annotation.Nonnull;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.actions.PacketHandling;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.actions.packet.handling.Permit;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.matches.ace.type.AceIp;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.matches.ace.type.ace.ip.ace.ip.version.AceIpv4;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4Prefix;
@@ -34,52 +34,61 @@ import org.openvpp.jvpp.core.future.FutureJVppCore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-class AceIp4Writer extends AbstractAceWriter<AceIp> {
+final class AceIp4Writer extends AbstractAceWriter<AceIp> {
- private static final Logger LOG = LoggerFactory.getLogger(AceIp4Writer.class);
- private static final int TABLE_MASK_LENGTH = 48; // number of bytes
+ @VisibleForTesting
static final int MATCH_N_VECTORS = 3; // number of 16B vectors
- static final int TABLE_MEM_SIZE = 8 * 1024;
- private static final int IP_MASK_LENGTH = 32; // number of bits
- private static final int IP_VERSION_OFFSET = 14;
- private static final int DSCP_OFFSET = 15;
- private static final int SRC_IP_OFFSET = 26;
- private static final int DST_IP_OFFSET = 30;
+ private static final Logger LOG = LoggerFactory.getLogger(AceIp4Writer.class);
+ private static final int TABLE_MASK_LENGTH = 48;
+ private static final int IP4_MASK_BIT_LENGTH = 32;
+
+ private static final int IP_VERSION_OFFSET = 14; // first 14 bytes represent L2 header (2x6 + etherType(2))
private static final int IP_VERSION_MASK = 0xf0;
+ private static final int DSCP_OFFSET = 15;
private static final int DSCP_MASK = 0xfc;
+ private static final int IP4_LEN = 4;
+ private static final int SRC_IP_OFFSET = IP_VERSION_OFFSET + 12;
+ private static final int DST_IP_OFFSET = SRC_IP_OFFSET + IP4_LEN;
public AceIp4Writer(@Nonnull final FutureJVppCore futureJVppCore) {
super(futureJVppCore);
}
- @Override
- public ClassifyAddDelTable getClassifyAddDelTableRequest(@Nonnull final PacketHandling action,
- @Nonnull final AceIp aceIp,
- @Nonnull final int nextTableIndex) {
- checkArgument(aceIp.getAceIpVersion() instanceof AceIpv4, "Expected AceIpv4 version, but was %", aceIp);
- final AceIpv4 ipVersion = (AceIpv4) aceIp.getAceIpVersion();
-
- final ClassifyAddDelTable request = new ClassifyAddDelTable();
- request.isAdd = 1;
- request.tableIndex = -1; // value not present
+ private static byte[] toByteMask(final int prefixLength) {
+ final long mask = ((1L << prefixLength) - 1) << (IP4_MASK_BIT_LENGTH - prefixLength);
+ return Ints.toByteArray((int) mask);
+ }
- request.nbuckets = 1; // we expect exactly one session per table
+ private static byte[] toByteMask(final Ipv4Prefix ipv4Prefix) {
+ final int prefixLength = Byte.valueOf(ipv4Prefix.getValue().split("/")[1]);
+ return toByteMask(prefixLength);
+ }
- if (action instanceof Permit) {
- request.missNextIndex = 0; // for list of permit rules, deny (0) should be default action
- } else { // deny is default value
- request.missNextIndex = -1; // for list of deny rules, permit (-1) should be default action
+ private static byte[] toMatchValue(final Ipv4Prefix ipv4Prefix) {
+ final String[] split = ipv4Prefix.getValue().split("/");
+ final byte[] addressBytes = ipv4AddressNoZoneToArray(split[0]);
+ final byte[] mask = toByteMask(Byte.valueOf(split[1]));
+ for (int i = 0; i < addressBytes.length; ++i) {
+ addressBytes[i] &= mask[i];
}
+ return addressBytes;
+ }
- request.nextTableIndex = nextTableIndex;
+ @Override
+ public ClassifyAddDelTable createClassifyTable(@Nonnull final PacketHandling action,
+ @Nonnull final AceIp aceIp,
+ final int nextTableIndex) {
+ checkArgument(aceIp.getAceIpVersion() instanceof AceIpv4, "Expected AceIpv4 version, but was %", aceIp);
+ final AceIpv4 ipVersion = (AceIpv4) aceIp.getAceIpVersion();
+
+ final ClassifyAddDelTable request = createClassifyTable(action, nextTableIndex);
request.skipNVectors = 0; // match entire L2 and L3 header
request.matchNVectors = MATCH_N_VECTORS;
- request.memorySize = TABLE_MEM_SIZE;
boolean aceIsEmpty = true;
request.mask = new byte[TABLE_MASK_LENGTH];
- // First 14 bytes represent l2 header (2x6 + etherType(2)_
+ // First 14 bytes represent l2 header (2x6 + etherType(2))
if (aceIp.getProtocol() != null) { // Internet Protocol number
request.mask[IP_VERSION_OFFSET] = (byte) IP_VERSION_MASK; // first 4 bits
}
@@ -99,17 +108,18 @@ class AceIp4Writer extends AbstractAceWriter<AceIp> {
if (ipVersion.getSourceIpv4Network() != null) {
aceIsEmpty = false;
- System.arraycopy(toByteMask(ipVersion.getSourceIpv4Network()), 0, request.mask, SRC_IP_OFFSET, 4);
+ System.arraycopy(toByteMask(ipVersion.getSourceIpv4Network()), 0, request.mask, SRC_IP_OFFSET, IP4_LEN);
}
if (ipVersion.getDestinationIpv4Network() != null) {
aceIsEmpty = false;
- System.arraycopy(toByteMask(ipVersion.getDestinationIpv4Network()), 0, request.mask, DST_IP_OFFSET, 4);
+ System
+ .arraycopy(toByteMask(ipVersion.getDestinationIpv4Network()), 0, request.mask, DST_IP_OFFSET, IP4_LEN);
}
if (aceIsEmpty) {
throw new IllegalArgumentException(
- String.format("Ace %s does not define packet field matches", aceIp.toString()));
+ String.format("Ace %s does not define packet field match values", aceIp.toString()));
}
if (LOG.isDebugEnabled()) {
@@ -120,19 +130,13 @@ class AceIp4Writer extends AbstractAceWriter<AceIp> {
}
@Override
- public ClassifyAddDelSession getClassifyAddDelSessionRequest(@Nonnull final PacketHandling action,
- @Nonnull final AceIp aceIp,
- @Nonnull final int tableIndex) {
+ public ClassifyAddDelSession createClassifySession(@Nonnull final PacketHandling action,
+ @Nonnull final AceIp aceIp,
+ final int tableIndex) {
checkArgument(aceIp.getAceIpVersion() instanceof AceIpv4, "Expected AceIpv4 version, but was %", aceIp);
final AceIpv4 ipVersion = (AceIpv4) aceIp.getAceIpVersion();
- final ClassifyAddDelSession request = new ClassifyAddDelSession();
- request.isAdd = 1;
- request.tableIndex = tableIndex;
-
- if (action instanceof Permit) {
- request.hitNextIndex = -1;
- } // deny (0) is default value
+ final ClassifyAddDelSession request = createClassifySession(action, tableIndex);
request.match = new byte[TABLE_MASK_LENGTH];
boolean noMatch = true;
@@ -156,17 +160,18 @@ class AceIp4Writer extends AbstractAceWriter<AceIp> {
if (ipVersion.getSourceIpv4Network() != null) {
noMatch = false;
- System.arraycopy(toMatchValue(ipVersion.getSourceIpv4Network()), 0, request.match, SRC_IP_OFFSET, 4);
+ System.arraycopy(toMatchValue(ipVersion.getSourceIpv4Network()), 0, request.match, SRC_IP_OFFSET, IP4_LEN);
}
if (ipVersion.getDestinationIpv4Network() != null) {
noMatch = false;
- System.arraycopy(toMatchValue(ipVersion.getDestinationIpv4Network()), 0, request.match, DST_IP_OFFSET, 4);
+ System.arraycopy(toMatchValue(ipVersion.getDestinationIpv4Network()), 0, request.match, DST_IP_OFFSET,
+ IP4_LEN);
}
if (noMatch) {
throw new IllegalArgumentException(
- String.format("Ace %s does not define neither source nor destination MAC address", aceIp.toString()));
+ String.format("Ace %s does not define packet field match values", aceIp.toString()));
}
if (LOG.isDebugEnabled()) {
@@ -180,24 +185,4 @@ class AceIp4Writer extends AbstractAceWriter<AceIp> {
protected void setClassifyTable(@Nonnull final InputAclSetInterface request, final int tableIndex) {
request.ip4TableIndex = tableIndex;
}
-
- private static byte[] toByteMask(final int prefixLength) {
- final long mask = ((1L << prefixLength) - 1) << (IP_MASK_LENGTH - prefixLength);
- return Ints.toByteArray((int) mask);
- }
-
- private static byte[] toByteMask(final Ipv4Prefix ipv4Prefix) {
- final int prefixLength = Byte.valueOf(ipv4Prefix.getValue().split("/")[1]);
- return toByteMask(prefixLength);
- }
-
- private static byte[] toMatchValue(final Ipv4Prefix ipv4Prefix) {
- final String[] split = ipv4Prefix.getValue().split("/");
- final byte[] addressBytes = ipv4AddressNoZoneToArray(split[0]);
- final byte[] mask = toByteMask(Byte.valueOf(split[1]));
- for (int i = 0; i < addressBytes.length; ++i) {
- addressBytes[i] &= mask[i];
- }
- return addressBytes;
- }
}
diff --git a/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceIp6Writer.java b/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceIp6Writer.java
new file mode 100644
index 000000000..c46f2e19d
--- /dev/null
+++ b/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceIp6Writer.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ *
+ * 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 io.fd.honeycomb.translate.v3po.interfaces.acl;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.BitSet;
+import javax.annotation.Nonnull;
+import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.actions.PacketHandling;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.matches.ace.type.AceIp;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.matches.ace.type.ace.ip.ace.ip.version.AceIpv6;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv6Prefix;
+import org.openvpp.jvpp.core.dto.ClassifyAddDelSession;
+import org.openvpp.jvpp.core.dto.ClassifyAddDelTable;
+import org.openvpp.jvpp.core.dto.InputAclSetInterface;
+import org.openvpp.jvpp.core.future.FutureJVppCore;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+final class AceIp6Writer extends AbstractAceWriter<AceIp> {
+
+ @VisibleForTesting
+ static final int MATCH_N_VECTORS = 4; // number of 16B vectors
+ private static final Logger LOG = LoggerFactory.getLogger(AceIp6Writer.class);
+ private static final int TABLE_MASK_LENGTH = 64;
+ private static final int IP6_MASK_BIT_LENGTH = 128;
+
+ private static final int IP_VERSION_OFFSET = 14; // first 14 bytes represent L2 header (2x6 + etherType(2))
+ private static final int IP_VERSION_MASK = 0xf0;
+ private static final int DSCP_MASK1 = 0x0f;
+ private static final int DSCP_MASK2 = 0xc0;
+ private static final int IP6_LEN = 16;
+ private static final int SRC_IP_OFFSET = IP_VERSION_OFFSET + 8;
+ private static final int DST_IP_OFFSET = SRC_IP_OFFSET + IP6_LEN;
+
+ public AceIp6Writer(@Nonnull final FutureJVppCore futureJVppCore) {
+ super(futureJVppCore);
+ }
+
+ private static byte[] toByteMask(final int prefixLength) {
+ final BitSet mask = new BitSet(IP6_MASK_BIT_LENGTH);
+ mask.set(0, prefixLength, true);
+ if (prefixLength < IP6_MASK_BIT_LENGTH) {
+ mask.set(prefixLength, IP6_MASK_BIT_LENGTH, false);
+ }
+ return mask.toByteArray();
+ }
+
+ private static byte[] toByteMask(final Ipv6Prefix ipv6Prefix) {
+ final int prefixLength = Short.valueOf(ipv6Prefix.getValue().split("/")[1]);
+ return toByteMask(prefixLength);
+ }
+
+ private static byte[] toMatchValue(final Ipv6Prefix ipv6Prefix) {
+ final String[] split = ipv6Prefix.getValue().split("/");
+ final byte[] addressBytes;
+ try {
+ addressBytes = InetAddress.getByName(split[0]).getAddress();
+ } catch (UnknownHostException e) {
+ throw new IllegalArgumentException("Invalid IP6 address", e);
+ }
+ final byte[] mask = toByteMask(Short.valueOf(split[1]));
+ int pos = 0;
+ for (; pos < mask.length; ++pos) {
+ addressBytes[pos] &= mask[pos];
+ }
+ // mask can be shorter that address, so we need to clear rest of the address:
+ for (; pos < addressBytes.length; ++pos) {
+ addressBytes[pos] = 0;
+ }
+ return addressBytes;
+ }
+
+ @Override
+ public ClassifyAddDelTable createClassifyTable(@Nonnull final PacketHandling action,
+ @Nonnull final AceIp aceIp,
+ final int nextTableIndex) {
+ checkArgument(aceIp.getAceIpVersion() instanceof AceIpv6, "Expected AceIpv6 version, but was %", aceIp);
+ final AceIpv6 ipVersion = (AceIpv6) aceIp.getAceIpVersion();
+
+ final ClassifyAddDelTable request = createClassifyTable(action, nextTableIndex);
+ request.skipNVectors = 0; // match entire L2 and L3 header
+ request.matchNVectors = MATCH_N_VECTORS;
+
+ boolean aceIsEmpty = true;
+ request.mask = new byte[TABLE_MASK_LENGTH];
+ if (aceIp.getProtocol() != null) {
+ request.mask[IP_VERSION_OFFSET] |= IP_VERSION_MASK;
+ }
+
+ if (aceIp.getDscp() != null) {
+ aceIsEmpty = false;
+ // DCSP (bits 4-9 of IP6 header)
+ request.mask[IP_VERSION_OFFSET] |= DSCP_MASK1;
+ request.mask[IP_VERSION_OFFSET + 1] |= DSCP_MASK2;
+ }
+
+ if (aceIp.getSourcePortRange() != null) {
+ LOG.warn("L4 Header fields are not supported. Ignoring {}", aceIp.getSourcePortRange());
+ }
+
+ if (aceIp.getDestinationPortRange() != null) {
+ LOG.warn("L4 Header fields are not supported. Ignoring {}", aceIp.getDestinationPortRange());
+ }
+
+ if (ipVersion.getFlowLabel() != null) {
+ aceIsEmpty = false;
+ // bits 12-31
+ request.mask[IP_VERSION_OFFSET + 1] |= (byte) 0x0f;
+ request.mask[IP_VERSION_OFFSET + 2] = (byte) 0xff;
+ request.mask[IP_VERSION_OFFSET + 3] = (byte) 0xff;
+ }
+
+ if (ipVersion.getSourceIpv6Network() != null) {
+ aceIsEmpty = false;
+ final byte[] mask = toByteMask(ipVersion.getSourceIpv6Network());
+ System.arraycopy(mask, 0, request.mask, SRC_IP_OFFSET, mask.length);
+ }
+
+ if (ipVersion.getDestinationIpv6Network() != null) {
+ aceIsEmpty = false;
+ final byte[] mask = toByteMask(ipVersion.getDestinationIpv6Network());
+ System.arraycopy(mask, 0, request.mask, DST_IP_OFFSET, mask.length);
+ }
+
+ if (aceIsEmpty) {
+ throw new IllegalArgumentException(
+ String.format("Ace %s does not define packet field match values", aceIp.toString()));
+ }
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("ACE action={}, rule={} translated to table={}.", action, aceIp,
+ ReflectionToStringBuilder.toString(request));
+ }
+
+ return request;
+ }
+
+ @Override
+ public ClassifyAddDelSession createClassifySession(@Nonnull final PacketHandling action,
+ @Nonnull final AceIp aceIp,
+ final int tableIndex) {
+ checkArgument(aceIp.getAceIpVersion() instanceof AceIpv6, "Expected AceIpv6 version, but was %", aceIp);
+ final AceIpv6 ipVersion = (AceIpv6) aceIp.getAceIpVersion();
+
+ final ClassifyAddDelSession request = createClassifySession(action, tableIndex);
+ request.match = new byte[TABLE_MASK_LENGTH];
+ boolean noMatch = true;
+
+ if (aceIp.getProtocol() != null) {
+ request.match[IP_VERSION_OFFSET] |= (byte) (IP_VERSION_MASK & (aceIp.getProtocol().intValue() << 4));
+ }
+
+ if (aceIp.getDscp() != null) {
+ noMatch = false;
+ final int dscp = aceIp.getDscp().getValue();
+ // set bits 4-9 of IP6 header:
+ request.match[IP_VERSION_OFFSET] |= (byte) (DSCP_MASK1 & (dscp >> 2));
+ request.match[IP_VERSION_OFFSET + 1] |= (byte) (DSCP_MASK2 & (dscp << 6));
+ }
+
+ if (aceIp.getSourcePortRange() != null) {
+ LOG.warn("L4 Header fields are not supported. Ignoring {}", aceIp.getSourcePortRange());
+ }
+
+ if (aceIp.getDestinationPortRange() != null) {
+ LOG.warn("L4 Header fields are not supported. Ignoring {}", aceIp.getDestinationPortRange());
+ }
+
+ if (ipVersion.getFlowLabel() != null) {
+ noMatch = false;
+ final int flowLabel = ipVersion.getFlowLabel().getValue().intValue();
+ // bits 12-31
+ request.match[IP_VERSION_OFFSET + 1] |= (byte) (0x0f & (flowLabel >> 16));
+ request.match[IP_VERSION_OFFSET + 2] = (byte) (0xff & (flowLabel >> 8));
+ request.match[IP_VERSION_OFFSET + 3] = (byte) (0xff & flowLabel);
+ }
+
+ if (ipVersion.getSourceIpv6Network() != null) {
+ noMatch = false;
+ final byte[] match = toMatchValue(ipVersion.getSourceIpv6Network());
+ System.arraycopy(match, 0, request.match, SRC_IP_OFFSET, IP6_LEN);
+ }
+
+ if (ipVersion.getDestinationIpv6Network() != null) {
+ noMatch = false;
+ final byte[] match = toMatchValue(ipVersion.getDestinationIpv6Network());
+ System.arraycopy(match, 0, request.match, DST_IP_OFFSET, IP6_LEN);
+ }
+
+ if (noMatch) {
+ throw new IllegalArgumentException(
+ String.format("Ace %s does not define packet field match values", aceIp.toString()));
+ }
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("ACE action={}, rule={} translated to session={}.", action, aceIp,
+ ReflectionToStringBuilder.toString(request));
+ }
+ return request;
+ }
+
+ @Override
+ protected void setClassifyTable(@Nonnull final InputAclSetInterface request, final int tableIndex) {
+ request.ip6TableIndex = tableIndex;
+ }
+}
diff --git a/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceWriter.java b/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceWriter.java
index be3889fb0..2d66619fd 100644
--- a/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceWriter.java
+++ b/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceWriter.java
@@ -24,7 +24,20 @@ import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
import org.openvpp.jvpp.VppBaseCallException;
import org.openvpp.jvpp.core.dto.InputAclSetInterface;
+/**
+ * Writer responsible for translation of ietf-acl model ACEs to VPP's classify tables and sessions.
+ */
interface AceWriter {
+
+ /**
+ * Translates list of ACEs to chain of classify tables. Each ACE is translated into one classify table with single
+ * classify session. Also initializes input_acl_set_interface request message DTO with first classify table of the
+ * chain that was created.
+ *
+ * @param id uniquely identifies ietf-acl container
+ * @param aces list of access control entries
+ * @param request input_acl_set_interface request DTO
+ */
void write(@Nonnull final InstanceIdentifier<?> id, @Nonnull final List<Ace> aces,
@Nonnull final InputAclSetInterface request) throws VppBaseCallException, WriteTimeoutException;
}
diff --git a/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/acl/AclWriter.java b/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AclWriter.java
index 6c3b4efed..875e9dbae 100644
--- a/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/acl/AclWriter.java
+++ b/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AclWriter.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package io.fd.honeycomb.translate.v3po.acl;
+package io.fd.honeycomb.translate.v3po.interfaces.acl;
import io.fd.honeycomb.translate.spi.write.ListWriterCustomizer;
import io.fd.honeycomb.translate.write.WriteContext;
diff --git a/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/IetfAClWriter.java b/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/IetfAClWriter.java
index 58741cfeb..f4ba56d45 100644
--- a/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/IetfAClWriter.java
+++ b/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/IetfAClWriter.java
@@ -20,7 +20,6 @@ import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
-import io.fd.honeycomb.translate.v3po.acl.AclWriter;
import io.fd.honeycomb.translate.v3po.util.TranslateUtils;
import io.fd.honeycomb.translate.v3po.util.WriteTimeoutException;
import io.fd.honeycomb.translate.write.WriteContext;
@@ -65,13 +64,33 @@ public final class IetfAClWriter {
this.jvpp = Preconditions.checkNotNull(futureJVppCore, "futureJVppCore should not be null");
aceWriters.put(AclType.ETH, new AceEthWriter(futureJVppCore));
aceWriters.put(AclType.IP4, new AceIp4Writer(futureJVppCore));
+ aceWriters.put(AclType.IP6, new AceIp6Writer(futureJVppCore));
+ }
+
+ private static Stream<Ace> aclToAceStream(@Nonnull final Acl assignedAcl,
+ @Nonnull final WriteContext writeContext) {
+ final String aclName = assignedAcl.getName();
+ final Class<? extends AclBase> aclType = assignedAcl.getType();
+
+ // ietf-acl updates are handled first, so we use writeContext.readAfter
+ final Optional<org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.Acl>
+ aclOptional = writeContext.readAfter(AclWriter.ACL_ID.child(
+ org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.Acl.class,
+ new AclKey(aclName, aclType)));
+ checkArgument(aclOptional.isPresent(), "Acl lists not configured");
+ final org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.Acl
+ acl = aclOptional.get();
+
+ final AccessListEntries accessListEntries = acl.getAccessListEntries();
+ checkArgument(accessListEntries != null, "access list entries not configured");
+
+ return accessListEntries.getAce().stream();
}
void deleteAcl(@Nonnull final InstanceIdentifier<?> id, final int swIfIndex)
throws WriteTimeoutException, WriteFailedException.DeleteFailedException {
final ClassifyTableByInterface request = new ClassifyTableByInterface();
request.swIfIndex = swIfIndex;
- jvpp.classifyTableByInterface(request);
try {
final CompletionStage<ClassifyTableByInterfaceReply> cs = jvpp.classifyTableByInterface(request);
@@ -138,26 +157,6 @@ public final class IetfAClWriter {
}
- private static Stream<Ace> aclToAceStream(@Nonnull final Acl assignedAcl,
- @Nonnull final WriteContext writeContext) {
- final String aclName = assignedAcl.getName();
- final Class<? extends AclBase> aclType = assignedAcl.getType();
-
- // ietf-acl updates are handled first, so we use writeContext.readAfter
- final Optional<org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.Acl>
- aclOptional = writeContext.readAfter(AclWriter.ACL_ID.child(
- org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.Acl.class,
- new AclKey(aclName, aclType)));
- checkArgument(aclOptional.isPresent(), "Acl lists not configured");
- final org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.Acl
- acl = aclOptional.get();
-
- final AccessListEntries accessListEntries = acl.getAccessListEntries();
- checkArgument(accessListEntries != null, "access list entries not configured");
-
- return accessListEntries.getAce().stream();
- }
-
private enum AclType {
ETH, IP4, IP6;
diff --git a/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/Readme.adoc b/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/Readme.adoc
new file mode 100644
index 000000000..e59f72abe
--- /dev/null
+++ b/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/Readme.adoc
@@ -0,0 +1,32 @@
+= VPP to IETF-ACL model translation
+
+Package provides VPP translation code for draft-ietf-netmod-acl-model-08.
+Access control lists are mapped to chains of classify tables, each with single classify session.
+
+== Available operations
+
+=== Configuration data
+Configuration data for the model is stored in Honeycomb. Corresponding classify tables and sessions
+are not created until control access list is assigned to an interface.
+
+Classify tables and sessions are removed from VPP when ACL assignment is deleted.
+
+ACLs can be shared among interfaces, but each time, new instance of classify table chain would be created in VPP.
+
+ACLs that are assigned to an interface have to be unassigned before update/removal.
+
+=== Operational state
+Operational read in terms of ietf-acl model is not supported (would require storing additional metadata in vpp).
+As a consequence, configuration data initialization based on operational state is not possible.
+
+To check how ietf-acl model was translated to classify tables/session, low-level vpp-classfier model can be used.
+
+== Restrictions
+
+VPP classfier works in form of offsets and masks of 16B units.
+The offset always starts at the beginning of L2 Ethernet header
+of input packet. Because IP header can have variable length,
+source/destination port matching (L4 features of ietf-acl model) is not possible.
+
+Current implementation also assumes constant Ethernet header size
+(802.1Q headers are not supported). \ No newline at end of file