IPv6RA : Unsolicited RA support, Misc. fixes
Change-Id: I8d679849d51248d5e190d24abad32103f2074645
diff --git a/apps/routeradvertisement/src/main/java/org/onosproject/ra/RouterAdvertisementManager.java b/apps/routeradvertisement/src/main/java/org/onosproject/ra/RouterAdvertisementManager.java
index 91aea80..1c08c07 100644
--- a/apps/routeradvertisement/src/main/java/org/onosproject/ra/RouterAdvertisementManager.java
+++ b/apps/routeradvertisement/src/main/java/org/onosproject/ra/RouterAdvertisementManager.java
@@ -19,61 +19,67 @@
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.Modified;
import org.onlab.packet.EthType;
import org.onlab.packet.Ethernet;
import org.onlab.packet.ICMP6;
import org.onlab.packet.IPv6;
-import org.onlab.packet.IpAddress;
import org.onlab.packet.Ip6Address;
+import org.onlab.packet.IpAddress;
import org.onlab.packet.MacAddress;
-import org.onlab.packet.ndp.RouterAdvertisement;
import org.onlab.packet.ndp.NeighborDiscoveryOptions;
+import org.onlab.packet.ndp.RouterAdvertisement;
import org.onosproject.cfg.ComponentConfigService;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import org.onosproject.net.MastershipRole;
import org.onosproject.net.config.ConfigFactory;
import org.onosproject.net.config.NetworkConfigEvent;
import org.onosproject.net.config.NetworkConfigListener;
import org.onosproject.net.config.NetworkConfigRegistry;
+import org.onosproject.net.config.basics.InterfaceConfig;
import org.onosproject.net.config.basics.SubjectFactories;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.host.InterfaceIpAddress;
import org.onosproject.net.intf.Interface;
import org.onosproject.net.intf.InterfaceEvent;
import org.onosproject.net.intf.InterfaceListener;
import org.onosproject.net.intf.InterfaceService;
-import org.onosproject.net.ConnectPoint;
-import org.onosproject.net.flow.DefaultTrafficTreatment;
-import org.onosproject.net.flow.TrafficTreatment;
-import org.onosproject.net.host.InterfaceIpAddress;
import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.InboundPacket;
import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketProcessor;
import org.onosproject.net.packet.PacketService;
import org.onosproject.ra.config.RouterAdvertisementDeviceConfig;
+import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.osgi.service.component.ComponentContext;
import javax.annotation.concurrent.GuardedBy;
import java.nio.ByteBuffer;
+import java.util.AbstractMap;
+import java.util.Arrays;
import java.util.Dictionary;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-import java.util.Arrays;
import java.util.Optional;
import java.util.Set;
-import java.util.AbstractMap;
-
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.IntStream;
@@ -118,10 +124,10 @@
public MastershipService mastershipService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
- public DeviceService deviceService;
+ protected NetworkConfigRegistry networkConfigRegistry;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
- protected NetworkConfigRegistry networkConfigRegistry;
+ protected DeviceService deviceService;
@Property(name = PROP_RA_THREADS_POOL, intValue = DEFAULT_RA_THREADS_POOL_SIZE,
label = "Thread pool capacity")
@@ -148,7 +154,8 @@
protected boolean raGlobalConfigStatus = DEFAULT_RA_GLOBAL_PREFIX_CONF_STATUS;
@GuardedBy(value = "this")
- private final Map<ConnectPoint, ScheduledFuture<?>> transmitters = new LinkedHashMap<>();
+ private final Map<ConnectPoint, Map.Entry<ScheduledFuture<?>, List<InterfaceIpAddress>>> transmitters =
+ new LinkedHashMap<>();
@GuardedBy(value = "this")
private final Map<DeviceId, List<InterfaceIpAddress>> globalPrefixes = new LinkedHashMap<>();
@@ -163,7 +170,7 @@
prefixEntry = new AbstractMap.SimpleEntry<>(i.connectPoint(), i.ipAddressesList());
}
return prefixEntry;
- };
+ };
private ScheduledExecutorService executors = null;
@@ -185,33 +192,22 @@
private class InternalInterfaceListener implements InterfaceListener {
@Override
public void event(InterfaceEvent event) {
- Interface i = event.subject();
switch (event.type()) {
case INTERFACE_ADDED:
- if (mastershipService.getLocalRole(i.connectPoint().deviceId())
- == MastershipRole.MASTER) {
- activateRouterAdvertisement(i.connectPoint(),
- i.ipAddressesList());
- }
+ case INTERFACE_UPDATED:
+ clearTxWorkers();
+ loadGlobalPrefixConfig();
+ setupTxWorkers();
+ log.info("Configuration updated for {}", event.subject());
break;
case INTERFACE_REMOVED:
+ Interface i = event.subject();
if (mastershipService.getLocalRole(i.connectPoint().deviceId())
== MastershipRole.MASTER) {
- deactivateRouterAdvertisement(i.connectPoint(),
- i.ipAddressesList());
- }
- break;
- case INTERFACE_UPDATED:
- if (mastershipService.getLocalRole(i.connectPoint().deviceId())
- == MastershipRole.MASTER) {
- deactivateRouterAdvertisement(i.connectPoint(),
- i.ipAddressesList());
- activateRouterAdvertisement(i.connectPoint(),
- i.ipAddressesList());
+ deactivateRouterAdvertisement(i.connectPoint());
}
break;
default:
- break;
}
}
}
@@ -221,21 +217,21 @@
// Enables RA threads on 'connectPoint' with configured IPv6s
private synchronized void activateRouterAdvertisement(ConnectPoint connectPoint,
List<InterfaceIpAddress> addresses) {
- RAWorkerThread worker = new RAWorkerThread(connectPoint, addresses, raThreadDelay);
+ RAWorkerThread worker = new RAWorkerThread(connectPoint, addresses, raThreadDelay, null, null);
ScheduledFuture<?> handler = executors.scheduleAtFixedRate(worker, raThreadDelay,
raThreadDelay, TimeUnit.SECONDS);
- transmitters.put(connectPoint, handler);
+ transmitters.put(connectPoint, new AbstractMap.SimpleEntry<>(handler, addresses));
}
// Disables already activated RA threads on 'connectPoint'
- private synchronized void deactivateRouterAdvertisement(ConnectPoint connectPoint,
- List<InterfaceIpAddress> addresses) {
- // Note : Parameter addresses not used now, kept for future.
+ private synchronized List<InterfaceIpAddress> deactivateRouterAdvertisement(ConnectPoint connectPoint) {
if (connectPoint != null) {
- ScheduledFuture<?> handler = transmitters.get(connectPoint);
- handler.cancel(false);
+ Map.Entry<ScheduledFuture<?>, List<InterfaceIpAddress>> details = transmitters.get(connectPoint);
+ details.getKey().cancel(false);
transmitters.remove(connectPoint);
+ return details.getValue();
}
+ return null;
}
private synchronized void setupThreadPool() {
@@ -264,7 +260,7 @@
// Clear out Tx threads.
private synchronized void clearTxWorkers() {
- transmitters.entrySet().stream().forEach(i -> i.getValue().cancel(false));
+ transmitters.entrySet().stream().forEach(i -> i.getValue().getKey().cancel(false));
transmitters.clear();
}
@@ -293,33 +289,83 @@
});
}
+ // Handler for network configuration updates
private class InternalNetworkConfigListener implements NetworkConfigListener {
@Override
public void event(NetworkConfigEvent event) {
- if (event.configClass().equals(RouterAdvertisementDeviceConfig.class)) {
+ if (event.configClass().equals(RouterAdvertisementDeviceConfig.class)
+ || event.configClass().equals(InterfaceConfig.class)) {
switch (event.type()) {
case CONFIG_ADDED:
- log.info("Router Advertisement device Config added for {}", event.subject());
- clearTxWorkers();
- loadGlobalPrefixConfig();
- setupTxWorkers();
- break;
case CONFIG_UPDATED:
- log.info("Router Advertisement device updated for {}", event.subject());
clearTxWorkers();
loadGlobalPrefixConfig();
setupTxWorkers();
+ log.info("Configuration updated for {}", event.subject());
break;
- default :
- break;
+ default:
}
}
}
-
}
- private final InternalNetworkConfigListener networkConfigListener
- = new InternalNetworkConfigListener();
+ private final InternalNetworkConfigListener networkConfigListener
+ = new InternalNetworkConfigListener();
+
+ // Handler for device updates
+ private class InternalDeviceListener implements DeviceListener {
+
+ @Override
+ public void event(DeviceEvent event) {
+ switch (event.type()) {
+ case DEVICE_ADDED:
+ case PORT_UPDATED:
+ case PORT_ADDED:
+ case DEVICE_UPDATED:
+ case DEVICE_AVAILABILITY_CHANGED:
+ clearTxWorkers();
+ setupTxWorkers();
+ log.trace("Processed device event {} on {}", event.type(), event.subject());
+ break;
+ default:
+ }
+ }
+ }
+
+ private final InternalDeviceListener internalDeviceListener =
+ new InternalDeviceListener();
+
+ // Processor for Solicited RA packets
+ private class InternalPacketProcessor implements PacketProcessor {
+
+ @Override
+ public void process(PacketContext context) {
+ if (context.isHandled()) {
+ return;
+ }
+
+ // Ensure packet is IPv6 Solicited RA
+ InboundPacket pkt = context.inPacket();
+ Ethernet ethernet = pkt.parsed();
+ if ((ethernet == null) || (ethernet.getEtherType() != Ethernet.TYPE_IPV6)) {
+ return;
+ }
+ IPv6 ipv6Packet = (IPv6) ethernet.getPayload();
+ if (ipv6Packet.getNextHeader() != IPv6.PROTOCOL_ICMP6) {
+ return;
+ }
+ ICMP6 icmp6Packet = (ICMP6) ipv6Packet.getPayload();
+ if (icmp6Packet.getIcmpType() != ICMP6.ROUTER_SOLICITATION) {
+ return;
+ }
+
+ // Start solicited-RA handling thread
+ SolicitedRAWorkerThread sraWorkerThread = new SolicitedRAWorkerThread(pkt);
+ executors.schedule(sraWorkerThread, 0, TimeUnit.SECONDS);
+ }
+ }
+
+ InternalPacketProcessor processor = null;
@Activate
protected void activate(ComponentContext context) {
@@ -327,11 +373,18 @@
appId = coreService.registerApplication(APP_NAME);
componentConfigService.registerProperties(getClass());
+ // Packet processor for handling Router Solicitations
+ processor = new InternalPacketProcessor();
+ packetService.addProcessor(processor, PacketProcessor.director(3));
+
// Setup global prefix loading components
networkConfigRegistry.addListener(networkConfigListener);
networkConfigRegistry.registerConfigFactory(deviceConfigFactory);
loadGlobalPrefixConfig();
+ // Dynamic device updates handling
+ deviceService.addListener(internalDeviceListener);
+
// Setup pool and worker threads for existing interfaces
setupPoolAndTxWorkers();
}
@@ -409,6 +462,10 @@
// Unregister resources.
componentConfigService.unregisterProperties(getClass(), false);
interfaceService.removeListener(interfaceListener);
+ networkConfigRegistry.removeListener(networkConfigListener);
+ networkConfigRegistry.unregisterConfigFactory(deviceConfigFactory);
+ packetService.removeProcessor(processor);
+ deviceService.removeListener(internalDeviceListener);
// Clear pool & threads
clearPoolAndTxWorkers();
@@ -420,6 +477,8 @@
ConnectPoint connectPoint;
List<InterfaceIpAddress> ipAddresses;
int retransmitPeriod;
+ MacAddress solicitHostMac;
+ byte[] solicitHostAddress;
// Various fixed values in RA packet
public static final byte RA_HOP_LIMIT = (byte) 0xff;
@@ -431,10 +490,13 @@
public static final int RA_RETRANSMIT_CALIBRATION_PERIOD = 1;
- RAWorkerThread(ConnectPoint connectPoint, List<InterfaceIpAddress> ipAddresses, int period) {
+ RAWorkerThread(ConnectPoint connectPoint, List<InterfaceIpAddress> ipAddresses, int period,
+ MacAddress macAddress, byte[] ipv6Address) {
this.connectPoint = connectPoint;
this.ipAddresses = ipAddresses;
retransmitPeriod = period;
+ solicitHostMac = macAddress;
+ solicitHostAddress = ipv6Address;
}
public void run() {
@@ -497,7 +559,7 @@
// IPv6 header filling.
byte[] ip6AllNodesAddress = Ip6Address.valueOf("ff02::1").toOctets();
IPv6 ipv6 = new IPv6();
- ipv6.setDestinationAddress(ip6AllNodesAddress);
+ ipv6.setDestinationAddress((solicitHostAddress == null) ? ip6AllNodesAddress : solicitHostAddress);
/* RA packet L2 source address created from port MAC address.
* Note : As per RFC-4861 RAs should be sent from link-local address.
*/
@@ -516,8 +578,9 @@
byte[] l2Ipv6MulticastAddress = MacAddress.IPV6_MULTICAST.toBytes();
IntStream.range(1, 4).forEach(i -> l2Ipv6MulticastAddress[l2Ipv6MulticastAddress.length - i] =
ip6AllNodesAddress[ip6AllNodesAddress.length - i]);
-
- ethernet.setDestinationMACAddress(MacAddress.valueOf(l2Ipv6MulticastAddress));
+ // Provide unicast address for Solicit RA replays
+ ethernet.setDestinationMACAddress((solicitHostMac == null) ?
+ MacAddress.valueOf(l2Ipv6MulticastAddress) : solicitHostMac);
ethernet.setSourceMACAddress(macAddress.get().toBytes());
ethernet.setEtherType(EthType.EtherType.IPV6.ethType().toShort());
ethernet.setVlanID(Ethernet.VLAN_UNTAGGED);
@@ -530,6 +593,48 @@
OutboundPacket packet = new DefaultOutboundPacket(connectPoint.deviceId(),
treatment, stream);
packetService.emit(packet);
+ log.trace("Transmitted Unsolicited Router Advertisement on {}", connectPoint);
}
}
+
+ // Worker thread for processing IPv6 Solicited RA packets.
+ public class SolicitedRAWorkerThread implements Runnable {
+
+ private InboundPacket packet;
+
+ SolicitedRAWorkerThread(InboundPacket packet) {
+ this.packet = packet;
+ }
+
+ @Override
+ public void run() {
+
+ // TODO : Validate Router Solicitation
+
+ // Pause already running unsolicited RA threads in received connect point
+ ConnectPoint connectPoint = packet.receivedFrom();
+ List<InterfaceIpAddress> addresses = deactivateRouterAdvertisement(connectPoint);
+
+ /* Multicast RA(ie. Unsolicited RA) TX time is not preciously tracked so to make sure that
+ * Unicast RA(ie. Router Solicitation Response) is TXed before Mulicast RA
+ * logic adapted here is disable Mulicast RA, TX Unicast RA and then restore Multicast RA.
+ */
+ log.trace("Processing Router Solicitations from {}", connectPoint);
+ try {
+ Ethernet ethernet = packet.parsed();
+ IPv6 ipv6 = (IPv6) ethernet.getPayload();
+ RAWorkerThread worker = new RAWorkerThread(connectPoint,
+ addresses, raThreadDelay, ethernet.getSourceMAC(), ipv6.getSourceAddress());
+ // TODO : Estimate TX time as in RFC 4861, Section 6.2.6 and schedule TX based on it
+ CompletableFuture<Void> sraHandlerFuture = CompletableFuture.runAsync(worker, executors);
+ sraHandlerFuture.get();
+ } catch (Exception e) {
+ log.error("Failed to respond to router solicitation. {}", e);
+ } finally {
+ activateRouterAdvertisement(connectPoint, addresses);
+ log.trace("Restored Unsolicited Router Advertisements on {}", connectPoint);
+ }
+ }
+ }
+
}