blob: 924794b5a8bf409920e7bed5fb6c4a9e3384c131 [file] [log] [blame]
Pierb0328e42018-03-27 11:29:42 -07001/*
2 * Copyright 2018-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onosproject.segmentrouting.mcast;
18
Pier96f63cb2018-04-17 16:29:56 +020019import com.google.common.collect.ImmutableMap;
Pierb0328e42018-03-27 11:29:42 -070020import com.google.common.collect.ImmutableSet;
Pier96f63cb2018-04-17 16:29:56 +020021import com.google.common.collect.Maps;
22import com.google.common.hash.HashFunction;
23import com.google.common.hash.Hashing;
Pierb0328e42018-03-27 11:29:42 -070024import org.onlab.packet.Ethernet;
25import org.onlab.packet.IpAddress;
26import org.onlab.packet.MacAddress;
27import org.onlab.packet.VlanId;
28import org.onosproject.cluster.NodeId;
29import org.onosproject.core.ApplicationId;
30import org.onosproject.mcast.api.McastRoute;
31import org.onosproject.net.ConnectPoint;
32import org.onosproject.net.DeviceId;
33import org.onosproject.net.HostId;
34import org.onosproject.net.PortNumber;
35import org.onosproject.net.config.basics.McastConfig;
36import org.onosproject.net.flow.DefaultTrafficSelector;
37import org.onosproject.net.flow.DefaultTrafficTreatment;
38import org.onosproject.net.flow.TrafficSelector;
39import org.onosproject.net.flow.TrafficTreatment;
40import org.onosproject.net.flow.criteria.Criteria;
41import org.onosproject.net.flow.criteria.VlanIdCriterion;
42import org.onosproject.net.flow.instructions.Instructions;
43import org.onosproject.net.flowobjective.DefaultFilteringObjective;
44import org.onosproject.net.flowobjective.DefaultForwardingObjective;
45import org.onosproject.net.flowobjective.DefaultNextObjective;
46import org.onosproject.net.flowobjective.DefaultObjectiveContext;
47import org.onosproject.net.flowobjective.FilteringObjective;
48import org.onosproject.net.flowobjective.ForwardingObjective;
49import org.onosproject.net.flowobjective.NextObjective;
50import org.onosproject.net.flowobjective.ObjectiveContext;
51import org.onosproject.segmentrouting.SegmentRoutingManager;
52import org.onosproject.segmentrouting.SegmentRoutingService;
53import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
54import org.onosproject.segmentrouting.config.SegmentRoutingAppConfig;
55import org.slf4j.Logger;
56
57import java.util.Collection;
58import java.util.Collections;
59import java.util.Map;
60import java.util.Set;
61import java.util.stream.Collectors;
62
63import static org.onosproject.net.flow.criteria.Criterion.Type.VLAN_VID;
64import static org.onosproject.segmentrouting.SegmentRoutingManager.INTERNAL_VLAN;
65
66/**
67 * Utility class for Multicast Handler.
68 */
69class McastUtils {
70
71 // Internal reference to the log
72 private final Logger log;
73 // Internal reference to SR Manager
74 private SegmentRoutingManager srManager;
75 // Internal reference to the app id
76 private ApplicationId coreAppId;
Pier96f63cb2018-04-17 16:29:56 +020077 // Hashing function for the multicast hasher
78 private static final HashFunction HASH_FN = Hashing.md5();
79 // Read only cache of the Mcast leader
80 private Map<IpAddress, NodeId> mcastLeaderCache;
Pierb0328e42018-03-27 11:29:42 -070081
82 /**
83 * Builds a new McastUtils object.
84 *
85 * @param srManager the SR manager
86 * @param coreAppId the core application id
87 * @param log log reference of the McastHandler
88 */
89 McastUtils(SegmentRoutingManager srManager, ApplicationId coreAppId, Logger log) {
90 this.srManager = srManager;
91 this.coreAppId = coreAppId;
92 this.log = log;
Pier96f63cb2018-04-17 16:29:56 +020093 this.mcastLeaderCache = Maps.newConcurrentMap();
Pierb0328e42018-03-27 11:29:42 -070094 }
95
96 /**
97 * Get router mac using application config and the connect point.
98 *
99 * @param deviceId the device id
100 * @param port the port number
101 * @return the router mac if the port is configured, otherwise null
102 */
103 private MacAddress getRouterMac(DeviceId deviceId, PortNumber port) {
104 // Do nothing if the port is configured as suppressed
105 ConnectPoint connectPoint = new ConnectPoint(deviceId, port);
106 SegmentRoutingAppConfig appConfig = srManager.cfgService
107 .getConfig(srManager.appId(), SegmentRoutingAppConfig.class);
108 if (appConfig != null && appConfig.suppressSubnet().contains(connectPoint)) {
109 log.info("Ignore suppressed port {}", connectPoint);
110 return MacAddress.NONE;
111 }
112 // Get the router mac using the device configuration
113 MacAddress routerMac;
114 try {
115 routerMac = srManager.deviceConfiguration().getDeviceMac(deviceId);
116 } catch (DeviceConfigNotFoundException dcnfe) {
117 log.warn("Fail to push filtering objective since device is not configured. Abort");
118 return MacAddress.NONE;
119 }
120 return routerMac;
121 }
122
123 /**
124 * Adds filtering objective for given device and port.
125 *
126 * @param deviceId device ID
127 * @param port ingress port number
128 * @param assignedVlan assigned VLAN ID
129 * @param mcastIp the group address
130 * @param mcastRole the role of the device
131 */
132 void addFilterToDevice(DeviceId deviceId, PortNumber port, VlanId assignedVlan,
133 IpAddress mcastIp, McastRole mcastRole) {
134
135 MacAddress routerMac = getRouterMac(deviceId, port);
136 if (routerMac.equals(MacAddress.NONE)) {
137 return;
138 }
139
140 FilteringObjective.Builder filtObjBuilder = filterObjBuilder(port, assignedVlan, mcastIp,
141 routerMac, mcastRole);
142 ObjectiveContext context = new DefaultObjectiveContext(
143 (objective) -> log.debug("Successfully add filter on {}/{}, vlan {}",
144 deviceId, port.toLong(), assignedVlan),
145 (objective, error) ->
146 log.warn("Failed to add filter on {}/{}, vlan {}: {}",
147 deviceId, port.toLong(), assignedVlan, error));
148 srManager.flowObjectiveService.filter(deviceId, filtObjBuilder.add(context));
149 }
150
151 /**
152 * Removes filtering objective for given device and port.
153 *
154 * @param deviceId device ID
155 * @param port ingress port number
156 * @param assignedVlan assigned VLAN ID
157 * @param mcastIp multicast IP address
158 * @param mcastRole the multicast role of the device
159 */
160 void removeFilterToDevice(DeviceId deviceId, PortNumber port, VlanId assignedVlan,
161 IpAddress mcastIp, McastRole mcastRole) {
162
163 MacAddress routerMac = getRouterMac(deviceId, port);
164 if (routerMac.equals(MacAddress.NONE)) {
165 return;
166 }
167
168 FilteringObjective.Builder filtObjBuilder =
169 filterObjBuilder(port, assignedVlan, mcastIp, routerMac, mcastRole);
170 ObjectiveContext context = new DefaultObjectiveContext(
171 (objective) -> log.debug("Successfully removed filter on {}/{}, vlan {}",
172 deviceId, port.toLong(), assignedVlan),
173 (objective, error) ->
174 log.warn("Failed to remove filter on {}/{}, vlan {}: {}",
175 deviceId, port.toLong(), assignedVlan, error));
176 srManager.flowObjectiveService.filter(deviceId, filtObjBuilder.remove(context));
177 }
178
179 /**
180 * Gets assigned VLAN according to the value in the meta.
181 *
182 * @param nextObjective nextObjective to analyze
183 * @return assigned VLAN ID
184 */
185 VlanId assignedVlanFromNext(NextObjective nextObjective) {
186 return ((VlanIdCriterion) nextObjective.meta().getCriterion(VLAN_VID)).vlanId();
187 }
188
189 /**
190 * Gets ingress VLAN from McastConfig.
191 *
192 * @return ingress VLAN or VlanId.NONE if not configured
193 */
194 private VlanId ingressVlan() {
195 McastConfig mcastConfig =
196 srManager.cfgService.getConfig(coreAppId, McastConfig.class);
197 return (mcastConfig != null) ? mcastConfig.ingressVlan() : VlanId.NONE;
198 }
199
200 /**
201 * Gets egress VLAN from McastConfig.
202 *
203 * @return egress VLAN or VlanId.NONE if not configured
204 */
205 private VlanId egressVlan() {
206 McastConfig mcastConfig =
207 srManager.cfgService.getConfig(coreAppId, McastConfig.class);
208 return (mcastConfig != null) ? mcastConfig.egressVlan() : VlanId.NONE;
209 }
210
211 /**
212 * Gets assigned VLAN according to the value of egress VLAN.
213 * If connect point is specified, try to reuse the assigned VLAN on the connect point.
214 *
215 * @param cp connect point; Can be null if not specified
216 * @return assigned VLAN ID
217 */
218 VlanId assignedVlan(ConnectPoint cp) {
219 // Use the egressVlan if it is tagged
220 if (!egressVlan().equals(VlanId.NONE)) {
221 return egressVlan();
222 }
223 // Reuse unicast VLAN if the port has subnet configured
224 if (cp != null) {
225 VlanId untaggedVlan = srManager.getInternalVlanId(cp);
226 return (untaggedVlan != null) ? untaggedVlan : INTERNAL_VLAN;
227 }
228 // Use DEFAULT_VLAN if none of the above matches
229 return SegmentRoutingManager.INTERNAL_VLAN;
230 }
231
232 /**
233 * Gets source connect point of given multicast group.
234 *
235 * @param mcastIp multicast IP
236 * @return source connect point or null if not found
237 */
238 // FIXME To be addressed with multiple sources support
239 ConnectPoint getSource(IpAddress mcastIp) {
240 // FIXME we should support different types of routes
241 McastRoute mcastRoute = srManager.multicastRouteService.getRoutes().stream()
242 .filter(mcastRouteInternal -> mcastRouteInternal.group().equals(mcastIp))
243 .findFirst().orElse(null);
244 return mcastRoute == null ? null : srManager.multicastRouteService.sources(mcastRoute)
245 .stream()
246 .findFirst().orElse(null);
247 }
248
249 /**
250 * Gets sinks of given multicast group.
251 *
252 * @param mcastIp multicast IP
253 * @return map of sinks or empty map if not found
254 */
255 Map<HostId, Set<ConnectPoint>> getSinks(IpAddress mcastIp) {
256 // FIXME we should support different types of routes
257 McastRoute mcastRoute = srManager.multicastRouteService.getRoutes().stream()
258 .filter(mcastRouteInternal -> mcastRouteInternal.group().equals(mcastIp))
259 .findFirst().orElse(null);
260 return mcastRoute == null ?
261 Collections.emptyMap() :
262 srManager.multicastRouteService.routeData(mcastRoute).sinks();
263 }
264
265 /**
266 * Get sinks affected by this egress device.
267 *
268 * @param egressDevice the egress device
269 * @param mcastIp the mcast ip address
270 * @return the map of the sinks affected
271 */
272 Map<HostId, Set<ConnectPoint>> getAffectedSinks(DeviceId egressDevice,
273 IpAddress mcastIp) {
274 return getSinks(mcastIp).entrySet()
275 .stream()
276 .filter(hostIdSetEntry -> hostIdSetEntry.getValue().stream()
277 .map(ConnectPoint::deviceId)
278 .anyMatch(deviceId -> deviceId.equals(egressDevice))
279 ).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
280 }
281
282 /**
283 * Creates a next objective builder for multicast.
284 *
285 * @param mcastIp multicast group
286 * @param assignedVlan assigned VLAN ID
287 * @param outPorts set of output port numbers
288 * @param nextId the next id
289 * @return next objective builder
290 */
291 NextObjective.Builder nextObjBuilder(IpAddress mcastIp, VlanId assignedVlan,
292 Set<PortNumber> outPorts, Integer nextId) {
293 // If nextId is null allocate a new one
294 if (nextId == null) {
295 nextId = srManager.flowObjectiveService.allocateNextId();
296 }
297 // Build the meta selector with the fwd objective info
298 TrafficSelector metadata =
299 DefaultTrafficSelector.builder()
300 .matchVlanId(assignedVlan)
301 .matchIPDst(mcastIp.toIpPrefix())
302 .build();
303 // Define the nextobjective type
304 NextObjective.Builder nextObjBuilder = DefaultNextObjective
305 .builder().withId(nextId)
306 .withType(NextObjective.Type.BROADCAST)
307 .fromApp(srManager.appId())
308 .withMeta(metadata);
309 // Add the output ports
310 outPorts.forEach(port -> {
311 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
312 if (egressVlan().equals(VlanId.NONE)) {
313 tBuilder.popVlan();
314 }
315 tBuilder.setOutput(port);
316 nextObjBuilder.addTreatment(tBuilder.build());
317 });
318 // Done return the complete builder
319 return nextObjBuilder;
320 }
321
322 /**
323 * Creates a forwarding objective builder for multicast.
324 *
325 * @param mcastIp multicast group
326 * @param assignedVlan assigned VLAN ID
327 * @param nextId next ID of the L3 multicast group
328 * @return forwarding objective builder
329 */
330 ForwardingObjective.Builder fwdObjBuilder(IpAddress mcastIp,
331 VlanId assignedVlan, int nextId) {
332 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
333 // Let's the matching on the group address
334 // TODO SSM support in future
335 if (mcastIp.isIp6()) {
336 sbuilder.matchEthType(Ethernet.TYPE_IPV6);
337 sbuilder.matchIPv6Dst(mcastIp.toIpPrefix());
338 } else {
339 sbuilder.matchEthType(Ethernet.TYPE_IPV4);
340 sbuilder.matchIPDst(mcastIp.toIpPrefix());
341 }
342 // Then build the meta selector
343 TrafficSelector.Builder metabuilder = DefaultTrafficSelector.builder();
344 metabuilder.matchVlanId(assignedVlan);
345 // Finally return the completed builder
346 ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective.builder();
347 fwdBuilder.withSelector(sbuilder.build())
348 .withMeta(metabuilder.build())
349 .nextStep(nextId)
350 .withFlag(ForwardingObjective.Flag.SPECIFIC)
351 .fromApp(srManager.appId())
352 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
353 return fwdBuilder;
354 }
355
356 /**
357 * Creates a filtering objective builder for multicast.
358 *
359 * @param ingressPort ingress port of the multicast stream
360 * @param assignedVlan assigned VLAN ID
361 * @param mcastIp the group address
362 * @param routerMac router MAC. This is carried in metadata and used from some switches that
363 * need to put unicast entry before multicast entry in TMAC table.
364 * @param mcastRole the Multicast role
365 * @return filtering objective builder
366 */
367 private FilteringObjective.Builder filterObjBuilder(PortNumber ingressPort, VlanId assignedVlan,
368 IpAddress mcastIp, MacAddress routerMac, McastRole mcastRole) {
369 FilteringObjective.Builder filtBuilder = DefaultFilteringObjective.builder();
370 // Let's add the in port matching and the priority
371 filtBuilder.withKey(Criteria.matchInPort(ingressPort))
372 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
373 // According to the mcast role we match on the proper vlan
374 // If the role is null we are on the transit or on the egress
375 if (mcastRole == null) {
376 filtBuilder.addCondition(Criteria.matchVlanId(egressVlan()));
377 } else {
378 filtBuilder.addCondition(Criteria.matchVlanId(ingressVlan()));
379 }
380 // According to the IP type we set the proper match on the mac address
381 if (mcastIp.isIp4()) {
382 filtBuilder.addCondition(Criteria.matchEthDstMasked(MacAddress.IPV4_MULTICAST,
383 MacAddress.IPV4_MULTICAST_MASK));
384 } else {
385 filtBuilder.addCondition(Criteria.matchEthDstMasked(MacAddress.IPV6_MULTICAST,
386 MacAddress.IPV6_MULTICAST_MASK));
387 }
388 // We finally build the meta treatment
389 TrafficTreatment tt = DefaultTrafficTreatment.builder()
390 .pushVlan().setVlanId(assignedVlan)
391 .setEthDst(routerMac)
392 .build();
393 filtBuilder.withMeta(tt);
394 // Done, we return a permit filtering objective
395 return filtBuilder.permit().fromApp(srManager.appId());
396 }
397
398 /**
399 * Gets output ports information from treatments.
400 *
401 * @param treatments collection of traffic treatments
402 * @return set of output port numbers
403 */
404 Set<PortNumber> getPorts(Collection<TrafficTreatment> treatments) {
405 ImmutableSet.Builder<PortNumber> builder = ImmutableSet.builder();
406 treatments.forEach(treatment -> treatment.allInstructions().stream()
407 .filter(instr -> instr instanceof Instructions.OutputInstruction)
408 .forEach(instr -> builder.add(((Instructions.OutputInstruction) instr).port())));
409 return builder.build();
410 }
Pier96f63cb2018-04-17 16:29:56 +0200411
412 /**
413 * Returns the hash of the group address.
414 *
415 * @param ipAddress the ip address
416 * @return the hash of the address
417 */
418 private Long hasher(IpAddress ipAddress) {
419 return HASH_FN.newHasher()
420 .putBytes(ipAddress.toOctets())
421 .hash()
422 .asLong();
423 }
424
425 /**
426 * Given a multicast group define a leader for it.
427 *
428 * @param mcastIp the group address
429 * @return true if the instance is the leader of the group
430 */
431 boolean isLeader(IpAddress mcastIp) {
432 // Get our id
433 final NodeId currentNodeId = srManager.clusterService.getLocalNode().id();
434 // Get the leader for this group using the ip address as key
435 final NodeId leader = srManager.workPartitionService.getLeader(mcastIp, this::hasher);
436 // If there is not a leader, let's send an error
437 if (leader == null) {
438 log.error("Fail to elect a leader for {}.", mcastIp);
439 return false;
440 }
441 // Update cache and return operation result
442 mcastLeaderCache.put(mcastIp, leader);
443 return currentNodeId.equals(leader);
444 }
445
446 /**
447 * Given a multicast group withdraw its leader.
448 *
449 * @param mcastIp the group address
450 */
451 void withdrawLeader(IpAddress mcastIp) {
452 // For now just update the cache
453 mcastLeaderCache.remove(mcastIp);
454 }
455
456 Map<IpAddress, NodeId> getMcastLeaders(IpAddress mcastIp) {
457 // If mcast ip is present
458 if (mcastIp != null) {
459 return mcastLeaderCache.entrySet().stream()
460 .filter(entry -> entry.getKey().equals(mcastIp))
461 .collect(Collectors.toMap(Map.Entry::getKey,
462 Map.Entry::getValue));
463 }
464 // Otherwise take all the groups
465 return ImmutableMap.copyOf(mcastLeaderCache);
466 }
Pierb0328e42018-03-27 11:29:42 -0700467}