blob: c71de081051e198e2fdb954810667b64cea39fd0 [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 */
Brian O'Connorabafb502014-12-02 22:26:20 -080016package org.onosproject.openflow.controller.impl;
tom7ef8ff92014-09-17 13:08:06 -070017
Brian O'Connorf7215b82018-05-10 19:12:44 -070018import com.google.common.base.MoreObjects;
Brian O'Connorff278502015-09-22 14:49:52 -070019import com.google.common.base.Strings;
20import com.google.common.collect.ImmutableList;
Brian O'Connorf7215b82018-05-10 19:12:44 -070021import com.google.common.collect.Sets;
Yuta HIGUCHI6ee6b8c2017-05-09 14:44:30 -070022import io.netty.bootstrap.ServerBootstrap;
Brian O'Connorf7215b82018-05-10 19:12:44 -070023import io.netty.channel.Channel;
Yuta HIGUCHI6ee6b8c2017-05-09 14:44:30 -070024import io.netty.channel.ChannelOption;
25import io.netty.channel.EventLoopGroup;
26import io.netty.channel.epoll.EpollEventLoopGroup;
27import io.netty.channel.epoll.EpollServerSocketChannel;
28import io.netty.channel.group.ChannelGroup;
29import io.netty.channel.group.DefaultChannelGroup;
30import io.netty.channel.nio.NioEventLoopGroup;
31import io.netty.channel.socket.nio.NioServerSocketChannel;
32import io.netty.util.concurrent.GlobalEventExecutor;
Jonathan Harta0d12492015-07-16 12:03:41 -070033import org.onlab.util.ItemNotFoundException;
34import org.onosproject.net.DeviceId;
Brian O'Connorf69e3e32018-05-10 02:25:09 -070035import org.onosproject.net.config.NetworkConfigRegistry;
alshabibb452fd72015-04-22 20:46:20 -070036import org.onosproject.net.driver.DefaultDriverData;
37import org.onosproject.net.driver.DefaultDriverHandler;
38import org.onosproject.net.driver.Driver;
39import org.onosproject.net.driver.DriverService;
Brian O'Connorf69e3e32018-05-10 02:25:09 -070040import org.onosproject.openflow.config.OpenFlowDeviceConfig;
Brian O'Connorabafb502014-12-02 22:26:20 -080041import org.onosproject.openflow.controller.Dpid;
42import org.onosproject.openflow.controller.driver.OpenFlowAgent;
43import org.onosproject.openflow.controller.driver.OpenFlowSwitchDriver;
tom7ef8ff92014-09-17 13:08:06 -070044import org.projectfloodlight.openflow.protocol.OFDescStatsReply;
tom7ef8ff92014-09-17 13:08:06 -070045import org.projectfloodlight.openflow.protocol.OFVersion;
46import org.slf4j.Logger;
47import org.slf4j.LoggerFactory;
48
alshabib9b6c19c2015-09-26 12:19:27 -070049import javax.net.ssl.KeyManagerFactory;
50import javax.net.ssl.SSLContext;
alshabib9b6c19c2015-09-26 12:19:27 -070051import javax.net.ssl.TrustManagerFactory;
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -070052import java.io.File;
alshabib9b6c19c2015-09-26 12:19:27 -070053import java.io.FileInputStream;
Ray Milkey986a47a2018-01-25 11:38:51 -080054import java.io.IOException;
Thomas Vachuska6f94ded2015-02-21 14:02:38 -080055import java.lang.management.ManagementFactory;
56import java.lang.management.RuntimeMXBean;
Brian O'Connorf7215b82018-05-10 19:12:44 -070057import java.net.InetSocketAddress;
58import java.net.SocketAddress;
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -070059import java.security.DigestInputStream;
Ray Milkey986a47a2018-01-25 11:38:51 -080060import java.security.KeyManagementException;
alshabib9b6c19c2015-09-26 12:19:27 -070061import java.security.KeyStore;
Ray Milkey986a47a2018-01-25 11:38:51 -080062import java.security.KeyStoreException;
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -070063import java.security.MessageDigest;
Ray Milkey986a47a2018-01-25 11:38:51 -080064import java.security.NoSuchAlgorithmException;
65import java.security.UnrecoverableKeyException;
Brian O'Connorf69e3e32018-05-10 02:25:09 -070066import java.security.cert.Certificate;
Ray Milkey986a47a2018-01-25 11:38:51 -080067import java.security.cert.CertificateException;
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -070068import java.util.Arrays;
Brian O'Connorf7215b82018-05-10 19:12:44 -070069import java.util.Collection;
70import java.util.Collections;
Brian O'Connorff278502015-09-22 14:49:52 -070071import java.util.Dictionary;
Brian O'Connorf7215b82018-05-10 19:12:44 -070072import java.util.EnumSet;
Thomas Vachuska6f94ded2015-02-21 14:02:38 -080073import java.util.HashMap;
Brian O'Connorf7215b82018-05-10 19:12:44 -070074import java.util.Iterator;
Brian O'Connorff278502015-09-22 14:49:52 -070075import java.util.List;
Thomas Vachuska6f94ded2015-02-21 14:02:38 -080076import java.util.Map;
Brian O'Connorf69e3e32018-05-10 02:25:09 -070077import java.util.Objects;
78import java.util.Optional;
Brian O'Connorf7215b82018-05-10 19:12:44 -070079import java.util.Set;
Brian O'Connorff278502015-09-22 14:49:52 -070080import java.util.stream.Collectors;
81import java.util.stream.Stream;
Thomas Vachuska6f94ded2015-02-21 14:02:38 -080082
Brian O'Connorff278502015-09-22 14:49:52 -070083import static org.onlab.util.Tools.get;
Thomas Vachuska6f94ded2015-02-21 14:02:38 -080084import static org.onlab.util.Tools.groupedThreads;
Thomas Vachuska80b0a802015-07-17 08:43:30 -070085import static org.onosproject.net.DeviceId.deviceId;
86import static org.onosproject.openflow.controller.Dpid.uri;
Thomas Vachuska6f94ded2015-02-21 14:02:38 -080087
tom7ef8ff92014-09-17 13:08:06 -070088
89/**
90 * The main controller class. Handles all setup and network listeners
tom7ef8ff92014-09-17 13:08:06 -070091 */
92public class Controller {
93
Ray Milkey9c9cde42018-01-12 14:22:06 -080094 private static final Logger log = LoggerFactory.getLogger(Controller.class);
alshabib9eab22f2014-10-20 17:17:31 -070095
alshabib9b6c19c2015-09-26 12:19:27 -070096 private static final short MIN_KS_LENGTH = 6;
tom7ef8ff92014-09-17 13:08:06 -070097
Anton Chigrin4af4f872019-01-14 17:29:56 +020098 //Default queues settings
99 private static final short DEFAULT_QUEUE_SIZE = 5000;
100 private static final short FIRST_QUEUE_SIZE = 1000;
101 private static final short DEFAULT_BULK_SIZE = 100;
102 private static final short DEFAULT_QUEUE_ID = 7;
103
tom7ef8ff92014-09-17 13:08:06 -0700104 protected HashMap<String, String> controllerNodeIPsCache;
105
106 private ChannelGroup cg;
107
108 // Configuration options
Brian O'Connorff278502015-09-22 14:49:52 -0700109 protected List<Integer> openFlowPorts = ImmutableList.of(6633, 6653);
Yuta HIGUCHI8552b172016-07-25 12:10:08 -0700110 protected int workerThreads = 0;
Anton Chigrin4af4f872019-01-14 17:29:56 +0200111 protected int[] cfgQueueSizes = {FIRST_QUEUE_SIZE, 0, 0, 0, 0, 0, 0, DEFAULT_QUEUE_SIZE};
112 protected int[] cfgBulkSizes = new int[8];
tom7ef8ff92014-09-17 13:08:06 -0700113
114 // Start time of the controller
115 protected long systemStartTime;
116
117 private OpenFlowAgent agent;
118
Yuta HIGUCHI6ee6b8c2017-05-09 14:44:30 -0700119 private EventLoopGroup bossGroup;
120 private EventLoopGroup workerGroup;
tom7ef8ff92014-09-17 13:08:06 -0700121
Brian O'Connorf7215b82018-05-10 19:12:44 -0700122 enum TlsMode {
123 DISABLED, // TLS is not used for OpenFlow connections
124 ENABLED, // Clients are required use TLS and present a client certificate
125 STRICT, // Clients must use TLS, and certificate must match the one specified in netcfg
126 }
127 private static final EnumSet<TlsMode> TLS_ENABLED = EnumSet.of(TlsMode.ENABLED, TlsMode.STRICT);
128
129 protected TlsParams tlsParams;
alshabib5162a9d2015-12-07 17:49:37 -0800130 protected SSLContext sslContext;
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700131 protected KeyStore keyStore;
alshabib9b6c19c2015-09-26 12:19:27 -0700132
tom7ef8ff92014-09-17 13:08:06 -0700133 // Perf. related configuration
134 protected static final int SEND_BUFFER_SIZE = 4 * 1024 * 1024;
Yuta HIGUCHI2341e602017-03-08 20:10:08 -0800135
alshabibb452fd72015-04-22 20:46:20 -0700136 private DriverService driverService;
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700137 private NetworkConfigRegistry netCfgService;
tom7ef8ff92014-09-17 13:08:06 -0700138
Anton Chigrin4af4f872019-01-14 17:29:56 +0200139 public Controller() {
140 Arrays.fill(cfgBulkSizes, DEFAULT_BULK_SIZE);
141 }
Brian O'Connorf7215b82018-05-10 19:12:44 -0700142
Anton Chigrin4af4f872019-01-14 17:29:56 +0200143 public int getQueueSize(int queueId) {
144 return cfgQueueSizes[queueId];
145 }
146
147 public int getBulkSize(int queueId) {
148 return cfgBulkSizes[queueId];
149 }
Brian O'Connorf7215b82018-05-10 19:12:44 -0700150
tom7ef8ff92014-09-17 13:08:06 -0700151 // **************
152 // Initialization
153 // **************
154
Brian O'Connorf7215b82018-05-10 19:12:44 -0700155 private void addListeningPorts(Collection<Integer> ports) {
156 if (cg == null) {
157 return;
158 }
159 final ServerBootstrap bootstrap = createServerBootStrap();
160 bootstrap.option(ChannelOption.SO_REUSEADDR, true);
161 bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
162 bootstrap.childOption(ChannelOption.TCP_NODELAY, true);
163 bootstrap.childOption(ChannelOption.SO_SNDBUF, Controller.SEND_BUFFER_SIZE);
Yuta HIGUCHI6ee6b8c2017-05-09 14:44:30 -0700164// bootstrap.childOption(ChannelOption.WRITE_BUFFER_WATER_MARK,
165// new WriteBufferWaterMark(8 * 1024, 32 * 1024));
tom7ef8ff92014-09-17 13:08:06 -0700166
Brian O'Connorf7215b82018-05-10 19:12:44 -0700167 bootstrap.childHandler(new OFChannelInitializer(this, null, sslContext));
tom7ef8ff92014-09-17 13:08:06 -0700168
Brian O'Connorf7215b82018-05-10 19:12:44 -0700169 Set<Integer> existingPorts = cg.stream()
170 .map(Channel::localAddress)
171 .filter(InetSocketAddress.class::isInstance)
172 .map(InetSocketAddress.class::cast)
173 .map(InetSocketAddress::getPort)
174 .collect(Collectors.toSet());
175 ports.removeAll(existingPorts);
tom7ef8ff92014-09-17 13:08:06 -0700176
Brian O'Connorf7215b82018-05-10 19:12:44 -0700177 ports.forEach(port -> {
178 // TODO revisit if this is best way to listen to multiple ports
179 cg.add(bootstrap.bind(port).syncUninterruptibly().channel());
180 log.info("Listening for OF switch connections on {}", port);
181 });
182 }
183
184 private void removeListeningPorts(Collection<Integer> ports) {
185 if (cg == null) {
186 return;
187 }
188 Iterator<Channel> itr = cg.iterator();
189 while (itr.hasNext()) {
190 Channel c = itr.next();
191 SocketAddress addr = c.localAddress();
192 if (addr instanceof InetSocketAddress) {
193 InetSocketAddress inetAddr = (InetSocketAddress) addr;
194 Integer port = inetAddr.getPort();
195 if (ports.contains(port)) {
196 log.info("No longer listening for OF switch connections on {}", port);
197 c.close();
198 itr.remove();
199 }
200 }
201
202 }
tom7ef8ff92014-09-17 13:08:06 -0700203 }
204
205 private ServerBootstrap createServerBootStrap() {
206
Yuta HIGUCHI6ee6b8c2017-05-09 14:44:30 -0700207 int bossThreads = Math.max(1, openFlowPorts.size());
208 try {
209 bossGroup = new EpollEventLoopGroup(bossThreads, groupedThreads("onos/of", "boss-%d", log));
210 workerGroup = new EpollEventLoopGroup(workerThreads, groupedThreads("onos/of", "worker-%d", log));
211 ServerBootstrap bs = new ServerBootstrap()
212 .group(bossGroup, workerGroup)
213 .channel(EpollServerSocketChannel.class);
214 log.info("Using Epoll transport");
215 return bs;
216 } catch (Throwable e) {
217 log.debug("Failed to initialize native (epoll) transport: {}", e.getMessage());
tom7ef8ff92014-09-17 13:08:06 -0700218 }
Yuta HIGUCHI6ee6b8c2017-05-09 14:44:30 -0700219
220// Requires 4.1.11 or later
221// try {
222// bossGroup = new KQueueEventLoopGroup(bossThreads, groupedThreads("onos/of", "boss-%d", log));
223// workerGroup = new KQueueEventLoopGroup(workerThreads, groupedThreads("onos/of", "worker-%d", log));
224// ServerBootstrap bs = new ServerBootstrap()
225// .group(bossGroup, workerGroup)
226// .channel(KQueueServerSocketChannel.class);
227// log.info("Using Kqueue transport");
228// return bs;
229// } catch (Throwable e) {
230// log.debug("Failed to initialize native (kqueue) transport. ", e.getMessage());
231// }
232
233 bossGroup = new NioEventLoopGroup(bossThreads, groupedThreads("onos/of", "boss-%d", log));
234 workerGroup = new NioEventLoopGroup(workerThreads, groupedThreads("onos/of", "worker-%d", log));
235 log.info("Using Nio transport");
236 return new ServerBootstrap()
237 .group(bossGroup, workerGroup)
238 .channel(NioServerSocketChannel.class);
tom7ef8ff92014-09-17 13:08:06 -0700239 }
240
Anton Chigrin4af4f872019-01-14 17:29:56 +0200241 public void setQueueParams(Dictionary<?, ?> properties, String sizeParamName, String bulkParamName, int queueId) {
242 String queueSize = get(properties, sizeParamName);
243 if (!Strings.isNullOrEmpty(queueSize)) {
244 int size = Integer.parseInt(queueSize);
245 if (size > 0) {
246 this.cfgQueueSizes[queueId] = size;
247 } else {
248 throw new IllegalArgumentException(
249 String.format("%s value must be either a positive integer value", sizeParamName));
250 }
251 }
252 String bulkSize = get(properties, bulkParamName);
253 if (!Strings.isNullOrEmpty(bulkSize)) {
254 int bulk = Integer.parseInt(bulkSize);
255 if (bulk > 0) {
256 this.cfgBulkSizes[queueId] = bulk;
257 } else {
258 throw new IllegalArgumentException(
259 String.format("%s value must be either a positive integer value", bulkParamName));
260 }
261 }
262 }
263
Brian O'Connorff278502015-09-22 14:49:52 -0700264 public void setConfigParams(Dictionary<?, ?> properties) {
Brian O'Connorf7215b82018-05-10 19:12:44 -0700265 boolean restartRequired = setOpenFlowPorts(properties);
266 restartRequired |= setWorkerThreads(properties);
267 restartRequired |= setTlsParameters(properties);
268 if (restartRequired) {
269 restart();
tom7ef8ff92014-09-17 13:08:06 -0700270 }
Brian O'Connorf7215b82018-05-10 19:12:44 -0700271 }
272
273 /**
274 * Gets the list of listening ports from property dict.
275 *
276 * @param properties dictionary
277 * @return true if restart is required
278 */
279 private boolean setWorkerThreads(Dictionary<?, ?> properties) {
280 List<Integer> oldPorts = this.openFlowPorts;
281 String ports = get(properties, "openflowPorts");
282 List<Integer> newPorts = Collections.emptyList();
283 if (!Strings.isNullOrEmpty(ports)) {
284 newPorts = Stream.of(ports.split(","))
285 .map(s -> Integer.parseInt(s))
286 .collect(Collectors.toList());
287 }
288
289 Set<Integer> portsToAdd = Sets.newHashSet(newPorts);
290 portsToAdd.removeAll(oldPorts);
291 addListeningPorts(portsToAdd);
292
293 Set<Integer> portsToRemove = Sets.newHashSet(oldPorts);
294 portsToRemove.removeAll(newPorts);
295 removeListeningPorts(portsToRemove);
296
297 this.openFlowPorts = newPorts;
Brian O'Connorff278502015-09-22 14:49:52 -0700298 log.debug("OpenFlow ports set to {}", this.openFlowPorts);
Brian O'Connorf7215b82018-05-10 19:12:44 -0700299 return false; // restart is never required
300 }
301
302 /**
303 * Gets the number of worker threads from property dict.
304 *
305 * @param properties dictionary
306 * @return true if restart is required
307 */
308 private boolean setOpenFlowPorts(Dictionary<?, ?> properties) {
309 Integer oldValue = this.workerThreads;
Charles Chan45624b82015-08-24 00:29:20 +0800310
Brian O'Connorff278502015-09-22 14:49:52 -0700311 String threads = get(properties, "workerThreads");
312 if (!Strings.isNullOrEmpty(threads)) {
313 this.workerThreads = Integer.parseInt(threads);
314 }
tom7ef8ff92014-09-17 13:08:06 -0700315 log.debug("Number of worker threads set to {}", this.workerThreads);
Anton Chigrin4af4f872019-01-14 17:29:56 +0200316
317 setQueueParams(properties, "defaultQueueSize", "defaultBulkSize", DEFAULT_QUEUE_ID);
318 setQueueParams(properties, "queueSizeN0", "bulkSizeN0", 0);
319 setQueueParams(properties, "queueSizeN1", "bulkSizeN1", 1);
320 setQueueParams(properties, "queueSizeN2", "bulkSizeN2", 2);
321 setQueueParams(properties, "queueSizeN3", "bulkSizeN3", 3);
322 setQueueParams(properties, "queueSizeN4", "bulkSizeN4", 4);
323 setQueueParams(properties, "queueSizeN5", "bulkSizeN5", 5);
324 setQueueParams(properties, "queueSizeN6", "bulkSizeN6", 6);
325
Brian O'Connorf7215b82018-05-10 19:12:44 -0700326 return oldValue != this.workerThreads; // restart if number of threads has changed
327 }
328
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700329 static class TlsParams {
330 final TlsMode mode;
331 final String ksLocation;
332 final String tsLocation;
333 final String ksPwd;
334 final String tsPwd;
335 final byte[] ksSignature;
336 final byte[] tsSignature;
337
338 TlsParams(TlsMode mode, String ksLocation, String tsLocation,
339 String ksPwd, String tsPwd) {
340 this.mode = mode;
341 this.ksLocation = ksLocation;
342 this.tsLocation = tsLocation;
343 this.ksPwd = ksPwd;
344 this.tsPwd = tsPwd;
345 this.ksSignature = getSha1Checksum(ksLocation);
346 this.tsSignature = getSha1Checksum(tsLocation);
347 }
Brian O'Connorf7215b82018-05-10 19:12:44 -0700348
349 public char[] ksPwd() {
350 return ksPwd.toCharArray();
351 }
352
353 public char[] tsPwd() {
354 return tsPwd.toCharArray();
355 }
356
357 public boolean isTlsEnabled() {
358 return TLS_ENABLED.contains(mode);
359 }
360
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700361 public byte[] getSha1Checksum(String filepath) {
362 if (filepath == null) {
363 return new byte[0];
364 }
365 try {
366 MessageDigest digest = MessageDigest.getInstance("SHA1");
367 File f = new File(filepath);
368 FileInputStream is = new FileInputStream(f);
369 DigestInputStream dis = new DigestInputStream(is, digest);
370 byte[] buffer = new byte[1024];
371 while (dis.read(buffer) > 0) {
372 // nothing to do :)
373 }
柯志勇1006869555f80b62018-10-11 17:28:31 +0800374 is.close();
375 return dis.getMessageDigest().digest();
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700376 } catch (NoSuchAlgorithmException ignored) {
377 } catch (IOException e) {
378 log.info("Error reading file file: {}", filepath);
379 }
380 return new byte[0];
381 }
382
Brian O'Connorf7215b82018-05-10 19:12:44 -0700383 @Override
384 public int hashCode() {
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700385 if (mode == TlsMode.DISABLED) {
386 return Objects.hash(mode);
387 }
388 return Objects.hash(mode, ksLocation, tsLocation,
Yuta HIGUCHIcf03a0f2018-05-15 19:25:28 -0700389 ksPwd, tsPwd,
390 Arrays.hashCode(ksSignature),
391 Arrays.hashCode(tsSignature));
Brian O'Connorf7215b82018-05-10 19:12:44 -0700392 }
393
394 @Override
395 public boolean equals(Object obj) {
396 if (this == obj) {
397 return true;
398 }
399 if (obj instanceof TlsParams) {
400 final TlsParams that = (TlsParams) obj;
401 if (this.getClass() != that.getClass()) {
402 return false;
403 } else if (this.mode == that.mode && this.mode == TlsMode.DISABLED) {
404 // All disabled objects should be equal regardless of other params
405 return true;
406 }
407 return this.mode == that.mode &&
408 Objects.equals(this.ksLocation, that.ksLocation) &&
409 Objects.equals(this.tsLocation, that.tsLocation) &&
410 Objects.equals(this.ksPwd, that.ksPwd) &&
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700411 Objects.equals(this.tsPwd, that.tsPwd) &&
412 Arrays.equals(this.ksSignature, that.ksSignature) &&
413 Arrays.equals(this.tsSignature, that.tsSignature);
Brian O'Connorf7215b82018-05-10 19:12:44 -0700414 }
415 return false;
416 }
417
418 @Override
419 public String toString() {
420 return MoreObjects.toStringHelper(this)
421 .add("tlsMode", mode.toString().toLowerCase())
422 .add("ksLocation", ksLocation)
423 .add("tsLocation", tsLocation)
424 .toString();
425 }
426 }
427
428 /**
429 * Gets the TLS parameters from the properties dict, but fallback to the old approach of
430 * system properties if the parameters are missing from the dict.
431 *
432 * @param properties dictionary
433 * @return true if restart is required
434 */
435 private boolean setTlsParameters(Dictionary<?, ?> properties) {
436 TlsParams oldParams = this.tlsParams;
437
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700438 TlsMode mode = null;
Brian O'Connorf7215b82018-05-10 19:12:44 -0700439 String tlsString = get(properties, "tlsMode");
440 if (!Strings.isNullOrEmpty(tlsString)) {
441 try {
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700442 mode = TlsMode.valueOf(tlsString.toUpperCase());
Brian O'Connorf7215b82018-05-10 19:12:44 -0700443 } catch (IllegalArgumentException e) {
444 log.info("Invalid TLS mode {}. TLS is disabled.", tlsString);
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700445 mode = TlsMode.DISABLED;
Brian O'Connorf7215b82018-05-10 19:12:44 -0700446 }
447 } else {
448 // Fallback to system properties
449 // TODO this method of configuring TLS is deprecated and should be removed eventually
450 tlsString = System.getProperty("enableOFTLS");
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700451 mode = !Strings.isNullOrEmpty(tlsString) && Boolean.parseBoolean(tlsString) ?
Brian O'Connorf7215b82018-05-10 19:12:44 -0700452 TlsMode.ENABLED : TlsMode.DISABLED;
453 }
454
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700455 String ksLocation = null, tsLocation = null, ksPwd = null, tsPwd = null;
456 if (TLS_ENABLED.contains(mode)) {
457 ksLocation = get(properties, "keyStore");
458 if (Strings.isNullOrEmpty(ksLocation)) {
Brian O'Connorf7215b82018-05-10 19:12:44 -0700459 // Fallback to system properties
460 // TODO remove this eventually
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700461 ksLocation = System.getProperty("javax.net.ssl.keyStore");
Brian O'Connorf7215b82018-05-10 19:12:44 -0700462 }
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700463 if (Strings.isNullOrEmpty(ksLocation)) {
464 mode = TlsMode.DISABLED;
Brian O'Connorf7215b82018-05-10 19:12:44 -0700465 }
466
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700467 tsLocation = get(properties, "trustStore");
468 if (Strings.isNullOrEmpty(tsLocation)) {
Brian O'Connorf7215b82018-05-10 19:12:44 -0700469 // Fallback to system properties
470 // TODO remove this eventually
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700471 tsLocation = System.getProperty("javax.net.ssl.trustStore");
Brian O'Connorf7215b82018-05-10 19:12:44 -0700472 }
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700473 if (Strings.isNullOrEmpty(tsLocation)) {
474 mode = TlsMode.DISABLED;
Brian O'Connorf7215b82018-05-10 19:12:44 -0700475 }
476
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700477 ksPwd = get(properties, "keyStorePassword");
478 if (Strings.isNullOrEmpty(ksPwd)) {
Brian O'Connorf7215b82018-05-10 19:12:44 -0700479 // Fallback to system properties
480 // TODO remove this eventually
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700481 ksPwd = System.getProperty("javax.net.ssl.keyStorePassword");
Brian O'Connorf7215b82018-05-10 19:12:44 -0700482 }
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700483 if (Strings.isNullOrEmpty(ksPwd) || MIN_KS_LENGTH > ksPwd.length()) {
484 mode = TlsMode.DISABLED;
Brian O'Connorf7215b82018-05-10 19:12:44 -0700485 }
486
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700487 tsPwd = get(properties, "trustStorePassword");
488 if (Strings.isNullOrEmpty(tsPwd)) {
Brian O'Connorf7215b82018-05-10 19:12:44 -0700489 // Fallback to system properties
490 // TODO remove this eventually
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700491 tsPwd = System.getProperty("javax.net.ssl.trustStorePassword");
Brian O'Connorf7215b82018-05-10 19:12:44 -0700492 }
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700493 if (Strings.isNullOrEmpty(tsPwd) || MIN_KS_LENGTH > tsPwd.length()) {
494 mode = TlsMode.DISABLED;
Brian O'Connorf7215b82018-05-10 19:12:44 -0700495 }
496 }
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700497 this.tlsParams = new TlsParams(mode, ksLocation, tsLocation, ksPwd, tsPwd);
Brian O'Connorf7215b82018-05-10 19:12:44 -0700498 log.info("OpenFlow TLS Params: {}", tlsParams);
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700499 return !Objects.equals(this.tlsParams, oldParams); // restart if TLS params change
tom7ef8ff92014-09-17 13:08:06 -0700500 }
501
tom7ef8ff92014-09-17 13:08:06 -0700502 /**
503 * Initialize internal data structures.
504 */
Jonathan Hartbbd91d42015-02-27 11:18:04 -0800505 public void init() {
tom7ef8ff92014-09-17 13:08:06 -0700506 // These data structures are initialized here because other
507 // module's startUp() might be called before ours
Jonathan Hartbbd91d42015-02-27 11:18:04 -0800508 this.controllerNodeIPsCache = new HashMap<>();
tom7ef8ff92014-09-17 13:08:06 -0700509
tom7ef8ff92014-09-17 13:08:06 -0700510 this.systemStartTime = System.currentTimeMillis();
alshabib9b6c19c2015-09-26 12:19:27 -0700511
Yuta HIGUCHI6ee6b8c2017-05-09 14:44:30 -0700512 cg = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
513
Brian O'Connorf7215b82018-05-10 19:12:44 -0700514 if (tlsParams.isTlsEnabled()) {
Ray Milkey986a47a2018-01-25 11:38:51 -0800515 initSsl();
alshabib9b6c19c2015-09-26 12:19:27 -0700516 }
alshabib9b6c19c2015-09-26 12:19:27 -0700517 }
518
Ray Milkey986a47a2018-01-25 11:38:51 -0800519 private void initSsl() {
520 try {
521 TrustManagerFactory tmFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
522 KeyStore ts = KeyStore.getInstance("JKS");
Brian O'Connorf7215b82018-05-10 19:12:44 -0700523 ts.load(new FileInputStream(tlsParams.tsLocation), tlsParams.tsPwd());
Ray Milkey986a47a2018-01-25 11:38:51 -0800524 tmFactory.init(ts);
alshabib9b6c19c2015-09-26 12:19:27 -0700525
Ray Milkey986a47a2018-01-25 11:38:51 -0800526 KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700527 keyStore = KeyStore.getInstance("JKS");
Brian O'Connorf7215b82018-05-10 19:12:44 -0700528 keyStore.load(new FileInputStream(tlsParams.ksLocation), tlsParams.ksPwd());
529 kmf.init(keyStore, tlsParams.ksPwd());
alshabib9b6c19c2015-09-26 12:19:27 -0700530
Ray Milkey986a47a2018-01-25 11:38:51 -0800531 sslContext = SSLContext.getInstance("TLS");
532 sslContext.init(kmf.getKeyManagers(), tmFactory.getTrustManagers(), null);
533 } catch (NoSuchAlgorithmException | KeyStoreException | CertificateException |
534 IOException | KeyManagementException | UnrecoverableKeyException ex) {
535 log.error("SSL init failed: {}", ex.getMessage());
536 }
tom7ef8ff92014-09-17 13:08:06 -0700537 }
538
539 // **************
540 // Utility methods
541 // **************
542
543 public Map<String, Long> getMemory() {
Jonathan Hartbbd91d42015-02-27 11:18:04 -0800544 Map<String, Long> m = new HashMap<>();
tom7ef8ff92014-09-17 13:08:06 -0700545 Runtime runtime = Runtime.getRuntime();
546 m.put("total", runtime.totalMemory());
547 m.put("free", runtime.freeMemory());
548 return m;
549 }
550
551
Ray Milkeydc0ff192015-11-04 13:49:52 -0800552 public Long getSystemUptime() {
tom7ef8ff92014-09-17 13:08:06 -0700553 RuntimeMXBean rb = ManagementFactory.getRuntimeMXBean();
554 return rb.getUptime();
555 }
556
Ray Milkeydc0ff192015-11-04 13:49:52 -0800557 public long getSystemStartTime() {
558 return (this.systemStartTime);
559 }
560
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700561 public boolean isValidCertificate(Long dpid, Certificate peerCert) {
Brian O'Connorf7215b82018-05-10 19:12:44 -0700562 if (!tlsParams.isTlsEnabled()) {
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700563 return true;
564 }
565
Brian O'Connorf7215b82018-05-10 19:12:44 -0700566 if (netCfgService == null) {
567 // netcfg service not available; accept any cert if not in strict mode
568 return tlsParams.mode == TlsMode.ENABLED;
569 }
570
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700571 DeviceId deviceId = DeviceId.deviceId(Dpid.uri(new Dpid(dpid)));
572 OpenFlowDeviceConfig config =
573 netCfgService.getConfig(deviceId, OpenFlowDeviceConfig.class);
574 if (config == null) {
Brian O'Connorf7215b82018-05-10 19:12:44 -0700575 // Config not set for device, accept any cert if not in strict mode
576 return tlsParams.mode == TlsMode.ENABLED;
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700577 }
578
579 Optional<String> alias = config.keyAlias();
580 if (!alias.isPresent()) {
Brian O'Connorf7215b82018-05-10 19:12:44 -0700581 // Config for device does not specify a certificate chain, accept any cert if not in strict mode
582 return tlsParams.mode == TlsMode.ENABLED;
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700583 }
584
585 try {
586 Certificate configuredCert = keyStore.getCertificate(alias.get());
Brian O'Connorf7215b82018-05-10 19:12:44 -0700587 //TODO there's probably a better way to compare these
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700588 return Objects.deepEquals(peerCert, configuredCert);
589 } catch (KeyStoreException e) {
590 log.info("failed to load key", e);
591 }
592 return false;
593 }
594
tom7ef8ff92014-09-17 13:08:06 -0700595 /**
596 * Forward to the driver-manager to get an IOFSwitch instance.
Thomas Vachuska6f94ded2015-02-21 14:02:38 -0800597 *
Yuta HIGUCHI5c947272014-11-03 21:39:21 -0800598 * @param dpid data path id
599 * @param desc switch description
Thomas Vachuska6f94ded2015-02-21 14:02:38 -0800600 * @param ofv OpenFlow version
tom7ef8ff92014-09-17 13:08:06 -0700601 * @return switch instance
602 */
603 protected OpenFlowSwitchDriver getOFSwitchInstance(long dpid,
alshabibb452fd72015-04-22 20:46:20 -0700604 OFDescStatsReply desc,
605 OFVersion ofv) {
Jonathan Harta0d12492015-07-16 12:03:41 -0700606 Dpid dpidObj = new Dpid(dpid);
607
608 Driver driver;
609 try {
Sho SHIMIZUbc82ebb2015-08-25 10:15:21 -0700610 driver = driverService.getDriver(DeviceId.deviceId(Dpid.uri(dpidObj)));
Jonathan Harta0d12492015-07-16 12:03:41 -0700611 } catch (ItemNotFoundException e) {
612 driver = driverService.getDriver(desc.getMfrDesc(), desc.getHwDesc(), desc.getSwDesc());
613 }
alshabibb452fd72015-04-22 20:46:20 -0700614
Jonathan Harta33134e2016-07-27 17:33:35 -0700615 if (driver == null) {
Yuta HIGUCHI6ee6b8c2017-05-09 14:44:30 -0700616 log.error("No OpenFlow driver for {} : {}", dpidObj, desc);
Jonathan Harta33134e2016-07-27 17:33:35 -0700617 return null;
alshabibb452fd72015-04-22 20:46:20 -0700618 }
alshabibb452fd72015-04-22 20:46:20 -0700619
Yuta HIGUCHI6ee6b8c2017-05-09 14:44:30 -0700620 log.info("Driver '{}' assigned to device {}", driver.name(), dpidObj);
Jonathan Harta33134e2016-07-27 17:33:35 -0700621
622 if (!driver.hasBehaviour(OpenFlowSwitchDriver.class)) {
623 log.error("Driver {} does not support OpenFlowSwitchDriver behaviour", driver.name());
624 return null;
625 }
626
627 DefaultDriverHandler handler =
628 new DefaultDriverHandler(new DefaultDriverData(driver, deviceId(uri(dpidObj))));
629 OpenFlowSwitchDriver ofSwitchDriver =
630 driver.createBehaviour(handler, OpenFlowSwitchDriver.class);
631 ofSwitchDriver.init(dpidObj, desc, ofv);
632 ofSwitchDriver.setAgent(agent);
633 ofSwitchDriver.setRoleHandler(new RoleManager(ofSwitchDriver));
Jonathan Harta33134e2016-07-27 17:33:35 -0700634 return ofSwitchDriver;
tom7ef8ff92014-09-17 13:08:06 -0700635 }
636
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700637 public void start(OpenFlowAgent ag, DriverService driverService,
638 NetworkConfigRegistry netCfgService) {
tom7ef8ff92014-09-17 13:08:06 -0700639 log.info("Starting OpenFlow IO");
640 this.agent = ag;
alshabibb452fd72015-04-22 20:46:20 -0700641 this.driverService = driverService;
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700642 this.netCfgService = netCfgService;
Jonathan Hartbbd91d42015-02-27 11:18:04 -0800643 this.init();
Brian O'Connorf7215b82018-05-10 19:12:44 -0700644 this.addListeningPorts(this.openFlowPorts);
tom7ef8ff92014-09-17 13:08:06 -0700645 }
646
647
648 public void stop() {
649 log.info("Stopping OpenFlow IO");
Brian O'Connorf7215b82018-05-10 19:12:44 -0700650 if (cg != null) {
651 cg.close();
652 cg = null;
653 }
Yuta HIGUCHI6ee6b8c2017-05-09 14:44:30 -0700654
655 // Shut down all event loops to terminate all threads.
656 bossGroup.shutdownGracefully();
657 workerGroup.shutdownGracefully();
658
659 // Wait until all threads are terminated.
660 try {
661 bossGroup.terminationFuture().sync();
662 workerGroup.terminationFuture().sync();
663 } catch (InterruptedException e) {
664 log.warn("Interrupted while stopping", e);
665 Thread.currentThread().interrupt();
666 }
tom7ef8ff92014-09-17 13:08:06 -0700667 }
668
Brian O'Connorf7215b82018-05-10 19:12:44 -0700669 private void restart() {
670 // only restart if we are already running
671 if (cg != null) {
672 stop();
673 start(this.agent, this.driverService, this.netCfgService);
674 }
675 }
676
tom7ef8ff92014-09-17 13:08:06 -0700677}