blob: 09676b1a56fed3e8178a149d20af53c3baa0311a [file] [log] [blame]
/*
* Copyright 2016-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.incubator.net.neighbour.impl;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.SetMultimap;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Modified;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.Service;
import org.onlab.packet.Ethernet;
import org.onlab.packet.ICMP6;
import org.onlab.packet.IPv6;
import org.onlab.packet.IpAddress;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
import org.onlab.util.Tools;
import org.onosproject.cfg.ComponentConfigService;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.incubator.net.intf.Interface;
import org.onosproject.incubator.net.neighbour.NeighbourHandlerRegistration;
import org.onosproject.incubator.net.neighbour.NeighbourMessageActions;
import org.onosproject.incubator.net.neighbour.NeighbourMessageContext;
import org.onosproject.incubator.net.neighbour.NeighbourMessageHandler;
import org.onosproject.incubator.net.neighbour.NeighbourResolutionService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.edge.EdgePortService;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.host.HostService;
import org.onosproject.net.packet.InboundPacket;
import org.onosproject.net.packet.PacketContext;
import org.onosproject.net.packet.PacketProcessor;
import org.onosproject.net.packet.PacketService;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Dictionary;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.onlab.packet.Ethernet.TYPE_ARP;
import static org.onlab.packet.Ethernet.TYPE_IPV6;
import static org.onlab.packet.ICMP6.NEIGHBOR_ADVERTISEMENT;
import static org.onlab.packet.ICMP6.NEIGHBOR_SOLICITATION;
import static org.onlab.packet.IPv6.PROTOCOL_ICMP6;
import static org.onosproject.net.packet.PacketPriority.CONTROL;
/**
* Manages handlers for neighbour messages.
*/
@Service
@Component(immediate = true)
public class NeighbourResolutionManager implements NeighbourResolutionService {
private final Logger log = LoggerFactory.getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected HostService hostService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected EdgePortService edgeService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected PacketService packetService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected ComponentConfigService componentConfigService;
@Property(name = "arpEnabled", boolValue = true,
label = "Enable Address resolution protocol")
protected boolean arpEnabled = true;
@Property(name = "ndpEnabled", boolValue = false,
label = "Enable IPv6 neighbour discovery")
protected boolean ndpEnabled = false;
@Property(name = "requestInterceptsEnabled", boolValue = true,
label = "Enable requesting packet intercepts")
private boolean requestInterceptsEnabled = true;
private static final String APP_NAME = "org.onosproject.neighbour";
private ApplicationId appId;
private final SetMultimap<ConnectPoint, NeighbourHandlerRegistration> packetHandlers =
Multimaps.synchronizedSetMultimap(HashMultimap.create());
private final InternalPacketProcessor processor = new InternalPacketProcessor();
private NeighbourMessageActions actions;
@Activate
protected void activate(ComponentContext context) {
appId = coreService.registerApplication(APP_NAME);
componentConfigService.registerProperties(getClass());
modified(context);
actions = new DefaultNeighbourMessageActions(packetService, edgeService);
packetService.addProcessor(processor, PacketProcessor.director(1));
}
@Deactivate
protected void deactivate() {
cancelPackets();
packetService.removeProcessor(processor);
componentConfigService.unregisterProperties(getClass(), false);
}
@Modified
protected void modified(ComponentContext context) {
Dictionary<?, ?> properties = context.getProperties();
Boolean flag;
flag = Tools.isPropertyEnabled(properties, "ndpEnabled");
if (flag != null) {
ndpEnabled = flag;
log.info("IPv6 neighbor discovery is {}",
ndpEnabled ? "enabled" : "disabled");
}
flag = Tools.isPropertyEnabled(properties, "arpEnabled");
if (flag != null) {
arpEnabled = flag;
log.info("Address resolution protocol is {}",
arpEnabled ? "enabled" : "disabled");
}
flag = Tools.isPropertyEnabled(properties, "requestInterceptsEnabled");
if (flag == null) {
log.info("Request intercepts is not configured, " +
"using current value of {}", requestInterceptsEnabled);
} else {
requestInterceptsEnabled = flag;
log.info("Configured. Request intercepts is {}",
requestInterceptsEnabled ? "enabled" : "disabled");
}
synchronized (packetHandlers) {
if (!packetHandlers.isEmpty() && requestInterceptsEnabled) {
requestPackets();
} else {
cancelPackets();
}
}
}
private void requestPackets() {
if (arpEnabled) {
packetService.requestPackets(buildArpSelector(), CONTROL, appId);
} else {
packetService.cancelPackets(buildArpSelector(), CONTROL, appId);
}
if (ndpEnabled) {
packetService.requestPackets(buildNeighborSolicitationSelector(),
CONTROL, appId);
packetService.requestPackets(buildNeighborAdvertisementSelector(),
CONTROL, appId);
} else {
packetService.cancelPackets(buildNeighborSolicitationSelector(),
CONTROL, appId);
packetService.cancelPackets(buildNeighborAdvertisementSelector(),
CONTROL, appId);
}
}
private void cancelPackets() {
packetService.cancelPackets(buildArpSelector(), CONTROL, appId);
packetService.cancelPackets(buildNeighborSolicitationSelector(),
CONTROL, appId);
packetService.cancelPackets(buildNeighborAdvertisementSelector(),
CONTROL, appId);
}
private TrafficSelector buildArpSelector() {
return DefaultTrafficSelector.builder()
.matchEthType(TYPE_ARP)
.build();
}
private TrafficSelector buildNeighborSolicitationSelector() {
return DefaultTrafficSelector.builder()
.matchEthType(TYPE_IPV6)
.matchIPProtocol(PROTOCOL_ICMP6)
.matchIcmpv6Type(NEIGHBOR_SOLICITATION)
.build();
}
private TrafficSelector buildNeighborAdvertisementSelector() {
return DefaultTrafficSelector.builder()
.matchEthType(TYPE_IPV6)
.matchIPProtocol(PROTOCOL_ICMP6)
.matchIcmpv6Type(NEIGHBOR_ADVERTISEMENT)
.build();
}
@Override
public void registerNeighbourHandler(ConnectPoint connectPoint,
NeighbourMessageHandler handler,
ApplicationId appId) {
register(connectPoint, new HandlerRegistration(handler, appId));
}
@Override
public void registerNeighbourHandler(Interface intf,
NeighbourMessageHandler handler,
ApplicationId appId) {
register(intf.connectPoint(), new HandlerRegistration(handler, intf, appId));
}
private void register(ConnectPoint connectPoint, HandlerRegistration registration) {
synchronized (packetHandlers) {
if (packetHandlers.isEmpty()) {
requestPackets();
}
packetHandlers.put(connectPoint, registration);
}
}
@Override
public void unregisterNeighbourHandler(ConnectPoint connectPoint,
NeighbourMessageHandler handler,
ApplicationId appId) {
unregister(connectPoint, new HandlerRegistration(handler, appId));
}
@Override
public void unregisterNeighbourHandler(Interface intf,
NeighbourMessageHandler handler,
ApplicationId appId) {
unregister(intf.connectPoint(), new HandlerRegistration(handler, intf, appId));
}
private void unregister(ConnectPoint connectPoint, HandlerRegistration registration) {
synchronized (packetHandlers) {
packetHandlers.remove(connectPoint, registration);
if (packetHandlers.isEmpty()) {
cancelPackets();
}
}
}
@Override
public void unregisterNeighbourHandlers(ApplicationId appId) {
synchronized (packetHandlers) {
Iterator<NeighbourHandlerRegistration> it = packetHandlers.values().iterator();
while (it.hasNext()) {
NeighbourHandlerRegistration registration = it.next();
if (registration.appId().equals(appId)) {
it.remove();
}
}
if (packetHandlers.isEmpty()) {
cancelPackets();
}
}
}
@Override
public Map<ConnectPoint, Collection<NeighbourHandlerRegistration>> getHandlerRegistrations() {
return ImmutableMap.copyOf(Multimaps.asMap(packetHandlers));
}
private void handlePacket(PacketContext context) {
InboundPacket pkt = context.inPacket();
Ethernet ethPkt = pkt.parsed();
NeighbourMessageContext msgContext =
DefaultNeighbourMessageContext.createContext(ethPkt, pkt.receivedFrom(), actions);
if (msgContext == null) {
return;
}
if (handleMessage(msgContext)) {
context.block();
}
}
private boolean handleMessage(NeighbourMessageContext context) {
Collection<NeighbourHandlerRegistration> handlers = packetHandlers.get(context.inPort());
Collection<NeighbourHandlerRegistration> handled = handlers
.stream()
.filter(registration -> registration.intf() == null || matches(context, registration.intf()))
.collect(Collectors.toSet());
handled.forEach(registration -> registration.handler().handleMessage(context, hostService));
return !handled.isEmpty();
}
/**
* Checks that incoming packet matches the parameters of the interface.
* This means that if the interface specifies a particular parameter
* (VLAN, IP address, etc.) then the incoming packet should match those
* parameters.
*
* @param context incoming message context
* @param intf interface to check
* @return true if the incoming message matches the interface, otherwise false
*/
private boolean matches(NeighbourMessageContext context, Interface intf) {
checkNotNull(context);
checkNotNull(intf);
boolean matches = true;
// For non-broadcast packets, if the interface has a MAC address check that
// the destination MAC address of the packet matches the interface MAC
if (!context.dstMac().isBroadcast() &&
!intf.mac().equals(MacAddress.NONE) &&
!intf.mac().equals(context.dstMac())) {
matches = false;
}
// If the interface has a VLAN, check that the packet's VLAN matches
if (!intf.vlan().equals(VlanId.NONE) && !intf.vlan().equals(context.vlan())) {
matches = false;
}
// If the interface has IP addresses, check that the packet's target IP
// address matches one of the interface IP addresses
if (!intf.ipAddressesList().isEmpty() && !hasIp(intf, context.target())) {
matches = false;
}
return matches;
}
/**
* Returns true if the interface has the given IP address.
*
* @param intf interface to check
* @param ip IP address
* @return true if the IP is configured on the interface, otherwise false
*/
private boolean hasIp(Interface intf, IpAddress ip) {
return intf.ipAddressesList().stream()
.anyMatch(intfAddress -> intfAddress.ipAddress().equals(ip));
}
/**
* Stores a neighbour message handler registration.
*/
private class HandlerRegistration implements NeighbourHandlerRegistration {
private final Interface intf;
private final NeighbourMessageHandler handler;
private final ApplicationId appId;
/**
* Creates a new handler registration.
*
* @param handler neighbour message handler
*/
public HandlerRegistration(NeighbourMessageHandler handler, ApplicationId appId) {
this(handler, null, appId);
}
/**
* Creates a new handler registration.
*
* @param handler neighbour message handler
* @param intf interface
*/
public HandlerRegistration(NeighbourMessageHandler handler, Interface intf, ApplicationId appId) {
this.intf = intf;
this.handler = handler;
this.appId = appId;
}
@Override
public Interface intf() {
return intf;
}
@Override
public NeighbourMessageHandler handler() {
return handler;
}
@Override
public ApplicationId appId() {
return appId;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof HandlerRegistration)) {
return false;
}
HandlerRegistration that = (HandlerRegistration) other;
return Objects.equals(intf, that.intf) &&
Objects.equals(handler, that.handler) &&
Objects.equals(appId, that.appId);
}
@Override
public int hashCode() {
return Objects.hash(intf, handler, appId);
}
}
/**
* Packet processor for incoming packets.
*/
private class InternalPacketProcessor implements PacketProcessor {
@Override
public void process(PacketContext context) {
// Stop processing if the packet has been handled, since we
// can't do any more to it.
if (context.isHandled()) {
return;
}
InboundPacket pkt = context.inPacket();
Ethernet ethPkt = pkt.parsed();
if (ethPkt == null) {
return;
}
if (ethPkt.getEtherType() == TYPE_ARP) {
// handle ARP packets
handlePacket(context);
} else if (ethPkt.getEtherType() == TYPE_IPV6) {
IPv6 ipv6 = (IPv6) ethPkt.getPayload();
if (ipv6.getNextHeader() == IPv6.PROTOCOL_ICMP6) {
ICMP6 icmp6 = (ICMP6) ipv6.getPayload();
if (icmp6.getIcmpType() == NEIGHBOR_SOLICITATION ||
icmp6.getIcmpType() == NEIGHBOR_ADVERTISEMENT) {
// handle ICMPv6 solicitations and advertisements (NDP)
handlePacket(context);
}
}
}
}
}
}