| /* |
| * 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.host.impl; |
| |
| import org.jboss.netty.util.Timeout; |
| import org.jboss.netty.util.TimerTask; |
| 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.Timer; |
| import org.onosproject.incubator.net.intf.Interface; |
| import org.onosproject.incubator.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.host.InterfaceIpAddress; |
| 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.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 TimerTask { |
| |
| 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 Timeout 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) { |
| 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 = Timer.getTimer().newTimeout(this, 0, TimeUnit.MILLISECONDS); |
| } |
| } |
| } |
| |
| /** |
| * Stops the host monitor. |
| */ |
| void shutdown() { |
| synchronized (this) { |
| timeout.cancel(); |
| 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(Timeout timeout) throws Exception { |
| monitoredAddresses.forEach(this::probe); |
| |
| synchronized (this) { |
| this.timeout = Timer.getTimer().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) { |
| Interface intf = interfaceService.getMatchingInterface(targetIp); |
| |
| if (intf == null) { |
| return; |
| } |
| |
| if (!edgePortService.isEdgePoint(intf.connectPoint())) { |
| log.warn("Attempt to send probe out non-edge port: {}", intf); |
| return; |
| } |
| |
| for (InterfaceIpAddress ia : intf.ipAddressesList()) { |
| if (ia.subnetAddress().contains(targetIp)) { |
| sendProbe(intf.connectPoint(), targetIp, ia.ipAddress(), |
| intf.mac(), intf.vlan()); |
| } |
| } |
| } |
| |
| private 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; |
| } |
| |
| } |