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