blob: 6815c5d8a882c8089133e217297c044093acbd80 [file] [log] [blame]
Tunahan Sezen03e55272020-04-18 09:18:53 +00001/*
2 * Copyright 2017-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 */
16package org.opencord.maclearner.app.impl;
17
18import com.google.common.collect.ImmutableMap;
19import com.google.common.collect.ImmutableSet;
20import com.google.common.collect.Maps;
21import com.google.common.collect.Sets;
Tunahan Sezen1f65c902020-09-08 13:10:16 +000022import org.onlab.packet.EthType;
23import org.onlab.packet.IpAddress;
Tunahan Sezen03e55272020-04-18 09:18:53 +000024import org.onlab.packet.VlanId;
Andrea Campanellabccc74e2020-09-30 14:05:24 +020025import org.onlab.util.PredictableExecutor;
Tunahan Sezen03e55272020-04-18 09:18:53 +000026import org.onlab.util.Tools;
27import org.onosproject.cfg.ComponentConfigService;
Tunahan Sezen1f65c902020-09-08 13:10:16 +000028import org.onosproject.cluster.ClusterEvent;
29import org.onosproject.cluster.ClusterEventListener;
30import org.onosproject.cluster.ClusterService;
31import org.onosproject.cluster.ControllerNode;
32import org.onosproject.cluster.NodeId;
Tunahan Sezen03e55272020-04-18 09:18:53 +000033import org.onosproject.core.ApplicationId;
Tunahan Sezen1f65c902020-09-08 13:10:16 +000034import org.onosproject.net.ConnectPoint;
35import org.onosproject.net.Device;
Harsh Awasthi6bd73602022-01-31 18:40:25 +053036import org.onosproject.net.ElementId;
37import org.onosproject.net.Host;
Tunahan Sezen1f65c902020-09-08 13:10:16 +000038import org.onosproject.net.HostId;
39import org.onosproject.net.HostLocation;
40import org.onosproject.net.Link;
Harsh Awasthi6bd73602022-01-31 18:40:25 +053041import org.onosproject.net.Port;
Tunahan Sezen03e55272020-04-18 09:18:53 +000042import org.onosproject.net.device.DeviceEvent;
43import org.onosproject.net.device.DeviceListener;
44import org.onosproject.net.device.DeviceService;
Harsh Awasthi6bd73602022-01-31 18:40:25 +053045import org.onosproject.net.flow.DefaultTrafficTreatment;
46import org.onosproject.net.flow.TrafficTreatment;
47import org.onosproject.net.host.HostService;
Tunahan Sezen1f65c902020-09-08 13:10:16 +000048import org.onosproject.net.link.LinkService;
Harsh Awasthi6bd73602022-01-31 18:40:25 +053049import org.onosproject.net.packet.DefaultOutboundPacket;
50import org.onosproject.net.packet.OutboundPacket;
Tunahan Sezen1f65c902020-09-08 13:10:16 +000051import org.onosproject.net.topology.Topology;
52import org.onosproject.net.topology.TopologyService;
Tunahan Sezen03e55272020-04-18 09:18:53 +000053import org.onosproject.store.service.ConsistentMap;
54import org.onosproject.store.service.StorageService;
55import org.onosproject.store.service.Versioned;
56import org.opencord.maclearner.api.DefaultMacLearner;
Tunahan Sezen1f65c902020-09-08 13:10:16 +000057import org.opencord.maclearner.api.MacLearnerHostLocationService;
Tunahan Sezen03e55272020-04-18 09:18:53 +000058import org.opencord.maclearner.api.MacDeleteResult;
59import org.opencord.maclearner.api.MacLearnerEvent;
60import org.opencord.maclearner.api.MacLearnerKey;
61import org.opencord.maclearner.api.MacLearnerListener;
62import org.opencord.maclearner.api.MacLearnerProvider;
63import org.opencord.maclearner.api.MacLearnerProviderService;
64import org.opencord.maclearner.api.MacLearnerService;
65import org.opencord.maclearner.api.MacLearnerValue;
Harsh Awasthi6bd73602022-01-31 18:40:25 +053066import org.opencord.sadis.BaseInformationService;
67import org.opencord.sadis.SadisService;
68import org.opencord.sadis.SubscriberAndDeviceInformation;
Tunahan Sezen03e55272020-04-18 09:18:53 +000069import org.osgi.service.component.ComponentContext;
70import org.osgi.service.component.annotations.Activate;
71import org.osgi.service.component.annotations.Component;
72import org.osgi.service.component.annotations.Deactivate;
73import org.osgi.service.component.annotations.Modified;
74import org.osgi.service.component.annotations.Reference;
75import org.onlab.packet.DHCP;
76import org.onlab.packet.Ethernet;
77import org.onlab.packet.IPv4;
78import org.onlab.packet.MacAddress;
79import org.onlab.packet.UDP;
80import org.onlab.packet.dhcp.DhcpOption;
81import org.onlab.util.KryoNamespace;
82import org.onosproject.core.CoreService;
83import org.onosproject.net.DeviceId;
84import org.onosproject.net.PortNumber;
85import org.onosproject.net.packet.PacketContext;
86import org.onosproject.net.packet.PacketProcessor;
87import org.onosproject.net.packet.PacketService;
88import org.onosproject.net.provider.AbstractListenerProviderRegistry;
89import org.onosproject.net.provider.AbstractProviderService;
90import org.onosproject.store.LogicalTimestamp;
91import org.onosproject.store.serializers.KryoNamespaces;
92import org.onosproject.store.service.Serializer;
93import org.onosproject.store.service.WallClockTimestamp;
Harsh Awasthi6bd73602022-01-31 18:40:25 +053094import org.osgi.service.component.annotations.ReferenceCardinality;
95import org.osgi.service.component.annotations.ReferencePolicy;
Tunahan Sezen03e55272020-04-18 09:18:53 +000096import org.slf4j.Logger;
97import org.slf4j.LoggerFactory;
98
99import java.net.URI;
Harsh Awasthi6bd73602022-01-31 18:40:25 +0530100import java.nio.ByteBuffer;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000101import java.util.Date;
102import java.util.Dictionary;
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000103import java.util.List;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000104import java.util.Map;
105import java.util.Optional;
106import java.util.Properties;
107import java.util.Set;
108import java.util.concurrent.ExecutorService;
109import java.util.concurrent.Executors;
110import java.util.concurrent.ScheduledExecutorService;
111import java.util.concurrent.ScheduledFuture;
112import java.util.concurrent.TimeUnit;
113import java.util.stream.Collectors;
114
115import static com.google.common.base.Strings.isNullOrEmpty;
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000116import static java.util.stream.Collectors.toList;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000117import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
118import static org.onlab.util.Tools.groupedThreads;
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000119import static org.opencord.maclearner.app.impl.OsgiPropertyConstants.AUTO_CLEAR_MAC_MAPPING;
120import static org.opencord.maclearner.app.impl.OsgiPropertyConstants.AUTO_CLEAR_MAC_MAPPING_DEFAULT;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000121import static org.opencord.maclearner.app.impl.OsgiPropertyConstants.CACHE_DURATION_DEFAULT;
122import static org.opencord.maclearner.app.impl.OsgiPropertyConstants.CACHE_DURATION;
Harsh Awasthi6bd73602022-01-31 18:40:25 +0530123import static org.opencord.maclearner.app.impl.OsgiPropertyConstants.ENABLE_DHCP_FORWARD;
124import static org.opencord.maclearner.app.impl.OsgiPropertyConstants.ENABLE_DHCP_FORWARD_DEFAULT;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000125import static org.osgi.service.component.annotations.ReferenceCardinality.MANDATORY;
126
127/**
128 * Mac Learner Service implementation.
129 */
130@Component(immediate = true,
131 property = {
132 CACHE_DURATION + ":Integer=" + CACHE_DURATION_DEFAULT,
Harsh Awasthi6bd73602022-01-31 18:40:25 +0530133 AUTO_CLEAR_MAC_MAPPING + ":Boolean=" + AUTO_CLEAR_MAC_MAPPING_DEFAULT,
134 ENABLE_DHCP_FORWARD + ":Boolean=" + ENABLE_DHCP_FORWARD_DEFAULT
Tunahan Sezen03e55272020-04-18 09:18:53 +0000135 },
136 service = MacLearnerService.class
137)
138public class MacLearnerManager
139 extends AbstractListenerProviderRegistry<MacLearnerEvent, MacLearnerListener,
140 MacLearnerProvider, MacLearnerProviderService>
141 implements MacLearnerService {
142
143 private static final String MAC_LEARNER_APP = "org.opencord.maclearner";
144 private static final String MAC_LEARNER = "maclearner";
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000145 private static final String OLT_MANUFACTURER_KEY = "VOLTHA";
Tunahan Sezen03e55272020-04-18 09:18:53 +0000146 private ApplicationId appId;
147
148 private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
149 private ScheduledFuture scheduledFuture;
150
151 private final Logger log = LoggerFactory.getLogger(getClass());
152
Harsh Awasthi6bd73602022-01-31 18:40:25 +0530153 protected BaseInformationService<SubscriberAndDeviceInformation> subsService;
154
155 @Reference(cardinality = ReferenceCardinality.OPTIONAL,
156 bind = "bindSadisService",
157 unbind = "unbindSadisService",
158 policy = ReferencePolicy.DYNAMIC)
159 protected volatile SadisService sadisService;
160
Tunahan Sezen03e55272020-04-18 09:18:53 +0000161 @Reference(cardinality = MANDATORY)
162 protected CoreService coreService;
163
164 @Reference(cardinality = MANDATORY)
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000165 protected ClusterService clusterService;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000166
167 @Reference(cardinality = MANDATORY)
168 protected DeviceService deviceService;
169
170 @Reference(cardinality = MANDATORY)
171 protected PacketService packetService;
172
173 @Reference(cardinality = MANDATORY)
174 protected StorageService storageService;
175
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000176 @Reference(cardinality = MANDATORY)
177 protected TopologyService topologyService;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000178
179 @Reference(cardinality = MANDATORY)
180 protected ComponentConfigService componentConfigService;
181
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000182 @Reference(cardinality = MANDATORY)
183 protected MacLearnerHostLocationService hostLocService;
184
185 @Reference(cardinality = MANDATORY)
186 protected LinkService linkService;
187
Harsh Awasthi6bd73602022-01-31 18:40:25 +0530188 @Reference(cardinality = ReferenceCardinality.MANDATORY)
189 protected HostService hostService;
190
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000191 private final MacLearnerPacketProcessor macLearnerPacketProcessor =
Tunahan Sezen03e55272020-04-18 09:18:53 +0000192 new MacLearnerPacketProcessor();
193
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000194 private final DeviceListener deviceListener = new InternalDeviceListener();
195 private final ClusterEventListener clusterListener = new InternalClusterListener();
196
197 private ConsistentHasher hasher;
198 public static final int HASH_WEIGHT = 10;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000199
200 /**
Harsh Awasthi6bd73602022-01-31 18:40:25 +0530201 * Enables Dhcp forwarding.
202 */
203 protected boolean enableDhcpForward = ENABLE_DHCP_FORWARD_DEFAULT;
204
205 /**
Tunahan Sezen03e55272020-04-18 09:18:53 +0000206 * Minimum duration of mapping, mapping can be exist until 2*cacheDuration because of cleanerTimer fixed rate.
207 */
208 protected int cacheDurationSec = CACHE_DURATION_DEFAULT;
209
210 /**
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000211 * Removes mappings from MAC Address Map for removed events.
Tunahan Sezen03e55272020-04-18 09:18:53 +0000212 */
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000213 protected boolean autoClearMacMapping = AUTO_CLEAR_MAC_MAPPING_DEFAULT;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000214
215 private ConsistentMap<DeviceId, Set<PortNumber>> ignoredPortsMap;
216 private ConsistentMap<MacLearnerKey, MacLearnerValue> macAddressMap;
217
218 protected ExecutorService eventExecutor;
Andrea Campanellabccc74e2020-09-30 14:05:24 +0200219 // Packet workers - 0 will leverage available processors
220 private static final int DEFAULT_THREADS = 0;
221 private PredictableExecutor packetWorkers;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000222
223 @Activate
224 public void activate() {
225 eventExecutor = Executors.newFixedThreadPool(5, groupedThreads("onos/maclearner",
226 "events-%d", log));
227 appId = coreService.registerApplication(MAC_LEARNER_APP);
228 componentConfigService.registerProperties(getClass());
229 eventDispatcher.addSink(MacLearnerEvent.class, listenerRegistry);
230 macAddressMap = storageService.<MacLearnerKey, MacLearnerValue>consistentMapBuilder()
231 .withName(MAC_LEARNER)
232 .withSerializer(createSerializer())
233 .withApplicationId(appId)
234 .build();
235 ignoredPortsMap = storageService
236 .<DeviceId, Set<PortNumber>>consistentMapBuilder()
237 .withName("maclearner-ignored")
238 .withSerializer(createSerializer())
239 .withApplicationId(appId)
240 .build();
Andrea Campanellabccc74e2020-09-30 14:05:24 +0200241 packetWorkers = new PredictableExecutor(DEFAULT_THREADS,
242 groupedThreads("onos/mac-learner-host-loc-provider",
243 "packet-worker-%d", log));
Tunahan Sezen03e55272020-04-18 09:18:53 +0000244 //mac learner must process the packet before director processors
245 packetService.addProcessor(macLearnerPacketProcessor,
246 PacketProcessor.advisor(2));
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000247 List<NodeId> readyNodes = clusterService.getNodes().stream()
248 .filter(c -> clusterService.getState(c.id()) == ControllerNode.State.READY)
249 .map(ControllerNode::id)
250 .collect(toList());
251 hasher = new ConsistentHasher(readyNodes, HASH_WEIGHT);
252 clusterService.addListener(clusterListener);
253 deviceService.addListener(deviceListener);
Tunahan Sezen03e55272020-04-18 09:18:53 +0000254 createSchedulerForClearMacMappings();
Harsh Awasthi6bd73602022-01-31 18:40:25 +0530255
256 if (sadisService != null) {
257 subsService = sadisService.getSubscriberInfoService();
258 } else {
259 log.warn("Sadis is not running");
260 }
261
Tunahan Sezen03e55272020-04-18 09:18:53 +0000262 log.info("{} is started.", getClass().getSimpleName());
263 }
264
265 private Serializer createSerializer() {
266 return Serializer.using(KryoNamespace.newBuilder()
267 .register(KryoNamespace.newBuilder().build(MAC_LEARNER))
268 // not so robust way to avoid collision with other
269 // user supplied registrations
270 .nextId(KryoNamespaces.BEGIN_USER_CUSTOM_ID + 100)
271 .register(KryoNamespaces.BASIC)
272 .register(LogicalTimestamp.class)
273 .register(WallClockTimestamp.class)
274 .register(MacLearnerKey.class)
275 .register(MacLearnerValue.class)
276 .register(DeviceId.class)
277 .register(URI.class)
278 .register(PortNumber.class)
279 .register(VlanId.class)
280 .register(MacAddress.class)
281 .build(MAC_LEARNER + "-ecmap"));
282 }
283
284 private void createSchedulerForClearMacMappings() {
285 scheduledFuture = scheduledExecutorService.scheduleAtFixedRate(this::clearExpiredMacMappings,
286 0,
287 cacheDurationSec,
288 TimeUnit.SECONDS);
289 }
290
291 private void clearExpiredMacMappings() {
292 Date curDate = new Date();
293 for (Map.Entry<MacLearnerKey, Versioned<MacLearnerValue>> entry : macAddressMap.entrySet()) {
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000294 if (!isDeviceMine(entry.getKey().getDeviceId())) {
295 continue;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000296 }
297 if (curDate.getTime() - entry.getValue().value().getTimestamp() > cacheDurationSec * 1000) {
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000298 removeFromMacAddressMap(entry.getKey(), false);
Tunahan Sezen03e55272020-04-18 09:18:53 +0000299 }
300 }
301 }
302
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000303 /**
304 * Determines if this instance should handle this device based on
305 * consistent hashing.
306 *
307 * @param id device ID
308 * @return true if this instance should handle the device, otherwise false
309 */
310 private boolean isDeviceMine(DeviceId id) {
311 NodeId nodeId = hasher.hash(id.toString());
312 if (log.isDebugEnabled()) {
313 log.debug("Node that will handle {} is {}", id, nodeId);
314 }
315 return nodeId.equals(clusterService.getLocalNode().id());
316 }
317
Harsh Awasthi6bd73602022-01-31 18:40:25 +0530318 protected void bindSadisService(SadisService service) {
319 this.subsService = service.getSubscriberInfoService();
320 log.info("Sadis service is loaded");
321 }
322
323 protected void unbindSadisService(SadisService service) {
324 this.subsService = null;
325 log.info("Sadis service is unloaded");
326 }
327
Tunahan Sezen03e55272020-04-18 09:18:53 +0000328 @Deactivate
329 public void deactivate() {
330 if (scheduledFuture != null) {
331 scheduledFuture.cancel(true);
332 }
333 packetService.removeProcessor(macLearnerPacketProcessor);
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000334 clusterService.removeListener(clusterListener);
335 deviceService.removeListener(deviceListener);
Tunahan Sezen03e55272020-04-18 09:18:53 +0000336 eventDispatcher.removeSink(MacLearnerEvent.class);
Andrea Campanellabccc74e2020-09-30 14:05:24 +0200337 packetWorkers.shutdown();
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000338 if (eventExecutor != null) {
339 eventExecutor.shutdown();
340 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000341 componentConfigService.unregisterProperties(getClass(), false);
342 log.info("{} is stopped.", getClass().getSimpleName());
343 }
344
345 @Modified
346 public void modified(ComponentContext context) {
347 Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties();
348
349 String cacheDuration = Tools.get(properties, CACHE_DURATION);
350 if (!isNullOrEmpty(cacheDuration)) {
351 int cacheDur = Integer.parseInt(cacheDuration.trim());
352 if (cacheDurationSec != cacheDur) {
353 setMacMappingCacheDuration(cacheDur);
354 }
355 }
Harsh Awasthi6bd73602022-01-31 18:40:25 +0530356
357 Boolean o = Tools.isPropertyEnabled(properties, ENABLE_DHCP_FORWARD);
358 if (o != null) {
359 if (o != enableDhcpForward) {
360 log.info("Changing enableDhcpForward to: {} from {}", o, enableDhcpForward);
361 enableDhcpForward = o;
362 }
363 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000364 }
365
366 private Integer setMacMappingCacheDuration(Integer second) {
367 if (cacheDurationSec == second) {
368 log.info("Cache duration already: {}", second);
369 return second;
370 }
371 log.info("Changing cache duration to: {} second from {} second...", second, cacheDurationSec);
372 this.cacheDurationSec = second;
373 if (scheduledFuture != null) {
374 scheduledFuture.cancel(false);
375 }
376 createSchedulerForClearMacMappings();
377 return cacheDurationSec;
378 }
379
380 @Override
381 public void addPortToIgnore(DeviceId deviceId, PortNumber portNumber) {
382 log.info("Adding ignore port: {} {}", deviceId, portNumber);
383 Set<PortNumber> updatedPorts = Sets.newHashSet();
384 Versioned<Set<PortNumber>> storedPorts = ignoredPortsMap.get(deviceId);
385 if (storedPorts == null || !storedPorts.value().contains(portNumber)) {
386 if (storedPorts != null) {
387 updatedPorts.addAll(storedPorts.value());
388 }
389 updatedPorts.add(portNumber);
390 ignoredPortsMap.put(deviceId, updatedPorts);
391 log.info("Port:{} of device: {} is added to ignoredPortsMap.", portNumber, deviceId);
392 deleteMacMappings(deviceId, portNumber);
393 } else {
394 log.warn("Port:{} of device: {} is already ignored.", portNumber, deviceId);
395 }
396 }
397
398 @Override
399 public void removeFromIgnoredPorts(DeviceId deviceId, PortNumber portNumber) {
400 log.info("Removing ignore port: {} {}", deviceId, portNumber);
401 Versioned<Set<PortNumber>> storedPorts = ignoredPortsMap.get(deviceId);
402 if (storedPorts != null && storedPorts.value().contains(portNumber)) {
403 if (storedPorts.value().size() == 1) {
404 ignoredPortsMap.remove(deviceId);
405 } else {
406 Set<PortNumber> updatedPorts = Sets.newHashSet();
407 updatedPorts.addAll(storedPorts.value());
408 updatedPorts.remove(portNumber);
409 ignoredPortsMap.put(deviceId, updatedPorts);
410 }
411 log.info("Port:{} of device: {} is removed ignoredPortsMap.", portNumber, deviceId);
412 } else {
413 log.warn("Port:{} of device: {} is not found in ignoredPortsMap.", portNumber, deviceId);
414 }
415 }
416
417 @Override
418 public ImmutableMap<MacLearnerKey, MacAddress> getAllMappings() {
419 log.info("Getting all MAC Mappings");
420 Map<MacLearnerKey, MacAddress> immutableMap = Maps.newHashMap();
421 macAddressMap.entrySet().forEach(entry ->
422 immutableMap.put(entry.getKey(),
423 entry.getValue() != null ? entry.getValue().value().getMacAddress() : null));
424 return ImmutableMap.copyOf(immutableMap);
425 }
426
427 @Override
428 public Optional<MacAddress> getMacMapping(DeviceId deviceId, PortNumber portNumber, VlanId vlanId) {
429 log.info("Getting MAC mapping for: {} {} {}", deviceId, portNumber, vlanId);
430 Versioned<MacLearnerValue> value = macAddressMap.get(new MacLearnerKey(deviceId, portNumber, vlanId));
431 return value != null ? Optional.ofNullable(value.value().getMacAddress()) : Optional.empty();
432 }
433
434 @Override
435 public MacDeleteResult deleteMacMapping(DeviceId deviceId, PortNumber portNumber, VlanId vlanId) {
436 log.info("Deleting MAC mapping for: {} {} {}", deviceId, portNumber, vlanId);
437 MacLearnerKey key = new MacLearnerKey(deviceId, portNumber, vlanId);
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000438 return removeFromMacAddressMap(key, true);
Tunahan Sezen03e55272020-04-18 09:18:53 +0000439 }
440
441 @Override
442 public boolean deleteMacMappings(DeviceId deviceId, PortNumber portNumber) {
443 log.info("Deleting MAC mappings for: {} {}", deviceId, portNumber);
444 Set<Map.Entry<MacLearnerKey, Versioned<MacLearnerValue>>> entriesToDelete = macAddressMap.entrySet().stream()
445 .filter(entry -> entry.getKey().getDeviceId().equals(deviceId) &&
446 entry.getKey().getPortNumber().equals(portNumber))
447 .collect(Collectors.toSet());
448 if (entriesToDelete.isEmpty()) {
449 log.warn("MAC mapping not found for deviceId: {} and portNumber: {}", deviceId, portNumber);
450 return false;
451 }
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000452 entriesToDelete.forEach(e -> removeFromMacAddressMap(e.getKey(), true));
Tunahan Sezen03e55272020-04-18 09:18:53 +0000453 return true;
454 }
455
456 @Override
457 public boolean deleteMacMappings(DeviceId deviceId) {
458 log.info("Deleting MAC mappings for: {}", deviceId);
459 Set<Map.Entry<MacLearnerKey, Versioned<MacLearnerValue>>> entriesToDelete = macAddressMap.entrySet().stream()
460 .filter(entry -> entry.getKey().getDeviceId().equals(deviceId))
461 .collect(Collectors.toSet());
462 if (entriesToDelete.isEmpty()) {
463 log.warn("MAC mapping not found for deviceId: {}", deviceId);
464 return false;
465 }
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000466 entriesToDelete.forEach(e -> removeFromMacAddressMap(e.getKey(), true));
Tunahan Sezen03e55272020-04-18 09:18:53 +0000467 return true;
468 }
469
470 @Override
471 public ImmutableSet<DeviceId> getMappedDevices() {
472 Set<DeviceId> deviceIds = Sets.newHashSet();
473 for (Map.Entry<MacLearnerKey, MacAddress> entry : getAllMappings().entrySet()) {
474 deviceIds.add(entry.getKey().getDeviceId());
475 }
476 return ImmutableSet.copyOf(deviceIds);
477 }
478
479 @Override
480 public ImmutableSet<PortNumber> getMappedPorts() {
481 Set<PortNumber> portNumbers = Sets.newHashSet();
482 for (Map.Entry<MacLearnerKey, MacAddress> entry : getAllMappings().entrySet()) {
483 portNumbers.add(entry.getKey().getPortNumber());
484 }
485 return ImmutableSet.copyOf(portNumbers);
486 }
487
488 @Override
489 public ImmutableMap<DeviceId, Set<PortNumber>> getIgnoredPorts() {
490 log.info("Getting ignored ports");
491 Map<DeviceId, Set<PortNumber>> immutableMap = Maps.newHashMap();
492 ignoredPortsMap.forEach(entry -> immutableMap.put(entry.getKey(),
493 entry.getValue() != null ? entry.getValue().value() : Sets.newHashSet()));
494 return ImmutableMap.copyOf(immutableMap);
495 }
496
497 @Override
498 protected MacLearnerProviderService createProviderService(MacLearnerProvider provider) {
499 return new InternalMacLearnerProviderService(provider);
500 }
501
502 private static class InternalMacLearnerProviderService extends AbstractProviderService<MacLearnerProvider>
503 implements MacLearnerProviderService {
504
505 InternalMacLearnerProviderService(MacLearnerProvider provider) {
506 super(provider);
507 }
508 }
509
510 private void sendMacLearnerEvent(MacLearnerEvent.Type type, DeviceId deviceId,
511 PortNumber portNumber, VlanId vlanId, MacAddress macAddress) {
512 log.info("Sending MAC Learner Event: type: {} deviceId: {} portNumber: {} vlanId: {} macAddress: {}",
513 type, deviceId, portNumber, vlanId.toShort(), macAddress);
514 DefaultMacLearner macLearner = new DefaultMacLearner(deviceId, portNumber, vlanId, macAddress);
515 MacLearnerEvent macLearnerEvent = new MacLearnerEvent(type, macLearner);
516 post(macLearnerEvent);
517 }
518
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000519 private boolean isOltDevice(Device device) {
520 return device.manufacturer().contains(OLT_MANUFACTURER_KEY);
521 }
522
Tunahan Sezen03e55272020-04-18 09:18:53 +0000523 private class MacLearnerPacketProcessor implements PacketProcessor {
524
525 @Override
526 public void process(PacketContext context) {
Andrea Campanellabccc74e2020-09-30 14:05:24 +0200527 packetWorkers.submit(() -> processPacketInternal(context));
528 }
529
530 private void processPacketInternal(PacketContext context) {
Tunahan Sezen03e55272020-04-18 09:18:53 +0000531 // process the packet and get the payload
532 Ethernet packet = context.inPacket().parsed();
533
534 if (packet == null) {
535 log.warn("Packet is null");
536 return;
537 }
538
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000539 ConnectPoint cp = context.inPacket().receivedFrom();
540 DeviceId deviceId = cp.deviceId();
541 PortNumber sourcePort = cp.port();
542 MacAddress srcMac = packet.getSourceMAC();
543 MacAddress dstMac = packet.getDestinationMAC();
544
545 Device device = deviceService.getDevice(deviceId);
546 if (!isOltDevice(device)) { // not handle non OLT device packets
547 log.debug("Packet received from non-OLT device: {}. Returning.", deviceId);
548 return;
549 }
550
551 if (srcMac.isBroadcast() || srcMac.isMulticast()) {
552 log.debug("Broadcast or multicast packet received from: {}. Returning.", cp);
553 return;
554 }
555
556 // Ignore location probes
557 if (dstMac.isOnos() && !MacAddress.NONE.equals(dstMac)) {
558 log.debug("Location probe. cp: {}", cp);
559 return;
560 }
561
562 // If this arrived on control port, bail out.
563 if (cp.port().isLogical()) {
564 log.debug("Packet received from logical port: {}", cp);
565 return;
566 }
567
568 // If this is not an edge port, bail out.
569 Topology topology = topologyService.currentTopology();
570 if (topologyService.isInfrastructure(topology, cp)) {
571 log.debug("Packet received from non-edge port: {}", cp);
572 return;
573 }
574
575 VlanId vlan = VlanId.vlanId(packet.getVlanID());
576 VlanId outerVlan = VlanId.vlanId(packet.getQinQVID());
577 VlanId innerVlan = VlanId.NONE;
578 EthType outerTpid = EthType.EtherType.UNKNOWN.ethType();
579 // Set up values for double-tagged hosts
580 if (outerVlan.toShort() != Ethernet.VLAN_UNTAGGED) {
581 innerVlan = vlan;
582 vlan = outerVlan;
583 outerTpid = EthType.EtherType.lookup(packet.getQinQTPID()).ethType();
584 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000585
586 Versioned<Set<PortNumber>> ignoredPortsOfDevice = ignoredPortsMap.get(deviceId);
587 if (ignoredPortsOfDevice != null && ignoredPortsOfDevice.value().contains(sourcePort)) {
588 log.warn("Port Number: {} is in ignoredPortsMap. Returning", sourcePort);
589 return;
590 }
591
592 if (packet.getEtherType() == Ethernet.TYPE_IPV4) {
593 IPv4 ipv4Packet = (IPv4) packet.getPayload();
594
595 if (ipv4Packet.getProtocol() == IPv4.PROTOCOL_UDP) {
596 UDP udpPacket = (UDP) ipv4Packet.getPayload();
597 int udpSourcePort = udpPacket.getSourcePort();
598 if ((udpSourcePort == UDP.DHCP_CLIENT_PORT) || (udpSourcePort == UDP.DHCP_SERVER_PORT)) {
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000599 // Update host location
600 HostLocation hloc = new HostLocation(cp, System.currentTimeMillis());
601 HostLocation auxLocation = null;
602 Optional<Link> optLink = linkService.getDeviceLinks(deviceId).stream().findFirst();
603 if (optLink.isPresent()) {
604 Link link = optLink.get();
605 auxLocation = !link.src().deviceId().equals(deviceId) ?
606 new HostLocation(link.src(), System.currentTimeMillis()) :
607 new HostLocation(link.dst(), System.currentTimeMillis());
608 } else {
609 log.debug("Link not found for device {}", deviceId);
610 }
611 hostLocService.createOrUpdateHost(HostId.hostId(packet.getSourceMAC(), vlan),
Andrea Campanellabccc74e2020-09-30 14:05:24 +0200612 packet.getSourceMAC(), packet.getDestinationMAC(),
613 vlan, innerVlan, outerTpid,
614 hloc, auxLocation, null);
Tunahan Sezen03e55272020-04-18 09:18:53 +0000615 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
616 //This packet is dhcp.
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000617 processDhcpPacket(context, packet, dhcpPayload, sourcePort, deviceId, vlan);
Harsh Awasthi6bd73602022-01-31 18:40:25 +0530618
619 if (enableDhcpForward) {
620 // Forward DHCP Packet to either uni or nni.
621 forwardDhcpPacket(packet, dhcpPayload, device, vlan);
622 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000623 }
624 }
625 }
626 }
627
Harsh Awasthi6bd73602022-01-31 18:40:25 +0530628 /**
629 * Returns the connectPoint which is the uplink port of the OLT.
630 */
631 private ConnectPoint getUplinkConnectPointOfOlt(DeviceId dId) {
632
633 Device device = deviceService.getDevice(dId);
634
635 if (device == null) {
636 log.warn("Could not find device for device ID {}", dId);
637 return null;
638 }
639
640 SubscriberAndDeviceInformation deviceInfo = subsService.get(device.serialNumber());
641 if (deviceInfo != null) {
642 log.debug("getUplinkConnectPointOfOlt DeviceId: {} devInfo: {}", dId, deviceInfo);
643 PortNumber pNum = PortNumber.portNumber(deviceInfo.uplinkPort());
644 Port port = deviceService.getPort(device.id(), pNum);
645 if (port != null) {
646 return new ConnectPoint(device.id(), pNum);
647 } else {
648 log.warn("Unable to find Port in deviceService for deice ID : {}, port : {}", dId, pNum);
649 }
650 } else {
651 log.warn("Unable to find Sadis entry for device ID : {}, device serial : {}",
652 dId, device.serialNumber());
653 }
654
655 return null;
656 }
657
658 /***
659 * Forwards the packet to uni port or nni port based on the DHCP source port.
660 * Client DHCP packets are transparently forwarded to the nni port.
661 * Server DHCP replies are forwared to the respective uni port based on the (mac,vlan) lookup
662 */
663 private void forwardDhcpPacket(Ethernet packet, DHCP dhcpPayload, Device device, VlanId vlan) {
664 UDP udpPacket = (UDP) dhcpPayload.getParent();
665 int udpSourcePort = udpPacket.getSourcePort();
666 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
667
668 ConnectPoint destinationCp = null;
669
670 if (udpSourcePort == UDP.DHCP_CLIENT_PORT) {
671 destinationCp = getUplinkConnectPointOfOlt(device.id());
672 } else if (udpSourcePort == UDP.DHCP_SERVER_PORT) {
673 Host host = hostService.getHost(HostId.hostId(clientMacAddress, vlan));
674
675 ElementId elementId = host.location().elementId();
676 PortNumber portNumber = host.location().port();
677
678 destinationCp = new ConnectPoint(elementId, portNumber);
679 }
680
681 if (destinationCp == null) {
682 log.error("No connect point to send msg to DHCP message");
683 return;
684 }
685
686 if (log.isTraceEnabled()) {
687 VlanId printVlan = VlanId.NONE;
688
689 if (vlan != null) {
690 printVlan = vlan;
691 }
692
693 log.trace("Emitting : packet {}, with MAC {}, with VLAN {}, with connect point {}",
694 getDhcpPacketType(dhcpPayload), clientMacAddress, printVlan, destinationCp);
695 }
696
697 TrafficTreatment t = DefaultTrafficTreatment.builder()
698 .setOutput(destinationCp.port()).build();
699 OutboundPacket o = new DefaultOutboundPacket(destinationCp
700 .deviceId(), t, ByteBuffer.wrap(packet.serialize()));
701 packetService.emit(o);
702 }
703
Tunahan Sezen03e55272020-04-18 09:18:53 +0000704 //process the dhcp packet before forwarding
705 private void processDhcpPacket(PacketContext context, Ethernet packet,
706 DHCP dhcpPayload, PortNumber sourcePort, DeviceId deviceId, VlanId vlanId) {
707 if (dhcpPayload == null) {
708 log.warn("DHCP payload is null");
709 return;
710 }
711
712 DHCP.MsgType incomingPacketType = getDhcpPacketType(dhcpPayload);
713
714 if (incomingPacketType == null) {
715 log.warn("Incoming packet type is null!");
716 return;
717 }
718
719 log.info("Received DHCP Packet of type {} from {}",
720 incomingPacketType, context.inPacket().receivedFrom());
721
722 if (incomingPacketType.equals(DHCP.MsgType.DHCPDISCOVER) ||
723 incomingPacketType.equals(DHCP.MsgType.DHCPREQUEST)) {
724 addToMacAddressMap(deviceId, sourcePort, vlanId, packet.getSourceMAC());
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000725 } else if (incomingPacketType.equals(DHCP.MsgType.DHCPACK)) {
726 MacAddress hostMac = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
727 VlanId hostVlan = VlanId.vlanId(packet.getVlanID());
728 HostId hostId = HostId.hostId(hostMac, hostVlan);
729 hostLocService.updateHostIp(hostId, IpAddress.valueOf(dhcpPayload.getYourIPAddress()));
Tunahan Sezen03e55272020-04-18 09:18:53 +0000730 }
731 }
732
733 // get type of the DHCP packet
734 private DHCP.MsgType getDhcpPacketType(DHCP dhcpPayload) {
735
736 for (DhcpOption option : dhcpPayload.getOptions()) {
737 if (option.getCode() == OptionCode_MessageType.getValue()) {
738 byte[] data = option.getData();
739 return DHCP.MsgType.getType(data[0]);
740 }
741 }
742 return null;
743 }
744
745 private void addToMacAddressMap(DeviceId deviceId, PortNumber portNumber,
746 VlanId vlanId, MacAddress macAddress) {
747 Versioned<MacLearnerValue> prevMacAddress =
748 macAddressMap.put(new MacLearnerKey(deviceId, portNumber, vlanId),
749 new MacLearnerValue(macAddress, new Date().getTime()));
750 if (prevMacAddress != null && !prevMacAddress.value().getMacAddress().equals(macAddress)) {
751 sendMacLearnerEvent(MacLearnerEvent.Type.REMOVED,
752 deviceId,
753 portNumber,
754 vlanId,
755 prevMacAddress.value().getMacAddress());
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000756 }
757 if (prevMacAddress == null || !prevMacAddress.value().getMacAddress().equals(macAddress)) {
Tunahan Sezen03e55272020-04-18 09:18:53 +0000758 // Not sending event for already mapped
759 log.info("Mapped MAC: {} for port: {} of deviceId: {} and vlanId: {}",
760 macAddress, portNumber, deviceId, vlanId);
761 sendMacLearnerEvent(MacLearnerEvent.Type.ADDED, deviceId, portNumber, vlanId, macAddress);
762 }
763 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000764 }
765
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000766 private MacDeleteResult removeFromMacAddressMap(MacLearnerKey macLearnerKey, boolean vanishHost) {
Tunahan Sezen03e55272020-04-18 09:18:53 +0000767 Versioned<MacLearnerValue> verMacAddress = macAddressMap.remove(macLearnerKey);
768 if (verMacAddress != null) {
769 log.info("Mapping removed. deviceId: {} portNumber: {} vlanId: {} macAddress: {}",
770 macLearnerKey.getDeviceId(), macLearnerKey.getPortNumber(),
771 verMacAddress.value(), verMacAddress.value().getMacAddress());
772 sendMacLearnerEvent(MacLearnerEvent.Type.REMOVED,
773 macLearnerKey.getDeviceId(),
774 macLearnerKey.getPortNumber(),
775 macLearnerKey.getVlanId(),
776 verMacAddress.value().getMacAddress());
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000777 if (vanishHost) {
778 hostLocService.vanishHost(verMacAddress.value().getMacAddress(), macLearnerKey.getVlanId());
779 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000780 return MacDeleteResult.SUCCESSFUL;
781 } else {
782 log.warn("MAC not removed, because mapping not found for deviceId: {} and portNumber: {} and vlanId: {}",
783 macLearnerKey.getDeviceId(),
784 macLearnerKey.getPortNumber(),
785 macLearnerKey.getVlanId());
786 return MacDeleteResult.NOT_EXIST;
787 }
788 }
789
790 private class InternalDeviceListener implements DeviceListener {
791
792 @Override
793 public void event(DeviceEvent event) {
794 eventExecutor.execute(() -> {
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000795 Device device = event.subject();
796 log.debug("Device event received: {}", event.type());
Tunahan Sezen03e55272020-04-18 09:18:53 +0000797 switch (event.type()) {
798 case DEVICE_REMOVED:
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000799 if (autoClearMacMapping) {
800 deleteMacMappings(device.id());
801 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000802 break;
803 case PORT_REMOVED:
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000804 if (autoClearMacMapping) {
805 deleteMacMappings(device.id(), event.port().number());
806 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000807 break;
808 default:
809 log.debug("Unhandled device event for Mac Learner: {}", event.type());
810 }
811 });
812 }
813
814 @Override
815 public boolean isRelevant(DeviceEvent event) {
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000816 return isDeviceMine(event.subject().id());
Tunahan Sezen03e55272020-04-18 09:18:53 +0000817 }
818
819 }
820
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000821 private class InternalClusterListener implements ClusterEventListener {
822 @Override
823 public void event(ClusterEvent event) {
824 if (event.type() == ClusterEvent.Type.INSTANCE_READY) {
825 hasher.addServer(event.subject().id());
826 }
827 if (event.type() == ClusterEvent.Type.INSTANCE_DEACTIVATED) {
828 hasher.removeServer(event.subject().id());
829 }
830 }
831 }
832
Tunahan Sezen03e55272020-04-18 09:18:53 +0000833}