blob: e3daceb7f9d1f25f6ef81d73827f58a72acb8f12 [file] [log] [blame]
Jayakumar Thazhath3ec6aa22017-09-11 02:00:20 -04001/*
2 * Copyright 2017-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onosproject.ra;
18
19import org.apache.felix.scr.annotations.Activate;
20import org.apache.felix.scr.annotations.Component;
21import org.apache.felix.scr.annotations.Deactivate;
22import org.apache.felix.scr.annotations.Property;
23import org.apache.felix.scr.annotations.Reference;
24import org.apache.felix.scr.annotations.ReferenceCardinality;
jayakumarthazhath1cbde732017-10-25 10:01:00 -040025import org.apache.felix.scr.annotations.Modified;
Jayakumar Thazhath3ec6aa22017-09-11 02:00:20 -040026import org.onlab.packet.EthType;
27import org.onlab.packet.Ethernet;
28import org.onlab.packet.ICMP6;
29import org.onlab.packet.IPv6;
30import org.onlab.packet.IpAddress;
31import org.onlab.packet.Ip6Address;
32import org.onlab.packet.MacAddress;
33import org.onlab.packet.ndp.RouterAdvertisement;
34import org.onlab.packet.ndp.NeighborDiscoveryOptions;
35import org.onosproject.cfg.ComponentConfigService;
36import org.onosproject.core.ApplicationId;
37import org.onosproject.core.CoreService;
38import org.onosproject.mastership.MastershipService;
39import org.onosproject.net.MastershipRole;
40import org.onosproject.net.intf.Interface;
41import org.onosproject.net.intf.InterfaceEvent;
42import org.onosproject.net.intf.InterfaceListener;
43import org.onosproject.net.intf.InterfaceService;
44import org.onosproject.net.ConnectPoint;
45import org.onosproject.net.flow.DefaultTrafficTreatment;
46import org.onosproject.net.flow.TrafficTreatment;
47import org.onosproject.net.host.InterfaceIpAddress;
48import org.onosproject.net.packet.DefaultOutboundPacket;
49import org.onosproject.net.packet.OutboundPacket;
50import org.onosproject.net.packet.PacketService;
51import org.slf4j.Logger;
52import org.slf4j.LoggerFactory;
53import org.osgi.service.component.ComponentContext;
54
55import javax.annotation.concurrent.GuardedBy;
56import java.nio.ByteBuffer;
57import java.util.Dictionary;
58import java.util.LinkedHashMap;
59import java.util.List;
60import java.util.Map;
61import java.util.Arrays;
62import java.util.Optional;
63
64import java.util.concurrent.ScheduledExecutorService;
65import java.util.concurrent.ScheduledFuture;
66import java.util.concurrent.Executors;
67import java.util.concurrent.TimeUnit;
68import java.util.stream.IntStream;
69
70import static com.google.common.base.Strings.isNullOrEmpty;
71import static org.onlab.util.Tools.get;
72import static org.onlab.util.Tools.groupedThreads;
73
74/**
75 * Manages IPv6 Router Advertisements.
76 */
77@Component(immediate = true)
78public class RouterAdvertisementManager {
79
80 private final Logger log = LoggerFactory.getLogger(getClass());
81 private static final String PROP_RA_THREADS_POOL = "raPoolSize";
82 private static final int DEFAULT_RA_THREADS_POOL_SIZE = 10;
83 private static final String PROP_RA_THREADS_DELAY = "raThreadDelay";
84 private static final int DEFAULT_RA_THREADS_DELAY = 5;
jayakumarthazhath1cbde732017-10-25 10:01:00 -040085 private static final String PROP_RA_FLAG_MBIT_STATUS = "raFlagMbitStatus";
86 private static final boolean DEFAULT_RA_FLAG_MBIT_STATUS = false;
87 private static final String PROP_RA_FLAG_OBIT_STATUS = "raFlagObitStatus";
88 private static final boolean DEFAULT_RA_FLAG_OBIT_STATUS = false;
89 private static final String PROP_RA_OPTION_PREFIX_STATUS = "raOptionPrefixStatus";
90 private static final boolean DEFAULT_RA_OPTION_PREFIX_STATUS = true;
Jayakumar Thazhath3ec6aa22017-09-11 02:00:20 -040091
92 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
93 protected CoreService coreService;
94
95 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
96 PacketService packetService;
97
98 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
99 protected ComponentConfigService componentConfigService;
100
101 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
102 public InterfaceService interfaceService;
103
104 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
105 public MastershipService mastershipService;
106
107 @Property(name = PROP_RA_THREADS_POOL, intValue = DEFAULT_RA_THREADS_POOL_SIZE,
jayakumarthazhath1cbde732017-10-25 10:01:00 -0400108 label = "Thread pool capacity")
Jayakumar Thazhath3ec6aa22017-09-11 02:00:20 -0400109 protected int raPoolSize = DEFAULT_RA_THREADS_POOL_SIZE;
110
111 @Property(name = PROP_RA_THREADS_DELAY, intValue = DEFAULT_RA_THREADS_DELAY,
jayakumarthazhath1cbde732017-10-25 10:01:00 -0400112 label = "Thread delay in seconds")
Jayakumar Thazhath3ec6aa22017-09-11 02:00:20 -0400113 protected int raThreadDelay = DEFAULT_RA_THREADS_DELAY;
114
jayakumarthazhath1cbde732017-10-25 10:01:00 -0400115 @Property(name = PROP_RA_FLAG_MBIT_STATUS, boolValue = DEFAULT_RA_FLAG_MBIT_STATUS,
116 label = "Turn M-bit flag on/off")
117 protected boolean raFlagMbitStatus = DEFAULT_RA_FLAG_MBIT_STATUS;
118
119 @Property(name = PROP_RA_FLAG_OBIT_STATUS, boolValue = DEFAULT_RA_FLAG_OBIT_STATUS,
120 label = "Turn O-bit flag on/off")
121 protected boolean raFlagObitStatus = DEFAULT_RA_FLAG_OBIT_STATUS;
122
123 @Property(name = PROP_RA_OPTION_PREFIX_STATUS, boolValue = DEFAULT_RA_OPTION_PREFIX_STATUS,
124 label = "Prefix option support needed or not")
125 protected boolean raOptionPrefixStatus = DEFAULT_RA_OPTION_PREFIX_STATUS;
126
Jayakumar Thazhath3ec6aa22017-09-11 02:00:20 -0400127 private ScheduledExecutorService executors = null;
128
129 @GuardedBy(value = "this")
130 private final Map<ConnectPoint, ScheduledFuture<?>> transmitters = new LinkedHashMap<>();
131
132 private static final String APP_NAME = "org.onosproject.routeradvertisement";
133 private ApplicationId appId;
134
135 // Listener for handling dynamic interface modifications.
136 private class InternalInterfaceListener implements InterfaceListener {
137 @Override
138 public void event(InterfaceEvent event) {
139 Interface i = event.subject();
140 switch (event.type()) {
141 case INTERFACE_ADDED:
142 if (mastershipService.getLocalRole(i.connectPoint().deviceId())
143 == MastershipRole.MASTER) {
144 activateRouterAdvertisement(i.connectPoint(),
145 i.ipAddressesList());
146 }
147 break;
148 case INTERFACE_REMOVED:
149 if (mastershipService.getLocalRole(i.connectPoint().deviceId())
150 == MastershipRole.MASTER) {
151 deactivateRouterAdvertisement(i.connectPoint(),
152 i.ipAddressesList());
153 }
154 break;
155 case INTERFACE_UPDATED:
jayakumarthazhath1cbde732017-10-25 10:01:00 -0400156 if (mastershipService.getLocalRole(i.connectPoint().deviceId())
157 == MastershipRole.MASTER) {
158 deactivateRouterAdvertisement(i.connectPoint(),
159 i.ipAddressesList());
160 activateRouterAdvertisement(i.connectPoint(),
161 i.ipAddressesList());
162 }
Jayakumar Thazhath3ec6aa22017-09-11 02:00:20 -0400163 break;
164 default:
165 break;
166 }
167 }
168 }
169 private final InterfaceListener interfaceListener = new InternalInterfaceListener();
170
171 // Enables RA threads on 'connectPoint' with configured IPv6s
jayakumarthazhath1cbde732017-10-25 10:01:00 -0400172 private synchronized void activateRouterAdvertisement(ConnectPoint connectPoint,
173 List<InterfaceIpAddress> addresses) {
Jayakumar Thazhath3ec6aa22017-09-11 02:00:20 -0400174 RAWorkerThread worker = new RAWorkerThread(connectPoint, addresses, raThreadDelay);
175 ScheduledFuture<?> handler = executors.scheduleAtFixedRate(worker, raThreadDelay,
176 raThreadDelay, TimeUnit.SECONDS);
177 transmitters.put(connectPoint, handler);
Jayakumar Thazhath3ec6aa22017-09-11 02:00:20 -0400178 }
179
180 // Disables already activated RA threads on 'connectPoint'
jayakumarthazhath1cbde732017-10-25 10:01:00 -0400181 private synchronized void deactivateRouterAdvertisement(ConnectPoint connectPoint,
182 List<InterfaceIpAddress> addresses) {
Jayakumar Thazhath3ec6aa22017-09-11 02:00:20 -0400183 if (connectPoint != null) {
184 ScheduledFuture<?> handler = transmitters.get(connectPoint);
185 handler.cancel(false);
186 transmitters.remove(connectPoint);
187 }
Jayakumar Thazhath3ec6aa22017-09-11 02:00:20 -0400188 }
189
jayakumarthazhath1cbde732017-10-25 10:01:00 -0400190 private synchronized void setupThreadPool() {
Jayakumar Thazhath3ec6aa22017-09-11 02:00:20 -0400191 // Initialize RA thread pool
192 executors = Executors.newScheduledThreadPool(raPoolSize,
193 groupedThreads("RouterAdvertisement", "event-%d", log));
jayakumarthazhath1cbde732017-10-25 10:01:00 -0400194 }
Jayakumar Thazhath3ec6aa22017-09-11 02:00:20 -0400195
jayakumarthazhath1cbde732017-10-25 10:01:00 -0400196 private synchronized void clearThreadPool() {
197 // Release RA thread pool
198 executors.shutdown();
199 }
200
201 private synchronized void setupTxWorkers() {
202 // Start Router Advertisement transmission for all configured interfaces.
Jayakumar Thazhath3ec6aa22017-09-11 02:00:20 -0400203 interfaceService.getInterfaces()
204 .stream()
205 .filter(i -> mastershipService.getLocalRole(i.connectPoint().deviceId())
206 == MastershipRole.MASTER)
207 .filter(i -> i.ipAddressesList()
208 .stream()
209 .anyMatch(ia -> ia.ipAddress().version().equals(IpAddress.Version.INET6)))
210 .forEach(j ->
211 activateRouterAdvertisement(j.connectPoint(), j.ipAddressesList())
212 );
213 }
214
jayakumarthazhath1cbde732017-10-25 10:01:00 -0400215 private synchronized void clearTxWorkers() {
Jayakumar Thazhath3ec6aa22017-09-11 02:00:20 -0400216 // Clear out Router Advertisement Transmission for all configured interfaces.
217 interfaceService.getInterfaces()
218 .stream()
219 .filter(i -> mastershipService.getLocalRole(i.connectPoint().deviceId())
220 == MastershipRole.MASTER)
221 .filter(i -> i.ipAddressesList()
222 .stream()
223 .anyMatch(ia -> ia.ipAddress().version().equals(IpAddress.Version.INET6)))
224 .forEach(j ->
225 deactivateRouterAdvertisement(j.connectPoint(), j.ipAddressesList())
226 );
227 }
228
jayakumarthazhath1cbde732017-10-25 10:01:00 -0400229 // Setting up pool & workers.
230 private synchronized void setupPoolAndTxWorkers() {
231 setupThreadPool();
232 setupTxWorkers();
233 }
234
235 // Clearing pool & workers.
236 private synchronized void clearPoolAndTxWorkers() {
237 clearTxWorkers();
238 clearThreadPool();
239 }
240
241 @Activate
242 protected void activate(ComponentContext context) {
243 // Basic application registrations.
244 appId = coreService.registerApplication(APP_NAME);
245 componentConfigService.registerProperties(getClass());
246
247 // Interface listener for dynamic RA handling.
248 interfaceService.addListener(interfaceListener);
249
250 // Setup pool and worker threads for existing interfaces
251 setupPoolAndTxWorkers();
252 }
253
254 @Modified
255 protected void modified(ComponentContext context) {
256 int newRaPoolSize, newRaThreadDelay;
257
258 // Loading configured properties.
259 if (context != null) {
260 Dictionary<?, ?> properties = context.getProperties();
261 try {
262 // Handle change in pool size
263 String s = get(properties, PROP_RA_THREADS_POOL);
264 newRaPoolSize = isNullOrEmpty(s) ?
265 DEFAULT_RA_THREADS_POOL_SIZE : Integer.parseInt(s.trim());
266 if (newRaPoolSize != raPoolSize) {
267 raPoolSize = newRaPoolSize;
268 clearPoolAndTxWorkers();
269 setupPoolAndTxWorkers();
270 }
271
272 // Handle change in thread delay
273 s = get(properties, PROP_RA_THREADS_DELAY);
274 newRaThreadDelay = isNullOrEmpty(s) ?
275 DEFAULT_RA_THREADS_DELAY : Integer.parseInt(s.trim());
276 if (newRaThreadDelay != raThreadDelay) {
277 raThreadDelay = newRaThreadDelay;
278 clearTxWorkers();
279 setupTxWorkers();
280 }
281
282 // Handle M-flag changes
283 s = get(properties, PROP_RA_FLAG_MBIT_STATUS);
284 raFlagMbitStatus = isNullOrEmpty(s) ?
285 DEFAULT_RA_FLAG_MBIT_STATUS : Boolean.parseBoolean(s.trim());
286
287 // Handle O-flag changes
288 s = get(properties, PROP_RA_FLAG_OBIT_STATUS);
289 raFlagObitStatus = isNullOrEmpty(s) ?
290 DEFAULT_RA_FLAG_OBIT_STATUS : Boolean.parseBoolean(s.trim());
291
292 // Handle prefix option configuration
293 s = get(properties, PROP_RA_OPTION_PREFIX_STATUS);
294 raOptionPrefixStatus = isNullOrEmpty(s) ?
295 DEFAULT_RA_OPTION_PREFIX_STATUS : Boolean.parseBoolean(s.trim());
296
297 } catch (NumberFormatException e) {
298 log.warn("Component configuration had invalid value, aborting changes loading.", e);
299 }
300 }
301 }
302
303 @Deactivate
304 protected void deactivate() {
305 // Unregister resources.
306 componentConfigService.unregisterProperties(getClass(), false);
307 interfaceService.removeListener(interfaceListener);
308
309 // Clear pool & threads
310 clearPoolAndTxWorkers();
311 }
312
Jayakumar Thazhath3ec6aa22017-09-11 02:00:20 -0400313 // Worker thread for actually sending ICMPv6 RA packets.
314 private class RAWorkerThread implements Runnable {
315
316 ConnectPoint connectPoint;
317 List<InterfaceIpAddress> ipAddresses;
318 int retransmitPeriod;
319
320 // Various fixed values in RA packet
321 public static final byte RA_HOP_LIMIT = (byte) 0xff;
322 public static final short RA_ROUTER_LIFETIME = (short) 1800;
323 public static final int RA_OPTIONS_BUFFER_SIZE = 500;
324 public static final int RA_OPTION_MTU_VALUE = 1500;
325 public static final int RA_OPTION_PREFIX_VALID_LIFETIME = 600;
326 public static final int RA_OPTION_PREFIX_PREFERRED_LIFETIME = 600;
327 public static final int RA_RETRANSMIT_CALIBRATION_PERIOD = 1;
328
329
330 RAWorkerThread(ConnectPoint connectPoint, List<InterfaceIpAddress> ipAddresses, int period) {
331 this.connectPoint = connectPoint;
332 this.ipAddresses = ipAddresses;
333 retransmitPeriod = period;
334 }
335
336 public void run() {
337 // Router Advertisement header filling. Please refer RFC-2461.
338 RouterAdvertisement ra = new RouterAdvertisement();
339 ra.setCurrentHopLimit(RA_HOP_LIMIT);
jayakumarthazhath1cbde732017-10-25 10:01:00 -0400340 ra.setMFlag((byte) (raFlagMbitStatus ? 0x01 : 0x00));
341 ra.setOFlag((byte) (raFlagObitStatus ? 0x01 : 0x00));
Jayakumar Thazhath3ec6aa22017-09-11 02:00:20 -0400342 ra.setRouterLifetime(RA_ROUTER_LIFETIME);
343 ra.setReachableTime(0);
344 ra.setRetransmitTimer(retransmitPeriod + RA_RETRANSMIT_CALIBRATION_PERIOD);
345
346 // Option : Source link-layer address.
347 byte[] optionBuffer = new byte[RA_OPTIONS_BUFFER_SIZE];
348 ByteBuffer option = ByteBuffer.wrap(optionBuffer);
349 Optional<MacAddress> macAddress = interfaceService.getInterfacesByPort(connectPoint).stream()
350 .map(Interface::mac).findFirst();
351 if (!macAddress.isPresent()) {
352 log.warn("Unable to retrieve interface {} MAC address. Terminating RA transmission.", connectPoint);
353 return;
354 }
355 option.put(macAddress.get().toBytes());
356 ra.addOption(NeighborDiscoveryOptions.TYPE_SOURCE_LL_ADDRESS,
357 Arrays.copyOfRange(option.array(), 0, option.position()));
358
359 // Option : MTU.
360 option.rewind();
361 option.putShort((short) 0);
362 option.putInt(RA_OPTION_MTU_VALUE);
363 ra.addOption(NeighborDiscoveryOptions.TYPE_MTU,
364 Arrays.copyOfRange(option.array(), 0, option.position()));
365
366 // Option : Prefix information.
jayakumarthazhath1cbde732017-10-25 10:01:00 -0400367 if (raOptionPrefixStatus) {
368 ipAddresses.stream()
369 .filter(i -> i.ipAddress().version().equals(IpAddress.Version.INET6))
370 .forEach(i -> {
371 option.rewind();
372 option.put((byte) i.subnetAddress().prefixLength());
373 // Enable "onlink" option only.
374 option.put((byte) 0x80);
375 option.putInt(RA_OPTION_PREFIX_VALID_LIFETIME);
376 option.putInt(RA_OPTION_PREFIX_PREFERRED_LIFETIME);
377 // Clear reserved fields
378 option.putInt(0x00000000);
379 option.put(IpAddress.makeMaskedAddress(i.ipAddress(),
380 i.subnetAddress().prefixLength()).toOctets());
381 ra.addOption(NeighborDiscoveryOptions.TYPE_PREFIX_INFORMATION,
382 Arrays.copyOfRange(option.array(), 0, option.position()));
Jayakumar Thazhath3ec6aa22017-09-11 02:00:20 -0400383
jayakumarthazhath1cbde732017-10-25 10:01:00 -0400384 });
385 }
Jayakumar Thazhath3ec6aa22017-09-11 02:00:20 -0400386
387 // ICMPv6 header filling.
388 ICMP6 icmpv6 = new ICMP6();
389 icmpv6.setIcmpType(ICMP6.ROUTER_ADVERTISEMENT);
390 icmpv6.setIcmpCode((byte) 0);
391 icmpv6.setPayload(ra);
392
393 // IPv6 header filling.
394 byte[] ip6AllNodesAddress = Ip6Address.valueOf("ff02::1").toOctets();
395 IPv6 ipv6 = new IPv6();
396 ipv6.setDestinationAddress(ip6AllNodesAddress);
397 /* RA packet L2 source address created from port MAC address.
398 * Note : As per RFC-4861 RAs should be sent from link-local address.
399 */
400 ipv6.setSourceAddress(IPv6.getLinkLocalAddress(macAddress.get().toBytes()));
401 ipv6.setNextHeader(IPv6.PROTOCOL_ICMP6);
402 ipv6.setHopLimit(RA_HOP_LIMIT);
403 ipv6.setTrafficClass((byte) 0xe0);
404 ipv6.setPayload(icmpv6);
405
406 // Ethernet header filling.
407 Ethernet ethernet = new Ethernet();
408
409 /* Ethernet IPv6 multicast address creation.
410 * Refer : RFC 2624 section 7.
411 */
412 byte[] l2Ipv6MulticastAddress = MacAddress.IPV6_MULTICAST.toBytes();
413 IntStream.range(1, 4).forEach(i -> l2Ipv6MulticastAddress[l2Ipv6MulticastAddress.length - i] =
414 ip6AllNodesAddress[ip6AllNodesAddress.length - i]);
415
416 ethernet.setDestinationMACAddress(MacAddress.valueOf(l2Ipv6MulticastAddress));
417 ethernet.setSourceMACAddress(macAddress.get().toBytes());
418 ethernet.setEtherType(EthType.EtherType.IPV6.ethType().toShort());
419 ethernet.setVlanID(Ethernet.VLAN_UNTAGGED);
420 ethernet.setPayload(ipv6);
421 ethernet.setPad(false);
422
423 // Flush out PACKET_OUT.
424 ByteBuffer stream = ByteBuffer.wrap(ethernet.serialize());
425 TrafficTreatment treatment = DefaultTrafficTreatment.builder().setOutput(connectPoint.port()).build();
426 OutboundPacket packet = new DefaultOutboundPacket(connectPoint.deviceId(),
427 treatment, stream);
428 packetService.emit(packet);
429 }
430 }
431}