All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.onosproject.driver.pipeline.SoftRouterPipeline Maven / Gradle / Ivy

There is a newer version: 1.6.0
Show newest version
/*
 * 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.driver.pipeline;

import org.onlab.osgi.ServiceDirectory;
import org.onlab.packet.Ethernet;
import org.onlab.packet.IpPrefix;
import org.onlab.packet.VlanId;
import org.onlab.util.KryoNamespace;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.net.DeviceId;
import org.onosproject.net.behaviour.NextGroup;
import org.onosproject.net.behaviour.Pipeliner;
import org.onosproject.net.behaviour.PipelinerContext;
import org.onosproject.net.driver.AbstractHandlerBehaviour;
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.FlowRuleOperations;
import org.onosproject.net.flow.FlowRuleOperationsContext;
import org.onosproject.net.flow.FlowRuleService;
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.Criterion;
import org.onosproject.net.flow.criteria.EthCriterion;
import org.onosproject.net.flow.criteria.EthTypeCriterion;
import org.onosproject.net.flow.criteria.IPCriterion;
import org.onosproject.net.flow.criteria.PortCriterion;
import org.onosproject.net.flow.criteria.VlanIdCriterion;
import org.onosproject.net.flow.instructions.Instruction;
import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
import org.onosproject.net.flowobjective.FilteringObjective;
import org.onosproject.net.flowobjective.FlowObjectiveStore;
import org.onosproject.net.flowobjective.ForwardingObjective;
import org.onosproject.net.flowobjective.NextObjective;
import org.onosproject.net.flowobjective.Objective;
import org.onosproject.net.flowobjective.ObjectiveError;
import org.onosproject.store.serializers.KryoNamespaces;
import org.slf4j.Logger;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;

import static org.onlab.util.Tools.delay;
import static org.slf4j.LoggerFactory.getLogger;

/**
 *  Simple 2-Table Pipeline for Software/NPU based routers. This pipeline
 *  does not forward IP traffic to next-hop groups. Instead it forwards traffic
 *  using OF FlowMod actions.
 */
public class SoftRouterPipeline extends AbstractHandlerBehaviour implements Pipeliner {

    protected static final int FILTER_TABLE = 0;
    protected static final int FIB_TABLE = 1;

    private static final int DROP_PRIORITY = 0;
    private static final int DEFAULT_PRIORITY = 0x8000;
    private static final int HIGHEST_PRIORITY = 0xffff;

    private ServiceDirectory serviceDirectory;
    protected FlowRuleService flowRuleService;
    private CoreService coreService;
    private FlowObjectiveStore flowObjectiveStore;
    protected DeviceId deviceId;
    protected ApplicationId appId;
    private ApplicationId driverId;

    private KryoNamespace appKryo = new KryoNamespace.Builder()
        .register(DummyGroup.class)
        .register(KryoNamespaces.API)
        .register(byte[].class)
        .build();

    private final Logger log = getLogger(getClass());

    @Override
    public void init(DeviceId deviceId, PipelinerContext context) {
        this.serviceDirectory = context.directory();
        this.deviceId = deviceId;
        coreService = serviceDirectory.get(CoreService.class);
        flowRuleService = serviceDirectory.get(FlowRuleService.class);
        flowObjectiveStore = context.store();
        driverId = coreService.registerApplication(
                "org.onosproject.driver.SoftRouterPipeline");

        initializePipeline();
    }

    @Override
    public void filter(FilteringObjective filteringObjective) {
        if (filteringObjective.type() == FilteringObjective.Type.PERMIT) {
            processFilter(filteringObjective,
                          filteringObjective.op() == Objective.Operation.ADD,
                          filteringObjective.appId());
        } else {
            fail(filteringObjective, ObjectiveError.UNSUPPORTED);
        }
    }

    @Override
    public void forward(ForwardingObjective fwd) {
        Collection rules;
        FlowRuleOperations.Builder flowOpsBuilder = FlowRuleOperations.builder();

        rules = processForward(fwd);
        switch (fwd.op()) {
            case ADD:
                rules.stream()
                        .filter(Objects::nonNull)
                        .forEach(flowOpsBuilder::add);
                break;
            case REMOVE:
                rules.stream()
                        .filter(Objects::nonNull)
                        .forEach(flowOpsBuilder::remove);
                break;
            default:
                fail(fwd, ObjectiveError.UNKNOWN);
                log.warn("Unknown forwarding type {}", fwd.op());
        }


        flowRuleService.apply(flowOpsBuilder.build(new FlowRuleOperationsContext() {
            @Override
            public void onSuccess(FlowRuleOperations ops) {
                pass(fwd);
            }

            @Override
            public void onError(FlowRuleOperations ops) {
                fail(fwd, ObjectiveError.FLOWINSTALLATIONFAILED);
            }
        }));

    }

    @Override
    public void next(NextObjective nextObjective) {
        switch (nextObjective.type()) {
        case SIMPLE:
            Collection treatments = nextObjective.next();
            if (treatments.size() != 1) {
                log.error("Next Objectives of type Simple should only have a "
                        + "single Traffic Treatment. Next Objective Id:{}", nextObjective.id());
               fail(nextObjective, ObjectiveError.BADPARAMS);
               return;
            }
            processSimpleNextObjective(nextObjective);
            break;
        case HASHED:
        case BROADCAST:
        case FAILOVER:
            fail(nextObjective, ObjectiveError.UNSUPPORTED);
            log.warn("Unsupported next objective type {}", nextObjective.type());
            break;
        default:
            fail(nextObjective, ObjectiveError.UNKNOWN);
            log.warn("Unknown next objective type {}", nextObjective.type());
        }
    }

    private void pass(Objective obj) {
        obj.context().ifPresent(context -> context.onSuccess(obj));
    }

    private void fail(Objective obj, ObjectiveError error) {
        obj.context().ifPresent(context -> context.onError(obj, error));
    }

    private void initializePipeline() {
        //Drop rules for both tables
        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
        FlowRuleOperations.Builder ops = FlowRuleOperations.builder();

        treatment.drop();

        FlowRule rule = DefaultFlowRule.builder()
                .forDevice(deviceId)
                .withSelector(selector.build())
                .withTreatment(treatment.build())
                .withPriority(DROP_PRIORITY)
                .fromApp(driverId)
                .makePermanent()
                .forTable(FILTER_TABLE)
                .build();
        ops = ops.add(rule);

        rule = DefaultFlowRule.builder().forDevice(deviceId)
                .withSelector(selector.build())
                .withTreatment(treatment.build())
                .withPriority(DROP_PRIORITY)
                .fromApp(driverId)
                .makePermanent()
                .forTable(FIB_TABLE)
                .build();
        ops = ops.add(rule);

        flowRuleService.apply(ops.build(new FlowRuleOperationsContext() {
            @Override
            public void onSuccess(FlowRuleOperations ops) {
                log.info("Provisioned drop rules in both tables");
            }

            @Override
            public void onError(FlowRuleOperations ops) {
                log.info("Failed to provision drop rules");
            }
        }));
    }

    private void processFilter(FilteringObjective filt, boolean install,
                               ApplicationId applicationId) {
        // This driver only processes filtering criteria defined with switch
        // ports as the key
        PortCriterion p;
        EthCriterion e = null;
        VlanIdCriterion v = null;

        if (!filt.key().equals(Criteria.dummy()) &&
                filt.key().type() == Criterion.Type.IN_PORT) {
            p = (PortCriterion) filt.key();
        } else {
            log.warn("No key defined in filtering objective from app: {}. Not"
                    + "processing filtering objective", applicationId);
            fail(filt, ObjectiveError.UNKNOWN);
            return;
        }

        // convert filtering conditions for switch-intfs into flowrules
        FlowRuleOperations.Builder ops = FlowRuleOperations.builder();
        for (Criterion c : filt.conditions()) {
            if (c.type() == Criterion.Type.ETH_DST) {
                e = (EthCriterion) c;
            } else if (c.type() == Criterion.Type.VLAN_VID) {
                v = (VlanIdCriterion) c;
            } else {
                log.error("Unsupported filter {}", c);
                fail(filt, ObjectiveError.UNSUPPORTED);
                return;
            }
        }

        log.debug("adding Port/VLAN/MAC filtering rules in filter table: {}/{}/{}",
                  p.port(), v.vlanId(), e.mac());
        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
        selector.matchInPort(p.port());

        selector.matchEthDst(e.mac());
        selector.matchVlanId(v.vlanId());
        selector.matchEthType(Ethernet.TYPE_IPV4);
        if (!v.vlanId().equals(VlanId.NONE)) {
            treatment.popVlan();
        }
        treatment.transition(FIB_TABLE);
        FlowRule rule = DefaultFlowRule.builder()
                .forDevice(deviceId)
                .withSelector(selector.build())
                .withTreatment(treatment.build())
                .withPriority(DEFAULT_PRIORITY)
                .fromApp(applicationId)
                .makePermanent()
                .forTable(FILTER_TABLE).build();
        ops =  ops.add(rule);

        ops = install ? ops.add(rule) : ops.remove(rule);
        // apply filtering flow rules
        flowRuleService.apply(ops.build(new FlowRuleOperationsContext() {
            @Override
            public void onSuccess(FlowRuleOperations ops) {
                log.info("Applied filtering rules");
                pass(filt);
            }

            @Override
            public void onError(FlowRuleOperations ops) {
                log.info("Failed to apply filtering rules");
                fail(filt, ObjectiveError.FLOWINSTALLATIONFAILED);
            }
        }));
    }

    private Collection processForward(ForwardingObjective fwd) {
        switch (fwd.flag()) {
        case SPECIFIC:
            return processSpecific(fwd);
        case VERSATILE:
            return processVersatile(fwd);
        default:
            fail(fwd, ObjectiveError.UNKNOWN);
            log.warn("Unknown forwarding flag {}", fwd.flag());
        }
        return Collections.emptySet();
    }

    /**
     * SoftRouter has a single versatile table - the filter table.
     * This table can be used to filter entries that reach the next table (FIB table).
     * It can also be used to punt packets to the controller and/or bypass
     * the FIB table to forward out of a port.
     *
     * @param fwd The forwarding objective of type versatile
     * @return  A collection of flow rules meant to be delivered to the flowrule
     *          subsystem. May return empty collection in case of failures.
     */
    private Collection processVersatile(ForwardingObjective fwd) {
        log.debug("Received versatile fwd: to next:{}", fwd.nextId());
        Collection flowrules = new ArrayList<>();
        if (fwd.nextId() == null && fwd.treatment() == null) {
            log.error("Forwarding objective {} from {} must contain "
                    + "nextId or Treatment", fwd.selector(), fwd.appId());
            return Collections.emptySet();
        }
        TrafficTreatment.Builder ttBuilder = DefaultTrafficTreatment.builder();
        if (fwd.treatment() != null) {
            fwd.treatment().immediate().forEach(ins -> ttBuilder.add(ins));
        }
        //convert nextId to flow actions
        if (fwd.nextId() != null) {
            // only acceptable value is output to port
            NextGroup next = flowObjectiveStore.getNextGroup(fwd.nextId());
            if (next == null) {
                log.error("next-id {} does not exist in store", fwd.nextId());
                return Collections.emptySet();
            }
            TrafficTreatment nt = appKryo.deserialize(next.data());
            if (nt == null)  {
                log.error("Error in deserializing next-id {}", fwd.nextId());
                return Collections.emptySet();
            }
            for (Instruction ins : nt.allInstructions()) {
                if (ins instanceof OutputInstruction) {
                    ttBuilder.add(ins);
                }
            }
        }

        FlowRule rule = DefaultFlowRule.builder()
                .withSelector(fwd.selector())
                .withTreatment(ttBuilder.build())
                .makePermanent()
                .forDevice(deviceId)
                .fromApp(fwd.appId())
                .withPriority(fwd.priority())
                .build();

        flowrules.add(rule);

        return flowrules;
    }

    /**
     * SoftRouter has a single specific table - the FIB Table. It emulates
     * LPM matching of dstIP by using higher priority flows for longer prefixes.
     * Flows are forwarded using flow-actions
     *
     * @param fwd The forwarding objective of type simple
     * @return A collection of flow rules meant to be delivered to the flowrule
     *         subsystem. Typically the returned collection has a single flowrule.
     *         May return empty collection in case of failures.
     *
     */
    private Collection processSpecific(ForwardingObjective fwd) {
        log.debug("Processing specific forwarding objective to next:{}", fwd.nextId());
        TrafficSelector selector = fwd.selector();
        EthTypeCriterion ethType =
                (EthTypeCriterion) selector.getCriterion(Criterion.Type.ETH_TYPE);
        // XXX currently supporting only the L3 unicast table
        if (ethType == null || ethType.ethType().toShort() != Ethernet.TYPE_IPV4) {
            fail(fwd, ObjectiveError.UNSUPPORTED);
            return Collections.emptySet();
        }

        IpPrefix ipPrefix = ((IPCriterion)
                selector.getCriterion(Criterion.Type.IPV4_DST)).ip();

        TrafficSelector.Builder filteredSelector = DefaultTrafficSelector.builder()
                        .matchEthType(Ethernet.TYPE_IPV4);

        if (ipPrefix.prefixLength() != 0) {
            filteredSelector.matchIPDst(ipPrefix);
        }

        TrafficTreatment tt = null;
        if (fwd.nextId() != null) {
            NextGroup next = flowObjectiveStore.getNextGroup(fwd.nextId());
            if (next == null) {
                log.error("next-id {} does not exist in store", fwd.nextId());
                return Collections.emptySet();
            }
            tt = appKryo.deserialize(next.data());
            if (tt == null)  {
                log.error("Error in deserializing next-id {}", fwd.nextId());
                return Collections.emptySet();
            }
        }

        FlowRule.Builder ruleBuilder = DefaultFlowRule.builder()
                .fromApp(fwd.appId())
                .withPriority(fwd.priority())
                .forDevice(deviceId)
                .withSelector(filteredSelector.build());

        if (tt != null) {
            ruleBuilder.withTreatment(tt);
        }

        if (fwd.permanent()) {
            ruleBuilder.makePermanent();
        } else {
            ruleBuilder.makeTemporary(fwd.timeout());
        }

        ruleBuilder.forTable(FIB_TABLE);
        return Collections.singletonList(ruleBuilder.build());
    }

    /**
     * Next Objectives are stored as dummy groups for retrieval later
     * when Forwarding Objectives reference the next objective id. At that point
     * the dummy group is fetched from the distributed store and the enclosed
     * treatment is applied as a flow rule action.
     *
     * @param nextObj the next objective of type simple
     */
    private void processSimpleNextObjective(NextObjective nextObj) {
        // Simple next objective has a single treatment (not a collection)
        log.debug("Received nextObj {}", nextObj.id());
        // delay processing to emulate group creation
        delay(50);
        TrafficTreatment treatment = nextObj.next().iterator().next();
        flowObjectiveStore.putNextGroup(nextObj.id(),
                                        new DummyGroup(treatment));
    }

    private class DummyGroup implements NextGroup {
        TrafficTreatment nextActions;

        public DummyGroup(TrafficTreatment next) {
            this.nextActions = next;
        }

        @Override
        public byte[] data() {
            return appKryo.serialize(nextActions);
        }

    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy