blob: cb6df8d4584a3bf53be0ed75e4fee87b7447b284 [file] [log] [blame]
Thomas Vachuska781d18b2014-10-27 10:31:25 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2015-present Open Networking Foundation
tom7ef8ff92014-09-17 13:08:06 -07003 *
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07004 * 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
tom7ef8ff92014-09-17 13:08:06 -07007 *
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07008 * 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.
Thomas Vachuska781d18b2014-10-27 10:31:25 -070015 */
tom7ef8ff92014-09-17 13:08:06 -070016
Brian O'Connorabafb502014-12-02 22:26:20 -080017package org.onosproject.openflow.controller.impl;
tom7ef8ff92014-09-17 13:08:06 -070018
Brian O'Connorf7215b82018-05-10 19:12:44 -070019import com.google.common.base.MoreObjects;
Brian O'Connorff278502015-09-22 14:49:52 -070020import com.google.common.base.Strings;
21import com.google.common.collect.ImmutableList;
Brian O'Connorf7215b82018-05-10 19:12:44 -070022import com.google.common.collect.Sets;
Yuta HIGUCHI6ee6b8c2017-05-09 14:44:30 -070023import io.netty.bootstrap.ServerBootstrap;
Brian O'Connorf7215b82018-05-10 19:12:44 -070024import io.netty.channel.Channel;
Yuta HIGUCHI6ee6b8c2017-05-09 14:44:30 -070025import io.netty.channel.ChannelOption;
26import io.netty.channel.EventLoopGroup;
27import io.netty.channel.epoll.EpollEventLoopGroup;
28import io.netty.channel.epoll.EpollServerSocketChannel;
29import io.netty.channel.group.ChannelGroup;
30import io.netty.channel.group.DefaultChannelGroup;
31import io.netty.channel.nio.NioEventLoopGroup;
32import io.netty.channel.socket.nio.NioServerSocketChannel;
33import io.netty.util.concurrent.GlobalEventExecutor;
Jonathan Harta0d12492015-07-16 12:03:41 -070034import org.onlab.util.ItemNotFoundException;
35import org.onosproject.net.DeviceId;
Brian O'Connorf69e3e32018-05-10 02:25:09 -070036import org.onosproject.net.config.NetworkConfigRegistry;
alshabibb452fd72015-04-22 20:46:20 -070037import org.onosproject.net.driver.DefaultDriverData;
38import org.onosproject.net.driver.DefaultDriverHandler;
39import org.onosproject.net.driver.Driver;
40import org.onosproject.net.driver.DriverService;
Brian O'Connorf69e3e32018-05-10 02:25:09 -070041import org.onosproject.openflow.config.OpenFlowDeviceConfig;
Brian O'Connorabafb502014-12-02 22:26:20 -080042import org.onosproject.openflow.controller.Dpid;
43import org.onosproject.openflow.controller.driver.OpenFlowAgent;
44import org.onosproject.openflow.controller.driver.OpenFlowSwitchDriver;
tom7ef8ff92014-09-17 13:08:06 -070045import org.projectfloodlight.openflow.protocol.OFDescStatsReply;
tom7ef8ff92014-09-17 13:08:06 -070046import org.projectfloodlight.openflow.protocol.OFVersion;
47import org.slf4j.Logger;
48import org.slf4j.LoggerFactory;
49
alshabib9b6c19c2015-09-26 12:19:27 -070050import javax.net.ssl.KeyManagerFactory;
51import javax.net.ssl.SSLContext;
alshabib9b6c19c2015-09-26 12:19:27 -070052import javax.net.ssl.TrustManagerFactory;
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -070053import java.io.File;
alshabib9b6c19c2015-09-26 12:19:27 -070054import java.io.FileInputStream;
Ray Milkey986a47a2018-01-25 11:38:51 -080055import java.io.IOException;
Thomas Vachuska6f94ded2015-02-21 14:02:38 -080056import java.lang.management.ManagementFactory;
57import java.lang.management.RuntimeMXBean;
Brian O'Connorf7215b82018-05-10 19:12:44 -070058import java.net.InetSocketAddress;
59import java.net.SocketAddress;
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -070060import java.security.DigestInputStream;
Ray Milkey986a47a2018-01-25 11:38:51 -080061import java.security.KeyManagementException;
alshabib9b6c19c2015-09-26 12:19:27 -070062import java.security.KeyStore;
Ray Milkey986a47a2018-01-25 11:38:51 -080063import java.security.KeyStoreException;
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -070064import java.security.MessageDigest;
Ray Milkey986a47a2018-01-25 11:38:51 -080065import java.security.NoSuchAlgorithmException;
66import java.security.UnrecoverableKeyException;
Brian O'Connorf69e3e32018-05-10 02:25:09 -070067import java.security.cert.Certificate;
Ray Milkey986a47a2018-01-25 11:38:51 -080068import java.security.cert.CertificateException;
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -070069import java.util.Arrays;
Brian O'Connorf7215b82018-05-10 19:12:44 -070070import java.util.Collection;
71import java.util.Collections;
Brian O'Connorff278502015-09-22 14:49:52 -070072import java.util.Dictionary;
Brian O'Connorf7215b82018-05-10 19:12:44 -070073import java.util.EnumSet;
Thomas Vachuska6f94ded2015-02-21 14:02:38 -080074import java.util.HashMap;
Brian O'Connorf7215b82018-05-10 19:12:44 -070075import java.util.Iterator;
Brian O'Connorff278502015-09-22 14:49:52 -070076import java.util.List;
Thomas Vachuska6f94ded2015-02-21 14:02:38 -080077import java.util.Map;
Brian O'Connorf69e3e32018-05-10 02:25:09 -070078import java.util.Objects;
79import java.util.Optional;
Brian O'Connorf7215b82018-05-10 19:12:44 -070080import java.util.Set;
Brian O'Connorff278502015-09-22 14:49:52 -070081import java.util.stream.Collectors;
82import java.util.stream.Stream;
Thomas Vachuska6f94ded2015-02-21 14:02:38 -080083
Brian O'Connorff278502015-09-22 14:49:52 -070084import static org.onlab.util.Tools.get;
Thomas Vachuska6f94ded2015-02-21 14:02:38 -080085import static org.onlab.util.Tools.groupedThreads;
Thomas Vachuska80b0a802015-07-17 08:43:30 -070086import static org.onosproject.net.DeviceId.deviceId;
87import static org.onosproject.openflow.controller.Dpid.uri;
Thomas Vachuska6f94ded2015-02-21 14:02:38 -080088
tom7ef8ff92014-09-17 13:08:06 -070089
90/**
91 * The main controller class. Handles all setup and network listeners
tom7ef8ff92014-09-17 13:08:06 -070092 */
93public class Controller {
94
Ray Milkey9c9cde42018-01-12 14:22:06 -080095 private static final Logger log = LoggerFactory.getLogger(Controller.class);
alshabib9eab22f2014-10-20 17:17:31 -070096
alshabib9b6c19c2015-09-26 12:19:27 -070097 private static final short MIN_KS_LENGTH = 6;
tom7ef8ff92014-09-17 13:08:06 -070098
tom7ef8ff92014-09-17 13:08:06 -070099 protected HashMap<String, String> controllerNodeIPsCache;
100
101 private ChannelGroup cg;
102
103 // Configuration options
Brian O'Connorff278502015-09-22 14:49:52 -0700104 protected List<Integer> openFlowPorts = ImmutableList.of(6633, 6653);
Yuta HIGUCHI8552b172016-07-25 12:10:08 -0700105 protected int workerThreads = 0;
tom7ef8ff92014-09-17 13:08:06 -0700106
107 // Start time of the controller
108 protected long systemStartTime;
109
110 private OpenFlowAgent agent;
111
Yuta HIGUCHI6ee6b8c2017-05-09 14:44:30 -0700112 private EventLoopGroup bossGroup;
113 private EventLoopGroup workerGroup;
tom7ef8ff92014-09-17 13:08:06 -0700114
Brian O'Connorf7215b82018-05-10 19:12:44 -0700115 enum TlsMode {
116 DISABLED, // TLS is not used for OpenFlow connections
117 ENABLED, // Clients are required use TLS and present a client certificate
118 STRICT, // Clients must use TLS, and certificate must match the one specified in netcfg
119 }
120 private static final EnumSet<TlsMode> TLS_ENABLED = EnumSet.of(TlsMode.ENABLED, TlsMode.STRICT);
121
122 protected TlsParams tlsParams;
alshabib5162a9d2015-12-07 17:49:37 -0800123 protected SSLContext sslContext;
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700124 protected KeyStore keyStore;
alshabib9b6c19c2015-09-26 12:19:27 -0700125
tom7ef8ff92014-09-17 13:08:06 -0700126 // Perf. related configuration
127 protected static final int SEND_BUFFER_SIZE = 4 * 1024 * 1024;
Yuta HIGUCHI2341e602017-03-08 20:10:08 -0800128
alshabibb452fd72015-04-22 20:46:20 -0700129 private DriverService driverService;
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700130 private NetworkConfigRegistry netCfgService;
tom7ef8ff92014-09-17 13:08:06 -0700131
Brian O'Connorf7215b82018-05-10 19:12:44 -0700132
133
tom7ef8ff92014-09-17 13:08:06 -0700134 // **************
135 // Initialization
136 // **************
137
Brian O'Connorf7215b82018-05-10 19:12:44 -0700138 private void addListeningPorts(Collection<Integer> ports) {
139 if (cg == null) {
140 return;
141 }
142 final ServerBootstrap bootstrap = createServerBootStrap();
143 bootstrap.option(ChannelOption.SO_REUSEADDR, true);
144 bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
145 bootstrap.childOption(ChannelOption.TCP_NODELAY, true);
146 bootstrap.childOption(ChannelOption.SO_SNDBUF, Controller.SEND_BUFFER_SIZE);
Yuta HIGUCHI6ee6b8c2017-05-09 14:44:30 -0700147// bootstrap.childOption(ChannelOption.WRITE_BUFFER_WATER_MARK,
148// new WriteBufferWaterMark(8 * 1024, 32 * 1024));
tom7ef8ff92014-09-17 13:08:06 -0700149
Brian O'Connorf7215b82018-05-10 19:12:44 -0700150 bootstrap.childHandler(new OFChannelInitializer(this, null, sslContext));
tom7ef8ff92014-09-17 13:08:06 -0700151
Brian O'Connorf7215b82018-05-10 19:12:44 -0700152 Set<Integer> existingPorts = cg.stream()
153 .map(Channel::localAddress)
154 .filter(InetSocketAddress.class::isInstance)
155 .map(InetSocketAddress.class::cast)
156 .map(InetSocketAddress::getPort)
157 .collect(Collectors.toSet());
158 ports.removeAll(existingPorts);
tom7ef8ff92014-09-17 13:08:06 -0700159
Brian O'Connorf7215b82018-05-10 19:12:44 -0700160 ports.forEach(port -> {
161 // TODO revisit if this is best way to listen to multiple ports
162 cg.add(bootstrap.bind(port).syncUninterruptibly().channel());
163 log.info("Listening for OF switch connections on {}", port);
164 });
165 }
166
167 private void removeListeningPorts(Collection<Integer> ports) {
168 if (cg == null) {
169 return;
170 }
171 Iterator<Channel> itr = cg.iterator();
172 while (itr.hasNext()) {
173 Channel c = itr.next();
174 SocketAddress addr = c.localAddress();
175 if (addr instanceof InetSocketAddress) {
176 InetSocketAddress inetAddr = (InetSocketAddress) addr;
177 Integer port = inetAddr.getPort();
178 if (ports.contains(port)) {
179 log.info("No longer listening for OF switch connections on {}", port);
180 c.close();
181 itr.remove();
182 }
183 }
184
185 }
tom7ef8ff92014-09-17 13:08:06 -0700186 }
187
188 private ServerBootstrap createServerBootStrap() {
189
Yuta HIGUCHI6ee6b8c2017-05-09 14:44:30 -0700190 int bossThreads = Math.max(1, openFlowPorts.size());
191 try {
192 bossGroup = new EpollEventLoopGroup(bossThreads, groupedThreads("onos/of", "boss-%d", log));
193 workerGroup = new EpollEventLoopGroup(workerThreads, groupedThreads("onos/of", "worker-%d", log));
194 ServerBootstrap bs = new ServerBootstrap()
195 .group(bossGroup, workerGroup)
196 .channel(EpollServerSocketChannel.class);
197 log.info("Using Epoll transport");
198 return bs;
199 } catch (Throwable e) {
200 log.debug("Failed to initialize native (epoll) transport: {}", e.getMessage());
tom7ef8ff92014-09-17 13:08:06 -0700201 }
Yuta HIGUCHI6ee6b8c2017-05-09 14:44:30 -0700202
203// Requires 4.1.11 or later
204// try {
205// bossGroup = new KQueueEventLoopGroup(bossThreads, groupedThreads("onos/of", "boss-%d", log));
206// workerGroup = new KQueueEventLoopGroup(workerThreads, groupedThreads("onos/of", "worker-%d", log));
207// ServerBootstrap bs = new ServerBootstrap()
208// .group(bossGroup, workerGroup)
209// .channel(KQueueServerSocketChannel.class);
210// log.info("Using Kqueue transport");
211// return bs;
212// } catch (Throwable e) {
213// log.debug("Failed to initialize native (kqueue) transport. ", e.getMessage());
214// }
215
216 bossGroup = new NioEventLoopGroup(bossThreads, groupedThreads("onos/of", "boss-%d", log));
217 workerGroup = new NioEventLoopGroup(workerThreads, groupedThreads("onos/of", "worker-%d", log));
218 log.info("Using Nio transport");
219 return new ServerBootstrap()
220 .group(bossGroup, workerGroup)
221 .channel(NioServerSocketChannel.class);
tom7ef8ff92014-09-17 13:08:06 -0700222 }
223
Brian O'Connorff278502015-09-22 14:49:52 -0700224 public void setConfigParams(Dictionary<?, ?> properties) {
Brian O'Connorf7215b82018-05-10 19:12:44 -0700225 boolean restartRequired = setOpenFlowPorts(properties);
226 restartRequired |= setWorkerThreads(properties);
227 restartRequired |= setTlsParameters(properties);
228 if (restartRequired) {
229 restart();
tom7ef8ff92014-09-17 13:08:06 -0700230 }
Brian O'Connorf7215b82018-05-10 19:12:44 -0700231 }
232
233 /**
234 * Gets the list of listening ports from property dict.
235 *
236 * @param properties dictionary
237 * @return true if restart is required
238 */
239 private boolean setWorkerThreads(Dictionary<?, ?> properties) {
240 List<Integer> oldPorts = this.openFlowPorts;
241 String ports = get(properties, "openflowPorts");
242 List<Integer> newPorts = Collections.emptyList();
243 if (!Strings.isNullOrEmpty(ports)) {
244 newPorts = Stream.of(ports.split(","))
245 .map(s -> Integer.parseInt(s))
246 .collect(Collectors.toList());
247 }
248
249 Set<Integer> portsToAdd = Sets.newHashSet(newPorts);
250 portsToAdd.removeAll(oldPorts);
251 addListeningPorts(portsToAdd);
252
253 Set<Integer> portsToRemove = Sets.newHashSet(oldPorts);
254 portsToRemove.removeAll(newPorts);
255 removeListeningPorts(portsToRemove);
256
257 this.openFlowPorts = newPorts;
Brian O'Connorff278502015-09-22 14:49:52 -0700258 log.debug("OpenFlow ports set to {}", this.openFlowPorts);
Brian O'Connorf7215b82018-05-10 19:12:44 -0700259 return false; // restart is never required
260 }
261
262 /**
263 * Gets the number of worker threads from property dict.
264 *
265 * @param properties dictionary
266 * @return true if restart is required
267 */
268 private boolean setOpenFlowPorts(Dictionary<?, ?> properties) {
269 Integer oldValue = this.workerThreads;
Charles Chan45624b82015-08-24 00:29:20 +0800270
Brian O'Connorff278502015-09-22 14:49:52 -0700271 String threads = get(properties, "workerThreads");
272 if (!Strings.isNullOrEmpty(threads)) {
273 this.workerThreads = Integer.parseInt(threads);
274 }
tom7ef8ff92014-09-17 13:08:06 -0700275 log.debug("Number of worker threads set to {}", this.workerThreads);
Brian O'Connorf7215b82018-05-10 19:12:44 -0700276 return oldValue != this.workerThreads; // restart if number of threads has changed
277 }
278
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700279 static class TlsParams {
280 final TlsMode mode;
281 final String ksLocation;
282 final String tsLocation;
283 final String ksPwd;
284 final String tsPwd;
285 final byte[] ksSignature;
286 final byte[] tsSignature;
287
288 TlsParams(TlsMode mode, String ksLocation, String tsLocation,
289 String ksPwd, String tsPwd) {
290 this.mode = mode;
291 this.ksLocation = ksLocation;
292 this.tsLocation = tsLocation;
293 this.ksPwd = ksPwd;
294 this.tsPwd = tsPwd;
295 this.ksSignature = getSha1Checksum(ksLocation);
296 this.tsSignature = getSha1Checksum(tsLocation);
297 }
Brian O'Connorf7215b82018-05-10 19:12:44 -0700298
299 public char[] ksPwd() {
300 return ksPwd.toCharArray();
301 }
302
303 public char[] tsPwd() {
304 return tsPwd.toCharArray();
305 }
306
307 public boolean isTlsEnabled() {
308 return TLS_ENABLED.contains(mode);
309 }
310
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700311 public byte[] getSha1Checksum(String filepath) {
312 if (filepath == null) {
313 return new byte[0];
314 }
315 try {
316 MessageDigest digest = MessageDigest.getInstance("SHA1");
317 File f = new File(filepath);
318 FileInputStream is = new FileInputStream(f);
319 DigestInputStream dis = new DigestInputStream(is, digest);
320 byte[] buffer = new byte[1024];
321 while (dis.read(buffer) > 0) {
322 // nothing to do :)
323 }
324 dis.getMessageDigest().digest();
325 } catch (NoSuchAlgorithmException ignored) {
326 } catch (IOException e) {
327 log.info("Error reading file file: {}", filepath);
328 }
329 return new byte[0];
330 }
331
Brian O'Connorf7215b82018-05-10 19:12:44 -0700332 @Override
333 public int hashCode() {
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700334 if (mode == TlsMode.DISABLED) {
335 return Objects.hash(mode);
336 }
337 return Objects.hash(mode, ksLocation, tsLocation,
Yuta HIGUCHIcf03a0f2018-05-15 19:25:28 -0700338 ksPwd, tsPwd,
339 Arrays.hashCode(ksSignature),
340 Arrays.hashCode(tsSignature));
Brian O'Connorf7215b82018-05-10 19:12:44 -0700341 }
342
343 @Override
344 public boolean equals(Object obj) {
345 if (this == obj) {
346 return true;
347 }
348 if (obj instanceof TlsParams) {
349 final TlsParams that = (TlsParams) obj;
350 if (this.getClass() != that.getClass()) {
351 return false;
352 } else if (this.mode == that.mode && this.mode == TlsMode.DISABLED) {
353 // All disabled objects should be equal regardless of other params
354 return true;
355 }
356 return this.mode == that.mode &&
357 Objects.equals(this.ksLocation, that.ksLocation) &&
358 Objects.equals(this.tsLocation, that.tsLocation) &&
359 Objects.equals(this.ksPwd, that.ksPwd) &&
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700360 Objects.equals(this.tsPwd, that.tsPwd) &&
361 Arrays.equals(this.ksSignature, that.ksSignature) &&
362 Arrays.equals(this.tsSignature, that.tsSignature);
Brian O'Connorf7215b82018-05-10 19:12:44 -0700363 }
364 return false;
365 }
366
367 @Override
368 public String toString() {
369 return MoreObjects.toStringHelper(this)
370 .add("tlsMode", mode.toString().toLowerCase())
371 .add("ksLocation", ksLocation)
372 .add("tsLocation", tsLocation)
373 .toString();
374 }
375 }
376
377 /**
378 * Gets the TLS parameters from the properties dict, but fallback to the old approach of
379 * system properties if the parameters are missing from the dict.
380 *
381 * @param properties dictionary
382 * @return true if restart is required
383 */
384 private boolean setTlsParameters(Dictionary<?, ?> properties) {
385 TlsParams oldParams = this.tlsParams;
386
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700387 TlsMode mode = null;
Brian O'Connorf7215b82018-05-10 19:12:44 -0700388 String tlsString = get(properties, "tlsMode");
389 if (!Strings.isNullOrEmpty(tlsString)) {
390 try {
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700391 mode = TlsMode.valueOf(tlsString.toUpperCase());
Brian O'Connorf7215b82018-05-10 19:12:44 -0700392 } catch (IllegalArgumentException e) {
393 log.info("Invalid TLS mode {}. TLS is disabled.", tlsString);
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700394 mode = TlsMode.DISABLED;
Brian O'Connorf7215b82018-05-10 19:12:44 -0700395 }
396 } else {
397 // Fallback to system properties
398 // TODO this method of configuring TLS is deprecated and should be removed eventually
399 tlsString = System.getProperty("enableOFTLS");
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700400 mode = !Strings.isNullOrEmpty(tlsString) && Boolean.parseBoolean(tlsString) ?
Brian O'Connorf7215b82018-05-10 19:12:44 -0700401 TlsMode.ENABLED : TlsMode.DISABLED;
402 }
403
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700404 String ksLocation = null, tsLocation = null, ksPwd = null, tsPwd = null;
405 if (TLS_ENABLED.contains(mode)) {
406 ksLocation = get(properties, "keyStore");
407 if (Strings.isNullOrEmpty(ksLocation)) {
Brian O'Connorf7215b82018-05-10 19:12:44 -0700408 // Fallback to system properties
409 // TODO remove this eventually
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700410 ksLocation = System.getProperty("javax.net.ssl.keyStore");
Brian O'Connorf7215b82018-05-10 19:12:44 -0700411 }
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700412 if (Strings.isNullOrEmpty(ksLocation)) {
413 mode = TlsMode.DISABLED;
Brian O'Connorf7215b82018-05-10 19:12:44 -0700414 }
415
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700416 tsLocation = get(properties, "trustStore");
417 if (Strings.isNullOrEmpty(tsLocation)) {
Brian O'Connorf7215b82018-05-10 19:12:44 -0700418 // Fallback to system properties
419 // TODO remove this eventually
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700420 tsLocation = System.getProperty("javax.net.ssl.trustStore");
Brian O'Connorf7215b82018-05-10 19:12:44 -0700421 }
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700422 if (Strings.isNullOrEmpty(tsLocation)) {
423 mode = TlsMode.DISABLED;
Brian O'Connorf7215b82018-05-10 19:12:44 -0700424 }
425
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700426 ksPwd = get(properties, "keyStorePassword");
427 if (Strings.isNullOrEmpty(ksPwd)) {
Brian O'Connorf7215b82018-05-10 19:12:44 -0700428 // Fallback to system properties
429 // TODO remove this eventually
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700430 ksPwd = System.getProperty("javax.net.ssl.keyStorePassword");
Brian O'Connorf7215b82018-05-10 19:12:44 -0700431 }
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700432 if (Strings.isNullOrEmpty(ksPwd) || MIN_KS_LENGTH > ksPwd.length()) {
433 mode = TlsMode.DISABLED;
Brian O'Connorf7215b82018-05-10 19:12:44 -0700434 }
435
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700436 tsPwd = get(properties, "trustStorePassword");
437 if (Strings.isNullOrEmpty(tsPwd)) {
Brian O'Connorf7215b82018-05-10 19:12:44 -0700438 // Fallback to system properties
439 // TODO remove this eventually
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700440 tsPwd = System.getProperty("javax.net.ssl.trustStorePassword");
Brian O'Connorf7215b82018-05-10 19:12:44 -0700441 }
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700442 if (Strings.isNullOrEmpty(tsPwd) || MIN_KS_LENGTH > tsPwd.length()) {
443 mode = TlsMode.DISABLED;
Brian O'Connorf7215b82018-05-10 19:12:44 -0700444 }
445 }
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700446 this.tlsParams = new TlsParams(mode, ksLocation, tsLocation, ksPwd, tsPwd);
Brian O'Connorf7215b82018-05-10 19:12:44 -0700447 log.info("OpenFlow TLS Params: {}", tlsParams);
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700448 return !Objects.equals(this.tlsParams, oldParams); // restart if TLS params change
tom7ef8ff92014-09-17 13:08:06 -0700449 }
450
tom7ef8ff92014-09-17 13:08:06 -0700451 /**
452 * Initialize internal data structures.
453 */
Jonathan Hartbbd91d42015-02-27 11:18:04 -0800454 public void init() {
tom7ef8ff92014-09-17 13:08:06 -0700455 // These data structures are initialized here because other
456 // module's startUp() might be called before ours
Jonathan Hartbbd91d42015-02-27 11:18:04 -0800457 this.controllerNodeIPsCache = new HashMap<>();
tom7ef8ff92014-09-17 13:08:06 -0700458
tom7ef8ff92014-09-17 13:08:06 -0700459 this.systemStartTime = System.currentTimeMillis();
alshabib9b6c19c2015-09-26 12:19:27 -0700460
Yuta HIGUCHI6ee6b8c2017-05-09 14:44:30 -0700461 cg = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
462
Brian O'Connorf7215b82018-05-10 19:12:44 -0700463 if (tlsParams.isTlsEnabled()) {
Ray Milkey986a47a2018-01-25 11:38:51 -0800464 initSsl();
alshabib9b6c19c2015-09-26 12:19:27 -0700465 }
alshabib9b6c19c2015-09-26 12:19:27 -0700466 }
467
Ray Milkey986a47a2018-01-25 11:38:51 -0800468 private void initSsl() {
469 try {
470 TrustManagerFactory tmFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
471 KeyStore ts = KeyStore.getInstance("JKS");
Brian O'Connorf7215b82018-05-10 19:12:44 -0700472 ts.load(new FileInputStream(tlsParams.tsLocation), tlsParams.tsPwd());
Ray Milkey986a47a2018-01-25 11:38:51 -0800473 tmFactory.init(ts);
alshabib9b6c19c2015-09-26 12:19:27 -0700474
Ray Milkey986a47a2018-01-25 11:38:51 -0800475 KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700476 keyStore = KeyStore.getInstance("JKS");
Brian O'Connorf7215b82018-05-10 19:12:44 -0700477 keyStore.load(new FileInputStream(tlsParams.ksLocation), tlsParams.ksPwd());
478 kmf.init(keyStore, tlsParams.ksPwd());
alshabib9b6c19c2015-09-26 12:19:27 -0700479
Ray Milkey986a47a2018-01-25 11:38:51 -0800480 sslContext = SSLContext.getInstance("TLS");
481 sslContext.init(kmf.getKeyManagers(), tmFactory.getTrustManagers(), null);
482 } catch (NoSuchAlgorithmException | KeyStoreException | CertificateException |
483 IOException | KeyManagementException | UnrecoverableKeyException ex) {
484 log.error("SSL init failed: {}", ex.getMessage());
485 }
tom7ef8ff92014-09-17 13:08:06 -0700486 }
487
488 // **************
489 // Utility methods
490 // **************
491
492 public Map<String, Long> getMemory() {
Jonathan Hartbbd91d42015-02-27 11:18:04 -0800493 Map<String, Long> m = new HashMap<>();
tom7ef8ff92014-09-17 13:08:06 -0700494 Runtime runtime = Runtime.getRuntime();
495 m.put("total", runtime.totalMemory());
496 m.put("free", runtime.freeMemory());
497 return m;
498 }
499
500
Ray Milkeydc0ff192015-11-04 13:49:52 -0800501 public Long getSystemUptime() {
tom7ef8ff92014-09-17 13:08:06 -0700502 RuntimeMXBean rb = ManagementFactory.getRuntimeMXBean();
503 return rb.getUptime();
504 }
505
Ray Milkeydc0ff192015-11-04 13:49:52 -0800506 public long getSystemStartTime() {
507 return (this.systemStartTime);
508 }
509
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700510 public boolean isValidCertificate(Long dpid, Certificate peerCert) {
Brian O'Connorf7215b82018-05-10 19:12:44 -0700511 if (!tlsParams.isTlsEnabled()) {
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700512 return true;
513 }
514
Brian O'Connorf7215b82018-05-10 19:12:44 -0700515 if (netCfgService == null) {
516 // netcfg service not available; accept any cert if not in strict mode
517 return tlsParams.mode == TlsMode.ENABLED;
518 }
519
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700520 DeviceId deviceId = DeviceId.deviceId(Dpid.uri(new Dpid(dpid)));
521 OpenFlowDeviceConfig config =
522 netCfgService.getConfig(deviceId, OpenFlowDeviceConfig.class);
523 if (config == null) {
Brian O'Connorf7215b82018-05-10 19:12:44 -0700524 // Config not set for device, accept any cert if not in strict mode
525 return tlsParams.mode == TlsMode.ENABLED;
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700526 }
527
528 Optional<String> alias = config.keyAlias();
529 if (!alias.isPresent()) {
Brian O'Connorf7215b82018-05-10 19:12:44 -0700530 // Config for device does not specify a certificate chain, accept any cert if not in strict mode
531 return tlsParams.mode == TlsMode.ENABLED;
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700532 }
533
534 try {
535 Certificate configuredCert = keyStore.getCertificate(alias.get());
Brian O'Connorf7215b82018-05-10 19:12:44 -0700536 //TODO there's probably a better way to compare these
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700537 return Objects.deepEquals(peerCert, configuredCert);
538 } catch (KeyStoreException e) {
539 log.info("failed to load key", e);
540 }
541 return false;
542 }
543
tom7ef8ff92014-09-17 13:08:06 -0700544 /**
545 * Forward to the driver-manager to get an IOFSwitch instance.
Thomas Vachuska6f94ded2015-02-21 14:02:38 -0800546 *
Yuta HIGUCHI5c947272014-11-03 21:39:21 -0800547 * @param dpid data path id
548 * @param desc switch description
Thomas Vachuska6f94ded2015-02-21 14:02:38 -0800549 * @param ofv OpenFlow version
tom7ef8ff92014-09-17 13:08:06 -0700550 * @return switch instance
551 */
552 protected OpenFlowSwitchDriver getOFSwitchInstance(long dpid,
alshabibb452fd72015-04-22 20:46:20 -0700553 OFDescStatsReply desc,
554 OFVersion ofv) {
Jonathan Harta0d12492015-07-16 12:03:41 -0700555 Dpid dpidObj = new Dpid(dpid);
556
557 Driver driver;
558 try {
Sho SHIMIZUbc82ebb2015-08-25 10:15:21 -0700559 driver = driverService.getDriver(DeviceId.deviceId(Dpid.uri(dpidObj)));
Jonathan Harta0d12492015-07-16 12:03:41 -0700560 } catch (ItemNotFoundException e) {
561 driver = driverService.getDriver(desc.getMfrDesc(), desc.getHwDesc(), desc.getSwDesc());
562 }
alshabibb452fd72015-04-22 20:46:20 -0700563
Jonathan Harta33134e2016-07-27 17:33:35 -0700564 if (driver == null) {
Yuta HIGUCHI6ee6b8c2017-05-09 14:44:30 -0700565 log.error("No OpenFlow driver for {} : {}", dpidObj, desc);
Jonathan Harta33134e2016-07-27 17:33:35 -0700566 return null;
alshabibb452fd72015-04-22 20:46:20 -0700567 }
alshabibb452fd72015-04-22 20:46:20 -0700568
Yuta HIGUCHI6ee6b8c2017-05-09 14:44:30 -0700569 log.info("Driver '{}' assigned to device {}", driver.name(), dpidObj);
Jonathan Harta33134e2016-07-27 17:33:35 -0700570
571 if (!driver.hasBehaviour(OpenFlowSwitchDriver.class)) {
572 log.error("Driver {} does not support OpenFlowSwitchDriver behaviour", driver.name());
573 return null;
574 }
575
576 DefaultDriverHandler handler =
577 new DefaultDriverHandler(new DefaultDriverData(driver, deviceId(uri(dpidObj))));
578 OpenFlowSwitchDriver ofSwitchDriver =
579 driver.createBehaviour(handler, OpenFlowSwitchDriver.class);
580 ofSwitchDriver.init(dpidObj, desc, ofv);
581 ofSwitchDriver.setAgent(agent);
582 ofSwitchDriver.setRoleHandler(new RoleManager(ofSwitchDriver));
Jonathan Harta33134e2016-07-27 17:33:35 -0700583 return ofSwitchDriver;
tom7ef8ff92014-09-17 13:08:06 -0700584 }
585
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700586 public void start(OpenFlowAgent ag, DriverService driverService,
587 NetworkConfigRegistry netCfgService) {
tom7ef8ff92014-09-17 13:08:06 -0700588 log.info("Starting OpenFlow IO");
589 this.agent = ag;
alshabibb452fd72015-04-22 20:46:20 -0700590 this.driverService = driverService;
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700591 this.netCfgService = netCfgService;
Jonathan Hartbbd91d42015-02-27 11:18:04 -0800592 this.init();
Brian O'Connorf7215b82018-05-10 19:12:44 -0700593 this.addListeningPorts(this.openFlowPorts);
tom7ef8ff92014-09-17 13:08:06 -0700594 }
595
596
597 public void stop() {
598 log.info("Stopping OpenFlow IO");
Brian O'Connorf7215b82018-05-10 19:12:44 -0700599 if (cg != null) {
600 cg.close();
601 cg = null;
602 }
Yuta HIGUCHI6ee6b8c2017-05-09 14:44:30 -0700603
604 // Shut down all event loops to terminate all threads.
605 bossGroup.shutdownGracefully();
606 workerGroup.shutdownGracefully();
607
608 // Wait until all threads are terminated.
609 try {
610 bossGroup.terminationFuture().sync();
611 workerGroup.terminationFuture().sync();
612 } catch (InterruptedException e) {
613 log.warn("Interrupted while stopping", e);
614 Thread.currentThread().interrupt();
615 }
tom7ef8ff92014-09-17 13:08:06 -0700616 }
617
Brian O'Connorf7215b82018-05-10 19:12:44 -0700618 private void restart() {
619 // only restart if we are already running
620 if (cg != null) {
621 stop();
622 start(this.agent, this.driverService, this.netCfgService);
623 }
624 }
625
tom7ef8ff92014-09-17 13:08:06 -0700626}