/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.conscrypt; import static org.conscrypt.Preconditions.checkArgument; import static org.conscrypt.Preconditions.checkNotNull; import java.io.FileDescriptor; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketAddress; import java.net.SocketException; import java.nio.channels.SocketChannel; import java.util.ArrayList; import java.util.List; import javax.net.ssl.HandshakeCompletedEvent; import javax.net.ssl.HandshakeCompletedListener; import javax.net.ssl.SSLSession; /** * Base class for all (non-wrapping) Conscrypt socket implementations. */ abstract class ConscryptSocketBase extends AbstractConscryptSocket { final Socket socket; private final boolean autoClose; /** * The peer's DNS hostname if it was supplied during creation. Note that * this may be a raw IP address, so it should be checked before use with * extensions that don't use it like Server Name Indication (SNI). */ private String peerHostname; /** * The peer's port if it was supplied during creation. Should only be set if * {@link #peerHostname} is also set. */ private final int peerPort; private final PeerInfoProvider peerInfoProvider = new PeerInfoProvider() { @Override String getHostname() { return ConscryptSocketBase.this.getHostname(); } @Override String getHostnameOrIP() { return ConscryptSocketBase.this.getHostnameOrIP(); } @Override int getPort() { return ConscryptSocketBase.this.getPort(); } }; private final List listeners = new ArrayList(2); /** * Local cache of timeout to avoid getsockopt on every read and * write for non-wrapped sockets. Note that this is not used when delegating * to another socket. */ private int readTimeoutMilliseconds; ConscryptSocketBase() throws IOException { this.socket = this; this.peerHostname = null; this.peerPort = -1; this.autoClose = false; } ConscryptSocketBase(String hostname, int port) throws IOException { super(hostname, port); this.socket = this; this.peerHostname = hostname; this.peerPort = port; this.autoClose = false; } ConscryptSocketBase(InetAddress address, int port) throws IOException { super(address, port); this.socket = this; this.peerHostname = null; this.peerPort = -1; this.autoClose = false; } ConscryptSocketBase(String hostname, int port, InetAddress clientAddress, int clientPort) throws IOException { super(hostname, port, clientAddress, clientPort); this.socket = this; this.peerHostname = hostname; this.peerPort = port; this.autoClose = false; } ConscryptSocketBase(InetAddress address, int port, InetAddress clientAddress, int clientPort) throws IOException { super(address, port, clientAddress, clientPort); this.socket = this; this.peerHostname = null; this.peerPort = -1; this.autoClose = false; } ConscryptSocketBase(Socket socket, String hostname, int port, boolean autoClose) throws IOException { this.socket = checkNotNull(socket, "socket"); this.peerHostname = hostname; this.peerPort = port; this.autoClose = autoClose; } @Override public final void connect(SocketAddress endpoint) throws IOException { connect(endpoint, 0); } /** * Try to extract the peer's hostname if it's available from the endpoint address. */ @Override public final void connect(SocketAddress endpoint, int timeout) throws IOException { if (peerHostname == null && endpoint instanceof InetSocketAddress) { peerHostname = Platform.getHostStringFromInetSocketAddress((InetSocketAddress) endpoint); } if (isDelegating()) { socket.connect(endpoint, timeout); } else { super.connect(endpoint, timeout); } } @Override public void bind(SocketAddress bindpoint) throws IOException { if (isDelegating()) { socket.bind(bindpoint); } else { super.bind(bindpoint); } } @Override @SuppressWarnings("UnsynchronizedOverridesSynchronized") public void close() throws IOException { if (isDelegating()) { if (autoClose && !socket.isClosed()) { socket.close(); } } else { if (!super.isClosed()) { super.close(); } } } @Override public InetAddress getInetAddress() { if (isDelegating()) { return socket.getInetAddress(); } return super.getInetAddress(); } @Override public InetAddress getLocalAddress() { if (isDelegating()) { return socket.getLocalAddress(); } return super.getLocalAddress(); } @Override public int getLocalPort() { if (isDelegating()) { return socket.getLocalPort(); } return super.getLocalPort(); } @Override public SocketAddress getRemoteSocketAddress() { if (isDelegating()) { return socket.getRemoteSocketAddress(); } return super.getRemoteSocketAddress(); } @Override public SocketAddress getLocalSocketAddress() { if (isDelegating()) { return socket.getLocalSocketAddress(); } return super.getLocalSocketAddress(); } @Override public final int getPort() { if (isDelegating()) { return socket.getPort(); } if (peerPort != -1) { // Return the port that has been explicitly set in the constructor. return peerPort; } return super.getPort(); } @Override public void addHandshakeCompletedListener(HandshakeCompletedListener listener) { checkArgument(listener != null, "Provided listener is null"); listeners.add(listener); } @Override public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) { checkArgument(listener != null, "Provided listener is null"); if (!listeners.remove(listener)) { throw new IllegalArgumentException("Provided listener is not registered"); } } @Override public FileDescriptor getFileDescriptor$() { if (isDelegating()) { return Platform.getFileDescriptor(socket); } return Platform.getFileDescriptorFromSSLSocket(this); } @Override @SuppressWarnings("UnsynchronizedOverridesSynchronized") public final void setSoTimeout(int readTimeoutMilliseconds) throws SocketException { if (isDelegating()) { socket.setSoTimeout(readTimeoutMilliseconds); } else { super.setSoTimeout(readTimeoutMilliseconds); this.readTimeoutMilliseconds = readTimeoutMilliseconds; } } @Override @SuppressWarnings("UnsynchronizedOverridesSynchronized") public final int getSoTimeout() throws SocketException { if (isDelegating()) { return socket.getSoTimeout(); } return readTimeoutMilliseconds; } @Override public final void sendUrgentData(int data) throws IOException { throw new SocketException("Method sendUrgentData() is not supported."); } @Override public final void setOOBInline(boolean on) throws SocketException { throw new SocketException("Method setOOBInline() is not supported."); } @Override public boolean getOOBInline() throws SocketException { return false; } @Override public SocketChannel getChannel() { // TODO(nmittler): Support channels? return null; } @Override public InputStream getInputStream() throws IOException { if (isDelegating()) { return socket.getInputStream(); } return super.getInputStream(); } @Override public OutputStream getOutputStream() throws IOException { if (isDelegating()) { return socket.getOutputStream(); } return super.getOutputStream(); } @Override public void setTcpNoDelay(boolean on) throws SocketException { if (isDelegating()) { socket.setTcpNoDelay(on); } else { super.setTcpNoDelay(on); } } @Override public boolean getTcpNoDelay() throws SocketException { if (isDelegating()) { return socket.getTcpNoDelay(); } return super.getTcpNoDelay(); } @Override public void setSoLinger(boolean on, int linger) throws SocketException { if (isDelegating()) { socket.setSoLinger(on, linger); } else { super.setSoLinger(on, linger); } } @Override public int getSoLinger() throws SocketException { if (isDelegating()) { return socket.getSoLinger(); } return super.getSoLinger(); } @Override @SuppressWarnings("UnsynchronizedOverridesSynchronized") public void setSendBufferSize(int size) throws SocketException { if (isDelegating()) { socket.setSendBufferSize(size); } else { super.setSendBufferSize(size); } } @Override @SuppressWarnings("UnsynchronizedOverridesSynchronized") public int getSendBufferSize() throws SocketException { if (isDelegating()) { return socket.getSendBufferSize(); } return super.getSendBufferSize(); } @Override @SuppressWarnings("UnsynchronizedOverridesSynchronized") public void setReceiveBufferSize(int size) throws SocketException { if (isDelegating()) { socket.setReceiveBufferSize(size); } else { super.setReceiveBufferSize(size); } } @Override @SuppressWarnings("UnsynchronizedOverridesSynchronized") public int getReceiveBufferSize() throws SocketException { if (isDelegating()) { return socket.getReceiveBufferSize(); } return super.getReceiveBufferSize(); } @Override public void setKeepAlive(boolean on) throws SocketException { if (isDelegating()) { socket.setKeepAlive(on); } else { super.setKeepAlive(on); } } @Override public boolean getKeepAlive() throws SocketException { if (isDelegating()) { return socket.getKeepAlive(); } return super.getKeepAlive(); } @Override public void setTrafficClass(int tc) throws SocketException { if (isDelegating()) { socket.setTrafficClass(tc); } else { super.setTrafficClass(tc); } } @Override public int getTrafficClass() throws SocketException { if (isDelegating()) { return socket.getTrafficClass(); } return super.getTrafficClass(); } @Override public void setReuseAddress(boolean on) throws SocketException { if (isDelegating()) { socket.setReuseAddress(on); } else { super.setReuseAddress(on); } } @Override public boolean getReuseAddress() throws SocketException { if (isDelegating()) { return socket.getReuseAddress(); } return super.getReuseAddress(); } @Override public void shutdownInput() throws IOException { if (isDelegating()) { socket.shutdownInput(); } else { super.shutdownInput(); } } @Override public void shutdownOutput() throws IOException { if (isDelegating()) { socket.shutdownOutput(); } else { super.shutdownOutput(); } } @Override public boolean isConnected() { if (isDelegating()) { return socket.isConnected(); } return super.isConnected(); } @Override public boolean isBound() { if (isDelegating()) { return socket.isBound(); } return super.isBound(); } @Override public boolean isClosed() { if (isDelegating()) { return socket.isClosed(); } return super.isClosed(); } @Override public boolean isInputShutdown() { if (isDelegating()) { return socket.isInputShutdown(); } return super.isInputShutdown(); } @Override public boolean isOutputShutdown() { if (isDelegating()) { return socket.isOutputShutdown(); } return super.isOutputShutdown(); } @Override public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) { if (isDelegating()) { socket.setPerformancePreferences(connectionTime, latency, bandwidth); } else { super.setPerformancePreferences(connectionTime, latency, bandwidth); } } @Override public String toString() { StringBuilder builder = new StringBuilder("SSL socket over "); if (isDelegating()) { builder.append(socket.toString()); } else { builder.append(super.toString()); } return builder.toString(); } /** * Returns the hostname that was supplied during socket creation. No DNS resolution is * attempted before returning the hostname. */ @Override String getHostname() { return peerHostname; } /** * This method enables Server Name Indication * * @param hostname the desired SNI hostname, or null to disable */ @Override void setHostname(String hostname) { peerHostname = hostname; } /** * For the purposes of an SSLSession, we want a way to represent the supplied hostname * or the IP address in a textual representation. We do not want to perform reverse DNS * lookups on this address. */ @Override String getHostnameOrIP() { if (peerHostname != null) { return peerHostname; } InetAddress peerAddress = getInetAddress(); if (peerAddress != null) { return Platform.getOriginalHostNameFromInetAddress(peerAddress); } return null; } /** * Note write timeouts are not part of the javax.net.ssl.SSLSocket API */ @Override void setSoWriteTimeout(int writeTimeoutMilliseconds) throws SocketException { throw new SocketException("Method setSoWriteTimeout() is not supported."); } /** * Note write timeouts are not part of the javax.net.ssl.SSLSocket API */ @Override int getSoWriteTimeout() throws SocketException { return 0; } /** * Set the handshake timeout on this socket. This timeout is specified in * milliseconds and will be used only during the handshake process. */ @Override void setHandshakeTimeout(int handshakeTimeoutMilliseconds) throws SocketException { throw new SocketException("Method setHandshakeTimeout() is not supported."); } final void checkOpen() throws SocketException { if (isClosed()) { throw new SocketException("Socket is closed"); } } @Override final PeerInfoProvider peerInfoProvider() { return peerInfoProvider; } /** * Called by {@link #notifyHandshakeCompletedListeners()} to get the currently active session. * Unlike {@link #getSession()}, this method must not block. */ abstract SSLSession getActiveSession(); abstract void setApplicationProtocolSelector(ApplicationProtocolSelectorAdapter selector); final void notifyHandshakeCompletedListeners() { if (listeners != null && !listeners.isEmpty()) { // notify the listeners HandshakeCompletedEvent event = new HandshakeCompletedEvent(this, getActiveSession()); for (HandshakeCompletedListener listener : listeners) { try { listener.handshakeCompleted(event); } catch (RuntimeException e) { // The RI runs the handlers in a separate thread, // which we do not. But we try to preserve their // behavior of logging a problem and not killing // the handshaking thread just because a listener // has a problem. Thread thread = Thread.currentThread(); thread.getUncaughtExceptionHandler().uncaughtException(thread, e); } } } } private boolean isDelegating() { // Checking for null to handle the case of calling virtual methods in the super class // constructor. return socket != null && socket != this; } }