Activate code generation
[bgpcep.git] / framework / src / main / java / org / opendaylight / protocol / framework / SSLSocketChannel.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.protocol.framework;
9
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;
23 import java.util.Set;
24 import java.util.concurrent.Executor;
25
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;
32
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
35
36 /*
37  * Base class for an SSL-enabled socket channel. It is completed as
38  * SSLSocketChannel in one of the two Java version-specific files.
39  */
40 final class SSLSocketChannel extends SocketChannel implements SSLSelectableChannel {
41         private enum InternalState {
42                 /**
43                  * Freshly created socket. Must be connected.
44                  */
45                 IDLE,
46                 /**
47                  * Underlying TCP connection is being established.
48                  */
49                 CONNECTING,
50                 /**
51                  * Underlying TCP connection is established, we are currently
52                  * negotiating SSL session parameters.
53                  */
54                 NEGOTIATING,
55                 /**
56                  * Connection attempt has been resolved, we need the user
57                  * to call finishConnect().
58                  */
59                 CONNECT_RESOLVED,
60                 /**
61                  * We have notified the user that the connection has failed,
62                  * all we need to do is cleanup resources.
63                  */
64                 CONNECT_FAILED,
65                 /**
66                  * We have notified user that the connection has been
67                  * established and the channel is fully operational.
68                  */
69                 CONNECTED,
70                 /**
71                  * We are closing down the channel. From user's perspective
72                  * it is already dead, we just need to cleanup.
73                  */
74                 CLOSING,
75                 /**
76                  * The channel has been closed and all resources released.
77                  */
78                 CLOSED,
79         }
80
81         private static final Logger logger = LoggerFactory.getLogger(SSLSocketChannel.class);
82         private SSLServerSocketChannel parent;
83         final SocketChannel channel;
84         final SSLEngine engine;
85
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;
94
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;
102
103                 final SSLSession session = engine.getSession();
104                 fromNetwork = ByteBuffer.allocate(session.getPacketBufferSize());
105                 fromNetwork.limit(0);
106                 toNetwork = ByteBuffer.allocate(session.getPacketBufferSize());
107                 toNetwork.limit(0);
108                 toUser = ByteBuffer.allocate(session.getApplicationBufferSize());
109
110                 if (parent != null) {
111                         engine.setUseClientMode(false);
112                         engine.setWantClientAuth(true);
113                         engine.setNeedClientAuth(false);
114                         engine.beginHandshake();
115                         state = InternalState.NEGOTIATING;
116                 } else
117                         state = InternalState.IDLE;
118         }
119
120         public static SSLSocketChannel open(final SocketChannel channel, final SSLContext context,
121                         final Executor executor, final SSLServerSocketChannel parent) throws IOException {
122
123                 return new SSLSocketChannel(channel, context.createSSLEngine(), executor, parent);
124         }
125
126         @Override
127         public synchronized boolean connect(final SocketAddress remote) throws IOException {
128                 switch (state) {
129                 case CLOSED:
130                 case CLOSING:
131                 case CONNECT_FAILED:
132                         throw new ClosedChannelException();
133                 case CONNECTED:
134                         throw new AlreadyConnectedException();
135                 case CONNECTING:
136                 case CONNECT_RESOLVED:
137                 case NEGOTIATING:
138                         throw new ConnectionPendingException();
139                 case IDLE:
140                         if (channel.connect(remote)) {
141                                 engine.setUseClientMode(true);
142                                 engine.beginHandshake();
143                                 state = InternalState.NEGOTIATING;
144                         } else
145                                 state = InternalState.CONNECTING;
146                         return false;
147                 }
148
149                 throw new IllegalStateException("Unhandled state " + state);
150         }
151
152         @Override
153         public synchronized boolean finishConnect() throws IOException {
154                 logger.trace("Attempting to finish connection in state {}", state);
155
156                 switch (state) {
157                 case CLOSED:
158                 case CLOSING:
159                 case CONNECT_FAILED:
160                         throw new ClosedChannelException();
161                 case CONNECTED:
162                         return true;
163                 case CONNECT_RESOLVED:
164                         if (connectResult != null) {
165                                 state = InternalState.CONNECT_FAILED;
166                                 try {
167                                         logger.trace("Internal close after failed connect");
168                                         close();
169                                 } catch (IOException e) {
170                                         logger.trace("Failed to invoked internal close", e);
171                                 }
172                                 throw connectResult;
173                         }
174
175                         state = InternalState.CONNECTED;
176                         if (parent != null) {
177                                 parent.addNewChannel(this);
178                                 parent = null;
179                         }
180                         return true;
181                 case CONNECTING:
182                 case NEGOTIATING:
183                         return false;
184                 case IDLE:
185                         throw new NoConnectionPendingException();
186                 }
187
188                 throw new IllegalStateException("Unhandled state " + state);
189         }
190
191         @Override
192         public synchronized boolean isConnected() {
193                 return state == InternalState.CONNECTED;
194         }
195
196         @Override
197         public synchronized boolean isConnectionPending() {
198                 switch (state) {
199                 case CONNECTING:
200                 case CONNECT_RESOLVED:
201                 case NEGOTIATING:
202                         return true;
203                 default:
204                         return false;
205                 }
206         }
207
208         private int readNetwork() throws IOException {
209                 fromNetwork.compact();
210
211                 final int ret;
212                 try {
213                         ret = channel.read(fromNetwork);
214                 } finally {
215                         fromNetwork.flip();
216                 }
217
218                 logger.trace("Channel {} has input {} after {}", this, fromNetwork.remaining(), ret);
219                 return ret;
220         }
221
222         private int writeNetwork() throws IOException {
223                 toNetwork.flip();
224
225                 final int ret;
226                 try {
227                         ret = channel.write(toNetwork);
228                 } finally {
229                         toNetwork.compact();
230                 }
231
232                 logger.trace("Channel {} has output {} after {}", this, toNetwork.remaining(), ret);
233                 return ret;
234         }
235
236         private void checkChannelState() throws IOException {
237                 switch (state) {
238                 case CLOSED:
239                 case CLOSING:
240                 case CONNECT_FAILED:
241                         throw new ClosedChannelException();
242                 case CONNECTED:
243                         break;
244                 case CONNECT_RESOLVED:
245                 case CONNECTING:
246                 case IDLE:
247                 case NEGOTIATING:
248                         throw new NotYetConnectedException();
249                 }
250         }
251
252         private boolean checkReadState() throws IOException {
253                 checkChannelState();
254                 return closedInput;
255         }
256
257         @Override
258         public synchronized int read(final ByteBuffer dst) throws IOException {
259                 if (checkReadState())
260                         return -1;
261
262                 /*
263                  * If we have some data overflowed from negotiation, flush that
264                  * first.
265                  */
266                 if (toUser.position() != 0) {
267                         logger.trace("toUser has {}", toUser.position());
268                         toUser.flip();
269
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);
273                         toUser.compact();
274                         return xfer;
275                 }
276
277                 // We have input data, unwrap it
278                 if (fromNetwork.hasRemaining()) {
279                         final SSLEngineResult res = engine.unwrap(fromNetwork, dst);
280                         return res.bytesProduced();
281                 }
282
283                 // EOF on underlying stream, inform the engine
284                 if (readDone)
285                         engine.closeInbound();
286
287                 // SSL engine says there may be some more input
288                 if (!engine.isInboundDone())
289                         return 0;
290
291                 logger.trace("SSL engine indicates clean shutdown");
292                 return -1;
293         }
294
295         @Override
296         public synchronized long read(final ByteBuffer[] dsts, final int offset, final int length) throws IOException {
297                 if (checkReadState())
298                         return -1;
299
300                 /*
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.
304                  */
305                 for (int i = offset; i < length; ++i)
306                         if (dsts[i].remaining() != 0)
307                                 return read(dsts[i]);
308
309                 return 0;
310         }
311
312         @Override
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");
316         }
317
318         private void checkWriteState() throws IOException {
319                 checkChannelState();
320
321                 if (closedOutput)
322                         throw new ClosedChannelException();
323
324                 if (writeFailed != null)
325                         throw writeFailed;
326         }
327
328         @Override
329         public synchronized int write(final ByteBuffer src) throws IOException {
330                 checkWriteState();
331
332                 final SSLEngineResult res = engine.wrap(src, toNetwork);
333                 return res.bytesConsumed();
334         }
335
336         @Override
337         public synchronized long write(final ByteBuffer[] srcs, final int offset, final int length) throws IOException {
338                 checkWriteState();
339
340                 final SSLEngineResult res = engine.wrap(srcs, offset, length, toNetwork);
341                 return res.bytesConsumed();
342         }
343
344         @Override
345         protected synchronized void implCloseSelectableChannel() throws IOException {
346                 logger.trace("Closing channel in state {}", state);
347
348                 switch (state) {
349                 case CONNECTED:
350                         state = InternalState.CLOSING;
351                         engine.closeOutbound();
352                         break;
353                 case CLOSED:
354                 case CLOSING:
355                         // Nothing to do
356                         break;
357                 case CONNECT_FAILED:
358                 case CONNECT_RESOLVED:
359                 case CONNECTING:
360                 case IDLE:
361                 case NEGOTIATING:
362                         state = InternalState.CLOSED;
363                         channel.close();
364                         break;
365                 }
366         }
367
368         @Override
369         protected void implConfigureBlocking(final boolean block) throws IOException {
370                 channel.configureBlocking(block);
371         }
372
373         @Override
374         public synchronized int computeInterestOps(int userOps) {
375                 logger.trace("Interestops in state {} userOps {}", state, userOps);
376
377                 int ret = 0;
378
379                 switch (state) {
380                 case CLOSED:
381                 case CONNECT_FAILED:
382                 case CONNECT_RESOLVED:
383                 case IDLE:
384                         return 0;
385                 case CLOSING:
386                         if (engine.isOutboundDone() && toNetwork.position() == 0)
387                                 throw new IllegalStateException("Network flush completed, but still in CLOSING state");
388                         return SelectionKey.OP_WRITE;
389                 case CONNECTING:
390                         return SelectionKey.OP_CONNECT;
391                 case NEGOTIATING:
392                         userOps = 0;
393
394                         final HandshakeStatus st = engine.getHandshakeStatus();
395
396                         logger.trace("SSL Engine status {}", st);
397
398                         switch (st) {
399                         case NEED_UNWRAP:
400                                 userOps = SelectionKey.OP_READ;
401                                 break;
402                         case NEED_WRAP:
403                                 userOps = SelectionKey.OP_WRITE;
404                                 break;
405                         default:
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;
410                                 return 0;
411                         }
412
413                         // Intentional fall through
414                 case CONNECTED:
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;
419
420                         logger.trace("userOps {} fromNetwork {} toNetwork {} ret {}", userOps, fromNetwork.remaining(), toNetwork.remaining(), ret);
421                         return ret;
422                 }
423
424                 throw new IllegalStateException("Unhandled state " + state);
425         }
426
427         private void performIO() {
428                 logger.trace("IO operations in state {}", state);
429
430                 switch (state) {
431                 case CLOSED:
432                 case CONNECT_RESOLVED:
433                 case IDLE:
434                         // Nothing to do
435                         break;
436                 case CLOSING:
437                         boolean forceClose = false;
438                         if (!engine.isOutboundDone()) {
439                                 try {
440                                         engine.wrap(empty, toNetwork);
441                                 } catch (SSLException e) {
442                                         logger.trace("Failed to close down SSL engine outbound", e);
443                                 }
444                         }
445
446                         if (toNetwork.position() != 0) {
447                                 try {
448                                         writeNetwork();
449                                 } catch (IOException e) {
450                                         logger.trace("Failed to flush outstanding buffers, forcing close", e);
451                                         forceClose = true;
452                                 }
453                         }
454
455                         if (forceClose || (engine.isOutboundDone() && toNetwork.position() == 0)) {
456                                 logger.trace("Completed state flush");
457                                 state = InternalState.CLOSED;
458                                 try {
459                                         channel.close();
460                                 } catch (IOException e) {
461                                         logger.trace("Failed to close slave channel", e);
462                                 }
463                         }
464                         break;
465                 case CONNECT_FAILED:
466                         try {
467                                 logger.trace("Invoking internal close after failure");
468                                 close();
469                         } catch (IOException e) {
470                                 logger.trace("Internal fail closed", e);
471                         }
472                         break;
473                 case CONNECTED:
474                         try {
475                                 if (!readDone && readNetwork() < 0) {
476                                         readDone = true;
477                                         try {
478                                                 engine.closeInbound();
479                                         } catch (IOException e) {
480                                                 logger.trace("TLS reported close error", e);
481                                                 closeFailed = e;
482                                         }
483                                 }
484                         } catch (IOException e) {
485                                 logger.trace("Background read failed", e);
486                                 readDone = true;
487                         }
488
489                         try {
490                                 if (toNetwork.position() != 0)
491                                         writeNetwork();
492                         } catch (IOException e) {
493                                 logger.trace("Background write failed", e);
494                                 writeFailed = e;
495                                 toNetwork.clear();
496                         }
497                         break;
498                 case CONNECTING:
499                         try {
500                                 if (channel.finishConnect()) {
501                                         engine.setUseClientMode(true);
502                                         engine.beginHandshake();
503                                         state = InternalState.NEGOTIATING;
504                                 }
505                         } catch (IOException e) {
506                                 logger.trace("Finished connection with error", e);
507                                 connectResult = e;
508                                 state = InternalState.CONNECT_RESOLVED;
509                         }
510                         break;
511                 case NEGOTIATING:
512                         boolean needMore = true;
513
514                         do {
515                                 final HandshakeStatus st = engine.getHandshakeStatus();
516                                 if (st == HandshakeStatus.NEED_TASK) {
517                                         // Dispatch any blocking tasks that SSLEngine has for us.
518                                         while (true) {
519                                                 final Runnable r = engine.getDelegatedTask();
520                                                 if (r == null)
521                                                         break;
522
523                                                 executor.execute(r);
524                                         }
525                                         continue;
526                                 }
527
528                                 try {
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;
534                                                 break;
535                                         }
536                                         writeNetwork();
537                                 } catch (IOException e) {
538                                         logger.trace("IO error during SSL negotiation", e);
539                                         connectResult = e;
540                                         state = InternalState.CONNECT_RESOLVED;
541                                         break;
542                                 }
543
544                                 final SSLEngineResult res;
545                                 try {
546                                         logger.trace("Status {} fromNetwork {} toNetwork {} toUser {}", st, fromNetwork.remaining(), toNetwork.remaining(), toUser.remaining());
547
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)
552                                                         needMore = false;
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)
557                                                         needMore = false;
558                                         } else {
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;
563                                                 break;
564                                         }
565                                 } catch (SSLException e) {
566                                         logger.trace("SSL negotiation failed", e);
567                                         connectResult = e;
568                                         state = InternalState.CONNECT_RESOLVED;
569                                         break;
570                                 }
571
572                                 logger.trace("SSL needMore {} result {}", needMore, res);
573
574                                 if (res.getHandshakeStatus() == HandshakeStatus.FINISHED) {
575                                         final SSLSession s = engine.getSession();
576                                         logger.trace("SSL session established: {}", s);
577                                         state = InternalState.CONNECT_RESOLVED;
578                                         break;
579                                 }
580                         } while (needMore);
581                 }
582         }
583
584         @Override
585         public synchronized int computeReadyOps() {
586                 performIO();
587
588                 logger.trace("Readyops in state {}", state);
589
590                 switch (state) {
591                 case CLOSED:
592                 case CLOSING:
593                 case CONNECT_FAILED:
594                 case CONNECTING:
595                 case IDLE:
596                 case NEGOTIATING:
597                         return 0;
598                 case CONNECT_RESOLVED:
599                         return SelectionKey.OP_CONNECT;
600                 case CONNECTED:
601                         int ret = 0;
602
603                         if (toNetwork.hasRemaining() || writeFailed != null)
604                                 ret |= SelectionKey.OP_WRITE;
605                         if (fromNetwork.hasRemaining() || toUser.position() != 0)
606                                 ret |= SelectionKey.OP_READ;
607
608                         return ret;
609                 }
610
611                 throw new IllegalStateException("Unhandled state " + state);
612         }
613
614         @Override
615         public SocketChannel bind(final SocketAddress local) throws IOException {
616                 channel.bind(local);
617                 return this;
618         }
619
620         @Override
621         public SocketAddress getLocalAddress() throws IOException {
622                 return channel.getLocalAddress();
623         }
624
625         @Override
626         public SocketAddress getRemoteAddress() throws IOException {
627                 return channel.getRemoteAddress();
628         }
629
630         @Override
631         public synchronized SocketChannel shutdownInput() throws IOException {
632                 checkChannelState();
633
634                 if (!closedInput) {
635                         closedInput = true;
636                         if (closeFailed != null)
637                                 throw closeFailed;
638                         logger.debug("Socket {} input shut down", this);
639                 }
640
641                 return this;
642         }
643
644         @Override
645         public synchronized SocketChannel shutdownOutput() throws IOException {
646                 checkChannelState();
647
648                 if (!closedOutput) {
649                         closedOutput = true;
650                         engine.closeOutbound();
651                         logger.debug("Socket {} output shut down", this);
652                 }
653
654                 return this;
655         }
656
657         @Override
658         public <T> T getOption(SocketOption<T> name) throws IOException {
659                 return channel.getOption(name);
660         }
661
662         @Override
663         public <T> SocketChannel setOption(SocketOption<T> name, T value) throws IOException {
664                 channel.setOption(name, value);
665                 return this;
666         }
667
668         @Override
669         public Set<SocketOption<?>> supportedOptions() {
670                 return channel.supportedOptions();
671         }
672
673         synchronized boolean hasParent() {
674                 return parent != null;
675         }
676 }
677