/* * Copyright 2015 Open Networking Laboratory * * 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.segmentrouting; import org.onlab.packet.EthType; import org.onlab.packet.Ethernet; import org.onlab.packet.Ip4Address; import org.onlab.packet.Ip4Prefix; import org.onlab.packet.IpPrefix; import org.onlab.packet.MacAddress; import org.onlab.packet.MplsLabel; import org.onlab.packet.VlanId; import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException; import org.onosproject.segmentrouting.config.DeviceConfiguration; import org.onosproject.segmentrouting.grouphandler.NeighborSet; import org.onosproject.net.DeviceId; import org.onosproject.net.Port; import org.onosproject.net.PortNumber; import org.onosproject.net.flow.DefaultTrafficSelector; import org.onosproject.net.flow.DefaultTrafficTreatment; import org.onosproject.net.flow.TrafficSelector; import org.onosproject.net.flow.TrafficTreatment; import org.onosproject.net.flow.criteria.Criteria; import org.onosproject.net.flowobjective.DefaultFilteringObjective; import org.onosproject.net.flowobjective.DefaultForwardingObjective; import org.onosproject.net.flowobjective.FilteringObjective; import org.onosproject.net.flowobjective.ForwardingObjective; import org.onosproject.net.flowobjective.Objective; import org.onosproject.net.flowobjective.ObjectiveError; import org.onosproject.net.flowobjective.ForwardingObjective.Builder; import org.onosproject.net.flowobjective.ForwardingObjective.Flag; import org.onosproject.net.flowobjective.ObjectiveContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; import static com.google.common.base.Preconditions.checkNotNull; public class RoutingRulePopulator { private static final Logger log = LoggerFactory .getLogger(RoutingRulePopulator.class); private AtomicLong rulePopulationCounter; private SegmentRoutingManager srManager; private DeviceConfiguration config; private static final int HIGHEST_PRIORITY = 0xffff; private static final long OFPP_MAX = 0xffffff00L; /** * Creates a RoutingRulePopulator object. * * @param srManager segment routing manager reference */ public RoutingRulePopulator(SegmentRoutingManager srManager) { this.srManager = srManager; this.config = checkNotNull(srManager.deviceConfiguration); this.rulePopulationCounter = new AtomicLong(0); } /** * Resets the population counter. */ public void resetCounter() { rulePopulationCounter.set(0); } /** * Returns the number of rules populated. * * @return number of rules */ public long getCounter() { return rulePopulationCounter.get(); } /** * Populates IP flow rules for specific hosts directly connected to the * switch. * * @param deviceId switch ID to set the rules * @param hostIp host IP address * @param hostMac host MAC address * @param outPort port where the host is connected */ public void populateIpRuleForHost(DeviceId deviceId, Ip4Address hostIp, MacAddress hostMac, PortNumber outPort) { log.debug("Populate IP table entry for host {} at {}:{}", hostIp, deviceId, outPort); ForwardingObjective.Builder fwdBuilder; try { fwdBuilder = getForwardingObjectiveBuilder( deviceId, hostIp, hostMac, outPort); } catch (DeviceConfigNotFoundException e) { log.warn(e.getMessage() + " Aborting populateIpRuleForHost."); return; } srManager.flowObjectiveService. forward(deviceId, fwdBuilder.add(new SRObjectiveContext(deviceId, SRObjectiveContext.ObjectiveType.FORWARDING))); rulePopulationCounter.incrementAndGet(); } public void revokeIpRuleForHost(DeviceId deviceId, Ip4Address hostIp, MacAddress hostMac, PortNumber outPort) { log.debug("Revoke IP table entry for host {} at {}:{}", hostIp, deviceId, outPort); ForwardingObjective.Builder fwdBuilder; try { fwdBuilder = getForwardingObjectiveBuilder( deviceId, hostIp, hostMac, outPort); } catch (DeviceConfigNotFoundException e) { log.warn(e.getMessage() + " Aborting revokeIpRuleForHost."); return; } srManager.flowObjectiveService. forward(deviceId, fwdBuilder.remove(new SRObjectiveContext(deviceId, SRObjectiveContext.ObjectiveType.FORWARDING))); } private ForwardingObjective.Builder getForwardingObjectiveBuilder( DeviceId deviceId, Ip4Address hostIp, MacAddress hostMac, PortNumber outPort) throws DeviceConfigNotFoundException { MacAddress deviceMac; deviceMac = config.getDeviceMac(deviceId); TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder(); TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder(); sbuilder.matchIPDst(IpPrefix.valueOf(hostIp, IpPrefix.MAX_INET_MASK_LENGTH)); sbuilder.matchEthType(Ethernet.TYPE_IPV4); tbuilder.deferred() .setEthDst(hostMac) .setEthSrc(deviceMac) .setOutput(outPort); TrafficTreatment treatment = tbuilder.build(); TrafficSelector selector = sbuilder.build(); return DefaultForwardingObjective.builder() .fromApp(srManager.appId).makePermanent() .withSelector(selector).withTreatment(treatment) .withPriority(100).withFlag(ForwardingObjective.Flag.SPECIFIC); } /** * Populates IP flow rules for the subnets of the destination router. * * @param deviceId switch ID to set the rules * @param subnets subnet information * @param destSw destination switch ID * @param nextHops next hop switch ID list * @return true if all rules are set successfully, false otherwise */ public boolean populateIpRuleForSubnet(DeviceId deviceId, Set subnets, DeviceId destSw, Set nextHops) { for (IpPrefix subnet : subnets) { if (!populateIpRuleForRouter(deviceId, subnet, destSw, nextHops)) { return false; } } return true; } /** * Populates IP flow rules for the router IP address. * * @param deviceId target device ID to set the rules * @param ipPrefix the IP address of the destination router * @param destSw device ID of the destination router * @param nextHops next hop switch ID list * @return true if all rules are set successfully, false otherwise */ public boolean populateIpRuleForRouter(DeviceId deviceId, IpPrefix ipPrefix, DeviceId destSw, Set nextHops) { int segmentId; try { segmentId = config.getSegmentId(destSw); } catch (DeviceConfigNotFoundException e) { log.warn(e.getMessage() + " Aborting populateIpRuleForRouter."); return false; } TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder(); sbuilder.matchIPDst(ipPrefix); sbuilder.matchEthType(Ethernet.TYPE_IPV4); TrafficSelector selector = sbuilder.build(); TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder(); NeighborSet ns; TrafficTreatment treatment; // If the next hop is the same as the final destination, then MPLS label // is not set. if (nextHops.size() == 1 && nextHops.toArray()[0].equals(destSw)) { tbuilder.immediate().decNwTtl(); ns = new NeighborSet(nextHops); treatment = tbuilder.build(); } else { ns = new NeighborSet(nextHops, segmentId); treatment = null; } // setup metadata to pass to nextObjective - indicate the vlan on egress // if needed by the switch pipeline. Since neighbor sets are always to // other neighboring routers, there is no subnet assigned on those ports. TrafficSelector.Builder metabuilder = DefaultTrafficSelector.builder(selector); metabuilder.matchVlanId( VlanId.vlanId(SegmentRoutingManager.ASSIGNED_VLAN_NO_SUBNET)); int nextId = srManager.getNextObjectiveId(deviceId, ns, metabuilder.build()); if (nextId <= 0) { log.warn("No next objective in {} for ns: {}", deviceId, ns); return false; } ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective .builder() .fromApp(srManager.appId) .makePermanent() .nextStep(nextId) .withSelector(selector) .withPriority(100) .withFlag(ForwardingObjective.Flag.SPECIFIC); if (treatment != null) { fwdBuilder.withTreatment(treatment); } log.debug("Installing IPv4 forwarding objective " + "for router IP/subnet {} in switch {}", ipPrefix, deviceId); srManager.flowObjectiveService. forward(deviceId, fwdBuilder. add(new SRObjectiveContext(deviceId, SRObjectiveContext.ObjectiveType.FORWARDING))); rulePopulationCounter.incrementAndGet(); return true; } /** * Populates MPLS flow rules to all routers. * * @param deviceId target device ID of the switch to set the rules * @param destSwId destination switch device ID * @param nextHops next hops switch ID list * @return true if all rules are set successfully, false otherwise */ public boolean populateMplsRule(DeviceId deviceId, DeviceId destSwId, Set nextHops) { int segmentId; try { segmentId = config.getSegmentId(destSwId); } catch (DeviceConfigNotFoundException e) { log.warn(e.getMessage() + " Aborting populateMplsRule."); return false; } TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder(); List fwdObjBuilders = new ArrayList<>(); // TODO Handle the case of Bos == false sbuilder.matchEthType(Ethernet.MPLS_UNICAST); sbuilder.matchMplsLabel(MplsLabel.mplsLabel(segmentId)); TrafficSelector selector = sbuilder.build(); // setup metadata to pass to nextObjective - indicate the vlan on egress // if needed by the switch pipeline. Since mpls next-hops are always to // other neighboring routers, there is no subnet assigned on those ports. TrafficSelector.Builder metabuilder = DefaultTrafficSelector.builder(selector); metabuilder.matchVlanId( VlanId.vlanId(SegmentRoutingManager.ASSIGNED_VLAN_NO_SUBNET)); // If the next hop is the destination router for the segment, do pop if (nextHops.size() == 1 && destSwId.equals(nextHops.toArray()[0])) { log.debug("populateMplsRule: Installing MPLS forwarding objective for " + "label {} in switch {} with pop", segmentId, deviceId); // bos pop case (php) ForwardingObjective.Builder fwdObjBosBuilder = getMplsForwardingObjective(deviceId, nextHops, true, true, metabuilder.build()); if (fwdObjBosBuilder == null) { return false; } fwdObjBuilders.add(fwdObjBosBuilder); // XXX not-bos pop case, SR app multi-label not implemented yet /*ForwardingObjective.Builder fwdObjNoBosBuilder = getMplsForwardingObjective(deviceId, nextHops, true, false);*/ } else { // next hop is not destination, SR CONTINUE case (swap with self) log.debug("Installing MPLS forwarding objective for " + "label {} in switch {} without pop", segmentId, deviceId); // continue case with bos - this does get triggered in edge routers // and in core routers - driver can handle depending on availability // of MPLS ECMP or not ForwardingObjective.Builder fwdObjBosBuilder = getMplsForwardingObjective(deviceId, nextHops, false, true, metabuilder.build()); if (fwdObjBosBuilder == null) { return false; } fwdObjBuilders.add(fwdObjBosBuilder); // XXX continue case with not-bos - SR app multi label not implemented yet // also requires MPLS ECMP /*ForwardingObjective.Builder fwdObjNoBosBuilder = getMplsForwardingObjective(deviceId, nextHops, false, false); */ } for (ForwardingObjective.Builder fwdObjBuilder : fwdObjBuilders) { ((Builder) ((Builder) fwdObjBuilder.fromApp(srManager.appId) .makePermanent()).withSelector(selector) .withPriority(100)) .withFlag(ForwardingObjective.Flag.SPECIFIC); srManager.flowObjectiveService. forward(deviceId, fwdObjBuilder. add(new SRObjectiveContext(deviceId, SRObjectiveContext.ObjectiveType.FORWARDING))); rulePopulationCounter.incrementAndGet(); } return true; } private ForwardingObjective.Builder getMplsForwardingObjective( DeviceId deviceId, Set nextHops, boolean phpRequired, boolean isBos, TrafficSelector meta) { ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective .builder().withFlag(ForwardingObjective.Flag.SPECIFIC); TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder(); if (phpRequired) { // php case - pop should always be flow-action log.debug("getMplsForwardingObjective: php required"); tbuilder.deferred().copyTtlIn(); if (isBos) { tbuilder.deferred().popMpls(EthType.EtherType.IPV4.ethType()) .decNwTtl(); } else { tbuilder.deferred().popMpls(EthType.EtherType.MPLS_UNICAST.ethType()) .decMplsTtl(); } } else { // swap with self case - SR CONTINUE log.debug("getMplsForwardingObjective: php not required"); tbuilder.deferred().decMplsTtl(); } // All forwarding is via ECMP group, the metadata informs the driver // that the next-Objective will be used by MPLS flows. In other words, // MPLS ECMP is requested. It is up to the driver to decide if these // packets will be hashed or not. fwdBuilder.withTreatment(tbuilder.build()); NeighborSet ns = new NeighborSet(nextHops); log.debug("Trying to get a nextObjid for mpls rule on device:{} to ns:{}", deviceId, ns); int nextId = srManager.getNextObjectiveId(deviceId, ns, meta); if (nextId <= 0) { log.warn("No next objective in {} for ns: {}", deviceId, ns); return null; } fwdBuilder.nextStep(nextId); return fwdBuilder; } /** * Creates a filtering objective to permit all untagged packets with a * dstMac corresponding to the router's MAC address. For those pipelines * that need to internally assign vlans to untagged packets, this method * provides per-subnet vlan-ids as metadata. *

* Note that the vlan assignment is only done by the master-instance for a switch. * However we send the filtering objective from slave-instances as well, so * that drivers can obtain other information (like Router MAC and IP). * * @param deviceId the switch dpid for the router */ public void populateRouterMacVlanFilters(DeviceId deviceId) { log.debug("Installing per-port filtering objective for untagged " + "packets in device {}", deviceId); MacAddress deviceMac; try { deviceMac = config.getDeviceMac(deviceId); } catch (DeviceConfigNotFoundException e) { log.warn(e.getMessage() + " Aborting populateRouterMacVlanFilters."); return; } for (Port port : srManager.deviceService.getPorts(deviceId)) { if (port.number().toLong() > 0 && port.number().toLong() < OFPP_MAX) { Ip4Prefix portSubnet = config.getPortSubnet(deviceId, port.number()); VlanId assignedVlan = (portSubnet == null) ? VlanId.vlanId(SegmentRoutingManager.ASSIGNED_VLAN_NO_SUBNET) : srManager.getSubnetAssignedVlanId(deviceId, portSubnet); FilteringObjective.Builder fob = DefaultFilteringObjective.builder(); fob.withKey(Criteria.matchInPort(port.number())) .addCondition(Criteria.matchEthDst(deviceMac)) .addCondition(Criteria.matchVlanId(VlanId.NONE)); // vlan assignment is valid only if this instance is master if (srManager.mastershipService.isLocalMaster(deviceId)) { TrafficTreatment tt = DefaultTrafficTreatment.builder() .pushVlan().setVlanId(assignedVlan).build(); fob.setMeta(tt); } fob.permit().fromApp(srManager.appId); srManager.flowObjectiveService. filter(deviceId, fob.add(new SRObjectiveContext(deviceId, SRObjectiveContext.ObjectiveType.FILTER))); } } } /** * Creates a forwarding objective to punt all IP packets, destined to the * router's port IP addresses, to the controller. Note that the input * port should not be matched on, as these packets can come from any input. * Furthermore, these are applied only by the master instance. * * @param deviceId the switch dpid for the router */ public void populateRouterIpPunts(DeviceId deviceId) { Ip4Address routerIp; try { routerIp = config.getRouterIp(deviceId); } catch (DeviceConfigNotFoundException e) { log.warn(e.getMessage() + " Aborting populateRouterIpPunts."); return; } if (!srManager.mastershipService.isLocalMaster(deviceId)) { log.debug("Not installing port-IP punts - not the master for dev:{} ", deviceId); return; } ForwardingObjective.Builder puntIp = DefaultForwardingObjective.builder(); Set allIps = new HashSet(config.getPortIPs(deviceId)); allIps.add(routerIp); for (Ip4Address ipaddr : allIps) { TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder(); TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder(); sbuilder.matchEthType(Ethernet.TYPE_IPV4); sbuilder.matchIPDst(IpPrefix.valueOf(ipaddr, IpPrefix.MAX_INET_MASK_LENGTH)); tbuilder.setOutput(PortNumber.CONTROLLER); puntIp.withSelector(sbuilder.build()); puntIp.withTreatment(tbuilder.build()); puntIp.withFlag(Flag.VERSATILE) .withPriority(HIGHEST_PRIORITY) .makePermanent() .fromApp(srManager.appId); log.debug("Installing forwarding objective to punt port IP addresses"); srManager.flowObjectiveService. forward(deviceId, puntIp.add(new SRObjectiveContext(deviceId, SRObjectiveContext.ObjectiveType.FORWARDING))); } } /** * Populates a forwarding objective to send packets that miss other high * priority Bridging Table entries to a group that contains all ports of * its subnet. * * Note: We assume that packets sending from the edge switches to the hosts * have untagged VLAN. * The VLAN tag will be popped later in the flooding group. * * @param deviceId switch ID to set the rules */ public void populateSubnetBroadcastRule(DeviceId deviceId) { config.getSubnets(deviceId).forEach(subnet -> { int nextId = srManager.getSubnetNextObjectiveId(deviceId, subnet); VlanId vlanId = srManager.getSubnetAssignedVlanId(deviceId, subnet); /* Driver should treat objective with MacAddress.NONE as the * subnet broadcast rule */ TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder(); sbuilder.matchVlanId(vlanId); sbuilder.matchEthDst(MacAddress.NONE); ForwardingObjective.Builder fob = DefaultForwardingObjective.builder(); fob.withFlag(Flag.SPECIFIC) .withSelector(sbuilder.build()) .nextStep(nextId) .withPriority(5) .fromApp(srManager.appId) .makePermanent(); srManager.flowObjectiveService.forward( deviceId, fob.add(new SRObjectiveContext( deviceId, SRObjectiveContext.ObjectiveType.FORWARDING) ) ); }); } private static class SRObjectiveContext implements ObjectiveContext { enum ObjectiveType { FILTER, FORWARDING } final DeviceId deviceId; final ObjectiveType type; SRObjectiveContext(DeviceId deviceId, ObjectiveType type) { this.deviceId = deviceId; this.type = type; } @Override public void onSuccess(Objective objective) { log.debug("{} objective operation successful in device {}", type.name(), deviceId); } @Override public void onError(Objective objective, ObjectiveError error) { log.warn("{} objective {} operation failed with error: {} in device {}", type.name(), objective, error, deviceId); } } }