All Downloads are FREE. Search and download functionalities are using the official Maven repository.

biz.paluch.logging.gelf.intern.sender.GelfTCPSSLSender Maven / Gradle / Ivy

The newest version!
package biz.paluch.logging.gelf.intern.sender;

import java.io.IOException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLSession;

import biz.paluch.logging.gelf.intern.ErrorReporter;

/**
 * TCP with SSL {@link biz.paluch.logging.gelf.intern.GelfSender}.
 *
 * @author Mark Paluch
 * @author Alexander Katanov
 * @since 1.11
 */
public class GelfTCPSSLSender extends GelfTCPSender {

    private final int connectTimeoutMs;
    private final SSLContext sslContext;
    private final ThreadLocal sslNetworkBuffers = new ThreadLocal<>();
    private final ThreadLocal tempBuffers = new ThreadLocal<>();

    private volatile SSLEngine sslEngine;

    private volatile SSLSession sslSession;

    /**
     * @param host the host, must not be {@literal null}.
     * @param port the port.
     * @param connectTimeoutMs connection timeout, in {@link TimeUnit#MILLISECONDS}.
     * @param readTimeoutMs read timeout, in {@link TimeUnit#MILLISECONDS}.
     * @param deliveryAttempts number of delivery attempts.
     * @param keepAlive {@literal true} to enable TCP keep-alive.
     * @param errorReporter the error reporter, must not be {@literal null}.
     * @param sslContext the SSL context, must not be {@literal null}.
     * @throws IOException in case of I/O errors
     */
    public GelfTCPSSLSender(String host, int port, int connectTimeoutMs, int readTimeoutMs, int deliveryAttempts,
            boolean keepAlive, ErrorReporter errorReporter, SSLContext sslContext) throws IOException {

        super(host, port, connectTimeoutMs, readTimeoutMs, deliveryAttempts, keepAlive, errorReporter);

        this.connectTimeoutMs = connectTimeoutMs;
        this.sslContext = sslContext;
    }

    /**
     * @param host the host, must not be {@literal null}.
     * @param port the port.
     * @param connectTimeoutMs connection timeout, in {@link TimeUnit#MILLISECONDS}.
     * @param readTimeoutMs read timeout, in {@link TimeUnit#MILLISECONDS}.
     * @param deliveryAttempts number of delivery attempts.
     * @param keepAlive {@literal true} to enable TCP keep-alive.
     * @param backoff Backoff strategy to activate if a socket sender buffer is full and several attempts to write to the socket
     *        are unsuccessful due to it.
     * @param writeBackoffThreshold attempts to write to a socket before a backoff will be activated.
     * @param errorReporter the error reporter, must not be {@literal null}.
     * @param sslContext the SSL context, must not be {@literal null}.
     * @throws IOException in case of I/O errors
     */
    public GelfTCPSSLSender(String host, int port, int connectTimeoutMs, int readTimeoutMs, int deliveryAttempts,
            boolean keepAlive, BackOff backoff, int writeBackoffThreshold, ErrorReporter errorReporter, SSLContext sslContext)
            throws IOException {

        super(host, port, connectTimeoutMs, readTimeoutMs, deliveryAttempts, keepAlive, backoff, writeBackoffThreshold,
                errorReporter);

        this.connectTimeoutMs = connectTimeoutMs;
        this.sslContext = sslContext;
    }

    @Override
    protected boolean connect() throws IOException {
        if (super.connect()) {
            this.sslEngine = sslContext.createSSLEngine();
            this.sslEngine.setUseClientMode(true);
            this.sslSession = sslEngine.getSession();

            // Begin handshake
            sslEngine.beginHandshake();
            doHandshake(channel(), sslEngine, ByteBuffer.allocate(sslSession.getPacketBufferSize()),
                    ByteBuffer.allocate(sslSession.getPacketBufferSize()));
        }
        return false;
    }

    @Override
    protected boolean isConnected() throws IOException {

        SocketChannel socketChannel = channel();

        return socketChannel != null && socketChannel.isOpen() && isConnected(socketChannel);
    }

    @Override
    protected void write(ByteBuffer gelfBuffer) throws IOException {

        while (gelfBuffer.hasRemaining()) {

            read();

            ByteBuffer myNetData = getNetworkBuffer();
            // Generate SSL/TLS encoded data (handshake or application data)
            gelfBuffer.mark();
            SSLEngineResult res = sslEngine.wrap(gelfBuffer, myNetData);

            // Process status of call
            if (res.getStatus() == SSLEngineResult.Status.BUFFER_OVERFLOW) {
                this.sslNetworkBuffers.set(enlargeBuffer(gelfBuffer, myNetData));
                gelfBuffer.reset();
            }

            if (res.getStatus() == SSLEngineResult.Status.OK) {
                myNetData.flip();

                // Send SSL/TLS encoded data to peer
                while (myNetData.hasRemaining()) {
                    int written = channel().write(myNetData);
                    if (written == -1) {
                        throw new SocketException("Channel closed");
                    }
                }
            }
        }
    }

    private void read() throws IOException {

        ByteBuffer myNetData = getNetworkBuffer();
        ByteBuffer tempBuffer = getTempBuffer();

        if (channel().read(myNetData) < 0) {
            throw new SocketException("Channel closed");
        }

        // Process incoming handshaking data
        myNetData.flip();
        sslEngine.unwrap(myNetData, tempBuffer);
    }

    private ByteBuffer getNetworkBuffer() {

        ByteBuffer networkBuffer = this.sslNetworkBuffers.get();
        if (networkBuffer == null) {
            networkBuffer = ByteBuffer.allocateDirect(sslSession.getPacketBufferSize());
            this.sslNetworkBuffers.set(networkBuffer);
        }
        networkBuffer.clear();
        return networkBuffer;
    }

    private ByteBuffer getTempBuffer() {

        ByteBuffer tempBuffer = this.tempBuffers.get();
        if (tempBuffer == null) {
            tempBuffer = ByteBuffer.allocateDirect(sslSession.getApplicationBufferSize());
            this.tempBuffers.set(tempBuffer);
        }
        tempBuffer.clear();
        return tempBuffer;
    }

    private ByteBuffer enlargeBuffer(ByteBuffer src, ByteBuffer dst) {

        // Could attempt to drain the dst buffer of any already obtained
        // data, but we'll just increase it to the size needed.
        ByteBuffer buffer = ByteBuffer.allocate(dst.capacity() + src.remaining());
        dst.flip();
        buffer.put(dst);
        return buffer;
    }

    private void doHandshake(SocketChannel socketChannel, SSLEngine sslEngine, ByteBuffer myNetData, ByteBuffer peerNetData)
            throws IOException {

        // Create byte buffers to use for holding application data
        int appBufferSize = sslEngine.getSession().getApplicationBufferSize();
        ByteBuffer myAppData = ByteBuffer.allocate(appBufferSize);
        ByteBuffer peerAppData = ByteBuffer.allocate(appBufferSize);
        long handshakeBegin = System.currentTimeMillis();

        SSLEngineResult.HandshakeStatus hs = sslEngine.getHandshakeStatus();

        // Process handshaking message
        while (hs != SSLEngineResult.HandshakeStatus.FINISHED && hs != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {

            long handshakeDuration = System.currentTimeMillis() - handshakeBegin;

            if (handshakeDuration > connectTimeoutMs) {
                throw new SocketTimeoutException("SSL handshake timeout exceeded");
            }

            if (!isConnected(socketChannel)) {
                throw new SocketException("Channel closed");
            }

            switch (hs) {

                case NEED_UNWRAP:
                    // Receive handshaking data from peer
                    if (socketChannel.read(peerNetData) < 0) {
                        throw new SocketException("Channel closed");
                    }

                    // Process incoming handshaking data
                    peerNetData.flip();
                    SSLEngineResult res = sslEngine.unwrap(peerNetData, peerAppData);
                    peerNetData.compact();
                    hs = res.getHandshakeStatus();

                    // Check status
                    switch (res.getStatus()) {
                        case OK:
                            // Handle OK status
                            break;
                        case BUFFER_OVERFLOW:
                            peerAppData = enlargeBuffer(peerNetData, peerAppData);
                            break;

                        case BUFFER_UNDERFLOW:

                            break;
                    }
                    break;

                case NEED_WRAP:
                    // Empty the local network packet buffer.
                    myNetData.clear();

                    // Generate handshaking data
                    res = sslEngine.wrap(myAppData, myNetData);
                    hs = res.getHandshakeStatus();

                    // Check status
                    switch (res.getStatus()) {
                        case OK:
                            myNetData.flip();

                            // Send the handshaking data to peer
                            while (myNetData.hasRemaining()) {
                                if (socketChannel.write(myNetData) < 0) {
                                    // Handle closed channel
                                }
                            }
                            break;
                        case BUFFER_OVERFLOW:
                            myNetData = enlargeBuffer(myAppData, myNetData);
                            break;

                        case BUFFER_UNDERFLOW:
                            break;
                        case CLOSED:

                            if (sslEngine.isOutboundDone()) {
                                return;
                            } else {
                                sslEngine.closeOutbound();
                                hs = sslEngine.getHandshakeStatus();
                                break;
                            }

                        default:
                            throw new IOException("Cannot wrap data: " + res.getStatus());

                    }
                    break;

                case NEED_TASK:
                    Runnable task;
                    while ((task = sslEngine.getDelegatedTask()) != null) {
                        task.run();
                    }
                    hs = sslEngine.getHandshakeStatus();
                    break;

                case FINISHED:
                    return;
            }
        }
    }

    @Override
    public void close() {

        if (channel() != null) {

            try {
                closeSocketChannel();
            } catch (IOException e) {
                reportError(e.getMessage(), e);
            }
        }

        super.close();
    }

    @Override
    protected boolean isConnected(SocketChannel channel) {
        return super.isConnected(channel) && channel.isOpen();
    }

    private void closeSocketChannel() throws IOException {

        if (sslEngine != null) {

            sslEngine.closeOutbound();

            if (sslSession != null) {
                doHandshake(channel(), sslEngine, ByteBuffer.allocate(sslSession.getPacketBufferSize()),
                        ByteBuffer.allocate(sslSession.getPacketBufferSize()));
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy