blob: 950bedddb5a5a9cb0f6836430548fa7a95717560 [file] [log] [blame]
/*
* Copyright 2014-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.net.host.impl;
import org.onlab.packet.ARP;
import org.onlab.packet.Ethernet;
import org.onlab.packet.IPv6;
import org.onlab.packet.IpAddress;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
import org.onlab.packet.ndp.NeighborSolicitation;
import org.onlab.util.SharedScheduledExecutors;
import org.onosproject.net.intf.InterfaceService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.Host;
import org.onosproject.net.edge.EdgePortService;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.host.HostProvider;
import org.onosproject.net.packet.DefaultOutboundPacket;
import org.onosproject.net.packet.OutboundPacket;
import org.onosproject.net.packet.PacketService;
import org.onosproject.net.provider.ProviderId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
* Monitors hosts on the dataplane to detect changes in host data.
* <p>
* The HostMonitor can monitor hosts that have already been detected for
* changes. At an application's request, it can also monitor and actively
* probe for hosts that have not yet been detected (specified by IP address).
* </p>
*/
public class HostMonitor implements Runnable {
private Logger log = LoggerFactory.getLogger(getClass());
private PacketService packetService;
private HostManager hostManager;
private InterfaceService interfaceService;
private EdgePortService edgePortService;
private final Set<IpAddress> monitoredAddresses;
private final ConcurrentMap<ProviderId, HostProvider> hostProviders;
private static final long DEFAULT_PROBE_RATE = 30000; // milliseconds
private static final byte[] ZERO_MAC_ADDRESS = MacAddress.ZERO.toBytes();
private long probeRate = DEFAULT_PROBE_RATE;
private ScheduledFuture<?> timeout;
/**
* Creates a new host monitor.
*
* @param packetService packet service used to send packets on the data plane
* @param hostManager host manager used to look up host information and
* probe existing hosts
* @param interfaceService interface service for interface information
* @param edgePortService edge port service
*/
public HostMonitor(PacketService packetService, HostManager hostManager,
InterfaceService interfaceService,
EdgePortService edgePortService) {
this.packetService = packetService;
this.hostManager = hostManager;
this.interfaceService = interfaceService;
this.edgePortService = edgePortService;
monitoredAddresses = Collections.newSetFromMap(new ConcurrentHashMap<>());
hostProviders = new ConcurrentHashMap<>();
}
/**
* Adds an IP address to be monitored by the host monitor. The monitor will
* periodically probe the host to detect changes.
*
* @param ip IP address of the host to monitor
*/
void addMonitoringFor(IpAddress ip) {
if (monitoredAddresses.add(ip)) {
probe(ip);
}
}
/**
* Stops monitoring the given IP address.
*
* @param ip IP address to stop monitoring on
*/
void stopMonitoring(IpAddress ip) {
monitoredAddresses.remove(ip);
}
/**
* Starts the host monitor. Does nothing if the monitor is already running.
*/
void start() {
synchronized (this) {
if (timeout == null) {
timeout = SharedScheduledExecutors.newTimeout(this, 0, TimeUnit.MILLISECONDS);
}
}
}
/**
* Stops the host monitor.
*/
void shutdown() {
synchronized (this) {
timeout.cancel(true);
timeout = null;
}
}
/*
* Sets the probe rate.
*/
void setProbeRate(long probeRate) {
this.probeRate = probeRate;
}
/**
* Registers a host provider with the host monitor. The monitor can use the
* provider to probe hosts.
*
* @param provider the host provider to register
*/
void registerHostProvider(HostProvider provider) {
hostProviders.put(provider.id(), provider);
}
@Override
public void run() {
monitoredAddresses.forEach(this::probe);
synchronized (this) {
this.timeout = SharedScheduledExecutors.newTimeout(this, probeRate, TimeUnit.MILLISECONDS);
}
}
private void probe(IpAddress ip) {
Set<Host> hosts = hostManager.getHostsByIp(ip);
if (hosts.isEmpty()) {
sendRequest(ip);
} else {
for (Host host : hosts) {
HostProvider provider = hostProviders.get(host.providerId());
if (provider == null) {
hostProviders.remove(host.providerId(), null);
} else {
provider.triggerProbe(host);
}
}
}
}
/**
* Sends an ARP or NDP request for the given IP address.
*
* @param targetIp IP address to send the request for
*/
private void sendRequest(IpAddress targetIp) {
interfaceService.getMatchingInterfaces(targetIp).forEach(intf -> {
if (!edgePortService.isEdgePoint(intf.connectPoint())) {
log.warn("Aborting attempt to send probe out non-edge port: {}", intf);
return;
}
intf.ipAddressesList().stream()
.filter(ia -> ia.subnetAddress().contains(targetIp))
.forEach(ia -> {
log.debug("Sending probe for target:{} out of intf:{} vlan:{}",
targetIp, intf.connectPoint(), intf.vlan());
sendProbe(intf.connectPoint(), targetIp, ia.ipAddress(),
intf.mac(), intf.vlan());
// account for use-cases where tagged-vlan config is used
if (!intf.vlanTagged().isEmpty()) {
intf.vlanTagged().forEach(tag -> {
log.debug("Sending probe for target:{} out of intf:{} vlan:{}",
targetIp, intf.connectPoint(), tag);
sendProbe(intf.connectPoint(), targetIp, ia.ipAddress(),
intf.mac(), tag);
});
}
});
});
}
public void sendProbe(ConnectPoint connectPoint,
IpAddress targetIp,
IpAddress sourceIp,
MacAddress sourceMac,
VlanId vlan) {
Ethernet probePacket;
if (targetIp.isIp4()) {
// IPv4: Use ARP
probePacket = buildArpRequest(targetIp, sourceIp, sourceMac, vlan);
} else {
// IPv6: Use Neighbor Discovery. According to the NDP protocol,
// we should use the solicitation node address as IPv6 destination
// and the multicast mac address as Ethernet destination.
byte[] destIp = IPv6.getSolicitNodeAddress(targetIp.toOctets());
probePacket = NeighborSolicitation.buildNdpSolicit(
targetIp.toOctets(),
sourceIp.toOctets(),
destIp,
sourceMac.toBytes(),
IPv6.getMCastMacAddress(destIp),
vlan
);
}
if (probePacket == null) {
log.warn("Not able to build the probe packet");
return;
}
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setOutput(connectPoint.port())
.build();
OutboundPacket outboundPacket =
new DefaultOutboundPacket(connectPoint.deviceId(), treatment,
ByteBuffer.wrap(probePacket.serialize()));
packetService.emit(outboundPacket);
}
private Ethernet buildArpRequest(IpAddress targetIp, IpAddress sourceIp,
MacAddress sourceMac, VlanId vlan) {
ARP arp = new ARP();
arp.setHardwareType(ARP.HW_TYPE_ETHERNET)
.setHardwareAddressLength((byte) Ethernet.DATALAYER_ADDRESS_LENGTH)
.setProtocolType(ARP.PROTO_TYPE_IP)
.setProtocolAddressLength((byte) IpAddress.INET_BYTE_LENGTH)
.setOpCode(ARP.OP_REQUEST);
arp.setSenderHardwareAddress(sourceMac.toBytes())
.setSenderProtocolAddress(sourceIp.toOctets())
.setTargetHardwareAddress(ZERO_MAC_ADDRESS)
.setTargetProtocolAddress(targetIp.toOctets());
Ethernet ethernet = new Ethernet();
ethernet.setEtherType(Ethernet.TYPE_ARP)
.setDestinationMACAddress(MacAddress.BROADCAST)
.setSourceMACAddress(sourceMac)
.setPayload(arp);
if (!vlan.equals(VlanId.NONE)) {
ethernet.setVlanID(vlan.toShort());
}
ethernet.setPad(true);
return ethernet;
}
}