blob: e5e74c70e0f564e5af80a60e6f5090ad013689c1 [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.routing.fibinstaller;
import com.google.common.collect.ConcurrentHashMultiset;
import com.google.common.collect.Maps;
import com.google.common.collect.Multiset;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Modified;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.onlab.packet.Ethernet;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
import org.onlab.util.Tools;
import org.onosproject.app.ApplicationService;
import org.onosproject.cfg.ComponentConfigService;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.incubator.net.config.basics.McastConfig;
import org.onosproject.net.intf.Interface;
import org.onosproject.net.intf.InterfaceService;
import org.onosproject.routeservice.ResolvedRoute;
import org.onosproject.routeservice.Route;
import org.onosproject.routeservice.RouteEvent;
import org.onosproject.routeservice.RouteListener;
import org.onosproject.routeservice.RouteService;
import org.onosproject.net.DeviceId;
import org.onosproject.net.config.ConfigFactory;
import org.onosproject.net.config.NetworkConfigEvent;
import org.onosproject.net.config.NetworkConfigListener;
import org.onosproject.net.config.NetworkConfigRegistry;
import org.onosproject.net.config.NetworkConfigService;
import org.onosproject.net.config.basics.SubjectFactories;
import org.onosproject.net.device.DeviceService;
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.DefaultNextObjective;
import org.onosproject.net.flowobjective.DefaultObjectiveContext;
import org.onosproject.net.flowobjective.FilteringObjective;
import org.onosproject.net.flowobjective.FlowObjectiveService;
import org.onosproject.net.flowobjective.ForwardingObjective;
import org.onosproject.net.flowobjective.NextObjective;
import org.onosproject.net.flowobjective.ObjectiveContext;
import org.onosproject.routing.InterfaceProvisionRequest;
import org.onosproject.routing.NextHop;
import org.onosproject.routing.NextHopGroupKey;
import org.onosproject.routing.Router;
import org.onosproject.routing.RouterInfo;
import org.onosproject.routing.RoutingService;
import org.onosproject.routing.config.RoutingConfiguration;
import org.onosproject.routing.config.RoutersConfig;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Dictionary;
import java.util.Map;
import java.util.Set;
/**
* Programs routes to a single OpenFlow switch.
*/
@Component(immediate = true)
public class FibInstaller {
private final Logger log = LoggerFactory.getLogger(getClass());
private static final String APP_NAME = "org.onosproject.fibinstaller";
private static final int PRIORITY_OFFSET = 100;
private static final int PRIORITY_MULTIPLIER = 5;
// FIXME: This should be eliminated when we have an API in SR that
// programs the fabric switches for VR
public static final short ASSIGNED_VLAN = 4094;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected RouteService routeService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected InterfaceService interfaceService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected NetworkConfigService networkConfigService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected NetworkConfigRegistry networkConfigRegistry;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected ComponentConfigService componentConfigService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected FlowObjectiveService flowObjectiveService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DeviceService deviceService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected ApplicationService applicationService;
@Property(name = "routeToNextHop", boolValue = false,
label = "Install a /32 or /128 route to each next hop")
private boolean routeToNextHop = false;
// Device id of data-plane switch - should be learned from config
private DeviceId deviceId;
private Router interfaceManager;
private ApplicationId coreAppId;
private ApplicationId routerAppId;
private ApplicationId fibAppId;
// Reference count for how many times a next hop is used by a route
private final Multiset<IpAddress> nextHopsCount = ConcurrentHashMultiset.create();
// Mapping from prefix to its current next hop
private final Map<IpPrefix, IpAddress> prefixToNextHop = Maps.newHashMap();
// Mapping from next hop IP to next hop object containing group info
private final Map<IpAddress, Integer> nextHops = Maps.newHashMap();
private final InternalRouteListener routeListener = new InternalRouteListener();
private final InternalNetworkConfigListener configListener = new InternalNetworkConfigListener();
private ConfigFactory<ApplicationId, McastConfig> mcastConfigFactory =
new ConfigFactory<ApplicationId, McastConfig>(SubjectFactories.APP_SUBJECT_FACTORY,
McastConfig.class, "multicast") {
@Override
public McastConfig createConfig() {
return new McastConfig();
}
};
@Activate
protected void activate(ComponentContext context) {
componentConfigService.registerProperties(getClass());
modified(context);
RoutingConfiguration.register(networkConfigRegistry);
coreAppId = coreService.registerApplication(CoreService.CORE_APP_NAME);
routerAppId = coreService.registerApplication(RoutingService.ROUTER_APP_ID);
fibAppId = coreService.registerApplication(APP_NAME);
networkConfigRegistry.registerConfigFactory(mcastConfigFactory);
networkConfigService.addListener(configListener);
processRouterConfig();
applicationService.registerDeactivateHook(fibAppId, () -> cleanUp());
log.info("Started");
}
@Deactivate
protected void deactivate() {
networkConfigService.removeListener(configListener);
RoutingConfiguration.unregister(networkConfigRegistry);
componentConfigService.unregisterProperties(getClass(), false);
log.info("Stopped");
}
@Modified
protected void modified(ComponentContext context) {
Dictionary<?, ?> properties = context.getProperties();
if (properties == null) {
return;
}
String strRouteToNextHop = Tools.get(properties, "routeToNextHop");
routeToNextHop = Boolean.parseBoolean(strRouteToNextHop);
log.info("routeToNextHop set to {}", routeToNextHop);
}
private void processRouterConfig() {
Set<RoutersConfig.Router> routerConfigs =
RoutingConfiguration.getRouterConfigurations(networkConfigService, routerAppId);
if (routerConfigs.isEmpty()) {
log.info("Router config not available");
return;
}
RoutersConfig.Router routerConfig = routerConfigs.stream().findFirst().get();
if (interfaceManager == null) {
deviceId = routerConfig.controlPlaneConnectPoint().deviceId();
log.info("Router device ID is {}", deviceId);
routeService.addListener(routeListener);
interfaceManager = createRouter(RouterInfo.from(routerConfig));
} else {
interfaceManager.changeConfiguration(RouterInfo.from(routerConfig), false);
}
}
/**
* Removes filtering objectives and routes before deactivate.
*/
private void cleanUp() {
//remove the route listener
routeService.removeListener(routeListener);
//clean up the routes.
prefixToNextHop.entrySet().stream()
.map(e -> new Route(Route.Source.UNDEFINED, e.getKey(), e.getValue()))
.forEach(this::deleteRoute);
if (interfaceManager != null) {
interfaceManager.cleanup();
}
}
private Router createRouter(RouterInfo info) {
return new Router(
info,
interfaceService,
deviceService,
this::provisionInterface,
this::unprovisionInterface,
false);
}
private void updateRoute(ResolvedRoute route) {
addNextHop(route);
Integer nextId;
synchronized (this) {
nextId = nextHops.get(route.nextHop());
}
flowObjectiveService.forward(deviceId,
generateRibForwardingObj(route.prefix(), nextId).add());
log.trace("Sending forwarding objective {} -> nextId:{}", route, nextId);
}
private synchronized void deleteRoute(ResolvedRoute route) {
deleteRoute(route.route());
}
private void deleteRoute(Route route) {
//Integer nextId = nextHops.get(route.nextHop());
/* Group group = deleteNextHop(route.prefix());
if (group == null) {
log.warn("Group not found when deleting {}", route);
return;
}*/
flowObjectiveService.forward(deviceId,
generateRibForwardingObj(route.prefix(), null).remove());
}
private ForwardingObjective.Builder generateRibForwardingObj(IpPrefix prefix,
Integer nextId) {
TrafficSelector selector = buildIpSelectorFromIpPrefix(prefix).build();
int priority = prefix.prefixLength() * PRIORITY_MULTIPLIER + PRIORITY_OFFSET;
ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective.builder()
.fromApp(fibAppId)
.makePermanent()
.withSelector(selector)
.withPriority(priority)
.withFlag(ForwardingObjective.Flag.SPECIFIC);
if (nextId == null) {
// Route withdraws are not specified with next hops. Generating
// dummy treatment as there is no equivalent nextId info.
fwdBuilder.withTreatment(DefaultTrafficTreatment.builder().build());
} else {
fwdBuilder.nextStep(nextId);
}
return fwdBuilder;
}
/**
* Method to build IPv4 or IPv6 selector.
*
* @param prefixToMatch the prefix to match
* @return the traffic selector builder
*/
private TrafficSelector.Builder buildIpSelectorFromIpPrefix(IpPrefix prefixToMatch) {
TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder();
// If the prefix is IPv4
if (prefixToMatch.isIp4()) {
selectorBuilder.matchEthType(Ethernet.TYPE_IPV4);
selectorBuilder.matchIPDst(prefixToMatch);
return selectorBuilder;
}
// If the prefix is IPv6
selectorBuilder.matchEthType(Ethernet.TYPE_IPV6);
selectorBuilder.matchIPv6Dst(prefixToMatch);
return selectorBuilder;
}
private synchronized void addNextHop(ResolvedRoute route) {
prefixToNextHop.put(route.prefix(), route.nextHop());
if (nextHopsCount.count(route.nextHop()) == 0) {
// There was no next hop in the multiset
Interface egressIntf = interfaceService.getMatchingInterface(route.nextHop());
if (egressIntf == null) {
log.warn("no egress interface found for {}", route);
return;
}
NextHopGroupKey groupKey = new NextHopGroupKey(route.nextHop());
NextHop nextHop = new NextHop(route.nextHop(), route.nextHopMac(), groupKey);
TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder()
.setEthSrc(egressIntf.mac())
.setEthDst(nextHop.mac());
TrafficSelector.Builder metabuilder = null;
if (!egressIntf.vlan().equals(VlanId.NONE)) {
treatment.pushVlan()
.setVlanId(egressIntf.vlan())
.setVlanPcp((byte) 0);
} else {
// untagged outgoing port may require internal vlan in some pipelines
metabuilder = DefaultTrafficSelector.builder();
metabuilder.matchVlanId(VlanId.vlanId(ASSIGNED_VLAN));
}
treatment.setOutput(egressIntf.connectPoint().port());
int nextId = flowObjectiveService.allocateNextId();
NextObjective.Builder nextBuilder = DefaultNextObjective.builder()
.withId(nextId)
.addTreatment(treatment.build())
.withType(NextObjective.Type.SIMPLE)
.fromApp(fibAppId);
if (metabuilder != null) {
nextBuilder.withMeta(metabuilder.build());
}
NextObjective nextObjective = nextBuilder.add(); // TODO add callbacks
flowObjectiveService.next(deviceId, nextObjective);
nextHops.put(nextHop.ip(), nextId);
if (routeToNextHop) {
// Install route to next hop
ForwardingObjective fob =
generateRibForwardingObj(IpPrefix.valueOf(route.nextHop(), 32), nextId).add();
flowObjectiveService.forward(deviceId, fob);
}
}
nextHopsCount.add(route.nextHop());
}
/*private synchronized Group deleteNextHop(IpPrefix prefix) {
IpAddress nextHopIp = prefixToNextHop.remove(prefix);
NextHop nextHop = nextHops.get(nextHopIp);
if (nextHop == null) {
log.warn("No next hop found when removing prefix {}", prefix);
return null;
}
Group group = groupService.getGroup(deviceId,
new DefaultGroupKey(appKryo.
serialize(nextHop.group())));
// FIXME disabling group deletes for now until we verify the logic is OK
if (nextHopsCount.remove(nextHopIp, 1) <= 1) {
// There was one or less next hops, so there are now none
log.debug("removing group for next hop {}", nextHop);
nextHops.remove(nextHopIp);
groupService.removeGroup(deviceId,
new DefaultGroupKey(appKryo.build().serialize(nextHop.group())),
appId);
}
return group;
}*/
private void provisionInterface(InterfaceProvisionRequest intf) {
updateInterfaceFilters(intf, true);
}
private void unprovisionInterface(InterfaceProvisionRequest intf) {
updateInterfaceFilters(intf, false);
}
/**
* Installs or removes flow objectives relating to an interface.
*
* @param intf interface to update objectives for
* @param install true to install the objectives, false to remove them
*/
private void updateInterfaceFilters(InterfaceProvisionRequest intf, boolean install) {
updateFilteringObjective(intf, install);
updateMcastFilteringObjective(intf, install);
}
/**
* Installs or removes unicast filtering objectives relating to an interface.
*
* @param routerIntf interface to update objectives for
* @param install true to install the objectives, false to remove them
*/
private void updateFilteringObjective(InterfaceProvisionRequest routerIntf, boolean install) {
Interface intf = routerIntf.intf();
VlanId assignedVlan = (egressVlan().equals(VlanId.NONE)) ?
VlanId.vlanId(ASSIGNED_VLAN) :
egressVlan();
FilteringObjective.Builder fob = DefaultFilteringObjective.builder();
// first add filter for the interface
fob.withKey(Criteria.matchInPort(intf.connectPoint().port()))
.addCondition(Criteria.matchEthDst(intf.mac()))
.addCondition(Criteria.matchVlanId(intf.vlan()));
fob.withPriority(PRIORITY_OFFSET);
if (intf.vlan() == VlanId.NONE) {
TrafficTreatment tt = DefaultTrafficTreatment.builder()
.pushVlan().setVlanId(assignedVlan).build();
fob.withMeta(tt);
}
fob.permit().fromApp(fibAppId);
sendFilteringObjective(install, fob, intf);
// then add the same mac/vlan filters for control-plane connect point
fob.withKey(Criteria.matchInPort(routerIntf.controlPlaneConnectPoint().port()));
sendFilteringObjective(install, fob, intf);
}
/**
* Installs or removes multicast filtering objectives relating to an interface.
*
* @param routerIntf interface to update objectives for
* @param install true to install the objectives, false to remove them
*/
private void updateMcastFilteringObjective(InterfaceProvisionRequest routerIntf, boolean install) {
Interface intf = routerIntf.intf();
VlanId assignedVlan = (egressVlan().equals(VlanId.NONE)) ?
VlanId.vlanId(ASSIGNED_VLAN) :
egressVlan();
FilteringObjective.Builder fob = DefaultFilteringObjective.builder();
// first add filter for the interface
fob.withKey(Criteria.matchInPort(intf.connectPoint().port()))
.addCondition(Criteria.matchEthDstMasked(MacAddress.IPV4_MULTICAST,
MacAddress.IPV4_MULTICAST_MASK))
.addCondition(Criteria.matchVlanId(ingressVlan()));
fob.withPriority(PRIORITY_OFFSET);
TrafficTreatment tt = DefaultTrafficTreatment.builder()
.pushVlan().setVlanId(assignedVlan).build();
fob.withMeta(tt);
fob.permit().fromApp(fibAppId);
sendFilteringObjective(install, fob, intf);
}
private void sendFilteringObjective(boolean install, FilteringObjective.Builder fob,
Interface intf) {
ObjectiveContext context = new DefaultObjectiveContext(
(objective) -> log.info("Installed filter for interface {}", intf),
(objective, error) ->
log.error("Failed to install filter for interface {}: {}", intf, error));
FilteringObjective filter = install ? fob.add(context) : fob.remove(context);
flowObjectiveService.filter(deviceId, filter);
}
private VlanId ingressVlan() {
McastConfig mcastConfig =
networkConfigService.getConfig(coreAppId, McastConfig.class);
return (mcastConfig != null) ? mcastConfig.ingressVlan() : VlanId.NONE;
}
private VlanId egressVlan() {
McastConfig mcastConfig =
networkConfigService.getConfig(coreAppId, McastConfig.class);
return (mcastConfig != null) ? mcastConfig.egressVlan() : VlanId.NONE;
}
/**
* Listener for route changes.
*/
private class InternalRouteListener implements RouteListener {
@Override
public void event(RouteEvent event) {
ResolvedRoute route = event.subject();
switch (event.type()) {
case ROUTE_ADDED:
case ROUTE_UPDATED:
updateRoute(route);
break;
case ROUTE_REMOVED:
deleteRoute(route);
break;
default:
break;
}
}
}
/**
* Listener for network config events.
*/
private class InternalNetworkConfigListener implements NetworkConfigListener {
@Override
public void event(NetworkConfigEvent event) {
if (event.configClass().equals(RoutingService.ROUTER_CONFIG_CLASS)) {
switch (event.type()) {
case CONFIG_ADDED:
case CONFIG_UPDATED:
processRouterConfig();
break;
case CONFIG_REGISTERED:
break;
case CONFIG_UNREGISTERED:
break;
case CONFIG_REMOVED:
cleanUp();
break;
default:
break;
}
}
}
}
}