blob: 6cf1ddce4eb1c3e37b3252778d88243f3497ac7c [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 }
柯志勇1006869555f80b62018-10-11 17:28:31 +0800324 is.close();
325 return dis.getMessageDigest().digest();
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700326 } catch (NoSuchAlgorithmException ignored) {
327 } catch (IOException e) {
328 log.info("Error reading file file: {}", filepath);
329 }
330 return new byte[0];
331 }
332
Brian O'Connorf7215b82018-05-10 19:12:44 -0700333 @Override
334 public int hashCode() {
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700335 if (mode == TlsMode.DISABLED) {
336 return Objects.hash(mode);
337 }
338 return Objects.hash(mode, ksLocation, tsLocation,
Yuta HIGUCHIcf03a0f2018-05-15 19:25:28 -0700339 ksPwd, tsPwd,
340 Arrays.hashCode(ksSignature),
341 Arrays.hashCode(tsSignature));
Brian O'Connorf7215b82018-05-10 19:12:44 -0700342 }
343
344 @Override
345 public boolean equals(Object obj) {
346 if (this == obj) {
347 return true;
348 }
349 if (obj instanceof TlsParams) {
350 final TlsParams that = (TlsParams) obj;
351 if (this.getClass() != that.getClass()) {
352 return false;
353 } else if (this.mode == that.mode && this.mode == TlsMode.DISABLED) {
354 // All disabled objects should be equal regardless of other params
355 return true;
356 }
357 return this.mode == that.mode &&
358 Objects.equals(this.ksLocation, that.ksLocation) &&
359 Objects.equals(this.tsLocation, that.tsLocation) &&
360 Objects.equals(this.ksPwd, that.ksPwd) &&
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700361 Objects.equals(this.tsPwd, that.tsPwd) &&
362 Arrays.equals(this.ksSignature, that.ksSignature) &&
363 Arrays.equals(this.tsSignature, that.tsSignature);
Brian O'Connorf7215b82018-05-10 19:12:44 -0700364 }
365 return false;
366 }
367
368 @Override
369 public String toString() {
370 return MoreObjects.toStringHelper(this)
371 .add("tlsMode", mode.toString().toLowerCase())
372 .add("ksLocation", ksLocation)
373 .add("tsLocation", tsLocation)
374 .toString();
375 }
376 }
377
378 /**
379 * Gets the TLS parameters from the properties dict, but fallback to the old approach of
380 * system properties if the parameters are missing from the dict.
381 *
382 * @param properties dictionary
383 * @return true if restart is required
384 */
385 private boolean setTlsParameters(Dictionary<?, ?> properties) {
386 TlsParams oldParams = this.tlsParams;
387
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700388 TlsMode mode = null;
Brian O'Connorf7215b82018-05-10 19:12:44 -0700389 String tlsString = get(properties, "tlsMode");
390 if (!Strings.isNullOrEmpty(tlsString)) {
391 try {
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700392 mode = TlsMode.valueOf(tlsString.toUpperCase());
Brian O'Connorf7215b82018-05-10 19:12:44 -0700393 } catch (IllegalArgumentException e) {
394 log.info("Invalid TLS mode {}. TLS is disabled.", tlsString);
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700395 mode = TlsMode.DISABLED;
Brian O'Connorf7215b82018-05-10 19:12:44 -0700396 }
397 } else {
398 // Fallback to system properties
399 // TODO this method of configuring TLS is deprecated and should be removed eventually
400 tlsString = System.getProperty("enableOFTLS");
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700401 mode = !Strings.isNullOrEmpty(tlsString) && Boolean.parseBoolean(tlsString) ?
Brian O'Connorf7215b82018-05-10 19:12:44 -0700402 TlsMode.ENABLED : TlsMode.DISABLED;
403 }
404
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700405 String ksLocation = null, tsLocation = null, ksPwd = null, tsPwd = null;
406 if (TLS_ENABLED.contains(mode)) {
407 ksLocation = get(properties, "keyStore");
408 if (Strings.isNullOrEmpty(ksLocation)) {
Brian O'Connorf7215b82018-05-10 19:12:44 -0700409 // Fallback to system properties
410 // TODO remove this eventually
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700411 ksLocation = System.getProperty("javax.net.ssl.keyStore");
Brian O'Connorf7215b82018-05-10 19:12:44 -0700412 }
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700413 if (Strings.isNullOrEmpty(ksLocation)) {
414 mode = TlsMode.DISABLED;
Brian O'Connorf7215b82018-05-10 19:12:44 -0700415 }
416
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700417 tsLocation = get(properties, "trustStore");
418 if (Strings.isNullOrEmpty(tsLocation)) {
Brian O'Connorf7215b82018-05-10 19:12:44 -0700419 // Fallback to system properties
420 // TODO remove this eventually
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700421 tsLocation = System.getProperty("javax.net.ssl.trustStore");
Brian O'Connorf7215b82018-05-10 19:12:44 -0700422 }
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700423 if (Strings.isNullOrEmpty(tsLocation)) {
424 mode = TlsMode.DISABLED;
Brian O'Connorf7215b82018-05-10 19:12:44 -0700425 }
426
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700427 ksPwd = get(properties, "keyStorePassword");
428 if (Strings.isNullOrEmpty(ksPwd)) {
Brian O'Connorf7215b82018-05-10 19:12:44 -0700429 // Fallback to system properties
430 // TODO remove this eventually
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700431 ksPwd = System.getProperty("javax.net.ssl.keyStorePassword");
Brian O'Connorf7215b82018-05-10 19:12:44 -0700432 }
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700433 if (Strings.isNullOrEmpty(ksPwd) || MIN_KS_LENGTH > ksPwd.length()) {
434 mode = TlsMode.DISABLED;
Brian O'Connorf7215b82018-05-10 19:12:44 -0700435 }
436
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700437 tsPwd = get(properties, "trustStorePassword");
438 if (Strings.isNullOrEmpty(tsPwd)) {
Brian O'Connorf7215b82018-05-10 19:12:44 -0700439 // Fallback to system properties
440 // TODO remove this eventually
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700441 tsPwd = System.getProperty("javax.net.ssl.trustStorePassword");
Brian O'Connorf7215b82018-05-10 19:12:44 -0700442 }
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700443 if (Strings.isNullOrEmpty(tsPwd) || MIN_KS_LENGTH > tsPwd.length()) {
444 mode = TlsMode.DISABLED;
Brian O'Connorf7215b82018-05-10 19:12:44 -0700445 }
446 }
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700447 this.tlsParams = new TlsParams(mode, ksLocation, tsLocation, ksPwd, tsPwd);
Brian O'Connorf7215b82018-05-10 19:12:44 -0700448 log.info("OpenFlow TLS Params: {}", tlsParams);
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700449 return !Objects.equals(this.tlsParams, oldParams); // restart if TLS params change
tom7ef8ff92014-09-17 13:08:06 -0700450 }
451
tom7ef8ff92014-09-17 13:08:06 -0700452 /**
453 * Initialize internal data structures.
454 */
Jonathan Hartbbd91d42015-02-27 11:18:04 -0800455 public void init() {
tom7ef8ff92014-09-17 13:08:06 -0700456 // These data structures are initialized here because other
457 // module's startUp() might be called before ours
Jonathan Hartbbd91d42015-02-27 11:18:04 -0800458 this.controllerNodeIPsCache = new HashMap<>();
tom7ef8ff92014-09-17 13:08:06 -0700459
tom7ef8ff92014-09-17 13:08:06 -0700460 this.systemStartTime = System.currentTimeMillis();
alshabib9b6c19c2015-09-26 12:19:27 -0700461
Yuta HIGUCHI6ee6b8c2017-05-09 14:44:30 -0700462 cg = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
463
Brian O'Connorf7215b82018-05-10 19:12:44 -0700464 if (tlsParams.isTlsEnabled()) {
Ray Milkey986a47a2018-01-25 11:38:51 -0800465 initSsl();
alshabib9b6c19c2015-09-26 12:19:27 -0700466 }
alshabib9b6c19c2015-09-26 12:19:27 -0700467 }
468
Ray Milkey986a47a2018-01-25 11:38:51 -0800469 private void initSsl() {
470 try {
471 TrustManagerFactory tmFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
472 KeyStore ts = KeyStore.getInstance("JKS");
Brian O'Connorf7215b82018-05-10 19:12:44 -0700473 ts.load(new FileInputStream(tlsParams.tsLocation), tlsParams.tsPwd());
Ray Milkey986a47a2018-01-25 11:38:51 -0800474 tmFactory.init(ts);
alshabib9b6c19c2015-09-26 12:19:27 -0700475
Ray Milkey986a47a2018-01-25 11:38:51 -0800476 KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700477 keyStore = KeyStore.getInstance("JKS");
Brian O'Connorf7215b82018-05-10 19:12:44 -0700478 keyStore.load(new FileInputStream(tlsParams.ksLocation), tlsParams.ksPwd());
479 kmf.init(keyStore, tlsParams.ksPwd());
alshabib9b6c19c2015-09-26 12:19:27 -0700480
Ray Milkey986a47a2018-01-25 11:38:51 -0800481 sslContext = SSLContext.getInstance("TLS");
482 sslContext.init(kmf.getKeyManagers(), tmFactory.getTrustManagers(), null);
483 } catch (NoSuchAlgorithmException | KeyStoreException | CertificateException |
484 IOException | KeyManagementException | UnrecoverableKeyException ex) {
485 log.error("SSL init failed: {}", ex.getMessage());
486 }
tom7ef8ff92014-09-17 13:08:06 -0700487 }
488
489 // **************
490 // Utility methods
491 // **************
492
493 public Map<String, Long> getMemory() {
Jonathan Hartbbd91d42015-02-27 11:18:04 -0800494 Map<String, Long> m = new HashMap<>();
tom7ef8ff92014-09-17 13:08:06 -0700495 Runtime runtime = Runtime.getRuntime();
496 m.put("total", runtime.totalMemory());
497 m.put("free", runtime.freeMemory());
498 return m;
499 }
500
501
Ray Milkeydc0ff192015-11-04 13:49:52 -0800502 public Long getSystemUptime() {
tom7ef8ff92014-09-17 13:08:06 -0700503 RuntimeMXBean rb = ManagementFactory.getRuntimeMXBean();
504 return rb.getUptime();
505 }
506
Ray Milkeydc0ff192015-11-04 13:49:52 -0800507 public long getSystemStartTime() {
508 return (this.systemStartTime);
509 }
510
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700511 public boolean isValidCertificate(Long dpid, Certificate peerCert) {
Brian O'Connorf7215b82018-05-10 19:12:44 -0700512 if (!tlsParams.isTlsEnabled()) {
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700513 return true;
514 }
515
Brian O'Connorf7215b82018-05-10 19:12:44 -0700516 if (netCfgService == null) {
517 // netcfg service not available; accept any cert if not in strict mode
518 return tlsParams.mode == TlsMode.ENABLED;
519 }
520
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700521 DeviceId deviceId = DeviceId.deviceId(Dpid.uri(new Dpid(dpid)));
522 OpenFlowDeviceConfig config =
523 netCfgService.getConfig(deviceId, OpenFlowDeviceConfig.class);
524 if (config == null) {
Brian O'Connorf7215b82018-05-10 19:12:44 -0700525 // Config not set for device, accept any cert if not in strict mode
526 return tlsParams.mode == TlsMode.ENABLED;
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700527 }
528
529 Optional<String> alias = config.keyAlias();
530 if (!alias.isPresent()) {
Brian O'Connorf7215b82018-05-10 19:12:44 -0700531 // Config for device does not specify a certificate chain, accept any cert if not in strict mode
532 return tlsParams.mode == TlsMode.ENABLED;
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700533 }
534
535 try {
536 Certificate configuredCert = keyStore.getCertificate(alias.get());
Brian O'Connorf7215b82018-05-10 19:12:44 -0700537 //TODO there's probably a better way to compare these
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700538 return Objects.deepEquals(peerCert, configuredCert);
539 } catch (KeyStoreException e) {
540 log.info("failed to load key", e);
541 }
542 return false;
543 }
544
tom7ef8ff92014-09-17 13:08:06 -0700545 /**
546 * Forward to the driver-manager to get an IOFSwitch instance.
Thomas Vachuska6f94ded2015-02-21 14:02:38 -0800547 *
Yuta HIGUCHI5c947272014-11-03 21:39:21 -0800548 * @param dpid data path id
549 * @param desc switch description
Thomas Vachuska6f94ded2015-02-21 14:02:38 -0800550 * @param ofv OpenFlow version
tom7ef8ff92014-09-17 13:08:06 -0700551 * @return switch instance
552 */
553 protected OpenFlowSwitchDriver getOFSwitchInstance(long dpid,
alshabibb452fd72015-04-22 20:46:20 -0700554 OFDescStatsReply desc,
555 OFVersion ofv) {
Jonathan Harta0d12492015-07-16 12:03:41 -0700556 Dpid dpidObj = new Dpid(dpid);
557
558 Driver driver;
559 try {
Sho SHIMIZUbc82ebb2015-08-25 10:15:21 -0700560 driver = driverService.getDriver(DeviceId.deviceId(Dpid.uri(dpidObj)));
Jonathan Harta0d12492015-07-16 12:03:41 -0700561 } catch (ItemNotFoundException e) {
562 driver = driverService.getDriver(desc.getMfrDesc(), desc.getHwDesc(), desc.getSwDesc());
563 }
alshabibb452fd72015-04-22 20:46:20 -0700564
Jonathan Harta33134e2016-07-27 17:33:35 -0700565 if (driver == null) {
Yuta HIGUCHI6ee6b8c2017-05-09 14:44:30 -0700566 log.error("No OpenFlow driver for {} : {}", dpidObj, desc);
Jonathan Harta33134e2016-07-27 17:33:35 -0700567 return null;
alshabibb452fd72015-04-22 20:46:20 -0700568 }
alshabibb452fd72015-04-22 20:46:20 -0700569
Yuta HIGUCHI6ee6b8c2017-05-09 14:44:30 -0700570 log.info("Driver '{}' assigned to device {}", driver.name(), dpidObj);
Jonathan Harta33134e2016-07-27 17:33:35 -0700571
572 if (!driver.hasBehaviour(OpenFlowSwitchDriver.class)) {
573 log.error("Driver {} does not support OpenFlowSwitchDriver behaviour", driver.name());
574 return null;
575 }
576
577 DefaultDriverHandler handler =
578 new DefaultDriverHandler(new DefaultDriverData(driver, deviceId(uri(dpidObj))));
579 OpenFlowSwitchDriver ofSwitchDriver =
580 driver.createBehaviour(handler, OpenFlowSwitchDriver.class);
581 ofSwitchDriver.init(dpidObj, desc, ofv);
582 ofSwitchDriver.setAgent(agent);
583 ofSwitchDriver.setRoleHandler(new RoleManager(ofSwitchDriver));
Jonathan Harta33134e2016-07-27 17:33:35 -0700584 return ofSwitchDriver;
tom7ef8ff92014-09-17 13:08:06 -0700585 }
586
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700587 public void start(OpenFlowAgent ag, DriverService driverService,
588 NetworkConfigRegistry netCfgService) {
tom7ef8ff92014-09-17 13:08:06 -0700589 log.info("Starting OpenFlow IO");
590 this.agent = ag;
alshabibb452fd72015-04-22 20:46:20 -0700591 this.driverService = driverService;
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700592 this.netCfgService = netCfgService;
Jonathan Hartbbd91d42015-02-27 11:18:04 -0800593 this.init();
Brian O'Connorf7215b82018-05-10 19:12:44 -0700594 this.addListeningPorts(this.openFlowPorts);
tom7ef8ff92014-09-17 13:08:06 -0700595 }
596
597
598 public void stop() {
599 log.info("Stopping OpenFlow IO");
Brian O'Connorf7215b82018-05-10 19:12:44 -0700600 if (cg != null) {
601 cg.close();
602 cg = null;
603 }
Yuta HIGUCHI6ee6b8c2017-05-09 14:44:30 -0700604
605 // Shut down all event loops to terminate all threads.
606 bossGroup.shutdownGracefully();
607 workerGroup.shutdownGracefully();
608
609 // Wait until all threads are terminated.
610 try {
611 bossGroup.terminationFuture().sync();
612 workerGroup.terminationFuture().sync();
613 } catch (InterruptedException e) {
614 log.warn("Interrupted while stopping", e);
615 Thread.currentThread().interrupt();
616 }
tom7ef8ff92014-09-17 13:08:06 -0700617 }
618
Brian O'Connorf7215b82018-05-10 19:12:44 -0700619 private void restart() {
620 // only restart if we are already running
621 if (cg != null) {
622 stop();
623 start(this.agent, this.driverService, this.netCfgService);
624 }
625 }
626
tom7ef8ff92014-09-17 13:08:06 -0700627}