blob: 37f273bddfa474008edaace0bddf32068bae6c03 [file] [log] [blame]
/*
* Copyright 2018-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.segmentrouting.mcast;
import com.google.common.collect.ImmutableSet;
import org.onlab.packet.Ethernet;
import org.onlab.packet.IpAddress;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
import org.onosproject.cluster.NodeId;
import org.onosproject.core.ApplicationId;
import org.onosproject.mcast.api.McastRoute;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import org.onosproject.net.HostId;
import org.onosproject.net.PortNumber;
import org.onosproject.net.config.basics.McastConfig;
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.flow.criteria.VlanIdCriterion;
import org.onosproject.net.flow.instructions.Instructions;
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.ForwardingObjective;
import org.onosproject.net.flowobjective.NextObjective;
import org.onosproject.net.flowobjective.ObjectiveContext;
import org.onosproject.segmentrouting.SegmentRoutingManager;
import org.onosproject.segmentrouting.SegmentRoutingService;
import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
import org.onosproject.segmentrouting.config.SegmentRoutingAppConfig;
import org.slf4j.Logger;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static org.onosproject.net.flow.criteria.Criterion.Type.VLAN_VID;
import static org.onosproject.segmentrouting.SegmentRoutingManager.INTERNAL_VLAN;
/**
* Utility class for Multicast Handler.
*/
class McastUtils {
// Internal reference to the log
private final Logger log;
// Internal reference to SR Manager
private SegmentRoutingManager srManager;
// Internal reference to the app id
private ApplicationId coreAppId;
/**
* Builds a new McastUtils object.
*
* @param srManager the SR manager
* @param coreAppId the core application id
* @param log log reference of the McastHandler
*/
McastUtils(SegmentRoutingManager srManager, ApplicationId coreAppId, Logger log) {
this.srManager = srManager;
this.coreAppId = coreAppId;
this.log = log;
}
/**
* Given a connect point define a leader for it.
*
* @param source the source connect point
* @return true if this instance is the leader, otherwise false
*/
boolean isLeader(ConnectPoint source) {
// Continue only when we have the mastership on the operation
if (!srManager.mastershipService.isLocalMaster(source.deviceId())) {
// When the source is available we just check the mastership
if (srManager.deviceService.isAvailable(source.deviceId())) {
return false;
}
// Fallback with Leadership service
// source id is used a topic
NodeId leader = srManager.leadershipService.runForLeadership(
source.deviceId().toString()).leaderNodeId();
// Verify if this node is the leader
if (!srManager.clusterService.getLocalNode().id().equals(leader)) {
return false;
}
}
// Done
return true;
}
/**
* Get router mac using application config and the connect point.
*
* @param deviceId the device id
* @param port the port number
* @return the router mac if the port is configured, otherwise null
*/
private MacAddress getRouterMac(DeviceId deviceId, PortNumber port) {
// Do nothing if the port is configured as suppressed
ConnectPoint connectPoint = new ConnectPoint(deviceId, port);
SegmentRoutingAppConfig appConfig = srManager.cfgService
.getConfig(srManager.appId(), SegmentRoutingAppConfig.class);
if (appConfig != null && appConfig.suppressSubnet().contains(connectPoint)) {
log.info("Ignore suppressed port {}", connectPoint);
return MacAddress.NONE;
}
// Get the router mac using the device configuration
MacAddress routerMac;
try {
routerMac = srManager.deviceConfiguration().getDeviceMac(deviceId);
} catch (DeviceConfigNotFoundException dcnfe) {
log.warn("Fail to push filtering objective since device is not configured. Abort");
return MacAddress.NONE;
}
return routerMac;
}
/**
* Adds filtering objective for given device and port.
*
* @param deviceId device ID
* @param port ingress port number
* @param assignedVlan assigned VLAN ID
* @param mcastIp the group address
* @param mcastRole the role of the device
*/
void addFilterToDevice(DeviceId deviceId, PortNumber port, VlanId assignedVlan,
IpAddress mcastIp, McastRole mcastRole) {
MacAddress routerMac = getRouterMac(deviceId, port);
if (routerMac.equals(MacAddress.NONE)) {
return;
}
FilteringObjective.Builder filtObjBuilder = filterObjBuilder(port, assignedVlan, mcastIp,
routerMac, mcastRole);
ObjectiveContext context = new DefaultObjectiveContext(
(objective) -> log.debug("Successfully add filter on {}/{}, vlan {}",
deviceId, port.toLong(), assignedVlan),
(objective, error) ->
log.warn("Failed to add filter on {}/{}, vlan {}: {}",
deviceId, port.toLong(), assignedVlan, error));
srManager.flowObjectiveService.filter(deviceId, filtObjBuilder.add(context));
}
/**
* Removes filtering objective for given device and port.
*
* @param deviceId device ID
* @param port ingress port number
* @param assignedVlan assigned VLAN ID
* @param mcastIp multicast IP address
* @param mcastRole the multicast role of the device
*/
void removeFilterToDevice(DeviceId deviceId, PortNumber port, VlanId assignedVlan,
IpAddress mcastIp, McastRole mcastRole) {
MacAddress routerMac = getRouterMac(deviceId, port);
if (routerMac.equals(MacAddress.NONE)) {
return;
}
FilteringObjective.Builder filtObjBuilder =
filterObjBuilder(port, assignedVlan, mcastIp, routerMac, mcastRole);
ObjectiveContext context = new DefaultObjectiveContext(
(objective) -> log.debug("Successfully removed filter on {}/{}, vlan {}",
deviceId, port.toLong(), assignedVlan),
(objective, error) ->
log.warn("Failed to remove filter on {}/{}, vlan {}: {}",
deviceId, port.toLong(), assignedVlan, error));
srManager.flowObjectiveService.filter(deviceId, filtObjBuilder.remove(context));
}
/**
* Gets assigned VLAN according to the value in the meta.
*
* @param nextObjective nextObjective to analyze
* @return assigned VLAN ID
*/
VlanId assignedVlanFromNext(NextObjective nextObjective) {
return ((VlanIdCriterion) nextObjective.meta().getCriterion(VLAN_VID)).vlanId();
}
/**
* Gets ingress VLAN from McastConfig.
*
* @return ingress VLAN or VlanId.NONE if not configured
*/
private VlanId ingressVlan() {
McastConfig mcastConfig =
srManager.cfgService.getConfig(coreAppId, McastConfig.class);
return (mcastConfig != null) ? mcastConfig.ingressVlan() : VlanId.NONE;
}
/**
* Gets egress VLAN from McastConfig.
*
* @return egress VLAN or VlanId.NONE if not configured
*/
private VlanId egressVlan() {
McastConfig mcastConfig =
srManager.cfgService.getConfig(coreAppId, McastConfig.class);
return (mcastConfig != null) ? mcastConfig.egressVlan() : VlanId.NONE;
}
/**
* Gets assigned VLAN according to the value of egress VLAN.
* If connect point is specified, try to reuse the assigned VLAN on the connect point.
*
* @param cp connect point; Can be null if not specified
* @return assigned VLAN ID
*/
VlanId assignedVlan(ConnectPoint cp) {
// Use the egressVlan if it is tagged
if (!egressVlan().equals(VlanId.NONE)) {
return egressVlan();
}
// Reuse unicast VLAN if the port has subnet configured
if (cp != null) {
VlanId untaggedVlan = srManager.getInternalVlanId(cp);
return (untaggedVlan != null) ? untaggedVlan : INTERNAL_VLAN;
}
// Use DEFAULT_VLAN if none of the above matches
return SegmentRoutingManager.INTERNAL_VLAN;
}
/**
* Gets source connect point of given multicast group.
*
* @param mcastIp multicast IP
* @return source connect point or null if not found
*/
// FIXME To be addressed with multiple sources support
ConnectPoint getSource(IpAddress mcastIp) {
// FIXME we should support different types of routes
McastRoute mcastRoute = srManager.multicastRouteService.getRoutes().stream()
.filter(mcastRouteInternal -> mcastRouteInternal.group().equals(mcastIp))
.findFirst().orElse(null);
return mcastRoute == null ? null : srManager.multicastRouteService.sources(mcastRoute)
.stream()
.findFirst().orElse(null);
}
/**
* Gets sinks of given multicast group.
*
* @param mcastIp multicast IP
* @return map of sinks or empty map if not found
*/
Map<HostId, Set<ConnectPoint>> getSinks(IpAddress mcastIp) {
// FIXME we should support different types of routes
McastRoute mcastRoute = srManager.multicastRouteService.getRoutes().stream()
.filter(mcastRouteInternal -> mcastRouteInternal.group().equals(mcastIp))
.findFirst().orElse(null);
return mcastRoute == null ?
Collections.emptyMap() :
srManager.multicastRouteService.routeData(mcastRoute).sinks();
}
/**
* Get sinks affected by this egress device.
*
* @param egressDevice the egress device
* @param mcastIp the mcast ip address
* @return the map of the sinks affected
*/
Map<HostId, Set<ConnectPoint>> getAffectedSinks(DeviceId egressDevice,
IpAddress mcastIp) {
return getSinks(mcastIp).entrySet()
.stream()
.filter(hostIdSetEntry -> hostIdSetEntry.getValue().stream()
.map(ConnectPoint::deviceId)
.anyMatch(deviceId -> deviceId.equals(egressDevice))
).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
/**
* Creates a next objective builder for multicast.
*
* @param mcastIp multicast group
* @param assignedVlan assigned VLAN ID
* @param outPorts set of output port numbers
* @param nextId the next id
* @return next objective builder
*/
NextObjective.Builder nextObjBuilder(IpAddress mcastIp, VlanId assignedVlan,
Set<PortNumber> outPorts, Integer nextId) {
// If nextId is null allocate a new one
if (nextId == null) {
nextId = srManager.flowObjectiveService.allocateNextId();
}
// Build the meta selector with the fwd objective info
TrafficSelector metadata =
DefaultTrafficSelector.builder()
.matchVlanId(assignedVlan)
.matchIPDst(mcastIp.toIpPrefix())
.build();
// Define the nextobjective type
NextObjective.Builder nextObjBuilder = DefaultNextObjective
.builder().withId(nextId)
.withType(NextObjective.Type.BROADCAST)
.fromApp(srManager.appId())
.withMeta(metadata);
// Add the output ports
outPorts.forEach(port -> {
TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
if (egressVlan().equals(VlanId.NONE)) {
tBuilder.popVlan();
}
tBuilder.setOutput(port);
nextObjBuilder.addTreatment(tBuilder.build());
});
// Done return the complete builder
return nextObjBuilder;
}
/**
* Creates a forwarding objective builder for multicast.
*
* @param mcastIp multicast group
* @param assignedVlan assigned VLAN ID
* @param nextId next ID of the L3 multicast group
* @return forwarding objective builder
*/
ForwardingObjective.Builder fwdObjBuilder(IpAddress mcastIp,
VlanId assignedVlan, int nextId) {
TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
// Let's the matching on the group address
// TODO SSM support in future
if (mcastIp.isIp6()) {
sbuilder.matchEthType(Ethernet.TYPE_IPV6);
sbuilder.matchIPv6Dst(mcastIp.toIpPrefix());
} else {
sbuilder.matchEthType(Ethernet.TYPE_IPV4);
sbuilder.matchIPDst(mcastIp.toIpPrefix());
}
// Then build the meta selector
TrafficSelector.Builder metabuilder = DefaultTrafficSelector.builder();
metabuilder.matchVlanId(assignedVlan);
// Finally return the completed builder
ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective.builder();
fwdBuilder.withSelector(sbuilder.build())
.withMeta(metabuilder.build())
.nextStep(nextId)
.withFlag(ForwardingObjective.Flag.SPECIFIC)
.fromApp(srManager.appId())
.withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
return fwdBuilder;
}
/**
* Creates a filtering objective builder for multicast.
*
* @param ingressPort ingress port of the multicast stream
* @param assignedVlan assigned VLAN ID
* @param mcastIp the group address
* @param routerMac router MAC. This is carried in metadata and used from some switches that
* need to put unicast entry before multicast entry in TMAC table.
* @param mcastRole the Multicast role
* @return filtering objective builder
*/
private FilteringObjective.Builder filterObjBuilder(PortNumber ingressPort, VlanId assignedVlan,
IpAddress mcastIp, MacAddress routerMac, McastRole mcastRole) {
FilteringObjective.Builder filtBuilder = DefaultFilteringObjective.builder();
// Let's add the in port matching and the priority
filtBuilder.withKey(Criteria.matchInPort(ingressPort))
.withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
// According to the mcast role we match on the proper vlan
// If the role is null we are on the transit or on the egress
if (mcastRole == null) {
filtBuilder.addCondition(Criteria.matchVlanId(egressVlan()));
} else {
filtBuilder.addCondition(Criteria.matchVlanId(ingressVlan()));
}
// According to the IP type we set the proper match on the mac address
if (mcastIp.isIp4()) {
filtBuilder.addCondition(Criteria.matchEthDstMasked(MacAddress.IPV4_MULTICAST,
MacAddress.IPV4_MULTICAST_MASK));
} else {
filtBuilder.addCondition(Criteria.matchEthDstMasked(MacAddress.IPV6_MULTICAST,
MacAddress.IPV6_MULTICAST_MASK));
}
// We finally build the meta treatment
TrafficTreatment tt = DefaultTrafficTreatment.builder()
.pushVlan().setVlanId(assignedVlan)
.setEthDst(routerMac)
.build();
filtBuilder.withMeta(tt);
// Done, we return a permit filtering objective
return filtBuilder.permit().fromApp(srManager.appId());
}
/**
* Gets output ports information from treatments.
*
* @param treatments collection of traffic treatments
* @return set of output port numbers
*/
Set<PortNumber> getPorts(Collection<TrafficTreatment> treatments) {
ImmutableSet.Builder<PortNumber> builder = ImmutableSet.builder();
treatments.forEach(treatment -> treatment.allInstructions().stream()
.filter(instr -> instr instanceof Instructions.OutputInstruction)
.forEach(instr -> builder.add(((Instructions.OutputInstruction) instr).port())));
return builder.build();
}
}