blob: 8da5065dfcc4725662c851aa61be2103eda7ee42 [file] [log] [blame]
/*
* Copyright 2017-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.ra;
import com.google.common.collect.ImmutableMap;
import org.onlab.packet.EthType;
import org.onlab.packet.Ethernet;
import org.onlab.packet.ICMP6;
import org.onlab.packet.IPv6;
import org.onlab.packet.Ip6Address;
import org.onlab.packet.IpAddress;
import org.onlab.packet.MacAddress;
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.MastershipEvent;
import org.onosproject.mastership.MastershipListener;
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.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.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.Modified;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.IntStream;
import static com.google.common.base.Strings.isNullOrEmpty;
import static org.onlab.util.Tools.get;
import static org.onlab.util.Tools.groupedThreads;
/**
* Manages IPv6 Router Advertisements.
*/
@Component(immediate = true, service = RoutingAdvertisementService.class)
public class RouterAdvertisementManager implements RoutingAdvertisementService {
private final Logger log = LoggerFactory.getLogger(getClass());
private static final String PROP_RA_THREADS_POOL = "raPoolSize";
private static final int DEFAULT_RA_THREADS_POOL_SIZE = 10;
private static final String PROP_RA_THREADS_DELAY = "raThreadDelay";
private static final int DEFAULT_RA_THREADS_DELAY = 5;
private static final String PROP_RA_FLAG_MBIT_STATUS = "raFlagMbitStatus";
private static final boolean DEFAULT_RA_FLAG_MBIT_STATUS = false;
private static final String PROP_RA_FLAG_OBIT_STATUS = "raFlagObitStatus";
private static final boolean DEFAULT_RA_FLAG_OBIT_STATUS = false;
private static final String PROP_RA_OPTION_PREFIX_STATUS = "raOptionPrefixStatus";
private static final boolean DEFAULT_RA_OPTION_PREFIX_STATUS = false;
private static final String PROP_RA_GLOBAL_PREFIX_CONF_STATUS = "raGlobalPrefixConfStatus";
private static final boolean DEFAULT_RA_GLOBAL_PREFIX_CONF_STATUS = true;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
PacketService packetService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected ComponentConfigService componentConfigService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
public InterfaceService interfaceService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
public MastershipService mastershipService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected NetworkConfigRegistry networkConfigRegistry;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected DeviceService deviceService;
//@Property(name = PROP_RA_THREADS_POOL, intValue = DEFAULT_RA_THREADS_POOL_SIZE,
// label = "Thread pool capacity")
protected int raPoolSize = DEFAULT_RA_THREADS_POOL_SIZE;
//@Property(name = PROP_RA_THREADS_DELAY, intValue = DEFAULT_RA_THREADS_DELAY,
// label = "Thread delay in seconds")
protected int raThreadDelay = DEFAULT_RA_THREADS_DELAY;
//@Property(name = PROP_RA_FLAG_MBIT_STATUS, boolValue = DEFAULT_RA_FLAG_MBIT_STATUS,
// label = "Turn M-bit flag on/off")
protected boolean raFlagMbitStatus = DEFAULT_RA_FLAG_MBIT_STATUS;
//@Property(name = PROP_RA_FLAG_OBIT_STATUS, boolValue = DEFAULT_RA_FLAG_OBIT_STATUS,
// label = "Turn O-bit flag on/off")
protected boolean raFlagObitStatus = DEFAULT_RA_FLAG_OBIT_STATUS;
//@Property(name = PROP_RA_OPTION_PREFIX_STATUS, boolValue = DEFAULT_RA_OPTION_PREFIX_STATUS,
// label = "Prefix option support needed or not")
protected boolean raOptionPrefixStatus = DEFAULT_RA_OPTION_PREFIX_STATUS;
//@Property(name = PROP_RA_GLOBAL_PREFIX_CONF_STATUS, boolValue = DEFAULT_RA_GLOBAL_PREFIX_CONF_STATUS,
// label = "Global prefix configuration support on/off")
protected boolean raGlobalConfigStatus = DEFAULT_RA_GLOBAL_PREFIX_CONF_STATUS;
@GuardedBy(value = "this")
private final Map<ConnectPoint, Map.Entry<ScheduledFuture<?>, List<InterfaceIpAddress>>> transmitters =
new LinkedHashMap<>();
// TODO: should consider using concurrent variants
@GuardedBy(value = "this")
private final Map<DeviceId, List<InterfaceIpAddress>> globalPrefixes = new LinkedHashMap<>();
@Override
public synchronized ImmutableMap<DeviceId, List<InterfaceIpAddress>> getGlobalPrefixes() {
return ImmutableMap.copyOf(globalPrefixes);
}
@SuppressWarnings("GuardedBy")
@GuardedBy(value = "this")
private Function<Interface, Map.Entry<ConnectPoint, List<InterfaceIpAddress>>> prefixGenerator =
i -> {
Map.Entry<ConnectPoint, List<InterfaceIpAddress>> prefixEntry;
if (raGlobalConfigStatus && globalPrefixes.containsKey(i.connectPoint().deviceId())) {
prefixEntry = new AbstractMap.SimpleEntry<>(i.connectPoint(),
globalPrefixes.get(i.connectPoint().deviceId()));
} else {
prefixEntry = new AbstractMap.SimpleEntry<>(i.connectPoint(), i.ipAddressesList());
}
return prefixEntry;
};
private ScheduledExecutorService executors = null;
private static final String APP_NAME = "org.onosproject.routeradvertisement";
private ApplicationId appId;
private final ConfigFactory<DeviceId, RouterAdvertisementDeviceConfig> deviceConfigFactory =
new ConfigFactory<DeviceId, RouterAdvertisementDeviceConfig>(
SubjectFactories.DEVICE_SUBJECT_FACTORY,
RouterAdvertisementDeviceConfig.class, "routeradvertisement") {
@Override
public RouterAdvertisementDeviceConfig createConfig() {
return new RouterAdvertisementDeviceConfig();
}
};
// Listener for handling dynamic interface modifications.
private class InternalInterfaceListener implements InterfaceListener {
@Override
public void event(InterfaceEvent event) {
switch (event.type()) {
case INTERFACE_ADDED:
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());
}
break;
default:
}
}
}
private final InterfaceListener interfaceListener = new InternalInterfaceListener();
// Enables RA threads on 'connectPoint' with configured IPv6s
private synchronized void activateRouterAdvertisement(ConnectPoint connectPoint,
List<InterfaceIpAddress> addresses) {
RAWorkerThread worker = new RAWorkerThread(connectPoint, addresses, raThreadDelay, null, null);
ScheduledFuture<?> handler = executors.scheduleAtFixedRate(worker, raThreadDelay,
raThreadDelay, TimeUnit.SECONDS);
transmitters.put(connectPoint, new AbstractMap.SimpleEntry<>(handler, addresses));
}
// Disables already activated RA threads on 'connectPoint'
private synchronized List<InterfaceIpAddress> deactivateRouterAdvertisement(ConnectPoint connectPoint) {
if (connectPoint != null) {
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() {
executors = Executors.newScheduledThreadPool(raPoolSize,
groupedThreads("RouterAdvertisement", "event-%d", log));
}
private synchronized void clearThreadPool() {
executors.shutdown();
}
// Start Tx threads for all configured interfaces.
private synchronized void setupTxWorkers() {
interfaceService.getInterfaces()
.stream()
.filter(i -> mastershipService.getLocalRole(i.connectPoint().deviceId())
== MastershipRole.MASTER)
.map(prefixGenerator::apply)
.filter(i -> i.getValue()
.stream()
.anyMatch(ia -> ia.ipAddress().version().equals(IpAddress.Version.INET6)))
.forEach(j ->
activateRouterAdvertisement(j.getKey(), j.getValue())
);
}
// Clear out Tx threads.
private synchronized void clearTxWorkers() {
transmitters.entrySet().stream().forEach(i -> i.getValue().getKey().cancel(false));
transmitters.clear();
}
private synchronized void setupPoolAndTxWorkers() {
setupThreadPool();
setupTxWorkers();
}
private synchronized void clearPoolAndTxWorkers() {
clearTxWorkers();
clearThreadPool();
}
@SuppressWarnings("GuardedBy")
// Loading global prefixes for devices from network configuration
private synchronized void loadGlobalPrefixConfig() {
globalPrefixes.clear();
Set<DeviceId> deviceSubjects =
networkConfigRegistry.getSubjects(DeviceId.class, RouterAdvertisementDeviceConfig.class);
deviceSubjects.forEach(subject -> {
RouterAdvertisementDeviceConfig config =
networkConfigRegistry.getConfig(subject, RouterAdvertisementDeviceConfig.class);
if (config != null) {
List<InterfaceIpAddress> ips = config.prefixes();
globalPrefixes.put(subject, ips);
}
});
}
// Handler for network configuration updates
private class InternalNetworkConfigListener implements NetworkConfigListener {
@Override
public void event(NetworkConfigEvent event) {
if (event.configClass().equals(RouterAdvertisementDeviceConfig.class)
|| event.configClass().equals(InterfaceConfig.class)) {
switch (event.type()) {
case CONFIG_ADDED:
case CONFIG_UPDATED:
clearTxWorkers();
loadGlobalPrefixConfig();
setupTxWorkers();
log.info("Configuration updated for {}", event.subject());
break;
default:
}
}
}
}
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 class InternalMastershipListener implements MastershipListener {
@Override
public void event(MastershipEvent event) {
switch (event.type()) {
case MASTER_CHANGED:
clearTxWorkers();
setupTxWorkers();
log.trace("Processed mastership event {} on {}", event.type(), event.subject());
break;
case BACKUPS_CHANGED:
case SUSPENDED:
default:
break;
}
}
}
private final InternalDeviceListener internalDeviceListener = new InternalDeviceListener();
private final InternalMastershipListener internalMastershipListener = new InternalMastershipListener();
// 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) {
// Basic application registrations.
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();
// Register device and mastership event listener
deviceService.addListener(internalDeviceListener);
mastershipService.addListener(internalMastershipListener);
// Setup pool and worker threads for existing interfaces
setupPoolAndTxWorkers();
}
@Modified
protected void modified(ComponentContext context) {
int newRaPoolSize, newRaThreadDelay;
// Loading configured properties.
if (context != null) {
Dictionary<?, ?> properties = context.getProperties();
try {
// Handle change in pool size
String s = get(properties, PROP_RA_THREADS_POOL);
newRaPoolSize = isNullOrEmpty(s) ?
DEFAULT_RA_THREADS_POOL_SIZE : Integer.parseInt(s.trim());
if (newRaPoolSize != raPoolSize) {
raPoolSize = newRaPoolSize;
clearPoolAndTxWorkers();
setupPoolAndTxWorkers();
log.info("Thread pool size updated to {}", raPoolSize);
}
// Handle change in thread delay
s = get(properties, PROP_RA_THREADS_DELAY);
newRaThreadDelay = isNullOrEmpty(s) ?
DEFAULT_RA_THREADS_DELAY : Integer.parseInt(s.trim());
if (newRaThreadDelay != raThreadDelay) {
raThreadDelay = newRaThreadDelay;
clearTxWorkers();
setupTxWorkers();
log.info("Thread delay updated to {}", raThreadDelay);
}
// Handle M-flag changes
s = get(properties, PROP_RA_FLAG_MBIT_STATUS);
if (!isNullOrEmpty(s)) {
raFlagMbitStatus = Boolean.parseBoolean(s.trim());
log.info("RA M-flag set {}", s);
}
// Handle O-flag changes
s = get(properties, PROP_RA_FLAG_OBIT_STATUS);
if (!isNullOrEmpty(s)) {
raFlagObitStatus = Boolean.parseBoolean(s.trim());
log.info("RA O-flag set {}", s);
}
// Handle prefix option configuration
s = get(properties, PROP_RA_OPTION_PREFIX_STATUS);
if (!isNullOrEmpty(s)) {
raOptionPrefixStatus = Boolean.parseBoolean(s.trim());
String status = raOptionPrefixStatus ? "enabled" : "disabled";
log.info("RA prefix option {}", status);
}
s = get(properties, PROP_RA_GLOBAL_PREFIX_CONF_STATUS);
if (!isNullOrEmpty(s)) {
raGlobalConfigStatus = Boolean.parseBoolean(s.trim());
clearTxWorkers();
setupTxWorkers();
String status = raOptionPrefixStatus ? "enabled" : "disabled";
log.info("RA global configuration file loading {}", status);
}
} catch (NumberFormatException e) {
log.warn("Component configuration had invalid value, aborting changes loading.", e);
}
}
}
@Deactivate
protected void deactivate() {
// Unregister resources.
componentConfigService.unregisterProperties(getClass(), false);
interfaceService.removeListener(interfaceListener);
networkConfigRegistry.removeListener(networkConfigListener);
networkConfigRegistry.unregisterConfigFactory(deviceConfigFactory);
packetService.removeProcessor(processor);
deviceService.removeListener(internalDeviceListener);
mastershipService.removeListener(internalMastershipListener);
// Clear pool & threads
clearPoolAndTxWorkers();
}
// Worker thread for actually sending ICMPv6 RA packets.
private class RAWorkerThread implements Runnable {
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;
public static final short RA_ROUTER_LIFETIME = (short) 1800;
public static final int RA_OPTIONS_BUFFER_SIZE = 500;
public static final int RA_OPTION_MTU_VALUE = 1500;
public static final int RA_OPTION_PREFIX_VALID_LIFETIME = 600;
public static final int RA_OPTION_PREFIX_PREFERRED_LIFETIME = 600;
public static final int RA_RETRANSMIT_CALIBRATION_PERIOD = 1;
RAWorkerThread(ConnectPoint connectPoint, List<InterfaceIpAddress> ipAddresses, int period,
MacAddress macAddress, byte[] ipv6Address) {
this.connectPoint = connectPoint;
this.ipAddresses = ipAddresses;
retransmitPeriod = period;
solicitHostMac = macAddress;
solicitHostAddress = ipv6Address;
}
@Override
public void run() {
// Router Advertisement header filling. Please refer RFC-2461.
RouterAdvertisement ra = new RouterAdvertisement();
ra.setCurrentHopLimit(RA_HOP_LIMIT);
ra.setMFlag((byte) (raFlagMbitStatus ? 0x01 : 0x00));
ra.setOFlag((byte) (raFlagObitStatus ? 0x01 : 0x00));
ra.setRouterLifetime(RA_ROUTER_LIFETIME);
ra.setReachableTime(0);
ra.setRetransmitTimer(retransmitPeriod + RA_RETRANSMIT_CALIBRATION_PERIOD);
// Option : Source link-layer address.
byte[] optionBuffer = new byte[RA_OPTIONS_BUFFER_SIZE];
ByteBuffer option = ByteBuffer.wrap(optionBuffer);
Optional<MacAddress> macAddress = interfaceService.getInterfacesByPort(connectPoint).stream()
.map(Interface::mac).findFirst();
if (!macAddress.isPresent()) {
log.warn("Unable to retrieve interface {} MAC address. Terminating RA transmission.", connectPoint);
return;
}
option.put(macAddress.get().toBytes());
ra.addOption(NeighborDiscoveryOptions.TYPE_SOURCE_LL_ADDRESS,
Arrays.copyOfRange(option.array(), 0, option.position()));
// Option : MTU.
option.rewind();
option.putShort((short) 0);
option.putInt(RA_OPTION_MTU_VALUE);
ra.addOption(NeighborDiscoveryOptions.TYPE_MTU,
Arrays.copyOfRange(option.array(), 0, option.position()));
// Option : Prefix information.
if (raOptionPrefixStatus) {
ipAddresses.stream()
.filter(i -> i.ipAddress().version().equals(IpAddress.Version.INET6))
.forEach(i -> {
option.rewind();
option.put((byte) i.subnetAddress().prefixLength());
// Enable "onlink" option only.
option.put((byte) 0x80);
option.putInt(RA_OPTION_PREFIX_VALID_LIFETIME);
option.putInt(RA_OPTION_PREFIX_PREFERRED_LIFETIME);
// Clear reserved fields
option.putInt(0x00000000);
option.put(IpAddress.makeMaskedAddress(i.ipAddress(),
i.subnetAddress().prefixLength()).toOctets());
ra.addOption(NeighborDiscoveryOptions.TYPE_PREFIX_INFORMATION,
Arrays.copyOfRange(option.array(), 0, option.position()));
});
}
// ICMPv6 header filling.
ICMP6 icmpv6 = new ICMP6();
icmpv6.setIcmpType(ICMP6.ROUTER_ADVERTISEMENT);
icmpv6.setIcmpCode((byte) 0);
icmpv6.setPayload(ra);
// IPv6 header filling.
byte[] ip6AllNodesAddress = Ip6Address.valueOf("ff02::1").toOctets();
IPv6 ipv6 = new IPv6();
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.
*/
ipv6.setSourceAddress(IPv6.getLinkLocalAddress(macAddress.get().toBytes()));
ipv6.setNextHeader(IPv6.PROTOCOL_ICMP6);
ipv6.setHopLimit(RA_HOP_LIMIT);
ipv6.setTrafficClass((byte) 0xe0);
ipv6.setPayload(icmpv6);
// Ethernet header filling.
Ethernet ethernet = new Ethernet();
/* Ethernet IPv6 multicast address creation.
* Refer : RFC 2624 section 7.
*/
byte[] l2Ipv6MulticastAddress = MacAddress.IPV6_MULTICAST.toBytes();
IntStream.range(1, 4).forEach(i -> l2Ipv6MulticastAddress[l2Ipv6MulticastAddress.length - i] =
ip6AllNodesAddress[ip6AllNodesAddress.length - i]);
// 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);
ethernet.setPayload(ipv6);
ethernet.setPad(false);
// Flush out PACKET_OUT.
ByteBuffer stream = ByteBuffer.wrap(ethernet.serialize());
TrafficTreatment treatment = DefaultTrafficTreatment.builder().setOutput(connectPoint.port()).build();
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);
}
}
}
}