2 * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.protocol.framework;
10 import java.io.EOFException;
11 import java.io.IOException;
12 import java.net.Socket;
13 import java.net.SocketAddress;
14 import java.net.SocketOption;
15 import java.nio.ByteBuffer;
16 import java.nio.channels.AlreadyConnectedException;
17 import java.nio.channels.ClosedChannelException;
18 import java.nio.channels.ConnectionPendingException;
19 import java.nio.channels.NoConnectionPendingException;
20 import java.nio.channels.NotYetConnectedException;
21 import java.nio.channels.SelectionKey;
22 import java.nio.channels.SocketChannel;
24 import java.util.concurrent.Executor;
26 import javax.net.ssl.SSLContext;
27 import javax.net.ssl.SSLEngine;
28 import javax.net.ssl.SSLEngineResult;
29 import javax.net.ssl.SSLEngineResult.HandshakeStatus;
30 import javax.net.ssl.SSLException;
31 import javax.net.ssl.SSLSession;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
37 * Base class for an SSL-enabled socket channel. It is completed as
38 * SSLSocketChannel in one of the two Java version-specific files.
40 final class SSLSocketChannel extends SocketChannel implements SSLSelectableChannel {
41 private enum InternalState {
43 * Freshly created socket. Must be connected.
47 * Underlying TCP connection is being established.
51 * Underlying TCP connection is established, we are currently
52 * negotiating SSL session parameters.
56 * Connection attempt has been resolved, we need the user
57 * to call finishConnect().
61 * We have notified the user that the connection has failed,
62 * all we need to do is cleanup resources.
66 * We have notified user that the connection has been
67 * established and the channel is fully operational.
71 * We are closing down the channel. From user's perspective
72 * it is already dead, we just need to cleanup.
76 * The channel has been closed and all resources released.
81 private static final Logger logger = LoggerFactory.getLogger(SSLSocketChannel.class);
82 private SSLServerSocketChannel parent;
83 final SocketChannel channel;
84 final SSLEngine engine;
86 private final ByteBuffer fromNetwork, toNetwork, toUser;
87 private final ByteBuffer empty = ByteBuffer.allocate(0);
88 private final Executor executor;
89 private IOException connectResult = null;
90 private IOException writeFailed = null, closeFailed = null;
91 private boolean readDone = false;
92 private boolean closedInput = false, closedOutput = false;
93 private InternalState state;
95 private SSLSocketChannel(final SocketChannel channel, final SSLEngine engine,
96 final Executor executor, final SSLServerSocketChannel parent) throws SSLException {
97 super(channel.provider());
98 this.executor = executor;
99 this.channel = channel;
100 this.parent = parent;
101 this.engine = engine;
103 final SSLSession session = engine.getSession();
104 fromNetwork = ByteBuffer.allocate(session.getPacketBufferSize());
105 fromNetwork.limit(0);
106 toNetwork = ByteBuffer.allocate(session.getPacketBufferSize());
108 toUser = ByteBuffer.allocate(session.getApplicationBufferSize());
110 if (parent != null) {
111 engine.setUseClientMode(false);
112 engine.setWantClientAuth(true);
113 engine.setNeedClientAuth(false);
114 engine.beginHandshake();
115 state = InternalState.NEGOTIATING;
117 state = InternalState.IDLE;
120 public static SSLSocketChannel open(final SocketChannel channel, final SSLContext context,
121 final Executor executor, final SSLServerSocketChannel parent) throws IOException {
123 return new SSLSocketChannel(channel, context.createSSLEngine(), executor, parent);
127 public synchronized boolean connect(final SocketAddress remote) throws IOException {
132 throw new ClosedChannelException();
134 throw new AlreadyConnectedException();
136 case CONNECT_RESOLVED:
138 throw new ConnectionPendingException();
140 if (channel.connect(remote)) {
141 engine.setUseClientMode(true);
142 engine.beginHandshake();
143 state = InternalState.NEGOTIATING;
145 state = InternalState.CONNECTING;
149 throw new IllegalStateException("Unhandled state " + state);
153 public synchronized boolean finishConnect() throws IOException {
154 logger.trace("Attempting to finish connection in state {}", state);
160 throw new ClosedChannelException();
163 case CONNECT_RESOLVED:
164 if (connectResult != null) {
165 state = InternalState.CONNECT_FAILED;
167 logger.trace("Internal close after failed connect");
169 } catch (IOException e) {
170 logger.trace("Failed to invoked internal close", e);
175 state = InternalState.CONNECTED;
176 if (parent != null) {
177 parent.addNewChannel(this);
185 throw new NoConnectionPendingException();
188 throw new IllegalStateException("Unhandled state " + state);
192 public synchronized boolean isConnected() {
193 return state == InternalState.CONNECTED;
197 public synchronized boolean isConnectionPending() {
200 case CONNECT_RESOLVED:
208 private int readNetwork() throws IOException {
209 fromNetwork.compact();
213 ret = channel.read(fromNetwork);
218 logger.trace("Channel {} has input {} after {}", this, fromNetwork.remaining(), ret);
222 private int writeNetwork() throws IOException {
227 ret = channel.write(toNetwork);
232 logger.trace("Channel {} has output {} after {}", this, toNetwork.remaining(), ret);
236 private void checkChannelState() throws IOException {
241 throw new ClosedChannelException();
244 case CONNECT_RESOLVED:
248 throw new NotYetConnectedException();
252 private boolean checkReadState() throws IOException {
258 public synchronized int read(final ByteBuffer dst) throws IOException {
259 if (checkReadState())
263 * If we have some data overflowed from negotiation, flush that
266 if (toUser.position() != 0) {
267 logger.trace("toUser has {}", toUser.position());
270 final int xfer = Math.min(toUser.remaining(), dst.remaining());
271 dst.put(toUser.array(), toUser.arrayOffset() + toUser.position(), xfer);
272 toUser.position(toUser.position() + xfer);
277 // We have input data, unwrap it
278 if (fromNetwork.hasRemaining()) {
279 final SSLEngineResult res = engine.unwrap(fromNetwork, dst);
280 return res.bytesProduced();
283 // EOF on underlying stream, inform the engine
285 engine.closeInbound();
287 // SSL engine says there may be some more input
288 if (!engine.isInboundDone())
291 logger.trace("SSL engine indicates clean shutdown");
296 public synchronized long read(final ByteBuffer[] dsts, final int offset, final int length) throws IOException {
297 if (checkReadState())
301 * Search for the first buffer with available data and perform
302 * a single-buffer read into it. Not completely efficient, but
303 * does the work required.
305 for (int i = offset; i < length; ++i)
306 if (dsts[i].remaining() != 0)
307 return read(dsts[i]);
313 public Socket socket() {
314 // We do not support this operation, everyone should use Java 7 interfaces
315 throw new UnsupportedOperationException("SSLSocketChannel does not provide a fake Socket implementation");
318 private void checkWriteState() throws IOException {
322 throw new ClosedChannelException();
324 if (writeFailed != null)
329 public synchronized int write(final ByteBuffer src) throws IOException {
332 final SSLEngineResult res = engine.wrap(src, toNetwork);
333 return res.bytesConsumed();
337 public synchronized long write(final ByteBuffer[] srcs, final int offset, final int length) throws IOException {
340 final SSLEngineResult res = engine.wrap(srcs, offset, length, toNetwork);
341 return res.bytesConsumed();
345 protected synchronized void implCloseSelectableChannel() throws IOException {
346 logger.trace("Closing channel in state {}", state);
350 state = InternalState.CLOSING;
351 engine.closeOutbound();
358 case CONNECT_RESOLVED:
362 state = InternalState.CLOSED;
369 protected void implConfigureBlocking(final boolean block) throws IOException {
370 channel.configureBlocking(block);
374 public synchronized int computeInterestOps(int userOps) {
375 logger.trace("Interestops in state {} userOps {}", state, userOps);
382 case CONNECT_RESOLVED:
386 if (engine.isOutboundDone() && toNetwork.position() == 0)
387 throw new IllegalStateException("Network flush completed, but still in CLOSING state");
388 return SelectionKey.OP_WRITE;
390 return SelectionKey.OP_CONNECT;
394 final HandshakeStatus st = engine.getHandshakeStatus();
396 logger.trace("SSL Engine status {}", st);
400 userOps = SelectionKey.OP_READ;
403 userOps = SelectionKey.OP_WRITE;
406 logger.trace("Unexpected SSLEngine handshake status {}", st);
407 connectResult = new IOException("Unexpected SSLEngine handshake status " + st);
408 connectResult.fillInStackTrace();
409 state = InternalState.CONNECT_RESOLVED;
413 // Intentional fall through
415 if ((userOps & SelectionKey.OP_READ) != 0 && !fromNetwork.hasRemaining())
416 ret |= SelectionKey.OP_READ;
417 if ((userOps & SelectionKey.OP_WRITE) != 0 && !toNetwork.hasRemaining())
418 ret |= SelectionKey.OP_WRITE;
420 logger.trace("userOps {} fromNetwork {} toNetwork {} ret {}", userOps, fromNetwork.remaining(), toNetwork.remaining(), ret);
424 throw new IllegalStateException("Unhandled state " + state);
427 private void performIO() {
428 logger.trace("IO operations in state {}", state);
432 case CONNECT_RESOLVED:
437 boolean forceClose = false;
438 if (!engine.isOutboundDone()) {
440 engine.wrap(empty, toNetwork);
441 } catch (SSLException e) {
442 logger.trace("Failed to close down SSL engine outbound", e);
446 if (toNetwork.position() != 0) {
449 } catch (IOException e) {
450 logger.trace("Failed to flush outstanding buffers, forcing close", e);
455 if (forceClose || (engine.isOutboundDone() && toNetwork.position() == 0)) {
456 logger.trace("Completed state flush");
457 state = InternalState.CLOSED;
460 } catch (IOException e) {
461 logger.trace("Failed to close slave channel", e);
467 logger.trace("Invoking internal close after failure");
469 } catch (IOException e) {
470 logger.trace("Internal fail closed", e);
475 if (!readDone && readNetwork() < 0) {
478 engine.closeInbound();
479 } catch (IOException e) {
480 logger.trace("TLS reported close error", e);
484 } catch (IOException e) {
485 logger.trace("Background read failed", e);
490 if (toNetwork.position() != 0)
492 } catch (IOException e) {
493 logger.trace("Background write failed", e);
500 if (channel.finishConnect()) {
501 engine.setUseClientMode(true);
502 engine.beginHandshake();
503 state = InternalState.NEGOTIATING;
505 } catch (IOException e) {
506 logger.trace("Finished connection with error", e);
508 state = InternalState.CONNECT_RESOLVED;
512 boolean needMore = true;
515 final HandshakeStatus st = engine.getHandshakeStatus();
516 if (st == HandshakeStatus.NEED_TASK) {
517 // Dispatch any blocking tasks that SSLEngine has for us.
519 final Runnable r = engine.getDelegatedTask();
529 if (readNetwork() < 0) {
530 logger.trace("Unexpected end of stream during negotiation");
531 connectResult = new EOFException("Unexpected end-of-channel during SSL negotiation");
532 connectResult.fillInStackTrace();
533 state = InternalState.CONNECT_RESOLVED;
537 } catch (IOException e) {
538 logger.trace("IO error during SSL negotiation", e);
540 state = InternalState.CONNECT_RESOLVED;
544 final SSLEngineResult res;
546 logger.trace("Status {} fromNetwork {} toNetwork {} toUser {}", st, fromNetwork.remaining(), toNetwork.remaining(), toUser.remaining());
548 if (st == HandshakeStatus.NEED_UNWRAP) {
549 // SSLEngine needs to read some data from the network
550 res = engine.unwrap(fromNetwork, toUser);
551 if (res.getStatus() == SSLEngineResult.Status.BUFFER_UNDERFLOW)
553 } else if (st == HandshakeStatus.NEED_WRAP) {
554 // SSLEngine needs to write some data to the network
555 res = engine.wrap(empty, toNetwork);
556 if (res.getStatus() == SSLEngineResult.Status.BUFFER_OVERFLOW)
559 logger.trace("Unexpected state {} in SSL negotiation", engine.getHandshakeStatus());
560 connectResult = new IOException("Unexpected SSL negotiation state");
561 connectResult.fillInStackTrace();
562 state = InternalState.CONNECT_RESOLVED;
565 } catch (SSLException e) {
566 logger.trace("SSL negotiation failed", e);
568 state = InternalState.CONNECT_RESOLVED;
572 logger.trace("SSL needMore {} result {}", needMore, res);
574 if (res.getHandshakeStatus() == HandshakeStatus.FINISHED) {
575 final SSLSession s = engine.getSession();
576 logger.trace("SSL session established: {}", s);
577 state = InternalState.CONNECT_RESOLVED;
585 public synchronized int computeReadyOps() {
588 logger.trace("Readyops in state {}", state);
598 case CONNECT_RESOLVED:
599 return SelectionKey.OP_CONNECT;
603 if (toNetwork.hasRemaining() || writeFailed != null)
604 ret |= SelectionKey.OP_WRITE;
605 if (fromNetwork.hasRemaining() || toUser.position() != 0)
606 ret |= SelectionKey.OP_READ;
611 throw new IllegalStateException("Unhandled state " + state);
615 public SocketChannel bind(final SocketAddress local) throws IOException {
621 public SocketAddress getLocalAddress() throws IOException {
622 return channel.getLocalAddress();
626 public SocketAddress getRemoteAddress() throws IOException {
627 return channel.getRemoteAddress();
631 public synchronized SocketChannel shutdownInput() throws IOException {
636 if (closeFailed != null)
638 logger.debug("Socket {} input shut down", this);
645 public synchronized SocketChannel shutdownOutput() throws IOException {
650 engine.closeOutbound();
651 logger.debug("Socket {} output shut down", this);
658 public <T> T getOption(SocketOption<T> name) throws IOException {
659 return channel.getOption(name);
663 public <T> SocketChannel setOption(SocketOption<T> name, T value) throws IOException {
664 channel.setOption(name, value);
669 public Set<SocketOption<?>> supportedOptions() {
670 return channel.supportedOptions();
673 synchronized boolean hasParent() {
674 return parent != null;