X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=mappingservice%2Fimplementation%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Flispflowmapping%2Fimplementation%2Flisp%2FMapServer.java;h=65e659ccc3a71156208015d74c4e0d925858bafd;hb=06bf26a3f90cc2787a3b373cd8731f19a39a7c2f;hp=b22e9f8437c71b94d746d6ac91f336b062a42730;hpb=78b9f322c2928eff268bae179b8f30f47f91734f;p=lispflowmapping.git diff --git a/mappingservice/implementation/src/main/java/org/opendaylight/lispflowmapping/implementation/lisp/MapServer.java b/mappingservice/implementation/src/main/java/org/opendaylight/lispflowmapping/implementation/lisp/MapServer.java index b22e9f843..65e659ccc 100644 --- a/mappingservice/implementation/src/main/java/org/opendaylight/lispflowmapping/implementation/lisp/MapServer.java +++ b/mappingservice/implementation/src/main/java/org/opendaylight/lispflowmapping/implementation/lisp/MapServer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 Contextream, Inc. and others. All rights reserved. + * Copyright (c) 2014, 2017 Contextream, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, @@ -8,66 +8,100 @@ package org.opendaylight.lispflowmapping.implementation.lisp; +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Enumeration; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.Set; - +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.BooleanUtils; -import org.apache.commons.lang3.exception.ExceptionUtils; -import org.opendaylight.controller.sal.binding.api.NotificationListener; -import org.opendaylight.controller.sal.binding.api.NotificationService; -import org.opendaylight.lispflowmapping.implementation.authentication.LispAuthenticationUtil; +import org.opendaylight.controller.md.sal.binding.api.NotificationService; +import org.opendaylight.lispflowmapping.config.ConfigIni; +import org.opendaylight.lispflowmapping.implementation.util.LoggingUtil; +import org.opendaylight.lispflowmapping.implementation.util.MSNotificationInputUtil; import org.opendaylight.lispflowmapping.interfaces.dao.SubKeys; -import org.opendaylight.lispflowmapping.interfaces.dao.SubscriberRLOC; +import org.opendaylight.lispflowmapping.interfaces.dao.Subscriber; import org.opendaylight.lispflowmapping.interfaces.lisp.IMapNotifyHandler; import org.opendaylight.lispflowmapping.interfaces.lisp.IMapServerAsync; +import org.opendaylight.lispflowmapping.interfaces.lisp.ISmrNotificationListener; +import org.opendaylight.lispflowmapping.interfaces.lisp.SmrEvent; import org.opendaylight.lispflowmapping.interfaces.mappingservice.IMappingService; -import org.opendaylight.lispflowmapping.lisp.util.LcafSourceDestHelper; -import org.opendaylight.lispflowmapping.lisp.util.LispAFIConvertor; +import org.opendaylight.lispflowmapping.lisp.authentication.LispAuthenticationUtil; +import org.opendaylight.lispflowmapping.lisp.type.LispMessage; +import org.opendaylight.lispflowmapping.lisp.type.MappingData; +import org.opendaylight.lispflowmapping.lisp.util.LispAddressStringifier; +import org.opendaylight.lispflowmapping.lisp.util.LispAddressUtil; import org.opendaylight.lispflowmapping.lisp.util.MapNotifyBuilderHelper; import org.opendaylight.lispflowmapping.lisp.util.MapRequestUtil; +import org.opendaylight.lispflowmapping.lisp.util.MappingRecordUtil; import org.opendaylight.lispflowmapping.lisp.util.MaskUtil; -import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev150820.MapRegister; -import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev150820.eidrecords.EidRecord; -import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev150820.eidrecords.EidRecordBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev150820.eidtolocatorrecords.EidToLocatorRecord; -import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev150820.eidtolocatorrecords.EidToLocatorRecordBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev150820.lispaddress.LispAddressContainer; -import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev150820.lispaddress.lispaddresscontainer.address.LcafSourceDest; -import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev150820.mapnotifymessage.MapNotifyBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev150820.maprequestnotification.MapRequestBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.mappingservice.rev150906.MappingChange; +import org.opendaylight.lispflowmapping.lisp.util.SourceDestKeyHelper; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.PortNumber; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.lisp.address.types.rev151105.lisp.address.Address; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.lisp.address.types.rev151105.lisp.address.address.SourceDestKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.inet.binary.types.rev160303.IpAddressBinary; +import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.binary.address.types.rev160504.augmented.lisp.address.address.Ipv4PrefixBinary; +import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.binary.address.types.rev160504.augmented.lisp.address.address.Ipv4PrefixBinaryBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.binary.address.types.rev160504.augmented.lisp.address.address.Ipv6PrefixBinary; +import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.binary.address.types.rev160504.augmented.lisp.address.address.Ipv6PrefixBinaryBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.MapRegister; +import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.SiteId; +import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.eid.container.Eid; +import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.eid.container.EidBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.eid.list.EidItem; +import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.eid.list.EidItemBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.mapnotifymessage.MapNotifyBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.mapping.authkey.container.MappingAuthkey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.mapping.record.container.MappingRecord; +import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.mapping.record.list.MappingRecordItem; +import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.mapping.record.list.MappingRecordItemBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.maprequestnotification.MapRequestBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.transport.address.TransportAddress; +import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.lisp.proto.rev151105.transport.address.TransportAddressBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.mappingservice.rev150906.MappingChanged; import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.mappingservice.rev150906.MappingOrigin; -import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.mappingservice.rev150906.SiteId; +import org.opendaylight.yang.gen.v1.urn.opendaylight.lfm.mappingservice.rev150906.OdlMappingserviceListener; +import org.opendaylight.yangtools.concepts.ListenerRegistration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.base.Preconditions; - -public class MapServer implements IMapServerAsync { +public class MapServer implements IMapServerAsync, OdlMappingserviceListener, ISmrNotificationListener { - protected static final Logger LOG = LoggerFactory.getLogger(MapServer.class); + private static final Logger LOG = LoggerFactory.getLogger(MapServer.class); + private static final byte[] ALL_ZEROES_XTR_ID = new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,0}; private IMappingService mapService; - private boolean authenticate; private boolean subscriptionService; private IMapNotifyHandler notifyHandler; private NotificationService notificationService; + private ListenerRegistration mapServerListenerRegistration; + private SmrScheduler scheduler; - public MapServer(IMappingService mapService, boolean authenticate, boolean subscriptionService, - IMapNotifyHandler notifyHandler, NotificationService notificationService) { + public MapServer(IMappingService mapService, boolean subscriptionService, + IMapNotifyHandler notifyHandler, NotificationService notificationService) { Preconditions.checkNotNull(mapService); this.mapService = mapService; - this.authenticate = authenticate; this.subscriptionService = subscriptionService; this.notifyHandler = notifyHandler; this.notificationService = notificationService; if (notificationService != null) { - subscribeToMappingNotifications(); + notificationService.registerNotificationListener(this); } + scheduler = new SmrScheduler(); } @Override @@ -75,131 +109,209 @@ public class MapServer implements IMapServerAsync { this.subscriptionService = subscriptionService; } - @Override - public boolean shouldAuthenticate() { - return authenticate; - } + @SuppressWarnings("unchecked") + public void handleMapRegister(MapRegister mapRegister) { + boolean mappingUpdated = false; + boolean merge = ConfigIni.getInstance().mappingMergeIsSet() && mapRegister.isMergeEnabled(); + Set subscribers = Sets.newConcurrentHashSet(); + MappingRecord oldMapping; - @Override - public void setShouldAuthenticate(boolean shouldAuthenticate) { - authenticate = shouldAuthenticate; - } + if (merge) { + if (!mapRegister.isXtrSiteIdPresent() || mapRegister.getXtrId() == null) { + LOG.error("Merge bit is set in Map-Register, but xTR-ID is not present. Will not merge."); + merge = false; + } else if (Arrays.equals(mapRegister.getXtrId().getValue(), ALL_ZEROES_XTR_ID)) { + LOG.warn("Merge bit is set in Map-Register, but xTR-ID is all zeroes."); + } + } - public void handleMapRegister(MapRegister mapRegister) { - boolean failed = false; - String password = null; - for (EidToLocatorRecord record : mapRegister.getEidToLocatorRecord()) { - if (authenticate) { - password = mapService.getAuthenticationKey(record.getLispAddressContainer()); - if (!LispAuthenticationUtil.validate(mapRegister, password)) { - LOG.warn("Authentication failed"); - failed = true; - break; - } - } - EidToLocatorRecord oldMapping = (EidToLocatorRecord) mapService.getMapping(MappingOrigin.Southbound, - record.getLispAddressContainer()); - mapService.addMapping(MappingOrigin.Southbound, record.getLispAddressContainer(), getSiteId(mapRegister), - record); - if (subscriptionService && !record.equals(oldMapping)) { - LOG.debug("Sending SMRs for subscribers of {}", record.getLispAddressContainer()); - Set subscribers = getSubscribers(record.getLispAddressContainer()); - sendSmrs(record, subscribers); - } - } - if (!failed) { + for (MappingRecordItem record : mapRegister.getMappingRecordItem()) { + MappingRecord mapping = record.getMappingRecord(); + Eid eid = mapping.getEid(); + MappingData mappingData = new MappingData(mapping, System.currentTimeMillis()); + mappingData.setMergeEnabled(merge); + mappingData.setXtrId(mapRegister.getXtrId()); + + oldMapping = getMappingRecord(mapService.getMapping(MappingOrigin.Southbound, eid)); + mapService.addMapping(MappingOrigin.Southbound, eid, getSiteId(mapRegister), mappingData); + if (oldMapping != null + && MappingRecordUtil.isNegativeMapping(oldMapping) + && !oldMapping.getEid().equals(eid)) { + if (subscriptionService) { + // Here we save the subscribers of the OLD mapping before removing. We will add to this set the + // subscribers of the NEW mapping below (since the EIDs are different, the result of + // mappingChanged() will be true, and then send an SMR to all subscribers with the EID of the NEW + // mapping only. + Set oldMappingSubscribers = mapService.getSubscribers(oldMapping.getEid()); + if (oldMappingSubscribers != null) { + subscribers.addAll(oldMappingSubscribers); + LoggingUtil.logSubscribers(LOG, oldMapping.getEid(), subscribers); + } + } + mapService.removeMapping(MappingOrigin.Southbound, oldMapping.getEid()); + } + + if (subscriptionService) { + MappingRecord newMapping = merge + ? getMappingRecord(mapService.getMapping(MappingOrigin.Southbound, eid)) : mapping; + + if (mappingChanged(oldMapping, newMapping)) { + if (LOG.isDebugEnabled()) { + LOG.debug("Mapping update occured for {} SMRs will be sent for its subscribers.", + LispAddressStringifier.getString(eid)); + } + Set newMappingSubscribers = mapService.getSubscribers(eid); + if (oldMapping != null && !oldMapping.getEid().equals(eid)) { + newMappingSubscribers = addParentSubscribers(eid, newMappingSubscribers); + } + if (newMappingSubscribers != null) { + subscribers.addAll(newMappingSubscribers); + LoggingUtil.logSubscribers(LOG, eid, subscribers); + } + handleSmr(eid, subscribers); + mappingUpdated = true; + } + } + } + if (BooleanUtils.isTrue(mapRegister.isWantMapNotify())) { + LOG.trace("MapRegister wants MapNotify"); MapNotifyBuilder builder = new MapNotifyBuilder(); - if (BooleanUtils.isTrue(mapRegister.isWantMapNotify())) { - LOG.trace("MapRegister wants MapNotify"); + List rlocs = null; + if (merge) { + Set notifyRlocs = new HashSet(); + List mergedMappings = new ArrayList(); + for (MappingRecordItem record : mapRegister.getMappingRecordItem()) { + MappingRecord mapping = record.getMappingRecord(); + MappingRecord currentRecord = getMappingRecord(mapService.getMapping(MappingOrigin.Southbound, + mapping.getEid())); + mergedMappings.add(new MappingRecordItemBuilder().setMappingRecord(currentRecord).build()); + Set sourceRlocs = (Set) mapService.getData( + MappingOrigin.Southbound, mapping.getEid(), SubKeys.SRC_RLOCS); + if (sourceRlocs != null) { + notifyRlocs.addAll(sourceRlocs); + } + } + MapNotifyBuilderHelper.setFromMapRegisterAndMappingRecordItems(builder, mapRegister, mergedMappings); + // send map-notify to merge group only when mapping record is changed + if (mappingUpdated) { + rlocs = getTransportAddresses(notifyRlocs); + } + } else { MapNotifyBuilderHelper.setFromMapRegister(builder, mapRegister); - if (authenticate) { + } + List mappings = builder.getMappingRecordItem(); + if (mappings != null && mappings.get(0) != null && mappings.get(0).getMappingRecord() != null + && mappings.get(0).getMappingRecord().getEid() != null) { + MappingAuthkey authkey = mapService.getAuthenticationKey(mappings.get(0).getMappingRecord().getEid()); + if (authkey != null) { builder.setAuthenticationData(LispAuthenticationUtil.createAuthenticationData(builder.build(), - password)); + authkey.getKeyString())); } - notifyHandler.handleMapNotify(builder.build()); } + notifyHandler.handleMapNotify(builder.build(), rlocs); + } + } + + private static List getTransportAddresses(Set addresses) { + List rlocs = new ArrayList(); + for (IpAddressBinary address : addresses) { + TransportAddressBuilder tab = new TransportAddressBuilder(); + tab.setIpAddress(address); + tab.setPort(new PortNumber(LispMessage.PORT_NUM)); + rlocs.add(tab.build()); } + return rlocs; } - private SiteId getSiteId(MapRegister mapRegister) { + private static SiteId getSiteId(MapRegister mapRegister) { return (mapRegister.getSiteId() != null) ? new SiteId(mapRegister.getSiteId()) : null; } - private class MappingChangedNotificationHandler implements NotificationListener { - @Override - public void onNotification(MappingChanged notification) { - if (subscriptionService) { - sendSmrs(new EidToLocatorRecordBuilder(notification.getMapping()).build(), getSubscribers(notification - .getMapping().getLispAddressContainer())); - if (notification.getChange().equals(MappingChange.Removed)) { - removeSubscribers(notification.getMapping().getLispAddressContainer()); + private static MappingRecord getMappingRecord(MappingData mappingData) { + return (mappingData != null) ? mappingData.getRecord() : null; + } + + @Override + public void onMappingChanged(MappingChanged notification) { + LOG.trace("MappingChanged event of type: `{}'", notification.getChangeType()); + if (subscriptionService) { + Eid eid = notification.getMappingRecord().getEid(); + Set subscribers = MSNotificationInputUtil.toSubscriberSet(notification.getSubscriberItem()); + LoggingUtil.logSubscribers(LOG, eid, subscribers); + if (mapService.isMaster()) { + sendSmrs(eid, subscribers); + if (eid.getAddress() instanceof SourceDestKey) { + Set dstSubscribers = MSNotificationInputUtil.toSubscriberSetFromDst( + notification.getDstSubscriberItem()); + LoggingUtil.logSubscribers(LOG, SourceDestKeyHelper.getDstBinary(eid), dstSubscribers); + sendSmrs(SourceDestKeyHelper.getDstBinary(eid), dstSubscribers); } } } } - private void subscribeToMappingNotifications() { - notificationService.registerNotificationListener(MappingChanged.class, new MappingChangedNotificationHandler()); + private static boolean mappingChanged(MappingRecord oldMapping, MappingRecord newMapping) { + // We only check for fields we care about + // XXX: This code needs to be checked and updated when the YANG model for MappingRecord is modified + Preconditions.checkNotNull(newMapping, "The new mapping should never be null"); + if (oldMapping == null) { + LOG.trace("mappingChanged(): old mapping is null"); + return true; + } else if (!Objects.equals(oldMapping.getEid(), newMapping.getEid())) { + LOG.trace("mappingChanged(): EID"); + return true; + } else if (!Objects.equals(oldMapping.getLocatorRecord(), newMapping.getLocatorRecord())) { + LOG.trace("mappingChanged(): RLOC"); + return true; + } else if (!Objects.equals(oldMapping.getAction(), newMapping.getAction())) { + LOG.trace("mappingChanged(): action"); + return true; + } else if (!Objects.equals(oldMapping.getRecordTtl(), newMapping.getRecordTtl())) { + LOG.trace("mappingChanged(): TTL"); + return true; + } else if (!Objects.equals(oldMapping.getMapVersion(), newMapping.getMapVersion())) { + LOG.trace("mappingChanged(): mapping version"); + return true; + } + return false; } - private void sendSmrs(EidToLocatorRecord record, Set subscribers) { - LispAddressContainer eid = record.getLispAddressContainer(); - handleSmr(record, subscribers, notifyHandler); + private void handleSmr(Eid eid, Set subscribers) { + sendSmrs(eid, subscribers); // For SrcDst LCAF also send SMRs to Dst prefix - if (eid.getAddress() instanceof LcafSourceDest) { - LispAddressContainer dstAddr = LispAFIConvertor.toContainer(LcafSourceDestHelper.getDstAfi(eid)); - short dstMask = LcafSourceDestHelper.getDstMask(eid); - subscribers = getSubscribers(dstAddr); - EidToLocatorRecord newRecord = new EidToLocatorRecordBuilder().setAction(record.getAction()) - .setAuthoritative(record.isAuthoritative()).setLocatorRecord(record.getLocatorRecord()) - .setMapVersion(record.getMapVersion()).setRecordTtl(record.getRecordTtl()) - .setLispAddressContainer(dstAddr).setMaskLength(dstMask).build(); - handleSmr(newRecord, subscribers, notifyHandler); + if (eid.getAddress() instanceof SourceDestKey) { + Eid dstAddr = SourceDestKeyHelper.getDstBinary(eid); + Set dstSubs = mapService.getSubscribers(dstAddr); + sendSmrs(dstAddr, dstSubs); } } - private void handleSmr(EidToLocatorRecord record, Set subscribers, IMapNotifyHandler callback) { + private void sendSmrs(Eid eid, Set subscribers) { if (subscribers == null) { return; } - MapRequestBuilder mrb = MapRequestUtil.prepareSMR(record.getLispAddressContainer(), - LispAFIConvertor.toContainer(getLocalAddress())); + final MapRequestBuilder mrb = MapRequestUtil.prepareSMR(eid, LispAddressUtil.toRloc(getLocalAddress())); LOG.trace("Built SMR packet: " + mrb.build().toString()); - for (SubscriberRLOC subscriber : subscribers) { - if (subscriber.timedOut()) { - LOG.trace("Lazy removing expired subscriber entry " + subscriber.toString()); - subscribers.remove(subscriber); - } else { - try { - // The address stored in the SMR's EID record is used as Source EID in the SMR-invoked Map-Request. - // To ensure consistent behavior it is set to the value used to originally request a given mapping - mrb.setEidRecord(new ArrayList()); - mrb.getEidRecord() - .add(new EidRecordBuilder() - .setMask( - (short) MaskUtil.getMaxMask(LispAFIConvertor.toAFI(subscriber.getSrcEid()))) - .setLispAddressContainer(subscriber.getSrcEid()).build()); - callback.handleSMR(mrb.build(), subscriber.getSrcRloc()); - } catch (Exception e) { - LOG.error("Errors encountered while handling SMR:" + ExceptionUtils.getStackTrace(e)); - } - } - } - addSubscribers(record.getLispAddressContainer(), subscribers); - } - @SuppressWarnings("unchecked") - private Set getSubscribers(LispAddressContainer address) { - return (Set) mapService.getData(MappingOrigin.Southbound, address, SubKeys.SUBSCRIBERS); + scheduler.scheduleSmrs(mrb, subscribers.iterator()); } - private void removeSubscribers(LispAddressContainer address) { - mapService.removeData(MappingOrigin.Southbound, address, SubKeys.SUBSCRIBERS); - } + private Set addParentSubscribers(Eid eid, Set subscribers) { + Eid parentPrefix = mapService.getParentPrefix(eid); + if (parentPrefix == null) { + return subscribers; + } - private void addSubscribers(LispAddressContainer address, Set subscribers) { - mapService.addData(MappingOrigin.Southbound, address, SubKeys.SUBSCRIBERS, subscribers); + Set parentSubscribers = mapService.getSubscribers(parentPrefix); + if (parentSubscribers != null) { + if (subscribers != null) { + subscribers.addAll(parentSubscribers); + } else { + subscribers = parentSubscribers; + } + } + return subscribers; } private static InetAddress getLocalAddress() { @@ -207,21 +319,206 @@ public class MapServer implements IMapServerAsync { Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); while (interfaces.hasMoreElements()) { NetworkInterface current = interfaces.nextElement(); - LOG.debug("Interface " + current.toString()); - if (!current.isUp() || current.isLoopback() || current.isVirtual()) + LOG.trace("Interface " + current.toString()); + if (!current.isUp() || current.isLoopback() || current.isVirtual()) { continue; + } Enumeration addresses = current.getInetAddresses(); while (addresses.hasMoreElements()) { - InetAddress current_addr = addresses.nextElement(); + InetAddress currentAddr = addresses.nextElement(); // Skip loopback and link local addresses - if (current_addr.isLoopbackAddress() || current_addr.isLinkLocalAddress()) + if (currentAddr.isLoopbackAddress() || currentAddr.isLinkLocalAddress()) { continue; - LOG.debug(current_addr.getHostAddress()); - return current_addr; + } + LOG.debug(currentAddr.getHostAddress()); + return currentAddr; } } } catch (SocketException se) { + LOG.debug("Caught socket exception", se); } return null; } + + @Override + public void onSmrInvokedReceived(SmrEvent event) { + scheduler.smrReceived(event); + } + + /** + * Task scheduler is responsible for resending SMR messages to a subscriber (xTR) + * {@value ConfigIni#LISP_SMR_RETRY_COUNT} times, or until {@link ISmrNotificationListener#onSmrInvokedReceived} + * is triggered. + */ + private class SmrScheduler { + final int cpuCores = Runtime.getRuntime().availableProcessors(); + private final ThreadFactory threadFactory = new ThreadFactoryBuilder() + .setNameFormat("smr-executor-%d").build(); + private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(cpuCores * 2, threadFactory); + private final Map>> eidFutureMap = Maps.newConcurrentMap(); + + void scheduleSmrs(MapRequestBuilder mrb, Iterator subscribers) { + final Eid srcEid = fixSrcEidMask(mrb.getSourceEid().getEid()); + cancelExistingFuturesForEid(srcEid); + + final Map> subscriberFutureMap = Maps.newConcurrentMap(); + + // Using Iterator ensures that we don't get a ConcurrentModificationException when removing a Subscriber + // from a Set. + while (subscribers.hasNext()) { + Subscriber subscriber = subscribers.next(); + if (subscriber.timedOut()) { + LOG.debug("Lazy removing expired subscriber entry " + subscriber.getString()); + subscribers.remove(); + } else { + final ScheduledFuture future = executor.scheduleAtFixedRate(new CancellableRunnable( + mrb, subscriber), 0L, ConfigIni.getInstance().getSmrTimeout(), TimeUnit.MILLISECONDS); + subscriberFutureMap.put(subscriber, future); + } + } + + if (subscriberFutureMap.isEmpty()) { + return; + } + eidFutureMap.put(srcEid, subscriberFutureMap); + } + + void smrReceived(SmrEvent event) { + final List subscriberList = event.getSubscriberList(); + for (Subscriber subscriber : subscriberList) { + if (LOG.isTraceEnabled()) { + LOG.trace("SMR-invoked event, EID {}, subscriber {}", + LispAddressStringifier.getString(event.getEid()), + subscriber.getString()); + LOG.trace("eidFutureMap: {}", eidFutureMap); + } + final Map> subscriberFutureMap = eidFutureMap.get(event.getEid()); + if (subscriberFutureMap != null) { + final ScheduledFuture future = subscriberFutureMap.get(subscriber); + if (future != null && !future.isCancelled()) { + future.cancel(true); + if (LOG.isDebugEnabled()) { + LOG.debug("SMR-invoked MapRequest received, scheduled task for subscriber {}, EID {} with" + + " nonce {} has been cancelled", subscriber.getString(), + LispAddressStringifier.getString(event.getEid()), event.getNonce()); + } + subscriberFutureMap.remove(subscriber); + } else { + if (future == null) { + LOG.trace("No outstanding SMR tasks for EID {}, subscriber {}", + LispAddressStringifier.getString(event.getEid()), subscriber.getString()); + } else { + LOG.trace("Future {} is cancelled", future); + } + } + if (subscriberFutureMap.isEmpty()) { + eidFutureMap.remove(event.getEid()); + } + } else { + if (LOG.isTraceEnabled()) { + LOG.trace("No outstanding SMR tasks for EID {}", + LispAddressStringifier.getString(event.getEid())); + } + } + } + } + + private void cancelExistingFuturesForEid(Eid eid) { + synchronized (eidFutureMap) { + if (eidFutureMap.containsKey(eid)) { + final Map> subscriberFutureMap = eidFutureMap.get(eid); + Iterator oldSubscribers = subscriberFutureMap.keySet().iterator(); + while (oldSubscribers.hasNext()) { + Subscriber subscriber = oldSubscribers.next(); + ScheduledFuture subscriberFuture = subscriberFutureMap.get(subscriber); + subscriberFuture.cancel(true); + } + eidFutureMap.remove(eid); + } + } + } + + /* + * See https://bugs.opendaylight.org/show_bug.cgi?id=8469#c1 why this is necessary. + * + * TL;DR The sourceEid field in the MapRequestBuilder object will be serialized to a packet on the wire, and + * a Map-Request can't store the prefix length in the source EID. + * + * Since we store all prefixes as binary internally, we only care about and fix those address types. + */ + private Eid fixSrcEidMask(Eid eid) { + Address address = eid.getAddress(); + if (address instanceof Ipv4PrefixBinary) { + return new EidBuilder(eid).setAddress(new Ipv4PrefixBinaryBuilder((Ipv4PrefixBinary) address) + .setIpv4MaskLength(MaskUtil.IPV4_MAX_MASK).build()).build(); + } else if (address instanceof Ipv6PrefixBinary) { + return new EidBuilder(eid).setAddress(new Ipv6PrefixBinaryBuilder((Ipv6PrefixBinary) address) + .setIpv6MaskLength(MaskUtil.IPV6_MAX_MASK).build()).build(); + } + return eid; + } + + private final class CancellableRunnable implements Runnable { + private MapRequestBuilder mrb; + private Subscriber subscriber; + private int executionCount = 1; + + CancellableRunnable(MapRequestBuilder mrb, Subscriber subscriber) { + this.mrb = mrb; + this.subscriber = subscriber; + } + + @SuppressWarnings("checkstyle:IllegalCatch") + @Override + public void run() { + final Eid srcEid = mrb.getSourceEid().getEid(); + + try { + // The address stored in the SMR's EID record is used as Source EID in the SMR-invoked + // Map-Request. To ensure consistent behavior it is set to the value used to originally request + // a given mapping. + if (executionCount <= ConfigIni.getInstance().getSmrRetryCount()) { + synchronized (mrb) { + mrb.setEidItem(new ArrayList()); + mrb.getEidItem().add(new EidItemBuilder().setEid(subscriber.getSrcEid()).build()); + notifyHandler.handleSMR(mrb.build(), subscriber.getSrcRloc()); + if (LOG.isTraceEnabled()) { + LOG.trace("Attempt #{} to send SMR to subscriber {} for EID {}", + executionCount, + subscriber.getString(), + LispAddressStringifier.getString(mrb.getSourceEid().getEid())); + } + } + } else { + LOG.trace("Cancelling execution of a SMR Map-Request after {} failed attempts.", + executionCount - 1); + cancelAndRemove(subscriber, srcEid); + return; + } + } catch (Exception e) { + LOG.error("Errors encountered while handling SMR:", e); + cancelAndRemove(subscriber, srcEid); + return; + } + executionCount++; + } + + private void cancelAndRemove(Subscriber sub, Eid eid) { + final Map> subscriberFutureMap = eidFutureMap.get(eid); + if (subscriberFutureMap == null) { + LOG.warn("Couldn't find subscriber {} in SMR scheduler internal list", sub); + return; + } + + if (subscriberFutureMap.containsKey(sub)) { + ScheduledFuture eidFuture = subscriberFutureMap.get(sub); + subscriberFutureMap.remove(sub); + eidFuture.cancel(false); + } + if (subscriberFutureMap.isEmpty()) { + eidFutureMap.remove(eid); + } + } + } + } }