From 31c95db0e5a2dfeb3bcee7f8c6b9a40b7807ad2c Mon Sep 17 00:00:00 2001 From: Marek Gradzki Date: Thu, 25 Aug 2016 16:58:55 +0200 Subject: HONEYCOMB-139: ietf-acl translation layer. IP4 L3 ACL support Change-Id: I5e5af0d7609aa594790b35a387ec8701f1f6b6df Signed-off-by: Marek Gradzki --- .../v3po/interfaces/acl/AceIp4Writer.java | 203 +++++++++++++++++++++ .../v3po/interfaces/acl/IetfAClWriter.java | 1 + .../v3po/interfaces/acl/AceIp4WriterTest.java | 106 +++++++++++ 3 files changed, 310 insertions(+) create mode 100644 v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceIp4Writer.java create mode 100644 v3po/v3po2vpp/src/test/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceIp4WriterTest.java (limited to 'v3po') 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 new file mode 100644 index 000000000..ac110f98b --- /dev/null +++ b/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceIp4Writer.java @@ -0,0 +1,203 @@ +/* + * 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 static io.fd.honeycomb.translate.v3po.util.TranslateUtils.ipv4AddressNoZoneToArray; + +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; +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; + +class AceIp4Writer extends AbstractAceWriter { + + private static final Logger LOG = LoggerFactory.getLogger(AceIp4Writer.class); + private static final int TABLE_MASK_LENGTH = 48; // number of bytes + 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 int IP_VERSION_MASK = 0xf0; + private static final int DSCP_MASK = 0xfc; + + 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 + + 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.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)_ + if (aceIp.getProtocol() != null) { // Internet Protocol number + request.mask[IP_VERSION_OFFSET] = (byte) IP_VERSION_MASK; // first 4 bits + } + + if (aceIp.getDscp() != null) { + aceIsEmpty = false; + request.mask[DSCP_OFFSET] = (byte) DSCP_MASK; // first 6 bits + } + + 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.getSourceIpv4Network() != null) { + aceIsEmpty = false; + System.arraycopy(toByteMask(ipVersion.getSourceIpv4Network()), 0, request.mask, SRC_IP_OFFSET, 4); + } + + if (ipVersion.getDestinationIpv4Network() != null) { + aceIsEmpty = false; + System.arraycopy(toByteMask(ipVersion.getDestinationIpv4Network()), 0, request.mask, DST_IP_OFFSET, 4); + } + + if (aceIsEmpty) { + throw new IllegalArgumentException( + String.format("Ace %s does not define packet field matches", aceIp.toString())); + } + + if (LOG.isDebugEnabled()) { + LOG.debug("ACE action={}, rule={} translated to table={}.", action, aceIp, + ReflectionToStringBuilder.toString(request)); + } + return request; + } + + @Override + public ClassifyAddDelSession getClassifyAddDelSessionRequest(@Nonnull final PacketHandling action, + @Nonnull final AceIp aceIp, + @Nonnull 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 + + 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; + request.match[DSCP_OFFSET] = (byte) (DSCP_MASK & (aceIp.getDscp().getValue() << 2)); + } + + 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.getSourceIpv4Network() != null) { + noMatch = false; + System.arraycopy(toMatchValue(ipVersion.getSourceIpv4Network()), 0, request.match, SRC_IP_OFFSET, 4); + } + + if (ipVersion.getDestinationIpv4Network() != null) { + noMatch = false; + System.arraycopy(toMatchValue(ipVersion.getDestinationIpv4Network()), 0, request.match, DST_IP_OFFSET, 4); + } + + if (noMatch) { + throw new IllegalArgumentException( + String.format("Ace %s does not define neither source nor destination MAC address", 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.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/IetfAClWriter.java b/v3po/v3po2vpp/src/main/java/io/fd/honeycomb/translate/v3po/interfaces/acl/IetfAClWriter.java index 5eb4af5e1..58741cfeb 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 @@ -64,6 +64,7 @@ public final class IetfAClWriter { public IetfAClWriter(@Nonnull final FutureJVppCore futureJVppCore) { this.jvpp = Preconditions.checkNotNull(futureJVppCore, "futureJVppCore should not be null"); aceWriters.put(AclType.ETH, new AceEthWriter(futureJVppCore)); + aceWriters.put(AclType.IP4, new AceIp4Writer(futureJVppCore)); } void deleteAcl(@Nonnull final InstanceIdentifier id, final int swIfIndex) diff --git a/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceIp4WriterTest.java b/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceIp4WriterTest.java new file mode 100644 index 000000000..5be0ea069 --- /dev/null +++ b/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/translate/v3po/interfaces/acl/AceIp4WriterTest.java @@ -0,0 +1,106 @@ +/* + * 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 org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.mockito.MockitoAnnotations.initMocks; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +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.DenyBuilder; +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.AceIpBuilder; +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.AceIpv4Builder; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Dscp; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4Prefix; +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; + +public class AceIp4WriterTest { + @Mock + private FutureJVppCore jvpp; + private AceIp4Writer writer; + private PacketHandling action; + private AceIp aceIp; + + @Before + public void setUp() throws Exception { + initMocks(this); + writer = new AceIp4Writer(jvpp); + action = new DenyBuilder().setDeny(true).build(); + aceIp = new AceIpBuilder() + .setProtocol((short) 4) + .setDscp(new Dscp((short) 11)) + .setAceIpVersion(new AceIpv4Builder() + .setSourceIpv4Network(new Ipv4Prefix("1.2.3.4/32")) + .setDestinationIpv4Network(new Ipv4Prefix("1.2.4.5/24")) + .build()) + .build(); + } + + @Test + public void testGetClassifyAddDelTableRequest() throws Exception { + final int nextTableIndex = 42; + final ClassifyAddDelTable request = writer.getClassifyAddDelTableRequest(action, aceIp, nextTableIndex); + + assertEquals(1, request.isAdd); + assertEquals(-1, request.tableIndex); + assertEquals(1, request.nbuckets); + assertEquals(-1, request.missNextIndex); + assertEquals(nextTableIndex, request.nextTableIndex); + assertEquals(0, request.skipNVectors); + assertEquals(AceIp4Writer.MATCH_N_VECTORS, request.matchNVectors); + assertEquals(AceIp4Writer.TABLE_MEM_SIZE, request.memorySize); + + byte[] expectedMask = new byte[] { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xf0, (byte) 0xfc, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, + -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + assertArrayEquals(expectedMask, request.mask); + } + + @Test + public void testGetClassifyAddDelSessionRequest() throws Exception { + final int tableIndex = 123; + final ClassifyAddDelSession request = writer.getClassifyAddDelSessionRequest(action, aceIp, tableIndex); + + assertEquals(1, request.isAdd); + assertEquals(tableIndex, request.tableIndex); + assertEquals(0, request.hitNextIndex); + + byte[] expectedMatch = new byte[] { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0x40, (byte) 0x2c, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 1, 2, + 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + assertArrayEquals(expectedMatch, request.match); + } + + @Test + public void testSetClassifyTable() throws Exception { + final int tableIndex = 321; + final InputAclSetInterface request = new InputAclSetInterface(); + writer.setClassifyTable(request, tableIndex); + assertEquals(tableIndex, request.ip4TableIndex); + } +} \ No newline at end of file -- cgit 1.2.3-korg