blob: df943e2e10358242ac9d1e1ef5a3b15255480a3a [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;
25import org.onlab.util.Tools;
26import org.onosproject.cfg.ComponentConfigService;
Tunahan Sezen1f65c902020-09-08 13:10:16 +000027import org.onosproject.cluster.ClusterEvent;
28import org.onosproject.cluster.ClusterEventListener;
29import org.onosproject.cluster.ClusterService;
30import org.onosproject.cluster.ControllerNode;
31import org.onosproject.cluster.NodeId;
Tunahan Sezen03e55272020-04-18 09:18:53 +000032import org.onosproject.core.ApplicationId;
Tunahan Sezen1f65c902020-09-08 13:10:16 +000033import org.onosproject.net.ConnectPoint;
34import org.onosproject.net.Device;
35import org.onosproject.net.HostId;
36import org.onosproject.net.HostLocation;
37import org.onosproject.net.Link;
Tunahan Sezen03e55272020-04-18 09:18:53 +000038import org.onosproject.net.device.DeviceEvent;
39import org.onosproject.net.device.DeviceListener;
40import org.onosproject.net.device.DeviceService;
Tunahan Sezen1f65c902020-09-08 13:10:16 +000041import org.onosproject.net.link.LinkService;
42import org.onosproject.net.topology.Topology;
43import org.onosproject.net.topology.TopologyService;
Tunahan Sezen03e55272020-04-18 09:18:53 +000044import org.onosproject.store.service.ConsistentMap;
45import org.onosproject.store.service.StorageService;
46import org.onosproject.store.service.Versioned;
47import org.opencord.maclearner.api.DefaultMacLearner;
Tunahan Sezen1f65c902020-09-08 13:10:16 +000048import org.opencord.maclearner.api.MacLearnerHostLocationService;
Tunahan Sezen03e55272020-04-18 09:18:53 +000049import org.opencord.maclearner.api.MacDeleteResult;
50import org.opencord.maclearner.api.MacLearnerEvent;
51import org.opencord.maclearner.api.MacLearnerKey;
52import org.opencord.maclearner.api.MacLearnerListener;
53import org.opencord.maclearner.api.MacLearnerProvider;
54import org.opencord.maclearner.api.MacLearnerProviderService;
55import org.opencord.maclearner.api.MacLearnerService;
56import org.opencord.maclearner.api.MacLearnerValue;
57import org.osgi.service.component.ComponentContext;
58import org.osgi.service.component.annotations.Activate;
59import org.osgi.service.component.annotations.Component;
60import org.osgi.service.component.annotations.Deactivate;
61import org.osgi.service.component.annotations.Modified;
62import org.osgi.service.component.annotations.Reference;
63import org.onlab.packet.DHCP;
64import org.onlab.packet.Ethernet;
65import org.onlab.packet.IPv4;
66import org.onlab.packet.MacAddress;
67import org.onlab.packet.UDP;
68import org.onlab.packet.dhcp.DhcpOption;
69import org.onlab.util.KryoNamespace;
70import org.onosproject.core.CoreService;
71import org.onosproject.net.DeviceId;
72import org.onosproject.net.PortNumber;
73import org.onosproject.net.packet.PacketContext;
74import org.onosproject.net.packet.PacketProcessor;
75import org.onosproject.net.packet.PacketService;
76import org.onosproject.net.provider.AbstractListenerProviderRegistry;
77import org.onosproject.net.provider.AbstractProviderService;
78import org.onosproject.store.LogicalTimestamp;
79import org.onosproject.store.serializers.KryoNamespaces;
80import org.onosproject.store.service.Serializer;
81import org.onosproject.store.service.WallClockTimestamp;
82import org.slf4j.Logger;
83import org.slf4j.LoggerFactory;
84
85import java.net.URI;
86import java.util.Date;
87import java.util.Dictionary;
Tunahan Sezen1f65c902020-09-08 13:10:16 +000088import java.util.List;
Tunahan Sezen03e55272020-04-18 09:18:53 +000089import java.util.Map;
90import java.util.Optional;
91import java.util.Properties;
92import java.util.Set;
93import java.util.concurrent.ExecutorService;
94import java.util.concurrent.Executors;
95import java.util.concurrent.ScheduledExecutorService;
96import java.util.concurrent.ScheduledFuture;
97import java.util.concurrent.TimeUnit;
98import java.util.stream.Collectors;
99
100import static com.google.common.base.Strings.isNullOrEmpty;
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000101import static java.util.stream.Collectors.toList;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000102import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
103import static org.onlab.util.Tools.groupedThreads;
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000104import static org.opencord.maclearner.app.impl.OsgiPropertyConstants.AUTO_CLEAR_MAC_MAPPING;
105import static org.opencord.maclearner.app.impl.OsgiPropertyConstants.AUTO_CLEAR_MAC_MAPPING_DEFAULT;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000106import static org.opencord.maclearner.app.impl.OsgiPropertyConstants.CACHE_DURATION_DEFAULT;
107import static org.opencord.maclearner.app.impl.OsgiPropertyConstants.CACHE_DURATION;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000108import static org.osgi.service.component.annotations.ReferenceCardinality.MANDATORY;
109
110/**
111 * Mac Learner Service implementation.
112 */
113@Component(immediate = true,
114 property = {
115 CACHE_DURATION + ":Integer=" + CACHE_DURATION_DEFAULT,
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000116 AUTO_CLEAR_MAC_MAPPING + ":Boolean=" + AUTO_CLEAR_MAC_MAPPING_DEFAULT
Tunahan Sezen03e55272020-04-18 09:18:53 +0000117 },
118 service = MacLearnerService.class
119)
120public class MacLearnerManager
121 extends AbstractListenerProviderRegistry<MacLearnerEvent, MacLearnerListener,
122 MacLearnerProvider, MacLearnerProviderService>
123 implements MacLearnerService {
124
125 private static final String MAC_LEARNER_APP = "org.opencord.maclearner";
126 private static final String MAC_LEARNER = "maclearner";
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000127 private static final String OLT_MANUFACTURER_KEY = "VOLTHA";
Tunahan Sezen03e55272020-04-18 09:18:53 +0000128 private ApplicationId appId;
129
130 private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
131 private ScheduledFuture scheduledFuture;
132
133 private final Logger log = LoggerFactory.getLogger(getClass());
134
135 @Reference(cardinality = MANDATORY)
136 protected CoreService coreService;
137
138 @Reference(cardinality = MANDATORY)
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000139 protected ClusterService clusterService;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000140
141 @Reference(cardinality = MANDATORY)
142 protected DeviceService deviceService;
143
144 @Reference(cardinality = MANDATORY)
145 protected PacketService packetService;
146
147 @Reference(cardinality = MANDATORY)
148 protected StorageService storageService;
149
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000150 @Reference(cardinality = MANDATORY)
151 protected TopologyService topologyService;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000152
153 @Reference(cardinality = MANDATORY)
154 protected ComponentConfigService componentConfigService;
155
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000156 @Reference(cardinality = MANDATORY)
157 protected MacLearnerHostLocationService hostLocService;
158
159 @Reference(cardinality = MANDATORY)
160 protected LinkService linkService;
161
162 private final MacLearnerPacketProcessor macLearnerPacketProcessor =
Tunahan Sezen03e55272020-04-18 09:18:53 +0000163 new MacLearnerPacketProcessor();
164
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000165 private final DeviceListener deviceListener = new InternalDeviceListener();
166 private final ClusterEventListener clusterListener = new InternalClusterListener();
167
168 private ConsistentHasher hasher;
169 public static final int HASH_WEIGHT = 10;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000170
171 /**
172 * Minimum duration of mapping, mapping can be exist until 2*cacheDuration because of cleanerTimer fixed rate.
173 */
174 protected int cacheDurationSec = CACHE_DURATION_DEFAULT;
175
176 /**
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000177 * Removes mappings from MAC Address Map for removed events.
Tunahan Sezen03e55272020-04-18 09:18:53 +0000178 */
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000179 protected boolean autoClearMacMapping = AUTO_CLEAR_MAC_MAPPING_DEFAULT;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000180
181 private ConsistentMap<DeviceId, Set<PortNumber>> ignoredPortsMap;
182 private ConsistentMap<MacLearnerKey, MacLearnerValue> macAddressMap;
183
184 protected ExecutorService eventExecutor;
185
186 @Activate
187 public void activate() {
188 eventExecutor = Executors.newFixedThreadPool(5, groupedThreads("onos/maclearner",
189 "events-%d", log));
190 appId = coreService.registerApplication(MAC_LEARNER_APP);
191 componentConfigService.registerProperties(getClass());
192 eventDispatcher.addSink(MacLearnerEvent.class, listenerRegistry);
193 macAddressMap = storageService.<MacLearnerKey, MacLearnerValue>consistentMapBuilder()
194 .withName(MAC_LEARNER)
195 .withSerializer(createSerializer())
196 .withApplicationId(appId)
197 .build();
198 ignoredPortsMap = storageService
199 .<DeviceId, Set<PortNumber>>consistentMapBuilder()
200 .withName("maclearner-ignored")
201 .withSerializer(createSerializer())
202 .withApplicationId(appId)
203 .build();
204 //mac learner must process the packet before director processors
205 packetService.addProcessor(macLearnerPacketProcessor,
206 PacketProcessor.advisor(2));
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000207 List<NodeId> readyNodes = clusterService.getNodes().stream()
208 .filter(c -> clusterService.getState(c.id()) == ControllerNode.State.READY)
209 .map(ControllerNode::id)
210 .collect(toList());
211 hasher = new ConsistentHasher(readyNodes, HASH_WEIGHT);
212 clusterService.addListener(clusterListener);
213 deviceService.addListener(deviceListener);
Tunahan Sezen03e55272020-04-18 09:18:53 +0000214 createSchedulerForClearMacMappings();
215 log.info("{} is started.", getClass().getSimpleName());
216 }
217
218 private Serializer createSerializer() {
219 return Serializer.using(KryoNamespace.newBuilder()
220 .register(KryoNamespace.newBuilder().build(MAC_LEARNER))
221 // not so robust way to avoid collision with other
222 // user supplied registrations
223 .nextId(KryoNamespaces.BEGIN_USER_CUSTOM_ID + 100)
224 .register(KryoNamespaces.BASIC)
225 .register(LogicalTimestamp.class)
226 .register(WallClockTimestamp.class)
227 .register(MacLearnerKey.class)
228 .register(MacLearnerValue.class)
229 .register(DeviceId.class)
230 .register(URI.class)
231 .register(PortNumber.class)
232 .register(VlanId.class)
233 .register(MacAddress.class)
234 .build(MAC_LEARNER + "-ecmap"));
235 }
236
237 private void createSchedulerForClearMacMappings() {
238 scheduledFuture = scheduledExecutorService.scheduleAtFixedRate(this::clearExpiredMacMappings,
239 0,
240 cacheDurationSec,
241 TimeUnit.SECONDS);
242 }
243
244 private void clearExpiredMacMappings() {
245 Date curDate = new Date();
246 for (Map.Entry<MacLearnerKey, Versioned<MacLearnerValue>> entry : macAddressMap.entrySet()) {
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000247 if (!isDeviceMine(entry.getKey().getDeviceId())) {
248 continue;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000249 }
250 if (curDate.getTime() - entry.getValue().value().getTimestamp() > cacheDurationSec * 1000) {
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000251 removeFromMacAddressMap(entry.getKey(), false);
Tunahan Sezen03e55272020-04-18 09:18:53 +0000252 }
253 }
254 }
255
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000256 /**
257 * Determines if this instance should handle this device based on
258 * consistent hashing.
259 *
260 * @param id device ID
261 * @return true if this instance should handle the device, otherwise false
262 */
263 private boolean isDeviceMine(DeviceId id) {
264 NodeId nodeId = hasher.hash(id.toString());
265 if (log.isDebugEnabled()) {
266 log.debug("Node that will handle {} is {}", id, nodeId);
267 }
268 return nodeId.equals(clusterService.getLocalNode().id());
269 }
270
Tunahan Sezen03e55272020-04-18 09:18:53 +0000271 @Deactivate
272 public void deactivate() {
273 if (scheduledFuture != null) {
274 scheduledFuture.cancel(true);
275 }
276 packetService.removeProcessor(macLearnerPacketProcessor);
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000277 clusterService.removeListener(clusterListener);
278 deviceService.removeListener(deviceListener);
Tunahan Sezen03e55272020-04-18 09:18:53 +0000279 eventDispatcher.removeSink(MacLearnerEvent.class);
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000280 if (eventExecutor != null) {
281 eventExecutor.shutdown();
282 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000283 componentConfigService.unregisterProperties(getClass(), false);
284 log.info("{} is stopped.", getClass().getSimpleName());
285 }
286
287 @Modified
288 public void modified(ComponentContext context) {
289 Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties();
290
291 String cacheDuration = Tools.get(properties, CACHE_DURATION);
292 if (!isNullOrEmpty(cacheDuration)) {
293 int cacheDur = Integer.parseInt(cacheDuration.trim());
294 if (cacheDurationSec != cacheDur) {
295 setMacMappingCacheDuration(cacheDur);
296 }
297 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000298 }
299
300 private Integer setMacMappingCacheDuration(Integer second) {
301 if (cacheDurationSec == second) {
302 log.info("Cache duration already: {}", second);
303 return second;
304 }
305 log.info("Changing cache duration to: {} second from {} second...", second, cacheDurationSec);
306 this.cacheDurationSec = second;
307 if (scheduledFuture != null) {
308 scheduledFuture.cancel(false);
309 }
310 createSchedulerForClearMacMappings();
311 return cacheDurationSec;
312 }
313
314 @Override
315 public void addPortToIgnore(DeviceId deviceId, PortNumber portNumber) {
316 log.info("Adding ignore port: {} {}", deviceId, portNumber);
317 Set<PortNumber> updatedPorts = Sets.newHashSet();
318 Versioned<Set<PortNumber>> storedPorts = ignoredPortsMap.get(deviceId);
319 if (storedPorts == null || !storedPorts.value().contains(portNumber)) {
320 if (storedPorts != null) {
321 updatedPorts.addAll(storedPorts.value());
322 }
323 updatedPorts.add(portNumber);
324 ignoredPortsMap.put(deviceId, updatedPorts);
325 log.info("Port:{} of device: {} is added to ignoredPortsMap.", portNumber, deviceId);
326 deleteMacMappings(deviceId, portNumber);
327 } else {
328 log.warn("Port:{} of device: {} is already ignored.", portNumber, deviceId);
329 }
330 }
331
332 @Override
333 public void removeFromIgnoredPorts(DeviceId deviceId, PortNumber portNumber) {
334 log.info("Removing ignore port: {} {}", deviceId, portNumber);
335 Versioned<Set<PortNumber>> storedPorts = ignoredPortsMap.get(deviceId);
336 if (storedPorts != null && storedPorts.value().contains(portNumber)) {
337 if (storedPorts.value().size() == 1) {
338 ignoredPortsMap.remove(deviceId);
339 } else {
340 Set<PortNumber> updatedPorts = Sets.newHashSet();
341 updatedPorts.addAll(storedPorts.value());
342 updatedPorts.remove(portNumber);
343 ignoredPortsMap.put(deviceId, updatedPorts);
344 }
345 log.info("Port:{} of device: {} is removed ignoredPortsMap.", portNumber, deviceId);
346 } else {
347 log.warn("Port:{} of device: {} is not found in ignoredPortsMap.", portNumber, deviceId);
348 }
349 }
350
351 @Override
352 public ImmutableMap<MacLearnerKey, MacAddress> getAllMappings() {
353 log.info("Getting all MAC Mappings");
354 Map<MacLearnerKey, MacAddress> immutableMap = Maps.newHashMap();
355 macAddressMap.entrySet().forEach(entry ->
356 immutableMap.put(entry.getKey(),
357 entry.getValue() != null ? entry.getValue().value().getMacAddress() : null));
358 return ImmutableMap.copyOf(immutableMap);
359 }
360
361 @Override
362 public Optional<MacAddress> getMacMapping(DeviceId deviceId, PortNumber portNumber, VlanId vlanId) {
363 log.info("Getting MAC mapping for: {} {} {}", deviceId, portNumber, vlanId);
364 Versioned<MacLearnerValue> value = macAddressMap.get(new MacLearnerKey(deviceId, portNumber, vlanId));
365 return value != null ? Optional.ofNullable(value.value().getMacAddress()) : Optional.empty();
366 }
367
368 @Override
369 public MacDeleteResult deleteMacMapping(DeviceId deviceId, PortNumber portNumber, VlanId vlanId) {
370 log.info("Deleting MAC mapping for: {} {} {}", deviceId, portNumber, vlanId);
371 MacLearnerKey key = new MacLearnerKey(deviceId, portNumber, vlanId);
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000372 return removeFromMacAddressMap(key, true);
Tunahan Sezen03e55272020-04-18 09:18:53 +0000373 }
374
375 @Override
376 public boolean deleteMacMappings(DeviceId deviceId, PortNumber portNumber) {
377 log.info("Deleting MAC mappings for: {} {}", deviceId, portNumber);
378 Set<Map.Entry<MacLearnerKey, Versioned<MacLearnerValue>>> entriesToDelete = macAddressMap.entrySet().stream()
379 .filter(entry -> entry.getKey().getDeviceId().equals(deviceId) &&
380 entry.getKey().getPortNumber().equals(portNumber))
381 .collect(Collectors.toSet());
382 if (entriesToDelete.isEmpty()) {
383 log.warn("MAC mapping not found for deviceId: {} and portNumber: {}", deviceId, portNumber);
384 return false;
385 }
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000386 entriesToDelete.forEach(e -> removeFromMacAddressMap(e.getKey(), true));
Tunahan Sezen03e55272020-04-18 09:18:53 +0000387 return true;
388 }
389
390 @Override
391 public boolean deleteMacMappings(DeviceId deviceId) {
392 log.info("Deleting MAC mappings for: {}", deviceId);
393 Set<Map.Entry<MacLearnerKey, Versioned<MacLearnerValue>>> entriesToDelete = macAddressMap.entrySet().stream()
394 .filter(entry -> entry.getKey().getDeviceId().equals(deviceId))
395 .collect(Collectors.toSet());
396 if (entriesToDelete.isEmpty()) {
397 log.warn("MAC mapping not found for deviceId: {}", deviceId);
398 return false;
399 }
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000400 entriesToDelete.forEach(e -> removeFromMacAddressMap(e.getKey(), true));
Tunahan Sezen03e55272020-04-18 09:18:53 +0000401 return true;
402 }
403
404 @Override
405 public ImmutableSet<DeviceId> getMappedDevices() {
406 Set<DeviceId> deviceIds = Sets.newHashSet();
407 for (Map.Entry<MacLearnerKey, MacAddress> entry : getAllMappings().entrySet()) {
408 deviceIds.add(entry.getKey().getDeviceId());
409 }
410 return ImmutableSet.copyOf(deviceIds);
411 }
412
413 @Override
414 public ImmutableSet<PortNumber> getMappedPorts() {
415 Set<PortNumber> portNumbers = Sets.newHashSet();
416 for (Map.Entry<MacLearnerKey, MacAddress> entry : getAllMappings().entrySet()) {
417 portNumbers.add(entry.getKey().getPortNumber());
418 }
419 return ImmutableSet.copyOf(portNumbers);
420 }
421
422 @Override
423 public ImmutableMap<DeviceId, Set<PortNumber>> getIgnoredPorts() {
424 log.info("Getting ignored ports");
425 Map<DeviceId, Set<PortNumber>> immutableMap = Maps.newHashMap();
426 ignoredPortsMap.forEach(entry -> immutableMap.put(entry.getKey(),
427 entry.getValue() != null ? entry.getValue().value() : Sets.newHashSet()));
428 return ImmutableMap.copyOf(immutableMap);
429 }
430
431 @Override
432 protected MacLearnerProviderService createProviderService(MacLearnerProvider provider) {
433 return new InternalMacLearnerProviderService(provider);
434 }
435
436 private static class InternalMacLearnerProviderService extends AbstractProviderService<MacLearnerProvider>
437 implements MacLearnerProviderService {
438
439 InternalMacLearnerProviderService(MacLearnerProvider provider) {
440 super(provider);
441 }
442 }
443
444 private void sendMacLearnerEvent(MacLearnerEvent.Type type, DeviceId deviceId,
445 PortNumber portNumber, VlanId vlanId, MacAddress macAddress) {
446 log.info("Sending MAC Learner Event: type: {} deviceId: {} portNumber: {} vlanId: {} macAddress: {}",
447 type, deviceId, portNumber, vlanId.toShort(), macAddress);
448 DefaultMacLearner macLearner = new DefaultMacLearner(deviceId, portNumber, vlanId, macAddress);
449 MacLearnerEvent macLearnerEvent = new MacLearnerEvent(type, macLearner);
450 post(macLearnerEvent);
451 }
452
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000453 private boolean isOltDevice(Device device) {
454 return device.manufacturer().contains(OLT_MANUFACTURER_KEY);
455 }
456
Tunahan Sezen03e55272020-04-18 09:18:53 +0000457 private class MacLearnerPacketProcessor implements PacketProcessor {
458
459 @Override
460 public void process(PacketContext context) {
461 // process the packet and get the payload
462 Ethernet packet = context.inPacket().parsed();
463
464 if (packet == null) {
465 log.warn("Packet is null");
466 return;
467 }
468
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000469 ConnectPoint cp = context.inPacket().receivedFrom();
470 DeviceId deviceId = cp.deviceId();
471 PortNumber sourcePort = cp.port();
472 MacAddress srcMac = packet.getSourceMAC();
473 MacAddress dstMac = packet.getDestinationMAC();
474
475 Device device = deviceService.getDevice(deviceId);
476 if (!isOltDevice(device)) { // not handle non OLT device packets
477 log.debug("Packet received from non-OLT device: {}. Returning.", deviceId);
478 return;
479 }
480
481 if (srcMac.isBroadcast() || srcMac.isMulticast()) {
482 log.debug("Broadcast or multicast packet received from: {}. Returning.", cp);
483 return;
484 }
485
486 // Ignore location probes
487 if (dstMac.isOnos() && !MacAddress.NONE.equals(dstMac)) {
488 log.debug("Location probe. cp: {}", cp);
489 return;
490 }
491
492 // If this arrived on control port, bail out.
493 if (cp.port().isLogical()) {
494 log.debug("Packet received from logical port: {}", cp);
495 return;
496 }
497
498 // If this is not an edge port, bail out.
499 Topology topology = topologyService.currentTopology();
500 if (topologyService.isInfrastructure(topology, cp)) {
501 log.debug("Packet received from non-edge port: {}", cp);
502 return;
503 }
504
505 VlanId vlan = VlanId.vlanId(packet.getVlanID());
506 VlanId outerVlan = VlanId.vlanId(packet.getQinQVID());
507 VlanId innerVlan = VlanId.NONE;
508 EthType outerTpid = EthType.EtherType.UNKNOWN.ethType();
509 // Set up values for double-tagged hosts
510 if (outerVlan.toShort() != Ethernet.VLAN_UNTAGGED) {
511 innerVlan = vlan;
512 vlan = outerVlan;
513 outerTpid = EthType.EtherType.lookup(packet.getQinQTPID()).ethType();
514 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000515
516 Versioned<Set<PortNumber>> ignoredPortsOfDevice = ignoredPortsMap.get(deviceId);
517 if (ignoredPortsOfDevice != null && ignoredPortsOfDevice.value().contains(sourcePort)) {
518 log.warn("Port Number: {} is in ignoredPortsMap. Returning", sourcePort);
519 return;
520 }
521
522 if (packet.getEtherType() == Ethernet.TYPE_IPV4) {
523 IPv4 ipv4Packet = (IPv4) packet.getPayload();
524
525 if (ipv4Packet.getProtocol() == IPv4.PROTOCOL_UDP) {
526 UDP udpPacket = (UDP) ipv4Packet.getPayload();
527 int udpSourcePort = udpPacket.getSourcePort();
528 if ((udpSourcePort == UDP.DHCP_CLIENT_PORT) || (udpSourcePort == UDP.DHCP_SERVER_PORT)) {
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000529 // Update host location
530 HostLocation hloc = new HostLocation(cp, System.currentTimeMillis());
531 HostLocation auxLocation = null;
532 Optional<Link> optLink = linkService.getDeviceLinks(deviceId).stream().findFirst();
533 if (optLink.isPresent()) {
534 Link link = optLink.get();
535 auxLocation = !link.src().deviceId().equals(deviceId) ?
536 new HostLocation(link.src(), System.currentTimeMillis()) :
537 new HostLocation(link.dst(), System.currentTimeMillis());
538 } else {
539 log.debug("Link not found for device {}", deviceId);
540 }
541 hostLocService.createOrUpdateHost(HostId.hostId(packet.getSourceMAC(), vlan),
542 packet.getSourceMAC(), packet.getDestinationMAC(), vlan, innerVlan, outerTpid,
543 hloc, auxLocation, null);
Tunahan Sezen03e55272020-04-18 09:18:53 +0000544 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
545 //This packet is dhcp.
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000546 processDhcpPacket(context, packet, dhcpPayload, sourcePort, deviceId, vlan);
Tunahan Sezen03e55272020-04-18 09:18:53 +0000547 }
548 }
549 }
550 }
551
552 //process the dhcp packet before forwarding
553 private void processDhcpPacket(PacketContext context, Ethernet packet,
554 DHCP dhcpPayload, PortNumber sourcePort, DeviceId deviceId, VlanId vlanId) {
555 if (dhcpPayload == null) {
556 log.warn("DHCP payload is null");
557 return;
558 }
559
560 DHCP.MsgType incomingPacketType = getDhcpPacketType(dhcpPayload);
561
562 if (incomingPacketType == null) {
563 log.warn("Incoming packet type is null!");
564 return;
565 }
566
567 log.info("Received DHCP Packet of type {} from {}",
568 incomingPacketType, context.inPacket().receivedFrom());
569
570 if (incomingPacketType.equals(DHCP.MsgType.DHCPDISCOVER) ||
571 incomingPacketType.equals(DHCP.MsgType.DHCPREQUEST)) {
572 addToMacAddressMap(deviceId, sourcePort, vlanId, packet.getSourceMAC());
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000573 } else if (incomingPacketType.equals(DHCP.MsgType.DHCPACK)) {
574 MacAddress hostMac = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
575 VlanId hostVlan = VlanId.vlanId(packet.getVlanID());
576 HostId hostId = HostId.hostId(hostMac, hostVlan);
577 hostLocService.updateHostIp(hostId, IpAddress.valueOf(dhcpPayload.getYourIPAddress()));
Tunahan Sezen03e55272020-04-18 09:18:53 +0000578 }
579 }
580
581 // get type of the DHCP packet
582 private DHCP.MsgType getDhcpPacketType(DHCP dhcpPayload) {
583
584 for (DhcpOption option : dhcpPayload.getOptions()) {
585 if (option.getCode() == OptionCode_MessageType.getValue()) {
586 byte[] data = option.getData();
587 return DHCP.MsgType.getType(data[0]);
588 }
589 }
590 return null;
591 }
592
593 private void addToMacAddressMap(DeviceId deviceId, PortNumber portNumber,
594 VlanId vlanId, MacAddress macAddress) {
595 Versioned<MacLearnerValue> prevMacAddress =
596 macAddressMap.put(new MacLearnerKey(deviceId, portNumber, vlanId),
597 new MacLearnerValue(macAddress, new Date().getTime()));
598 if (prevMacAddress != null && !prevMacAddress.value().getMacAddress().equals(macAddress)) {
599 sendMacLearnerEvent(MacLearnerEvent.Type.REMOVED,
600 deviceId,
601 portNumber,
602 vlanId,
603 prevMacAddress.value().getMacAddress());
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000604 }
605 if (prevMacAddress == null || !prevMacAddress.value().getMacAddress().equals(macAddress)) {
Tunahan Sezen03e55272020-04-18 09:18:53 +0000606 // Not sending event for already mapped
607 log.info("Mapped MAC: {} for port: {} of deviceId: {} and vlanId: {}",
608 macAddress, portNumber, deviceId, vlanId);
609 sendMacLearnerEvent(MacLearnerEvent.Type.ADDED, deviceId, portNumber, vlanId, macAddress);
610 }
611 }
612
613 }
614
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000615 private MacDeleteResult removeFromMacAddressMap(MacLearnerKey macLearnerKey, boolean vanishHost) {
Tunahan Sezen03e55272020-04-18 09:18:53 +0000616 Versioned<MacLearnerValue> verMacAddress = macAddressMap.remove(macLearnerKey);
617 if (verMacAddress != null) {
618 log.info("Mapping removed. deviceId: {} portNumber: {} vlanId: {} macAddress: {}",
619 macLearnerKey.getDeviceId(), macLearnerKey.getPortNumber(),
620 verMacAddress.value(), verMacAddress.value().getMacAddress());
621 sendMacLearnerEvent(MacLearnerEvent.Type.REMOVED,
622 macLearnerKey.getDeviceId(),
623 macLearnerKey.getPortNumber(),
624 macLearnerKey.getVlanId(),
625 verMacAddress.value().getMacAddress());
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000626 if (vanishHost) {
627 hostLocService.vanishHost(verMacAddress.value().getMacAddress(), macLearnerKey.getVlanId());
628 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000629 return MacDeleteResult.SUCCESSFUL;
630 } else {
631 log.warn("MAC not removed, because mapping not found for deviceId: {} and portNumber: {} and vlanId: {}",
632 macLearnerKey.getDeviceId(),
633 macLearnerKey.getPortNumber(),
634 macLearnerKey.getVlanId());
635 return MacDeleteResult.NOT_EXIST;
636 }
637 }
638
639 private class InternalDeviceListener implements DeviceListener {
640
641 @Override
642 public void event(DeviceEvent event) {
643 eventExecutor.execute(() -> {
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000644 Device device = event.subject();
645 log.debug("Device event received: {}", event.type());
Tunahan Sezen03e55272020-04-18 09:18:53 +0000646 switch (event.type()) {
647 case DEVICE_REMOVED:
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000648 if (autoClearMacMapping) {
649 deleteMacMappings(device.id());
650 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000651 break;
652 case PORT_REMOVED:
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000653 if (autoClearMacMapping) {
654 deleteMacMappings(device.id(), event.port().number());
655 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000656 break;
657 default:
658 log.debug("Unhandled device event for Mac Learner: {}", event.type());
659 }
660 });
661 }
662
663 @Override
664 public boolean isRelevant(DeviceEvent event) {
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000665 return isDeviceMine(event.subject().id());
Tunahan Sezen03e55272020-04-18 09:18:53 +0000666 }
667
668 }
669
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000670 private class InternalClusterListener implements ClusterEventListener {
671 @Override
672 public void event(ClusterEvent event) {
673 if (event.type() == ClusterEvent.Type.INSTANCE_READY) {
674 hasher.addServer(event.subject().id());
675 }
676 if (event.type() == ClusterEvent.Type.INSTANCE_DEACTIVATED) {
677 hasher.removeServer(event.subject().id());
678 }
679 }
680 }
681
Tunahan Sezen03e55272020-04-18 09:18:53 +0000682}