diff --git a/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/DiscoveryContext.java b/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/DiscoveryContext.java index a9da92ab3e..04729e4d17 100644 --- a/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/DiscoveryContext.java +++ b/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/DiscoveryContext.java @@ -66,4 +66,11 @@ interface DiscoveryContext { * @param key link key */ void touchLink(LinkKey key); + + /** + * Returns the cluster-wide unique identifier. + * + * @return the cluster identifier + */ + String fingerprint(); } diff --git a/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/FingerprintProbeFromDevice.java b/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/FingerprintProbeFromDevice.java new file mode 100644 index 0000000000..f6e3c2d05d --- /dev/null +++ b/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/FingerprintProbeFromDevice.java @@ -0,0 +1,18 @@ +package org.onosproject.provider.lldp.impl; + +import org.onosproject.net.DeviceId; +import org.onosproject.net.config.basics.BasicFeatureConfig; + +/** + * A feature to send and receive probes carrying a cluster-unique fingerprint. + * Note that, as it leverages LinkDiscovery, disabling linkDiscovery will disable + * this function. + */ +public class FingerprintProbeFromDevice extends BasicFeatureConfig { + + protected FingerprintProbeFromDevice() { + // default:disabled + super(false); + } + +} diff --git a/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LinkDiscovery.java b/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LinkDiscovery.java index 4b962ae562..11b8cd36ad 100644 --- a/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LinkDiscovery.java +++ b/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LinkDiscovery.java @@ -16,6 +16,7 @@ package org.onosproject.provider.lldp.impl; import com.google.common.collect.Sets; + import org.jboss.netty.util.Timeout; import org.jboss.netty.util.TimerTask; import org.onlab.packet.Ethernet; @@ -59,13 +60,13 @@ class LinkDiscovery implements TimerTask { private final Device device; private final DiscoveryContext context; - private final ONOSLLDP lldpPacket; private final Ethernet ethPacket; private final Ethernet bddpEth; private Timeout timeout; private volatile boolean isStopped; - + // This LinkDiscovery can handle remote link probes (default false). + private volatile boolean fingerprinted; // Set of ports to be probed private final Set ports = Sets.newConcurrentHashSet(); @@ -81,22 +82,17 @@ class LinkDiscovery implements TimerTask { this.device = device; this.context = context; - lldpPacket = new ONOSLLDP(); - lldpPacket.setChassisId(device.chassisId()); - lldpPacket.setDevice(device.id().toString()); - ethPacket = new Ethernet(); ethPacket.setEtherType(Ethernet.TYPE_LLDP); ethPacket.setDestinationMACAddress(ONOSLLDP.LLDP_NICIRA); - ethPacket.setPayload(this.lldpPacket); ethPacket.setPad(true); bddpEth = new Ethernet(); - bddpEth.setPayload(lldpPacket); bddpEth.setEtherType(Ethernet.TYPE_BSN); bddpEth.setDestinationMACAddress(ONOSLLDP.BDDP_MULTICAST); bddpEth.setPad(true); + fingerprinted = false; isStopped = true; start(); log.debug("Started discovery manager for switch {}", device.id()); @@ -220,8 +216,8 @@ class LinkDiscovery implements TimerTask { if (port == null) { return null; } - lldpPacket.setPortId(port.intValue()); - ethPacket.setSourceMACAddress(SRC_MAC); + ONOSLLDP lldp = getLinkProbe(port); + ethPacket.setSourceMACAddress(SRC_MAC).setPayload(lldp); return new DefaultOutboundPacket(device.id(), builder().setOutput(portNumber(port)).build(), ByteBuffer.wrap(ethPacket.serialize())); @@ -237,13 +233,21 @@ class LinkDiscovery implements TimerTask { if (port == null) { return null; } - lldpPacket.setPortId(port.intValue()); - bddpEth.setSourceMACAddress(SRC_MAC); + ONOSLLDP lldp = getLinkProbe(port); + bddpEth.setSourceMACAddress(SRC_MAC).setPayload(lldp); return new DefaultOutboundPacket(device.id(), builder().setOutput(portNumber(port)).build(), ByteBuffer.wrap(bddpEth.serialize())); } + private ONOSLLDP getLinkProbe(Long port) { + return fingerprinted + ? ONOSLLDP.fingerprintedLLDP(device.id().toString(), device.chassisId(), + port.intValue(), context.fingerprint()) + : ONOSLLDP.onosLLDP(device.id().toString(), device.chassisId(), + port.intValue()); + } + private void sendProbes(Long portNumber) { log.trace("Sending probes out to {}@{}", portNumber, device.id()); OutboundPacket pkt = createOutBoundLldp(portNumber); @@ -258,4 +262,11 @@ class LinkDiscovery implements TimerTask { return ports.contains(portNumber); } + protected void enableFingerprint() { + fingerprinted = true; + } + + protected void disableFingerprint() { + fingerprinted = false; + } } diff --git a/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LldpLinkProvider.java b/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LldpLinkProvider.java index ce2826c343..a87b6a70e4 100644 --- a/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LldpLinkProvider.java +++ b/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LldpLinkProvider.java @@ -47,6 +47,7 @@ import org.apache.felix.scr.annotations.ReferenceCardinality; import org.onlab.packet.Ethernet; import org.onlab.util.Tools; import org.onosproject.cfg.ComponentConfigService; +import org.onosproject.cluster.ClusterMetadataService; import org.onosproject.cluster.ClusterService; import org.onosproject.core.ApplicationId; import org.onosproject.core.CoreService; @@ -133,6 +134,9 @@ public class LldpLinkProvider extends AbstractProvider implements LinkProvider { @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected NetworkConfigRegistry cfgRegistry; + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ClusterMetadataService clusterMetadataService; + private LinkProviderService providerService; private ScheduledExecutorService executor; @@ -185,6 +189,7 @@ public class LldpLinkProvider extends AbstractProvider implements LinkProvider { public static final String CONFIG_KEY = "suppression"; public static final String FEATURE_NAME = "linkDiscovery"; + public static final String FINGERPRINT_FEATURE_NAME = "fingerprint"; private final Set> factories = ImmutableSet.of( new ConfigFactory(APP_SUBJECT_FACTORY, @@ -208,6 +213,13 @@ public class LldpLinkProvider extends AbstractProvider implements LinkProvider { public LinkDiscoveryFromPort createConfig() { return new LinkDiscoveryFromPort(); } + }, + new ConfigFactory(DEVICE_SUBJECT_FACTORY, + FingerprintProbeFromDevice.class, FINGERPRINT_FEATURE_NAME) { + @Override + public FingerprintProbeFromDevice createConfig() { + return new FingerprintProbeFromDevice(); + } } ); @@ -385,6 +397,14 @@ public class LldpLinkProvider extends AbstractProvider implements LinkProvider { return isBlacklisted(new ConnectPoint(port.element().id(), port.number())); } + private boolean isFingerprinted(DeviceId did) { + FingerprintProbeFromDevice cfg = cfgRegistry.getConfig(did, FingerprintProbeFromDevice.class); + if (cfg == null) { + return false; + } + return cfg.enabled(); + } + /** * Updates discovery helper for specified device. * @@ -405,6 +425,11 @@ public class LldpLinkProvider extends AbstractProvider implements LinkProvider { } LinkDiscovery ld = discoverers.computeIfAbsent(device.id(), did -> new LinkDiscovery(device, context)); + if (isFingerprinted(device.id())) { + ld.enableFingerprint(); + } else { + ld.disableFingerprint(); + } if (ld.isStopped()) { ld.start(); } @@ -715,6 +740,11 @@ public class LldpLinkProvider extends AbstractProvider implements LinkProvider { public void touchLink(LinkKey key) { linkTimes.put(key, System.currentTimeMillis()); } + + @Override + public String fingerprint() { + return clusterMetadataService.getClusterMetadata().getName(); + } } static final EnumSet CONFIG_CHANGED @@ -760,6 +790,15 @@ public class LldpLinkProvider extends AbstractProvider implements LinkProvider { } } + } else if (event.configClass() == FingerprintProbeFromDevice.class && + CONFIG_CHANGED.contains(event.type())) { + + if (event.subject() instanceof DeviceId) { + final DeviceId did = (DeviceId) event.subject(); + Device device = deviceService.getDevice(did); + updateDevice(device); + } + } else if (event.configClass().equals(SuppressionConfig.class) && (event.type() == NetworkConfigEvent.Type.CONFIG_ADDED || event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED)) { diff --git a/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/LldpLinkProviderTest.java b/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/LldpLinkProviderTest.java index 758c34e604..28b08eae86 100644 --- a/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/LldpLinkProviderTest.java +++ b/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/LldpLinkProviderTest.java @@ -21,14 +21,22 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + import org.junit.After; import org.junit.Before; import org.junit.Test; import org.onlab.packet.ChassisId; import org.onlab.packet.Ethernet; +import org.onlab.packet.IpAddress; import org.onlab.packet.ONOSLLDP; import org.onosproject.cfg.ComponentConfigAdapter; +import org.onosproject.cluster.ClusterMetadata; +import org.onosproject.cluster.ClusterMetadataService; +import org.onosproject.cluster.ControllerNode; +import org.onosproject.cluster.DefaultControllerNode; import org.onosproject.cluster.NodeId; +import org.onosproject.cluster.Partition; import org.onosproject.cluster.RoleInfo; import org.onosproject.core.ApplicationId; import org.onosproject.core.CoreService; @@ -80,7 +88,6 @@ import java.util.concurrent.CompletableFuture; import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.replay; - import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertEquals; @@ -143,7 +150,7 @@ public class LldpLinkProviderTest { provider.packetService = packetService; provider.providerRegistry = linkRegistry; provider.masterService = masterService; - + provider.clusterMetadataService = new TestMetadataService(); provider.activate(null); } @@ -696,11 +703,9 @@ public class LldpLinkProviderTest { @Override public InboundPacket inPacket() { - ONOSLLDP lldp = new ONOSLLDP(); - lldp.setChassisId(device.chassisId()); - lldp.setPortId((int) pd1.number().toLong()); - lldp.setDevice(deviceService.getDevice(DID1).id().toString()); - + ONOSLLDP lldp = ONOSLLDP.onosLLDP(deviceService.getDevice(DID1).id().toString(), + device.chassisId(), + (int) pd1.number().toLong()); Ethernet ethPacket = new Ethernet(); ethPacket.setEtherType(Ethernet.TYPE_LLDP); @@ -708,8 +713,6 @@ public class LldpLinkProviderTest { ethPacket.setPayload(lldp); ethPacket.setPad(true); - - ethPacket.setSourceMACAddress("DE:AD:BE:EF:BA:11"); ConnectPoint cp = new ConnectPoint(device.id(), pd3.number()); @@ -941,4 +944,26 @@ public class LldpLinkProviderTest { return this; } } + + private final class TestMetadataService implements ClusterMetadataService { + @Override + public ClusterMetadata getClusterMetadata() { + final NodeId nid = new NodeId("test-node"); + final IpAddress addr = IpAddress.valueOf(0); + final Partition p = new Partition("test-pt", Sets.newHashSet(nid)); + return ClusterMetadata.builder() + .withName("test-cluster") + .withControllerNodes(Sets.newHashSet(new DefaultControllerNode(nid, addr))) + .withPartitions(Sets.newHashSet(p)).build(); + } + + @Override + public void setClusterMetadata(ClusterMetadata metadata) { + } + + @Override + public ControllerNode getLocalNode() { + return null; + } + } } diff --git a/utils/misc/src/main/java/org/onlab/packet/ONOSLLDP.java b/utils/misc/src/main/java/org/onlab/packet/ONOSLLDP.java index 4d5d58b23d..78a6ad341f 100644 --- a/utils/misc/src/main/java/org/onlab/packet/ONOSLLDP.java +++ b/utils/misc/src/main/java/org/onlab/packet/ONOSLLDP.java @@ -15,14 +15,21 @@ */ package org.onlab.packet; +import java.util.HashMap; + import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + import org.apache.commons.lang.ArrayUtils; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; +import static org.onlab.packet.LLDPOrganizationalTLV.OUI_LENGTH; +import static org.onlab.packet.LLDPOrganizationalTLV.SUBTYPE_LENGTH; + /** - * ONOS LLDP containing organizational TLV for ONOS device dicovery. + * ONOS LLDP containing organizational TLV for ONOS device discovery. */ public class ONOSLLDP extends LLDP { @@ -37,12 +44,16 @@ public class ONOSLLDP extends LLDP { public static final byte[] BDDP_MULTICAST = {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff}; - private static final byte NAME_SUBTYPE = 1; - private static final byte DEVICE_SUBTYPE = 2; - private static final short NAME_LENGTH = 4; //1 for subtype + 3 for OUI - private static final short DEVICE_LENGTH = 4; //1 for subtype + 3 for OUI - private final LLDPOrganizationalTLV nameTLV = new LLDPOrganizationalTLV(); - private final LLDPOrganizationalTLV deviceTLV = new LLDPOrganizationalTLV(); + protected static final byte NAME_SUBTYPE = 1; + protected static final byte DEVICE_SUBTYPE = 2; + protected static final byte DOMAIN_SUBTYPE = 3; + + private static final short NAME_LENGTH = OUI_LENGTH + SUBTYPE_LENGTH; + private static final short DEVICE_LENGTH = OUI_LENGTH + SUBTYPE_LENGTH; + private static final short DOMAIN_LENGTH = OUI_LENGTH + SUBTYPE_LENGTH; + + private final HashMap opttlvs = + Maps.newHashMap(); // TLV constants: type, size and subtype // Organizationally specific TLV also have packet offset and contents of TLV @@ -57,18 +68,24 @@ public class ONOSLLDP extends LLDP { private static final byte TTL_TLV_TYPE = 3; - private final byte[] ttlValue = new byte[] {0, 0x78}; - public ONOSLLDP() { + // Only needs to be accessed from LinkProbeFactory. + protected ONOSLLDP(byte ... subtype) { super(); + for (byte st : subtype) { + opttlvs.put(st, new LLDPOrganizationalTLV()); + } + // guarantee the following (name and device) TLVs exist + opttlvs.putIfAbsent(NAME_SUBTYPE, new LLDPOrganizationalTLV()); + opttlvs.putIfAbsent(DEVICE_SUBTYPE, new LLDPOrganizationalTLV()); setName(DEFAULT_NAME); setDevice(DEFAULT_DEVICE); - setOptionalTLVList(Lists.newArrayList(nameTLV, deviceTLV)); + + setOptionalTLVList(Lists.newArrayList(opttlvs.values())); setTtl(new LLDPTLV().setType(TTL_TLV_TYPE) .setLength((short) ttlValue.length) .setValue(ttlValue)); - } private ONOSLLDP(LLDP lldp) { @@ -79,17 +96,31 @@ public class ONOSLLDP extends LLDP { } public void setName(String name) { - nameTLV.setLength((short) (name.length() + NAME_LENGTH)); - nameTLV.setInfoString(name); - nameTLV.setSubType(NAME_SUBTYPE); - nameTLV.setOUI(ONLAB_OUI); + LLDPOrganizationalTLV nametlv = opttlvs.get(NAME_SUBTYPE); + nametlv.setLength((short) (name.length() + NAME_LENGTH)); + nametlv.setInfoString(name); + nametlv.setSubType(NAME_SUBTYPE); + nametlv.setOUI(ONLAB_OUI); } public void setDevice(String device) { - deviceTLV.setInfoString(device); - deviceTLV.setLength((short) (device.length() + DEVICE_LENGTH)); - deviceTLV.setSubType(DEVICE_SUBTYPE); - deviceTLV.setOUI(ONLAB_OUI); + LLDPOrganizationalTLV devicetlv = opttlvs.get(DEVICE_SUBTYPE); + devicetlv.setInfoString(device); + devicetlv.setLength((short) (device.length() + DEVICE_LENGTH)); + devicetlv.setSubType(DEVICE_SUBTYPE); + devicetlv.setOUI(ONLAB_OUI); + } + + public void setDomainInfo(String domainId) { + LLDPOrganizationalTLV domaintlv = opttlvs.get(DOMAIN_SUBTYPE); + if (domaintlv == null) { + // maybe warn people not to set this if remote probes aren't. + return; + } + domaintlv.setInfoString(domainId); + domaintlv.setLength((short) (domainId.length() + DOMAIN_LENGTH)); + domaintlv.setSubType(DOMAIN_SUBTYPE); + domaintlv.setOUI(ONLAB_OUI); } public void setChassisId(final ChassisId chassisId) { @@ -139,6 +170,24 @@ public class ONOSLLDP extends LLDP { return null; } + /** + * Gets the TLV associated with remote probing. This TLV will be null if + * remote probing is disabled. + * + * @return A TLV containing domain ID, or null. + */ + public LLDPOrganizationalTLV getDomainTLV() { + for (LLDPTLV tlv : this.getOptionalTLVList()) { + if (tlv.getType() == LLDPOrganizationalTLV.ORGANIZATIONAL_TLV_TYPE) { + LLDPOrganizationalTLV orgTLV = (LLDPOrganizationalTLV) tlv; + if (orgTLV.getSubType() == DOMAIN_SUBTYPE) { + return orgTLV; + } + } + } + return null; + } + public String getNameString() { LLDPOrganizationalTLV tlv = getNameTLV(); if (tlv != null) { @@ -155,6 +204,14 @@ public class ONOSLLDP extends LLDP { return null; } + public String getDomainString() { + LLDPOrganizationalTLV tlv = getDomainTLV(); + if (tlv != null) { + return new String(tlv.getInfoString(), StandardCharsets.UTF_8); + } + return null; + } + public Integer getPort() { ByteBuffer portBB = ByteBuffer.wrap(this.getPortId().getValue()); portBB.position(1); @@ -177,4 +234,40 @@ public class ONOSLLDP extends LLDP { } return null; } + + /** + * Creates a link probe for link discovery/verification. + * + * @param deviceId The device ID as a String + * @param chassisId The chassis ID of the device + * @param portNum Port number of port to send probe out of + * @return ONOSLLDP probe message + */ + public static ONOSLLDP onosLLDP(String deviceId, ChassisId chassisId, int portNum) { + ONOSLLDP probe = new ONOSLLDP(NAME_SUBTYPE, DEVICE_SUBTYPE); + probe.setPortId(portNum); + probe.setDevice(deviceId); + probe.setChassisId(chassisId); + return probe; + } + + /** + * Creates a link probe carrying a fingerprint unique to the ONOS cluster managing + * link discovery/verification. + * + * @param deviceId The device ID as a String + * @param chassisId The chassis ID of the device + * @param portNum Port number of port to send probe out of + * @param domainId The cluster's fingerprint + * @return ONOSLLDP probe message + */ + public static ONOSLLDP fingerprintedLLDP( + String deviceId, ChassisId chassisId, int portNum, String domainId) { + ONOSLLDP probe = new ONOSLLDP(NAME_SUBTYPE, DEVICE_SUBTYPE, DOMAIN_SUBTYPE); + probe.setPortId(portNum); + probe.setDevice(deviceId); + probe.setChassisId(chassisId); + probe.setDomainInfo(domainId); + return probe; + } }