/* * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.groupbasedpolicy.renderer.ofoverlay.mapper.external; import static org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.FlowUtils.addNxRegMatch; import static org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.FlowUtils.applyActionIns; import static org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.FlowUtils.instructions; import static org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.FlowUtils.nxOutputRegAction; import java.math.BigInteger; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.apache.commons.net.util.SubnetUtils; import org.apache.commons.net.util.SubnetUtils.SubnetInfo; import org.opendaylight.groupbasedpolicy.dto.IndexedTenant; import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.OfContext; import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.OfWriter; import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.FlowIdUtils; import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.FlowTable; import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.FlowUtils; import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.FlowUtils.RegMatch; import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.OrdinalFactory; import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.OrdinalFactory.EndpointFwdCtxOrdinals; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sff.ovs.rev140701.Node; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4Address; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4Prefix; import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.ActionBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowId; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.Flow; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.Match; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.MatchBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.common.rev140421.L2FloodDomainId; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.common.rev140421.SubnetId; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.endpoint.rev140421.endpoints.Endpoint; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.endpoint.rev140421.endpoints.EndpointL3; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.l3endpoint.rev151217.NatAddress; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.ofoverlay.rev140528.Segmentation; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.tenants.tenant.forwarding.context.L2FloodDomain; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.tenants.tenant.forwarding.context.Subnet; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId; import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.match.layer._3.match.Ipv4MatchBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.openflowjava.nx.match.rev140421.NxmNxReg5; import org.opendaylight.yang.gen.v1.urn.opendaylight.openflowjava.nx.match.rev140421.NxmNxReg7; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Preconditions; /** * <h1>Manage the table that assigns source endpoint group, bridge domain, and * router domain to registers to be used by other tables</h1> * * <i>Push VLAN flow</i><br> * Priority = 222<br> * see {@link #buildPushVlanFlow(Ipv4Address, Integer, int)}<br> * Matches:<br> * - ethernet type<br> * - L3 match<br> * - VLAN match<br> * Actions:<br> * - set_ethertype (VLAN)<br> * - output:port (Reg7) {@link NxmNxReg7}<br> * <p> * <i>Push VLAN flow - external domain</i><br> * Priority = 220<br> * see {@link #buildPushVlanFlow(NodeId, int, Integer, int)}<br> * Matches:<br> * - ethernet type<br> * - Reg7 {@link NxmNxReg7}<br> * - Reg5 {@link NxmNxReg5}<br> * - VLAN match<br> * Actions:<br> * - set_ethertype (VLAN)<br> * - output:port (Reg7) {@link NxmNxReg7}<br> * <p> * <i>Default flow</i><br> * Priority = 100<br> * Matches:<br> * - none<br> * Actions:<br> * - output:port (Reg7) {@link NxmNxReg7}<br> */ public class ExternalMapper extends FlowTable { protected static final Logger LOG = LoggerFactory.getLogger(ExternalMapper.class); public static short TABLE_ID; public ExternalMapper(OfContext ctx, short tableId) { super(ctx); TABLE_ID = tableId; } @Override public short getTableId() { return TABLE_ID; } @Override public void sync(Endpoint endpoint, OfWriter ofWriter) throws Exception { NodeId nodeId = ctx.getEndpointManager().getEndpointNodeId(endpoint); // Default drop all ofWriter.writeFlow(nodeId, TABLE_ID, dropFlow(Integer.valueOf(1), null, TABLE_ID)); /* * Default Egress flow. Other methods may write to this table to augment egress * functionality, such as bypassing/utilising the NAT table, or ServiceFunctionChaining */ ofWriter.writeFlow(nodeId, TABLE_ID, defaultFlow()); /* * When source address was translated to NAT address, it has to be figured out * whether or not the traffic should be tagged since external interfaces are * considered as trunk ports. * * Subnet to which NAT address belong have to be found so that * corresponding L2FloodDomain can be resolved. * * If the L2FloodDomain contains Segmentation augmentation, a Flow is generated * for applying VLAN tag against traffic with NAT IP as source address. * * Note: NetworkContainment of NAT EndpointL3 point's to subnet of original address. * This is why subnet of NAT IP is resolved here. */ Collection<EndpointL3> natL3Endpoints = ctx.getEndpointManager().getL3EndpointsWithNat(); for (EndpointL3 natL3Ep : natL3Endpoints) { if (endpoint.getL2Context().equals(natL3Ep.getL2Context()) && endpoint.getMacAddress().equals(natL3Ep.getMacAddress())) { IpAddress natIpAddress = natL3Ep.getAugmentation(NatAddress.class).getNatAddress(); Subnet natIpSubnet = resolveSubnetForIpv4Address(ctx.getTenant(natL3Ep.getTenant()), Preconditions.checkNotNull(natIpAddress.getIpv4Address(), "Endpoint {} does not have IPv4 address in NatAddress augmentation.", natL3Ep.getKey())); if (natIpSubnet != null && natIpSubnet.getParent() != null) { L2FloodDomain natEpl2Fd = ctx.getTenant(natL3Ep.getTenant()).resolveL2FloodDomain(new L2FloodDomainId(natIpSubnet.getParent().getValue())); if (natEpl2Fd != null && natEpl2Fd.getAugmentation(Segmentation.class) != null) { Integer vlanId = natEpl2Fd.getAugmentation(Segmentation.class).getSegmentationId(); ofWriter.writeFlow(nodeId, TABLE_ID, buildPushVlanFlow(natIpAddress.getIpv4Address(), vlanId, 222)); } } } } /* * Tagging should be also considered when traffic is routed or switched to external domain. * * If the L2FloodDomain of Endpoint contains Segmentation augmentation, a Flow * for applying VLAN tag is generated. The flow matches against REG5 holding * the L2FloodDomain and REG7 holding value of an external interface. */ Subnet sub = ctx.getTenant(endpoint.getTenant()).resolveSubnet(new SubnetId(endpoint.getNetworkContainment())); L2FloodDomain l2Fd = ctx.getTenant(endpoint.getTenant()).resolveL2FloodDomain(new L2FloodDomainId(sub.getParent().getValue())); if (l2Fd == null) { return; } Segmentation segmentation = l2Fd.getAugmentation(Segmentation.class); EndpointFwdCtxOrdinals endpointOrdinals = OrdinalFactory.getEndpointFwdCtxOrdinals(ctx, endpoint); if (segmentation == null || endpointOrdinals == null) { return; } Integer vlanId = segmentation.getSegmentationId(); for (Flow flow : buildPushVlanFlow(nodeId, endpointOrdinals.getFdId(), vlanId, 220)) { ofWriter.writeFlow(nodeId, TABLE_ID, flow); } } /** * Generates a {@link Flow} for tagging VLAN traffic based on given arguments. * * @param ipv4Address source IPv4 address * @param vlanId ID of VLAN tag to apply * @param priority priority of the flow in the table * @return {@link Flow} matching IPv4 source address, IPv4 ether-type and VLAN not set. */ private Flow buildPushVlanFlow(Ipv4Address ipv4Address, Integer vlanId, int priority) { // It is not needed here to match against external interfaces because // we only use NAT when going to external networks. Ipv4Prefix natIp = new Ipv4Prefix(ipv4Address.getValue() + "/32"); Match match = new MatchBuilder() .setEthernetMatch(FlowUtils.ethernetMatch(null, null, Long.valueOf(FlowUtils.IPv4))) .setLayer3Match(new Ipv4MatchBuilder().setIpv4Source(natIp).build()) .setVlanMatch(FlowUtils.vlanMatch(0, false)) .build(); List<ActionBuilder> pushVlanActions = new ArrayList<>(); pushVlanActions.addAll(FlowUtils.pushVlanActions(vlanId)); pushVlanActions.add(new ActionBuilder().setOrder(0).setAction(nxOutputRegAction(NxmNxReg7.class))); FlowId flowid = FlowIdUtils.newFlowId(TABLE_ID, "external_nat_push_vlan", match); return base().setPriority(priority) .setId(flowid) .setMatch(match) .setInstructions(FlowUtils.instructions(applyActionIns(pushVlanActions))) .build(); } public static Subnet resolveSubnetForIpv4Address(IndexedTenant t, Ipv4Address ipv4Addr) { Preconditions.checkNotNull(ipv4Addr); if (t == null || t.getTenant() == null || t.getTenant().getForwardingContext() == null) { return null; } List<Subnet> subnets = t.getTenant().getForwardingContext().getSubnet(); if (subnets != null) { for (Subnet subnet : subnets) { if (belongsToSubnet(ipv4Addr, subnet.getIpPrefix().getIpv4Prefix())) { return subnet; } } } return null; } private static boolean belongsToSubnet(Ipv4Address ipv4Address, Ipv4Prefix subnetPrefix) { SubnetUtils su = new SubnetUtils(subnetPrefix.getValue()); SubnetInfo si = su.getInfo(); return si.isInRange(ipv4Address.getValue()); } /** * Generates a {@link Flow} for tagging VLAN traffic based on given arguments. * * @param nodeId of {@link Node} from which external interfaces are resolved * @param fdId {@link L2FloodDomain} ordinal to match against * @param vlanId applied to the traffic * @param priority of flow in the table * @return {@link List} of {@link Flow} matching {@link L2FloodDomain} in REG5, * external interfaces of {@link Node} in REG7 and VLAN not set. */ private List<Flow> buildPushVlanFlow(NodeId nodeId, int fdId, Integer vlanId, int priority) { List<Flow> flows = new ArrayList<>(); for (Long portNum : ctx.getSwitchManager().getExternalPortNumbers(nodeId)) { MatchBuilder mb = new MatchBuilder().setVlanMatch(FlowUtils.vlanMatch(0, false)); addNxRegMatch(mb, RegMatch.of(NxmNxReg7.class, BigInteger.valueOf(portNum).longValue()), RegMatch.of(NxmNxReg5.class, Long.valueOf(fdId))); Match match = mb.build(); List<ActionBuilder> pushVlanActions = new ArrayList<>(); pushVlanActions.addAll(FlowUtils.pushVlanActions(vlanId)); pushVlanActions.add(new ActionBuilder().setOrder(0).setAction(nxOutputRegAction(NxmNxReg7.class))); FlowId flowid = FlowIdUtils.newFlowId(TABLE_ID, "external_push_vlan", match); flows.add(base().setPriority(priority) .setId(flowid) .setMatch(match) .setInstructions(FlowUtils.instructions(applyActionIns(pushVlanActions))) .build()); } return flows; } private Flow defaultFlow() { FlowId flowid = FlowIdUtils.newFlowId(TABLE_ID, "defaultExternalFlow", null); Flow flow = base().setPriority(100) .setId(flowid) .setInstructions(instructions(applyActionIns(nxOutputRegAction(NxmNxReg7.class)))) .build(); return flow; } }