diff options
author | 2016-08-26 12:37:45 +0200 | |
---|---|---|
committer | 2016-08-26 14:28:28 +0200 | |
commit | 1ee0526e2dc061ff73ba816eae5100f5c73397e0 (patch) | |
tree | a69f4eadd39d50cc316534559f81e6de776a4899 /v3po/v3po2vpp/src/main/java/io | |
parent | 31c95db0e5a2dfeb3bcee7f8c6b9a40b7807ad2c (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')
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 |