blob: cfefa84ef9245ab3a7df306988d966c3f10fcba4 [file] [log] [blame]
Jayakumar Thazhathf0f20892017-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;
25import org.onlab.packet.EthType;
26import org.onlab.packet.Ethernet;
27import org.onlab.packet.ICMP6;
28import org.onlab.packet.IPv6;
29import org.onlab.packet.IpAddress;
30import org.onlab.packet.Ip6Address;
31import org.onlab.packet.MacAddress;
32import org.onlab.packet.ndp.RouterAdvertisement;
33import org.onlab.packet.ndp.NeighborDiscoveryOptions;
34import org.onosproject.cfg.ComponentConfigService;
35import org.onosproject.core.ApplicationId;
36import org.onosproject.core.CoreService;
37import org.onosproject.mastership.MastershipService;
38import org.onosproject.net.MastershipRole;
39import org.onosproject.net.intf.Interface;
40import org.onosproject.net.intf.InterfaceEvent;
41import org.onosproject.net.intf.InterfaceListener;
42import org.onosproject.net.intf.InterfaceService;
43import org.onosproject.net.ConnectPoint;
44import org.onosproject.net.flow.DefaultTrafficTreatment;
45import org.onosproject.net.flow.TrafficTreatment;
46import org.onosproject.net.host.InterfaceIpAddress;
47import org.onosproject.net.packet.DefaultOutboundPacket;
48import org.onosproject.net.packet.OutboundPacket;
49import org.onosproject.net.packet.PacketService;
50import org.slf4j.Logger;
51import org.slf4j.LoggerFactory;
52import org.osgi.service.component.ComponentContext;
53
54import javax.annotation.concurrent.GuardedBy;
55import java.nio.ByteBuffer;
56import java.util.Dictionary;
57import java.util.LinkedHashMap;
58import java.util.List;
59import java.util.Map;
60import java.util.Arrays;
61import java.util.Optional;
62
63import java.util.concurrent.ScheduledExecutorService;
64import java.util.concurrent.ScheduledFuture;
65import java.util.concurrent.Executors;
66import java.util.concurrent.TimeUnit;
67import java.util.stream.IntStream;
68
69import static com.google.common.base.Strings.isNullOrEmpty;
70import static org.onlab.util.Tools.get;
71import static org.onlab.util.Tools.groupedThreads;
72
73/**
74 * Manages IPv6 Router Advertisements.
75 */
76@Component(immediate = true)
77public class RouterAdvertisementManager {
78
79 private final Logger log = LoggerFactory.getLogger(getClass());
80 private static final String PROP_RA_THREADS_POOL = "raPoolSize";
81 private static final int DEFAULT_RA_THREADS_POOL_SIZE = 10;
82 private static final String PROP_RA_THREADS_DELAY = "raThreadDelay";
83 private static final int DEFAULT_RA_THREADS_DELAY = 5;
84
85 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
86 protected CoreService coreService;
87
88 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
89 PacketService packetService;
90
91 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
92 protected ComponentConfigService componentConfigService;
93
94 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
95 public InterfaceService interfaceService;
96
97 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
98 public MastershipService mastershipService;
99
100 @Property(name = PROP_RA_THREADS_POOL, intValue = DEFAULT_RA_THREADS_POOL_SIZE,
101 label = "Router Advertisement thread pool capacity")
102 protected int raPoolSize = DEFAULT_RA_THREADS_POOL_SIZE;
103
104 @Property(name = PROP_RA_THREADS_DELAY, intValue = DEFAULT_RA_THREADS_DELAY,
105 label = "Router Advertisement thread delay in seconds")
106 protected int raThreadDelay = DEFAULT_RA_THREADS_DELAY;
107
108 private ScheduledExecutorService executors = null;
109
110 @GuardedBy(value = "this")
111 private final Map<ConnectPoint, ScheduledFuture<?>> transmitters = new LinkedHashMap<>();
112
113 private static final String APP_NAME = "org.onosproject.routeradvertisement";
114 private ApplicationId appId;
115
116 // Listener for handling dynamic interface modifications.
117 private class InternalInterfaceListener implements InterfaceListener {
118 @Override
119 public void event(InterfaceEvent event) {
120 Interface i = event.subject();
121 switch (event.type()) {
122 case INTERFACE_ADDED:
123 if (mastershipService.getLocalRole(i.connectPoint().deviceId())
124 == MastershipRole.MASTER) {
125 activateRouterAdvertisement(i.connectPoint(),
126 i.ipAddressesList());
127 }
128 break;
129 case INTERFACE_REMOVED:
130 if (mastershipService.getLocalRole(i.connectPoint().deviceId())
131 == MastershipRole.MASTER) {
132 deactivateRouterAdvertisement(i.connectPoint(),
133 i.ipAddressesList());
134 }
135 break;
136 case INTERFACE_UPDATED:
137 break;
138 default:
139 break;
140 }
141 }
142 }
143 private final InterfaceListener interfaceListener = new InternalInterfaceListener();
144
145 // Enables RA threads on 'connectPoint' with configured IPv6s
146 private void activateRouterAdvertisement(ConnectPoint connectPoint, List<InterfaceIpAddress> addresses) {
147 synchronized (this) {
148 RAWorkerThread worker = new RAWorkerThread(connectPoint, addresses, raThreadDelay);
149 ScheduledFuture<?> handler = executors.scheduleAtFixedRate(worker, raThreadDelay,
150 raThreadDelay, TimeUnit.SECONDS);
151 transmitters.put(connectPoint, handler);
152 }
153
154 }
155
156 // Disables already activated RA threads on 'connectPoint'
157 private void deactivateRouterAdvertisement(ConnectPoint connectPoint, List<InterfaceIpAddress> addresses) {
158 synchronized (this) {
159 if (connectPoint != null) {
160 ScheduledFuture<?> handler = transmitters.get(connectPoint);
161 handler.cancel(false);
162 transmitters.remove(connectPoint);
163 }
164 }
165 }
166
167 @Activate
168 protected void activate(ComponentContext context) {
169 // Basic application registrations.
170 appId = coreService.registerApplication(APP_NAME);
171 componentConfigService.registerProperties(getClass());
172
173 // Loading configured properties.
174 if (context != null) {
175 Dictionary<?, ?> properties = context.getProperties();
176 try {
177 String s = get(properties, PROP_RA_THREADS_POOL);
178 raPoolSize = isNullOrEmpty(s) ?
179 DEFAULT_RA_THREADS_POOL_SIZE : Integer.parseInt(s.trim());
180
181 s = get(properties, PROP_RA_THREADS_DELAY);
182 raThreadDelay = isNullOrEmpty(s) ?
183 DEFAULT_RA_THREADS_DELAY : Integer.parseInt(s.trim());
184
185 } catch (NumberFormatException e) {
186 log.warn("Component configuration had invalid value, loading default values.", e);
187 }
188 }
189
190 // Interface listener for dynamic RA handling.
191 interfaceService.addListener(interfaceListener);
192
193 // Initialize RA thread pool
194 executors = Executors.newScheduledThreadPool(raPoolSize,
195 groupedThreads("RouterAdvertisement", "event-%d", log));
196
197 // Start Router Advertisement Transmission for all configured interfaces.
198 interfaceService.getInterfaces()
199 .stream()
200 .filter(i -> mastershipService.getLocalRole(i.connectPoint().deviceId())
201 == MastershipRole.MASTER)
202 .filter(i -> i.ipAddressesList()
203 .stream()
204 .anyMatch(ia -> ia.ipAddress().version().equals(IpAddress.Version.INET6)))
205 .forEach(j ->
206 activateRouterAdvertisement(j.connectPoint(), j.ipAddressesList())
207 );
208 }
209
210 @Deactivate
211 protected void deactivate() {
212 // Unregister resources.
213 componentConfigService.unregisterProperties(getClass(), false);
214 interfaceService.removeListener(interfaceListener);
215
216 // 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
229 // Worker thread for actually sending ICMPv6 RA packets.
230 private class RAWorkerThread implements Runnable {
231
232 ConnectPoint connectPoint;
233 List<InterfaceIpAddress> ipAddresses;
234 int retransmitPeriod;
235
236 // Various fixed values in RA packet
237 public static final byte RA_HOP_LIMIT = (byte) 0xff;
238 public static final short RA_ROUTER_LIFETIME = (short) 1800;
239 public static final int RA_OPTIONS_BUFFER_SIZE = 500;
240 public static final int RA_OPTION_MTU_VALUE = 1500;
241 public static final int RA_OPTION_PREFIX_VALID_LIFETIME = 600;
242 public static final int RA_OPTION_PREFIX_PREFERRED_LIFETIME = 600;
243 public static final int RA_RETRANSMIT_CALIBRATION_PERIOD = 1;
244
245
246 RAWorkerThread(ConnectPoint connectPoint, List<InterfaceIpAddress> ipAddresses, int period) {
247 this.connectPoint = connectPoint;
248 this.ipAddresses = ipAddresses;
249 retransmitPeriod = period;
250 }
251
252 public void run() {
253 // Router Advertisement header filling. Please refer RFC-2461.
254 RouterAdvertisement ra = new RouterAdvertisement();
255 ra.setCurrentHopLimit(RA_HOP_LIMIT);
256 ra.setMFlag((byte) 0x01);
257 ra.setOFlag((byte) 0x00);
258 ra.setRouterLifetime(RA_ROUTER_LIFETIME);
259 ra.setReachableTime(0);
260 ra.setRetransmitTimer(retransmitPeriod + RA_RETRANSMIT_CALIBRATION_PERIOD);
261
262 // Option : Source link-layer address.
263 byte[] optionBuffer = new byte[RA_OPTIONS_BUFFER_SIZE];
264 ByteBuffer option = ByteBuffer.wrap(optionBuffer);
265 Optional<MacAddress> macAddress = interfaceService.getInterfacesByPort(connectPoint).stream()
266 .map(Interface::mac).findFirst();
267 if (!macAddress.isPresent()) {
268 log.warn("Unable to retrieve interface {} MAC address. Terminating RA transmission.", connectPoint);
269 return;
270 }
271 option.put(macAddress.get().toBytes());
272 ra.addOption(NeighborDiscoveryOptions.TYPE_SOURCE_LL_ADDRESS,
273 Arrays.copyOfRange(option.array(), 0, option.position()));
274
275 // Option : MTU.
276 option.rewind();
277 option.putShort((short) 0);
278 option.putInt(RA_OPTION_MTU_VALUE);
279 ra.addOption(NeighborDiscoveryOptions.TYPE_MTU,
280 Arrays.copyOfRange(option.array(), 0, option.position()));
281
282 // Option : Prefix information.
283 ipAddresses.stream()
284 .filter(i -> i.ipAddress().version().equals(IpAddress.Version.INET6))
285 .forEach(i -> {
286 option.rewind();
287 option.put((byte) i.subnetAddress().prefixLength());
288 // Enable "onlink" option only.
289 option.put((byte) 0x80);
290 option.putInt(RA_OPTION_PREFIX_VALID_LIFETIME);
291 option.putInt(RA_OPTION_PREFIX_PREFERRED_LIFETIME);
292 // Clear reserved fields
293 option.putInt(0x00000000);
294 option.put(IpAddress.makeMaskedAddress(i.ipAddress(),
295 i.subnetAddress().prefixLength()).toOctets());
296 ra.addOption(NeighborDiscoveryOptions.TYPE_PREFIX_INFORMATION,
297 Arrays.copyOfRange(option.array(), 0, option.position()));
298
299 });
300
301 // ICMPv6 header filling.
302 ICMP6 icmpv6 = new ICMP6();
303 icmpv6.setIcmpType(ICMP6.ROUTER_ADVERTISEMENT);
304 icmpv6.setIcmpCode((byte) 0);
305 icmpv6.setPayload(ra);
306
307 // IPv6 header filling.
308 byte[] ip6AllNodesAddress = Ip6Address.valueOf("ff02::1").toOctets();
309 IPv6 ipv6 = new IPv6();
310 ipv6.setDestinationAddress(ip6AllNodesAddress);
311 /* RA packet L2 source address created from port MAC address.
312 * Note : As per RFC-4861 RAs should be sent from link-local address.
313 */
314 ipv6.setSourceAddress(IPv6.getLinkLocalAddress(macAddress.get().toBytes()));
315 ipv6.setNextHeader(IPv6.PROTOCOL_ICMP6);
316 ipv6.setHopLimit(RA_HOP_LIMIT);
317 ipv6.setTrafficClass((byte) 0xe0);
318 ipv6.setPayload(icmpv6);
319
320 // Ethernet header filling.
321 Ethernet ethernet = new Ethernet();
322
323 /* Ethernet IPv6 multicast address creation.
324 * Refer : RFC 2624 section 7.
325 */
326 byte[] l2Ipv6MulticastAddress = MacAddress.IPV6_MULTICAST.toBytes();
327 IntStream.range(1, 4).forEach(i -> l2Ipv6MulticastAddress[l2Ipv6MulticastAddress.length - i] =
328 ip6AllNodesAddress[ip6AllNodesAddress.length - i]);
329
330 ethernet.setDestinationMACAddress(MacAddress.valueOf(l2Ipv6MulticastAddress));
331 ethernet.setSourceMACAddress(macAddress.get().toBytes());
332 ethernet.setEtherType(EthType.EtherType.IPV6.ethType().toShort());
333 ethernet.setVlanID(Ethernet.VLAN_UNTAGGED);
334 ethernet.setPayload(ipv6);
335 ethernet.setPad(false);
336
337 // Flush out PACKET_OUT.
338 ByteBuffer stream = ByteBuffer.wrap(ethernet.serialize());
339 TrafficTreatment treatment = DefaultTrafficTreatment.builder().setOutput(connectPoint.port()).build();
340 OutboundPacket packet = new DefaultOutboundPacket(connectPoint.deviceId(),
341 treatment, stream);
342 packetService.emit(packet);
343 }
344 }
345}