blob: d590570c4e7e4020770bf21c8d04d983accee7cd [file] [log] [blame]
/*
* Copyright 2018-present Open Networking Foundation
*
* 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.provider.hostprobing.impl;
import org.onlab.packet.ARP;
import org.onlab.packet.Ethernet;
import org.onlab.packet.IPv6;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.Ip6Address;
import org.onlab.packet.IpAddress;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
import org.onlab.packet.ndp.NeighborSolicitation;
import org.onosproject.mastership.MastershipService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.Host;
import org.onosproject.net.HostLocation;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.host.HostProbe;
import org.onosproject.net.host.HostProbingEvent;
import org.onosproject.net.host.HostProbingProvider;
import org.onosproject.net.host.HostProbingProviderRegistry;
import org.onosproject.net.host.HostProbingProviderService;
import org.onosproject.net.host.HostProvider;
import org.onosproject.net.host.HostProviderRegistry;
import org.onosproject.net.host.HostProviderService;
import org.onosproject.net.host.ProbeMode;
import org.onosproject.net.packet.DefaultOutboundPacket;
import org.onosproject.net.packet.OutboundPacket;
import org.onosproject.net.packet.PacketProcessor;
import org.onosproject.net.packet.PacketService;
import org.onosproject.net.provider.AbstractProvider;
import org.onosproject.net.provider.ProviderId;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.slf4j.Logger;
import java.nio.ByteBuffer;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import static java.util.concurrent.Executors.newScheduledThreadPool;
import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
import static org.onlab.util.Tools.groupedThreads;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Provider which sends host location probes to discover or verify a host at specific location.
*/
@Component(immediate = true, service = { HostProvider.class, HostProbingProvider.class })
public class DefaultHostProbingProvider extends AbstractProvider implements HostProvider, HostProbingProvider {
private final Logger log = getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY)
private HostProviderRegistry providerRegistry;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
private HostProbingProviderRegistry hostProbingProviderRegistry;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
private PacketService packetService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
private MastershipService mastershipService;
private HostProviderService providerService;
private HostProbingProviderService hostProbingProviderService;
private ExecutorService packetHandler;
private ExecutorService probeEventHandler;
private ScheduledExecutorService hostProber;
private final PacketProcessor packetProcessor = context ->
packetHandler.execute(() -> {
Ethernet eth = context.inPacket().parsed();
if (eth == null) {
return;
}
MacAddress srcMac = eth.getSourceMAC();
MacAddress destMac = eth.getDestinationMAC();
VlanId vlan = VlanId.vlanId(eth.getVlanID());
ConnectPoint heardOn = context.inPacket().receivedFrom();
// Receives a location probe. Invalid entry from the cache
if (destMac.isOnos() && !MacAddress.NONE.equals(destMac)) {
log.debug("Receives probe for {}/{} on {}", srcMac, vlan, heardOn);
hostProbingProviderService.removeProbingHost(destMac);
}
});
// TODO Make this configurable
private static final int PROBE_INIT_DELAY_MS = 1000;
private static final int DEFAULT_RETRY = 5;
/**
* Creates an OpenFlow host provider.
*/
public DefaultHostProbingProvider() {
super(new ProviderId("hostprobing", "org.onosproject.provider.hostprobing"));
}
@Activate
public void activate(ComponentContext context) {
providerService = providerRegistry.register(this);
hostProbingProviderService = hostProbingProviderRegistry.register(this);
packetHandler = newSingleThreadScheduledExecutor(groupedThreads("onos/host-loc-provider",
"packet-handler", log));
probeEventHandler = newSingleThreadScheduledExecutor(groupedThreads("onos/host-loc-provider",
"probe-handler", log));
hostProber = newScheduledThreadPool(32, groupedThreads("onos/host-loc-probe", "%d", log));
packetService.addProcessor(packetProcessor, PacketProcessor.advisor(1));
}
@Deactivate
public void deactivate() {
providerRegistry.unregister(this);
hostProbingProviderRegistry.unregister(this);
providerService = null;
packetService.removeProcessor(packetProcessor);
packetHandler.shutdown();
probeEventHandler.shutdown();
hostProber.shutdown();
}
@Override
public void triggerProbe(Host host) {
// Not doing anything at this moment...
}
@Override
public void processEvent(HostProbingEvent event) {
probeEventHandler.execute(() -> {
log.debug("Receiving HostProbingEvent {}", event);
HostProbe hostProbe = event.subject();
switch (event.type()) {
case PROBE_REQUESTED:
// Do nothing
break;
case PROBE_TIMEOUT:
// Retry probe until PROBE_FAIL
// TODO Only retry DISCOVER probes
probeHostInternal(hostProbe, hostProbe.connectPoint(),
hostProbe.mode(), hostProbe.probeMac(), hostProbe.retry());
break;
case PROBE_FAIL:
// Remove this location if this is a verify probe.
if (hostProbe.mode() == ProbeMode.VERIFY) {
providerService.removeLocationFromHost(hostProbe.id(),
(HostLocation) hostProbe.connectPoint());
}
break;
case PROBE_COMPLETED:
// Add this location if this is a discover probe.
if (hostProbe.mode() == ProbeMode.DISCOVER) {
HostLocation newLocation = new HostLocation(hostProbe.connectPoint(),
System.currentTimeMillis());
providerService.addLocationToHost(hostProbe.id(), newLocation);
}
break;
default:
log.warn("Unknown HostProbingEvent type: {}", event.type());
}
});
}
@Override
public void probeHost(Host host, ConnectPoint connectPoint, ProbeMode probeMode) {
probeHostInternal(host, connectPoint, probeMode, null, DEFAULT_RETRY);
}
// probeMac can be null if this is the very first probe and the mac is to-be-generated.
private void probeHostInternal(Host host, ConnectPoint connectPoint, ProbeMode probeMode,
MacAddress probeMac, int retry) {
if (!mastershipService.isLocalMaster(connectPoint.deviceId())) {
log.debug("Current node is not master of {}, abort probing {}", connectPoint.deviceId(), host);
return;
}
log.debug("probeHostInternal host={}, cp={}, mode={}, probeMac={}, retry={}", host, connectPoint,
probeMode, probeMac, retry);
Optional<IpAddress> ipOptional = host.ipAddresses().stream().findFirst();
if (ipOptional.isPresent()) {
probeMac = hostProbingProviderService.addProbingHost(host, connectPoint, probeMode, probeMac, retry);
IpAddress ip = ipOptional.get();
log.debug("Constructing {} probe for host {} with {}", probeMode, host.id(), ip);
Ethernet probe;
if (ip.isIp4()) {
probe = ARP.buildArpRequest(probeMac.toBytes(), Ip4Address.ZERO.toOctets(),
host.id().mac().toBytes(), ip.toOctets(),
host.id().mac().toBytes(), host.id().vlanId().toShort());
} else {
probe = NeighborSolicitation.buildNdpSolicit(
ip.getIp6Address(),
Ip6Address.valueOf(IPv6.getLinkLocalAddress(probeMac.toBytes())),
ip.getIp6Address(),
probeMac,
host.id().mac(),
host.id().vlanId());
}
// NOTE: delay the probe a little bit to wait for the store synchronization is done
hostProber.schedule(() ->
sendLocationProbe(probe, connectPoint), PROBE_INIT_DELAY_MS, TimeUnit.MILLISECONDS);
} else {
log.debug("Host {} has no IP address yet. Skip probing.", host);
}
}
/**
* Send the probe packet on given port.
*
* @param probe the probe packet
* @param connectPoint the port we want to probe
*/
private void sendLocationProbe(Ethernet probe, ConnectPoint connectPoint) {
log.debug("Sending probe for host {} on location {} with probeMac {}",
probe.getDestinationMAC(), connectPoint, probe.getSourceMAC());
TrafficTreatment treatment = DefaultTrafficTreatment.builder().setOutput(connectPoint.port()).build();
OutboundPacket outboundPacket = new DefaultOutboundPacket(connectPoint.deviceId(),
treatment, ByteBuffer.wrap(probe.serialize()));
packetService.emit(outboundPacket);
}
}