Ayaka Koshibe
Committed by Yuta HIGUCHI

[ONOS-4424] Tag LLDP/BDDP source address with fingerprint

Link probes incorporate cluster fingerprint in source
MAC address. This removes the need for an additional TLV and the
complexity associated with it, and also adds fingerprinting to
BDDPs for free.

 - fingerprint in Ethernet source address. The old default MAC
   value is only used when the CusterMetadata service isn't ready.
 - remove support for TLV fingerprint field and associated config
   knobs.
 - links at control domain boundary are classified as EDGE type links.

Change-Id: Idb07dd06fbeee25e9fcee3fbdddec7a7dbb2c397
......@@ -25,11 +25,14 @@ import org.onosproject.net.provider.ProviderId;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Verify.verify;
import static com.google.common.base.Charsets.UTF_8;
import com.google.common.base.MoreObjects;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.hash.Funnel;
import com.google.common.hash.PrimitiveSink;
/**
* Cluster metadata.
......@@ -40,14 +43,18 @@ import com.google.common.collect.Sets;
*/
public final class ClusterMetadata implements Provided {
// Name to use when the ClusterMetadataService is in transient state
public static final String NO_NAME = "";
private final ProviderId providerId;
private final String name;
private final Set<ControllerNode> nodes;
private final Set<Partition> partitions;
public static final Funnel<ClusterMetadata> HASH_FUNNEL = new Funnel<ClusterMetadata>() {
@Override
public void funnel(ClusterMetadata cm, PrimitiveSink into) {
into.putString(cm.name, UTF_8);
}
};
@SuppressWarnings("unused")
private ClusterMetadata() {
providerId = null;
......
/*
* Copyright 2014-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.net.link;
import com.google.common.hash.HashFunction;
import com.google.common.hash.HashCode;
import com.google.common.hash.Hashing;
import org.onosproject.cluster.ClusterMetadata;
/**
* Abstraction for an entity that provides information about infrastructure
* links that are discovered or verified using probe messages.
*/
public interface ProbedLinkProvider extends LinkProvider {
static final String DEFAULT_MAC = "DE:AD:BE:EF:BA:11";
static String defaultMac() {
return DEFAULT_MAC;
}
/**
* Build a stringified MAC address using the ClusterMetadata hash for uniqueness.
* Form of MAC is "02:eb" followed by four bytes of clusterMetadata hash.
*/
static String fingerprintMac(ClusterMetadata cm) {
if (cm == null) {
return DEFAULT_MAC;
}
HashFunction hf = Hashing.murmur3_32();
HashCode hc = hf.newHasher().putObject(cm, ClusterMetadata.HASH_FUNNEL).hash();
int unqf = hc.asInt();
StringBuilder sb = new StringBuilder();
sb.append("02:eb");
for (int i = 0; i < 4; i++) {
byte b = (byte) (unqf >> i * 8);
sb.append(String.format(":%02X", b));
}
return sb.toString();
}
}
/*
* Copyright 2015-present Open Networking Laboratory
* Copyright 2014-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -13,21 +13,38 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.provider.lldp.impl;
package org.onosproject.cluster;
import org.onosproject.net.DeviceId;
import org.onosproject.net.config.basics.BasicFeatureConfig;
import org.onlab.packet.IpAddress;
import com.google.common.collect.Sets;
/**
* A feature to send and receive probes carrying a cluster-unique fingerprint.
* Note that, as it leverages LinkDiscovery, disabling linkDiscovery will disable
* this function.
* Test adapter for the ClusterMetadata service.
*/
public class FingerprintProbeFromDevice extends BasicFeatureConfig<DeviceId> {
public class ClusterMetadataServiceAdapter implements ClusterMetadataService {
@Override
public ClusterMetadata getClusterMetadata() {
final NodeId nid = new NodeId("test-node");
final IpAddress addr = IpAddress.valueOf(0);
final Partition p = new DefaultPartition(PartitionId.from(1), Sets.newHashSet(nid));
return new ClusterMetadata("test-cluster",
Sets.newHashSet(new DefaultControllerNode(nid, addr)),
Sets.newHashSet(p));
}
@Override
public ControllerNode getLocalNode() {
return null;
}
@Override
public void addListener(ClusterMetadataEventListener listener) {
}
protected FingerprintProbeFromDevice() {
// default:disabled
super(false);
@Override
public void removeListener(ClusterMetadataEventListener listener) {
}
}
......
......@@ -29,7 +29,6 @@ import org.onlab.packet.Ethernet;
import org.onlab.util.SharedExecutors;
import org.onlab.util.Tools;
import org.onosproject.cfg.ComponentConfigService;
import org.onosproject.cluster.ClusterMetadata;
import org.onosproject.cluster.ClusterMetadataService;
import org.onosproject.cluster.ClusterService;
import org.onosproject.core.ApplicationId;
......@@ -53,7 +52,7 @@ import org.onosproject.net.device.DeviceService;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.link.DefaultLinkDescription;
import org.onosproject.net.link.LinkProvider;
import org.onosproject.net.link.ProbedLinkProvider;
import org.onosproject.net.link.LinkProviderRegistry;
import org.onosproject.net.link.LinkProviderService;
import org.onosproject.net.link.LinkService;
......@@ -85,7 +84,6 @@ import static org.onlab.packet.Ethernet.TYPE_BSN;
import static org.onlab.packet.Ethernet.TYPE_LLDP;
import static org.onlab.util.Tools.get;
import static org.onlab.util.Tools.groupedThreads;
import static org.onosproject.cluster.ClusterMetadata.NO_NAME;
import static org.onosproject.net.Link.Type.DIRECT;
import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY;
import static org.onosproject.net.config.basics.SubjectFactories.CONNECT_POINT_SUBJECT_FACTORY;
......@@ -96,7 +94,7 @@ import static org.slf4j.LoggerFactory.getLogger;
* Provider which uses LLDP and BDDP packets to detect network infrastructure links.
*/
@Component(immediate = true)
public class LldpLinkProvider extends AbstractProvider implements LinkProvider {
public class LldpLinkProvider extends AbstractProvider implements ProbedLinkProvider {
private static final String PROVIDER_NAME = "org.onosproject.provider.lldp";
......@@ -194,7 +192,6 @@ 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<ConfigFactory<?, ?>> factories = ImmutableSet.of(
new ConfigFactory<ApplicationId, SuppressionConfig>(APP_SUBJECT_FACTORY,
......@@ -218,19 +215,11 @@ public class LldpLinkProvider extends AbstractProvider implements LinkProvider {
public LinkDiscoveryFromPort createConfig() {
return new LinkDiscoveryFromPort();
}
},
new ConfigFactory<DeviceId, FingerprintProbeFromDevice>(DEVICE_SUBJECT_FACTORY,
FingerprintProbeFromDevice.class, FINGERPRINT_FEATURE_NAME) {
@Override
public FingerprintProbeFromDevice createConfig() {
return new FingerprintProbeFromDevice();
}
}
);
private final InternalConfigListener cfgListener = new InternalConfigListener();
/**
* Creates an OpenFlow link provider.
*/
......@@ -238,6 +227,17 @@ public class LldpLinkProvider extends AbstractProvider implements LinkProvider {
super(new ProviderId("lldp", PROVIDER_NAME));
}
private final String buildSrcMac() {
String srcMac = ProbedLinkProvider.fingerprintMac(clusterMetadataService.getClusterMetadata());
String defMac = ProbedLinkProvider.defaultMac();
if (srcMac.equals(defMac)) {
log.warn("Couldn't generate fingerprint. Using default value {}", defMac);
return defMac;
}
log.trace("Generated MAC address {}", srcMac);
return srcMac;
}
@Activate
public void activate(ComponentContext context) {
cfgService.registerProperties(getClass());
......@@ -404,14 +404,6 @@ 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.
*
......@@ -433,11 +425,6 @@ 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();
}
......@@ -753,14 +740,13 @@ public class LldpLinkProvider extends AbstractProvider implements LinkProvider {
}
@Override
public String fingerprint() {
ClusterMetadata mdata = clusterMetadataService.getClusterMetadata();
return mdata == null ? NO_NAME : mdata.getName();
public DeviceService deviceService() {
return deviceService;
}
@Override
public DeviceService deviceService() {
return deviceService;
public String fingerprint() {
return buildSrcMac();
}
}
......@@ -808,15 +794,6 @@ 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)) {
......
......@@ -34,6 +34,7 @@ import org.onosproject.net.link.LinkDescription;
import org.onosproject.net.packet.DefaultOutboundPacket;
import org.onosproject.net.packet.OutboundPacket;
import org.onosproject.net.packet.PacketContext;
import org.onosproject.net.link.ProbedLinkProvider;
import org.slf4j.Logger;
import java.nio.ByteBuffer;
......@@ -43,7 +44,6 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.onosproject.net.PortNumber.portNumber;
import static org.onosproject.net.flow.DefaultTrafficTreatment.builder;
import static org.slf4j.LoggerFactory.getLogger;
import static org.onosproject.cluster.ClusterMetadata.NO_NAME;
/**
* Run discovery process from a physical switch. Ports are initially labeled as
......@@ -56,8 +56,6 @@ public class LinkDiscovery implements TimerTask {
private final Logger log = getLogger(getClass());
private static final String SRC_MAC = "DE:AD:BE:EF:BA:11";
private final Device device;
private final LinkDiscoveryContext context;
......@@ -66,8 +64,6 @@ public class LinkDiscovery implements TimerTask {
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<Long> ports = Sets.newConcurrentHashSet();
......@@ -93,7 +89,6 @@ public class LinkDiscovery implements TimerTask {
bddpEth.setDestinationMACAddress(ONOSLLDP.BDDP_MULTICAST);
bddpEth.setPad(true);
fingerprinted = false;
isStopped = true;
start();
log.debug("Started discovery manager for switch {}", device.id());
......@@ -160,8 +155,12 @@ public class LinkDiscovery implements TimerTask {
ONOSLLDP onoslldp = ONOSLLDP.parseONOSLLDP(eth);
if (onoslldp != null) {
if (notMy(onoslldp)) {
return true;
Type lt;
if (notMy(eth.getSourceMAC().toString())) {
lt = Type.EDGE;
} else {
lt = eth.getEtherType() == Ethernet.TYPE_LLDP ?
Type.DIRECT : Type.INDIRECT;
}
PortNumber srcPort = portNumber(onoslldp.getPort());
......@@ -172,10 +171,7 @@ public class LinkDiscovery implements TimerTask {
ConnectPoint src = new ConnectPoint(srcDeviceId, srcPort);
ConnectPoint dst = new ConnectPoint(dstDeviceId, dstPort);
LinkDescription ld = eth.getEtherType() == Ethernet.TYPE_LLDP ?
new DefaultLinkDescription(src, dst, Type.DIRECT) :
new DefaultLinkDescription(src, dst, Type.INDIRECT);
LinkDescription ld = new DefaultLinkDescription(src, dst, lt);
try {
context.providerService().linkDetected(ld);
context.touchLink(LinkKey.linkKey(src, dst));
......@@ -188,24 +184,13 @@ public class LinkDiscovery implements TimerTask {
}
// true if *NOT* this cluster's own probe.
private boolean notMy(ONOSLLDP onoslldp) {
if (onoslldp.getDomainTLV() == null) {
// not finger-printed - but we can check the source
DeviceId src = DeviceId.deviceId(onoslldp.getDeviceString());
if (context.deviceService().getDevice(src) == null) {
return true;
}
return false;
}
String us = context.fingerprint();
String them = onoslldp.getDomainString();
// if: Our and/or their MetadataService in poorly state, conservative 'yes'
if (NO_NAME.equals(us) || NO_NAME.equals(them)) {
private boolean notMy(String mac) {
// if we are using DEFAULT_MAC, clustering hadn't initialized, so conservative 'yes'
String ourMac = context.fingerprint();
if (ProbedLinkProvider.defaultMac().equalsIgnoreCase(ourMac)) {
return true;
} else {
return !us.equals(them);
}
return !mac.equalsIgnoreCase(ourMac);
}
/**
......@@ -242,7 +227,7 @@ public class LinkDiscovery implements TimerTask {
return null;
}
ONOSLLDP lldp = getLinkProbe(port);
ethPacket.setSourceMACAddress(SRC_MAC).setPayload(lldp);
ethPacket.setSourceMACAddress(context.fingerprint()).setPayload(lldp);
return new DefaultOutboundPacket(device.id(),
builder().setOutput(portNumber(port)).build(),
ByteBuffer.wrap(ethPacket.serialize()));
......@@ -259,18 +244,14 @@ public class LinkDiscovery implements TimerTask {
return null;
}
ONOSLLDP lldp = getLinkProbe(port);
bddpEth.setSourceMACAddress(SRC_MAC).setPayload(lldp);
bddpEth.setSourceMACAddress(context.fingerprint()).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());
return ONOSLLDP.onosLLDP(device.id().toString(), device.chassisId(), port.intValue());
}
private void sendProbes(Long portNumber) {
......@@ -286,12 +267,4 @@ public class LinkDiscovery implements TimerTask {
public boolean containsPort(long portNumber) {
return ports.contains(portNumber);
}
public void enableFingerprint() {
fingerprinted = true;
}
public void disableFingerprint() {
fingerprinted = false;
}
}
......
......@@ -29,6 +29,7 @@ import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.onlab.packet.Ethernet;
import org.onlab.packet.ONOSLLDP;
import org.onosproject.cluster.ClusterMetadataService;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.mastership.MastershipService;
......@@ -50,7 +51,7 @@ import org.onosproject.net.device.DeviceService;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.link.DefaultLinkDescription;
import org.onosproject.net.link.LinkProvider;
import org.onosproject.net.link.ProbedLinkProvider;
import org.onosproject.net.link.LinkProviderRegistry;
import org.onosproject.net.link.LinkProviderService;
import org.onosproject.net.packet.InboundPacket;
......@@ -77,7 +78,7 @@ import static org.onosproject.net.PortNumber.portNumber;
@Component(immediate = true)
public class NetworkConfigLinksProvider
extends AbstractProvider
implements LinkProvider {
implements ProbedLinkProvider {
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected LinkProviderRegistry providerRegistry;
......@@ -97,6 +98,9 @@ public class NetworkConfigLinksProvider
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected ClusterMetadataService metadataService;
private static final String PROP_PROBE_RATE = "probeRate";
private static final int DEFAULT_PROBE_RATE = 3000;
@Property(name = PROP_PROBE_RATE, intValue = DEFAULT_PROBE_RATE,
......@@ -125,6 +129,17 @@ public class NetworkConfigLinksProvider
super(new ProviderId("lldp", PROVIDER_NAME));
}
private final String buildSrcMac() {
String srcMac = ProbedLinkProvider.fingerprintMac(metadataService.getClusterMetadata());
String defMac = ProbedLinkProvider.defaultMac();
if (srcMac.equals(defMac)) {
log.warn("Couldn't generate fingerprint. Using default value {}", defMac);
return defMac;
}
log.trace("Generated MAC address {}", srcMac);
return srcMac;
}
private void createLinks() {
netCfgService.getSubjects(LinkKey.class)
.forEach(linkKey -> configuredLinks.add(linkKey));
......@@ -240,7 +255,7 @@ public class NetworkConfigLinksProvider
@Override
public String fingerprint() {
return "";
return buildSrcMac();
}
@Override
......
......@@ -24,6 +24,7 @@ import org.junit.Test;
import org.onlab.packet.ChassisId;
import org.onlab.packet.Ethernet;
import org.onlab.packet.ONOSLLDP;
import org.onosproject.cluster.ClusterMetadataServiceAdapter;
import org.onosproject.core.CoreServiceAdapter;
import org.onosproject.mastership.MastershipServiceAdapter;
import org.onosproject.net.ConnectPoint;
......@@ -232,6 +233,7 @@ public class NetworkConfigLinksProviderTest {
provider.deviceService = new TestDeviceManager();
provider.masterService = new TestMastershipService();
provider.packetService = new TestPacketService();
provider.metadataService = new ClusterMetadataServiceAdapter();
provider.netCfgService = configRegistry;
provider.activate();
......
......@@ -250,24 +250,4 @@ public class ONOSLLDP extends LLDP {
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;
}
}
......