blob: 71e45b1d5ecb8ff5d885fdb4ff8286f3c3d9cbab [file] [log] [blame]
/*
* Copyright 2017-present Open Networking Foundation
*
* 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 org.onosproject.pipelines.fabric.impl.behaviour.pipeliner;
import com.google.common.collect.Lists;
import org.junit.Before;
import org.junit.Test;
import org.onlab.packet.Ethernet;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
import org.onlab.util.ImmutableByteSequence;
import org.onosproject.net.PortNumber;
import org.onosproject.net.flow.DefaultFlowRule;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.TableId;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.flow.criteria.Criteria;
import org.onosproject.net.flow.criteria.PiCriterion;
import org.onosproject.net.flowobjective.DefaultFilteringObjective;
import org.onosproject.net.flowobjective.FilteringObjective;
import org.onosproject.net.flowobjective.ObjectiveError;
import org.onosproject.net.pi.runtime.PiAction;
import org.onosproject.net.pi.runtime.PiActionParam;
import org.onosproject.pipelines.fabric.FabricConstants;
import java.util.Collection;
import java.util.List;
import static org.junit.Assert.assertEquals;
/**
* Test cases for fabric.p4 pipeline filtering control block.
*/
public class FilteringObjectiveTranslatorTest extends BaseObjectiveTranslatorTest {
public static final byte[] ONE = {1};
public static final byte[] ZERO = {0};
public static final short EXACT_MATCH_ETH_TYPE = (short) 0xFFFF;
private FilteringObjectiveTranslator translator;
@Before
public void setup() {
super.doSetup();
translator = new FilteringObjectiveTranslator(DEVICE_ID, capabilitiesHashed);
}
/**
* Creates one rule for ingress_port_vlan table and 3 rules for
* fwd_classifier table (IPv4, IPv6 and MPLS unicast) when the condition is
* VLAN + MAC.
*/
@Test
public void testRouterMacAndVlanFilter() throws FabricPipelinerException {
FilteringObjective filteringObjective = buildFilteringObjective(ROUTER_MAC);
ObjectiveTranslation actualTranslation = translator.translate(filteringObjective);
Collection<FlowRule> expectedFlowRules = Lists.newArrayList();
// in port vlan flow rule
expectedFlowRules.add(buildExpectedVlanInPortRule(
PORT_1,
VlanId.NONE,
VlanId.NONE,
VLAN_100,
FabricConstants.FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN));
// forwarding classifier ipv4
expectedFlowRules.addAll(buildExpectedFwdClassifierRule(
PORT_1,
ROUTER_MAC,
null,
Ethernet.TYPE_IPV4,
FilteringObjectiveTranslator.FWD_IPV4_ROUTING));
// forwarding classifier ipv6
expectedFlowRules.addAll(buildExpectedFwdClassifierRule(
PORT_1,
ROUTER_MAC,
null,
Ethernet.TYPE_IPV6,
FilteringObjectiveTranslator.FWD_IPV6_ROUTING));
// forwarding classifier mpls
expectedFlowRules.addAll(buildExpectedFwdClassifierRule(
PORT_1,
ROUTER_MAC,
null,
Ethernet.MPLS_UNICAST,
FilteringObjectiveTranslator.FWD_MPLS));
// ObjectiveTranslation.Builder expectedTranslationBuilder = ObjectiveTranslation.builder()
// .addFlowRule(inportFlowRuleExpected);
// for (FlowRule flowRule : classifierV4FlowRuleExpected) {
// expectedTranslationBuilder.addFlowRule(flowRule);
// }
// for (FlowRule flowRule : classifierV6FlowRuleExpected) {
// expectedTranslationBuilder.addFlowRule(flowRule);
// }
// for (FlowRule flowRule : classifierMplsFlowRuleExpected) {
// expectedTranslationBuilder.addFlowRule(flowRule);
// }
ObjectiveTranslation expectedTranslation = buildExpectedTranslation(expectedFlowRules);
assertEquals(expectedTranslation, actualTranslation);
}
/**
* Creates one rule for ingress_port_vlan table and one rule for
* fwd_classifier table (IPv4 multicast) when the condition is ipv4
* multicast mac address.
*/
@Test
public void testIpv4MulticastFwdClass() throws FabricPipelinerException {
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.pushVlan()
.setVlanId(VLAN_100)
.build();
FilteringObjective filteringObjective = DefaultFilteringObjective.builder()
.permit()
.withPriority(PRIORITY)
.withKey(Criteria.matchInPort(PORT_1))
.addCondition(Criteria.matchEthDstMasked(MacAddress.IPV4_MULTICAST, MacAddress.IPV4_MULTICAST_MASK))
.addCondition(Criteria.matchVlanId(VlanId.NONE))
.withMeta(treatment)
.fromApp(APP_ID)
.makePermanent()
.add();
ObjectiveTranslation actualTranslation = translator.translate(filteringObjective);
List<FlowRule> expectedFlowRules = Lists.newArrayList();
// in port vlan flow rule
expectedFlowRules.add(buildExpectedVlanInPortRule(
PORT_1,
VlanId.NONE,
VlanId.NONE,
VLAN_100,
FabricConstants.FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN));
// forwarding classifier
expectedFlowRules.addAll(buildExpectedFwdClassifierRule(
PORT_1,
MacAddress.IPV4_MULTICAST,
MacAddress.IPV4_MULTICAST_MASK,
Ethernet.TYPE_IPV4,
FilteringObjectiveTranslator.FWD_IPV4_ROUTING));
ObjectiveTranslation expectedTranslation = buildExpectedTranslation(expectedFlowRules);
assertEquals(expectedTranslation, actualTranslation);
}
/**
* Creates one rule for ingress_port_vlan table and one rule for
* fwd_classifier table (IPv6 multicast) when the condition is ipv6
* multicast mac address.
*/
@Test
public void testIpv6MulticastFwdClass() throws FabricPipelinerException {
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.pushVlan()
.setVlanId(VLAN_100)
.build();
FilteringObjective filteringObjective = DefaultFilteringObjective.builder()
.permit()
.withPriority(PRIORITY)
.withKey(Criteria.matchInPort(PORT_1))
.addCondition(Criteria.matchEthDstMasked(MacAddress.IPV6_MULTICAST, MacAddress.IPV6_MULTICAST_MASK))
.addCondition(Criteria.matchVlanId(VlanId.NONE))
.withMeta(treatment)
.fromApp(APP_ID)
.makePermanent()
.add();
ObjectiveTranslation actualTranslation = translator.translate(filteringObjective);
Collection<FlowRule> flowRules = Lists.newArrayList();
// in port vlan flow rule
flowRules.add(buildExpectedVlanInPortRule(
PORT_1,
VlanId.NONE,
VlanId.NONE,
VLAN_100,
FabricConstants.FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN));
flowRules.addAll(buildExpectedFwdClassifierRule(
PORT_1,
MacAddress.IPV6_MULTICAST,
MacAddress.IPV6_MULTICAST_MASK,
Ethernet.TYPE_IPV6,
FilteringObjectiveTranslator.FWD_IPV6_ROUTING));
ObjectiveTranslation expectedTranslation = buildExpectedTranslation(flowRules);
assertEquals(expectedTranslation, actualTranslation);
}
/**
* Creates only one rule for ingress_port_vlan table if there is no
* condition of destination mac address. The packet will be handled by
* bridging table by default.
*/
@Test
public void testFwdBridging() throws Exception {
FilteringObjective filteringObjective = buildFilteringObjective(null);
ObjectiveTranslation actualTranslation = translator.translate(filteringObjective);
// in port vlan flow rule
FlowRule flowRuleExpected = buildExpectedVlanInPortRule(
PORT_1,
VlanId.NONE,
VlanId.NONE,
VLAN_100,
FabricConstants.FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN);
// No rules in forwarding classifier, will do default action: set fwd type to bridging
ObjectiveTranslation expectedTranslation = ObjectiveTranslation.builder()
.addFlowRule(flowRuleExpected)
.build();
assertEquals(expectedTranslation, actualTranslation);
}
/**
* Test DENY objective.
*/
@Test
public void testDenyObjective() throws FabricPipelinerException {
FilteringObjective filteringObjective = DefaultFilteringObjective.builder()
.deny()
.withKey(Criteria.matchInPort(PORT_1))
.addCondition(Criteria.matchVlanId(VlanId.NONE))
.fromApp(APP_ID)
.makePermanent()
.withPriority(PRIORITY)
.add();
ObjectiveTranslation actualTranslation = translator.translate(filteringObjective);
TrafficSelector.Builder selector = DefaultTrafficSelector.builder()
.matchInPort(PORT_1)
.matchPi(buildPiCriterionVlan(null, null));
PiAction piAction = PiAction.builder()
.withId(FabricConstants.FABRIC_INGRESS_FILTERING_DENY)
.build();
FlowRule expectedFlowRule = DefaultFlowRule.builder()
.withPriority(PRIORITY)
.withSelector(selector.build())
.withTreatment(DefaultTrafficTreatment.builder()
.piTableAction(piAction).build())
.fromApp(APP_ID)
.forDevice(DEVICE_ID)
.makePermanent()
.forTable(FabricConstants.FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN)
.build();
ObjectiveTranslation expectedTranslation = ObjectiveTranslation.builder()
.addFlowRule(expectedFlowRule)
.build();
assertEquals(expectedTranslation, actualTranslation);
}
/**
* Test double VLAN pop filtering objective Creates one rule for
* ingress_port_vlan table and 3 rules for fwd_classifier table (IPv4, IPv6
* and MPLS unicast) when the condition is MAC + VLAN + INNER_VLAN.
*/
@Test
public void testPopVlan() throws FabricPipelinerException {
FilteringObjective filteringObjective = DefaultFilteringObjective.builder()
.withKey(Criteria.matchInPort(PORT_1))
.addCondition(Criteria.matchEthDst(ROUTER_MAC))
.addCondition(Criteria.matchVlanId(VLAN_100))
.addCondition(Criteria.matchInnerVlanId(VLAN_200))
.withPriority(PRIORITY)
.fromApp(APP_ID)
.withMeta(DefaultTrafficTreatment.builder()
.popVlan()
.build())
.permit()
.add();
ObjectiveTranslation actualTranslation = translator.translate(filteringObjective);
Collection<FlowRule> expectedFlowRules = Lists.newArrayList();
// Ingress port vlan rule
expectedFlowRules.add(buildExpectedVlanInPortRule(
PORT_1, VLAN_100, VLAN_200, VlanId.NONE,
FabricConstants.FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN));
// Forwarding classifier rules (ipv6, ipv4, mpls)
expectedFlowRules.addAll(buildExpectedFwdClassifierRule(
PORT_1, ROUTER_MAC, null, Ethernet.TYPE_IPV4,
FilteringObjectiveTranslator.FWD_IPV4_ROUTING));
expectedFlowRules.addAll(buildExpectedFwdClassifierRule(
PORT_1, ROUTER_MAC, null, Ethernet.TYPE_IPV6,
FilteringObjectiveTranslator.FWD_IPV6_ROUTING));
expectedFlowRules.addAll(buildExpectedFwdClassifierRule(
PORT_1, ROUTER_MAC, null, Ethernet.MPLS_UNICAST,
FilteringObjectiveTranslator.FWD_MPLS));
ObjectiveTranslation expectedTranslation = buildExpectedTranslation(expectedFlowRules);
assertEquals(expectedTranslation, actualTranslation);
}
/**
* Incorrect filtering key or filtering conditions test.
*/
@Test
public void badParamTest() {
// Filtering objective should contains filtering key
FilteringObjective filteringObjective = DefaultFilteringObjective.builder()
.permit()
.addCondition(Criteria.matchVlanId(VLAN_100))
.fromApp(APP_ID)
.makePermanent()
.add();
ObjectiveTranslation result1 = translator.translate(filteringObjective);
assertError(ObjectiveError.BADPARAMS, result1);
// Filtering objective should use in_port as key
filteringObjective = DefaultFilteringObjective.builder()
.permit()
.withKey(Criteria.matchEthDst(ROUTER_MAC))
.addCondition(Criteria.matchVlanId(VLAN_100))
.withMeta(DefaultTrafficTreatment.emptyTreatment())
.fromApp(APP_ID)
.makePermanent()
.add();
ObjectiveTranslation result2 = translator.translate(filteringObjective);
assertError(ObjectiveError.BADPARAMS, result2);
}
/**
* Test port update scenarios for filtering objective. Creates only one rule for
* ingress_port_vlan table.
*/
@Test
public void testPortUpdate() throws FabricPipelinerException {
// Tagged port scenario
FilteringObjective filteringObjective = DefaultFilteringObjective.builder()
.withKey(Criteria.matchInPort(PORT_1))
.addCondition(Criteria.matchEthDst(ROUTER_MAC))
.addCondition(Criteria.matchVlanId(VLAN_100))
.withPriority(PRIORITY)
.fromApp(APP_ID)
.withMeta(DefaultTrafficTreatment.builder()
.writeMetadata(2, 0xffffffffffffffffL)
.build())
.permit()
.add();
ObjectiveTranslation actualTranslation = translator.translate(filteringObjective);
Collection<FlowRule> expectedFlowRules = Lists.newArrayList();
// Ingress port vlan rule
expectedFlowRules.add(buildExpectedVlanInPortRule(
PORT_1, VLAN_100, null, VlanId.NONE,
FabricConstants.FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN));
ObjectiveTranslation expectedTranslation = buildExpectedTranslation(expectedFlowRules);
assertEquals(expectedTranslation, actualTranslation);
// Untagged port scenario
filteringObjective = DefaultFilteringObjective.builder()
.withKey(Criteria.matchInPort(PORT_1))
.addCondition(Criteria.matchEthDst(ROUTER_MAC))
.addCondition(Criteria.matchVlanId(VlanId.NONE))
.withPriority(PRIORITY)
.fromApp(APP_ID)
.withMeta(DefaultTrafficTreatment.builder()
.pushVlan()
.setVlanId(VLAN_200)
.writeMetadata(2, 0xffffffffffffffffL)
.build())
.permit()
.add();
actualTranslation = translator.translate(filteringObjective);
expectedFlowRules = Lists.newArrayList();
// Ingress port vlan rule
expectedFlowRules.add(buildExpectedVlanInPortRule(
PORT_1, VlanId.NONE, null, VLAN_200,
FabricConstants.FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN));
expectedTranslation = buildExpectedTranslation(expectedFlowRules);
assertEquals(expectedTranslation, actualTranslation);
}
/**
* Test no more ports scenario for filtering objective.
*/
@Test
public void testNoMorePorts() throws FabricPipelinerException {
// Tagged port scenario
FilteringObjective filteringObjective = DefaultFilteringObjective.builder()
.withKey(Criteria.matchInPort(PORT_1))
.addCondition(Criteria.matchEthDst(ROUTER_MAC))
.addCondition(Criteria.matchVlanId(VLAN_100))
.withPriority(PRIORITY)
.fromApp(APP_ID)
.withMeta(DefaultTrafficTreatment.builder().wipeDeferred().build())
.permit()
.add();
ObjectiveTranslation actualTranslation = translator.translate(filteringObjective);
Collection<FlowRule> expectedFlowRules = Lists.newArrayList();
// Ingress port vlan rule
expectedFlowRules.add(buildExpectedVlanInPortRule(
PORT_1, VLAN_100, null, VlanId.NONE,
FabricConstants.FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN));
// forwarding classifier ipv4
expectedFlowRules.addAll(buildExpectedFwdClassifierRule(
PORT_1,
ROUTER_MAC,
null,
Ethernet.TYPE_IPV4,
FilteringObjectiveTranslator.FWD_IPV4_ROUTING));
// forwarding classifier ipv6
expectedFlowRules.addAll(buildExpectedFwdClassifierRule(
PORT_1,
ROUTER_MAC,
null,
Ethernet.TYPE_IPV6,
FilteringObjectiveTranslator.FWD_IPV6_ROUTING));
// forwarding classifier mpls
expectedFlowRules.addAll(buildExpectedFwdClassifierRule(
PORT_1,
ROUTER_MAC,
null,
Ethernet.MPLS_UNICAST,
FilteringObjectiveTranslator.FWD_MPLS));
ObjectiveTranslation expectedTranslation = buildExpectedTranslation(expectedFlowRules);
assertEquals(expectedTranslation, actualTranslation);
}
/* Utilities */
private void assertError(ObjectiveError error, ObjectiveTranslation actualTranslation) {
ObjectiveTranslation expectedTranslation = ObjectiveTranslation.ofError(error);
assertEquals(expectedTranslation, actualTranslation);
}
private FilteringObjective buildFilteringObjective(MacAddress dstMac) {
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.pushVlan()
.setVlanId(VLAN_100)
.build();
DefaultFilteringObjective.Builder builder = DefaultFilteringObjective.builder()
.permit()
.withPriority(PRIORITY)
.withKey(Criteria.matchInPort(PORT_1));
if (dstMac != null) {
builder.addCondition(Criteria.matchEthDst(dstMac));
}
builder.addCondition(Criteria.matchVlanId(VlanId.NONE))
.withMeta(treatment)
.fromApp(APP_ID)
.makePermanent();
return builder.add();
}
private FlowRule buildExpectedVlanInPortRule(PortNumber inPort,
VlanId vlanId,
VlanId innerVlanId,
VlanId internalVlan,
TableId tableId) {
TrafficSelector.Builder selector = DefaultTrafficSelector.builder()
.matchInPort(inPort);
PiAction piAction;
selector.matchPi(buildPiCriterionVlan(vlanId, innerVlanId));
if (!vlanValid(vlanId)) {
piAction = PiAction.builder()
.withId(FabricConstants.FABRIC_INGRESS_FILTERING_PERMIT_WITH_INTERNAL_VLAN)
.withParameter(new PiActionParam(
FabricConstants.VLAN_ID, internalVlan.toShort()))
.build();
} else {
selector.matchVlanId(vlanId);
if (vlanValid(innerVlanId)) {
selector.matchInnerVlanId(innerVlanId);
}
piAction = PiAction.builder()
.withId(FabricConstants.FABRIC_INGRESS_FILTERING_PERMIT)
.build();
}
return DefaultFlowRule.builder()
.withPriority(PRIORITY)
.withSelector(selector.build())
.withTreatment(DefaultTrafficTreatment.builder()
.piTableAction(piAction).build())
.fromApp(APP_ID)
.forDevice(DEVICE_ID)
.makePermanent()
.forTable(tableId)
.build();
}
private boolean vlanValid(VlanId vlanId) {
return (vlanId != null && !vlanId.equals(VlanId.NONE));
}
private PiCriterion buildPiCriterionVlan(VlanId vlanId,
VlanId innerVlanId) {
PiCriterion.Builder piCriterionBuilder = PiCriterion.builder()
.matchExact(FabricConstants.HDR_VLAN_IS_VALID,
vlanValid(vlanId) ? ONE : ZERO);
return piCriterionBuilder.build();
}
private Collection<FlowRule> buildExpectedFwdClassifierRule(PortNumber inPort,
MacAddress dstMac,
MacAddress dstMacMask,
short ethType,
byte fwdClass) {
PiActionParam classParam = new PiActionParam(FabricConstants.FWD_TYPE,
ImmutableByteSequence.copyFrom(fwdClass));
PiAction fwdClassifierAction = PiAction.builder()
.withId(FabricConstants.FABRIC_INGRESS_FILTERING_SET_FORWARDING_TYPE)
.withParameter(classParam)
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.piTableAction(fwdClassifierAction)
.build();
TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder()
.matchInPort(inPort);
if (dstMacMask != null) {
sbuilder.matchEthDstMasked(dstMac, dstMacMask);
} else {
sbuilder.matchEthDstMasked(dstMac, MacAddress.EXACT_MASK);
}
// Special case for MPLS UNICAST forwarding, need to build 2 rules for MPLS+IPv4 and MPLS+IPv6
if (ethType == Ethernet.MPLS_UNICAST) {
return buildExpectedFwdClassifierRulesMpls(fwdClassifierAction, treatment, sbuilder);
}
sbuilder.matchPi(PiCriterion.builder()
.matchExact(FabricConstants.HDR_IP_ETH_TYPE, ethType)
.build());
TrafficSelector selector = sbuilder.build();
return List.of(DefaultFlowRule.builder()
.withPriority(PRIORITY)
.withSelector(selector)
.withTreatment(treatment)
.fromApp(APP_ID)
.forDevice(DEVICE_ID)
.makePermanent()
.forTable(FabricConstants.FABRIC_INGRESS_FILTERING_FWD_CLASSIFIER)
.build());
}
private Collection<FlowRule> buildExpectedFwdClassifierRulesMpls(PiAction fwdClassifierAction,
TrafficTreatment treatment,
TrafficSelector.Builder selectorBuilder) {
Collection<FlowRule> flowRules = Lists.newArrayList();
TrafficSelector selectorIpv4 = selectorBuilder
.add(PiCriterion.builder()
.matchTernary(FabricConstants.HDR_ETH_TYPE, Ethernet.MPLS_UNICAST, EXACT_MATCH_ETH_TYPE)
.matchExact(FabricConstants.HDR_IP_ETH_TYPE, Ethernet.TYPE_IPV4)
.build())
.build();
TrafficSelector selectorIpv6 = selectorBuilder
.add(PiCriterion.builder()
.matchTernary(FabricConstants.HDR_ETH_TYPE, Ethernet.MPLS_UNICAST, EXACT_MATCH_ETH_TYPE)
.matchExact(FabricConstants.HDR_IP_ETH_TYPE, Ethernet.TYPE_IPV6)
.build())
.build();
flowRules.add(DefaultFlowRule.builder()
.withPriority(PRIORITY + 1)
.withSelector(selectorIpv4)
.withTreatment(treatment)
.fromApp(APP_ID)
.forDevice(DEVICE_ID)
.makePermanent()
.forTable(FabricConstants.FABRIC_INGRESS_FILTERING_FWD_CLASSIFIER)
.build());
flowRules.add(DefaultFlowRule.builder()
.withPriority(PRIORITY + 1)
.withSelector(selectorIpv6)
.withTreatment(treatment)
.fromApp(APP_ID)
.forDevice(DEVICE_ID)
.makePermanent()
.forTable(FabricConstants.FABRIC_INGRESS_FILTERING_FWD_CLASSIFIER)
.build());
return flowRules;
}
private ObjectiveTranslation buildExpectedTranslation(Collection<FlowRule> flowRules)
throws FabricPipelinerException {
ObjectiveTranslation.Builder expectedTranslationBuilder = ObjectiveTranslation.builder();
for (FlowRule flowRule : flowRules) {
expectedTranslationBuilder.addFlowRule(flowRule);
}
return expectedTranslationBuilder.build();
}
}