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

com.oracle.coherence.common.internal.net.MultiplexedSocketProvider Maven / Gradle / Ivy

There is a newer version: 24.03
Show newest version
/*
 * Copyright (c) 2000, 2021, Oracle and/or its affiliates.
 *
 * Licensed under the Universal Permissive License v 1.0 as shown at
 * http://oss.oracle.com/licenses/upl.
 */
package com.oracle.coherence.common.internal.net;


import com.oracle.coherence.common.base.Blocking;
import com.oracle.coherence.common.collections.UnmodifiableSetCollection;
import com.oracle.coherence.common.io.Buffers;
import com.oracle.coherence.common.net.InetAddressComparator;
import com.oracle.coherence.common.net.InetAddresses;
import com.oracle.coherence.common.net.InetSocketAddress32;
import com.oracle.coherence.common.net.SafeSelectionHandler;
import com.oracle.coherence.common.net.SelectionService;
import com.oracle.coherence.common.net.SelectionServices;
import com.oracle.coherence.common.net.SocketProvider;
import com.oracle.coherence.common.net.TcpSocketProvider;
import com.oracle.coherence.common.util.Duration;

import java.net.ProtocolFamily;
import java.net.SocketOption;
import java.net.SocketTimeoutException;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.channels.spi.AbstractSelector;
import java.nio.channels.spi.AbstractSelectableChannel;
import java.nio.channels.spi.SelectorProvider;

import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.IOException;
import java.io.OutputStream;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;

import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import java.util.concurrent.*;

import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.LogRecord;


/**
 * MultiplexedSocketProvider produces a family of sockets which utilize
 * {@link InetSocketAddress32 extended port} values to allow for multiplexing
 * of sockets. The primary benefit of multiplexed server sockets is that it
 * allows a process to host many logical server sockets over a single inet
 * port, thus reducing firewall configuration.
 * 

* This SocketProvider makes use of {@link InetSocketAddress32} based addresses. * The break down of the port range is as follows. *

    *
  • * 0x00000000 .. 0x0000FFFF maps directly to standard inet based addresses, * i.e. non-multiplexed *
  • *
  • * 0x00010000 .. 0xFFFFFFFF maps to multiplexed sockets *
  • *
*

* Non-multiplexed sockets produced by this provider can communicate with * standard sockets, while multiplexed sockets can only communicate with other * multiplexed socket instances. *

* In the case of a multiplexed socket port, the upper sixteen bits represent * the actual inet port binding, and the lower sixteen bits represents the * sub-port or channel within the actual socket. The encoding of these two * 16-bit port values into a 32-bit int is as follows: *

nPort = ~(nPortBase <<< 16 | nPortSub)

* A sub-port of 0 represents a sub-ephemeral address. With the above encoding a * 32-bit port value of -1 thus represents a "double" ephemeral port which means * an ephemeral sub-port on an ephemeral port. *

* As a matter of convenience {@link #resolveAddress} supports resolving ports in a * dot delimited format, for instance 80.1 represents inet port 80, and sub-port 1. *

* Client sockets do not support local port bindings for ports above 0xFFFF. *

* Sub-ports in the range of 1..1023 inclusive are considered to be "well known" * and are not for general use. To make use of a sub port in this range, one * must be associated with a service and recorded in the {@link WellKnownSubPorts} * enum. Generally applications will either use ephemeral sub-ports, or sub-ports * of 1024 or greater. *

* The MultiplexedSocketProvider also supports bindings to non-local NAT addresses. * Specifically if there is a NAT address which routes to a local address, it is allowable * to bind to NAT address using this provider. *

* * @see InetSocketAddress32 * * @author mf 2010.12.27 */ public class MultiplexedSocketProvider implements SocketProvider { // ----- constructors --------------------------------------------------- /** * Construct a MultiplexedSocketProvider. * * @param deps the provider dependencies. */ public MultiplexedSocketProvider(Dependencies deps) { m_dependencies = copyDependencies(deps).validate(); } // ----- MultiplexedSocketProvider interface ---------------------------- /** * Return the provider's dependencies. * * @return the provider's dependencies */ public Dependencies getDependencies() { return m_dependencies; } // ----- SocketProvider interface --------------------------------------- /** * {@inheritDoc} *

* The address may be specified as is host:port, or host:base.sub where * the former uses an explicit 32 bit port, and the latter represents * the port as two 16 bit pairs from which a 32 bit port will be computed. */ @Override public SocketAddress resolveAddress(String sAddr) { int ofPort; int ofAddrEnd; if (sAddr.startsWith("[")) { // ipv6 formatted: [addr]:port ofAddrEnd = sAddr.lastIndexOf("]:") + 1; if (ofAddrEnd == 2) // 2 for [] { throw new IllegalArgumentException("address does not contain an hostname or ip"); } else if (ofAddrEnd == -1) { throw new IllegalArgumentException("address does not contain a port"); } ofPort = ofAddrEnd + 1; } else { // ipv4 formatted: addr:port ofAddrEnd = sAddr.lastIndexOf(':'); if (ofAddrEnd == 0) { throw new IllegalArgumentException("address does not contain an hostname of ip"); } else if (ofAddrEnd == -1) { throw new IllegalArgumentException("address does not contain a port"); } ofPort = ofAddrEnd + 1; } String sHost = sAddr.substring(0, ofAddrEnd); int ofPortSub = sAddr.indexOf('.', ofPort); if (ofPortSub == -1) { int nPort = Integer.parseInt(sAddr.substring(ofPort)); return new InetSocketAddress32(sHost, nPort); } else { int nPortBase = Integer.parseInt(sAddr.substring(ofPort, ofPortSub)); int nPortSub = Integer.parseInt(sAddr.substring(ofPortSub + 1)); return new InetSocketAddress32(sHost, getPort(nPortBase, nPortSub)); } } /** * {@inheritDoc} */ @Override public String getAddressString(Socket socket) { InetAddress addr = socket.getInetAddress(); if (addr == null) { return null; } // use host ip address String sAddr = addr.getHostAddress(); if (sAddr.contains(":")) { // ipv6 representation sAddr = "[" + sAddr + "]"; } int nPort = socket.getPort(); return sAddr + ":" + (isPortExtended(nPort) ? getBasePort(nPort) + "." + getSubPort(nPort) : nPort); } /** * {@inheritDoc} */ @Override public String getAddressString(ServerSocket socket) { InetAddress addr = socket.getInetAddress(); boolean fAny = addr.isAnyLocalAddress(); String sAddr; if (fAny) { // replace wildcard address with local hostname as this try { addr = InetAddress.getLocalHost(); } catch (UnknownHostException e) {} sAddr = addr.getHostName(); // Note: using addr.getCanonicalHostname generally returns an IP, thus defeating the purpose } else { // use host ip address sAddr = addr.getHostAddress(); } if (sAddr.contains(":")) { // ipv6 representation sAddr = "[" + sAddr + "]"; } int nPort = socket.getLocalPort(); return sAddr + ":" + (isPortExtended(nPort) ? getBasePort(nPort) + "." + getSubPort(nPort) : nPort); } /** * {@inheritDoc} */ @Override public ServerSocketChannel openServerSocketChannel() throws IOException { return new MultiplexedServerSocketChannel(this); } /** * {@inheritDoc} */ @Override public ServerSocket openServerSocket() throws IOException { return openServerSocketChannel().socket(); } /** * {@inheritDoc} */ @Override public SocketChannel openSocketChannel() throws IOException { return new MultiplexedSocketChannel(getDependencies() .getDelegateProvider().openSocketChannel(), /*addrLocal*/ null, /*bufHeader*/ null); } /** * {@inheritDoc} */ @Override public Socket openSocket() throws IOException { return new MultiplexedSocket(getDependencies().getDelegateProvider() .openSocket(), /*channel*/ null); } /** * {@inheritDoc} */ @Override public SocketProvider getDelegate() { return getDependencies().getDelegateProvider(); } // ----- inner class: MultiplexedSelectorProvider ----------------------- /** * MultiplexedSelectorProvider provides a SelectorProvider interface to * this SocketProvider. */ protected static class MultiplexedSelectorProvider extends SelectorProvider { // ----- constructors ------------------------------------------- public MultiplexedSelectorProvider(SelectorProvider delegate) { m_delegate = delegate; } public MultiplexedSelectorProvider(SocketProvider providerSocket) throws IOException { ServerSocketChannel chan = providerSocket.openServerSocketChannel(); m_delegate = chan.provider(); chan.close(); } // ----- SelectorProvider interface ----------------------------- @Override public DatagramChannel openDatagramChannel() throws IOException { throw new UnsupportedOperationException(); } @Override public DatagramChannel openDatagramChannel(ProtocolFamily family) throws IOException { throw new UnsupportedOperationException(); } @Override public Pipe openPipe() throws IOException { throw new UnsupportedOperationException(); } @Override public AbstractSelector openSelector() throws IOException { return new MultiplexedSelector(m_delegate.openSelector(), this); } @Override public ServerSocketChannel openServerSocketChannel() throws IOException { throw new UnsupportedOperationException(); } @Override public SocketChannel openSocketChannel() throws IOException { throw new UnsupportedOperationException(); } // ----- Object interface --------------------------------------- @Override public boolean equals(Object o) { if (o == this) { return true; } else if (o instanceof MultiplexedSelectorProvider) { return m_delegate.equals(((MultiplexedSelectorProvider) o).m_delegate); } else { return false; } } @Override public int hashCode() { return m_delegate.hashCode(); } // ----- data members ------------------------------------------- /** * The delegate SelectorProvider. */ protected SelectorProvider m_delegate; } // ----- inner class: MultiplexedSelector ------------------------------- /** * MultiplexedSelector is a Selector implementation for use with * Sockets produced by this provider. */ protected static class MultiplexedSelector extends WrapperSelector { // ----- constructors ------------------------------------------- /** * Construct a MultiplexedSelector. * * @param delegate the delegate selector * @param provider the corresponding SelectorProvider * * @throws IOException if an I/O error occurs */ protected MultiplexedSelector(Selector delegate, SelectorProvider provider) throws IOException { super(delegate, provider); m_setKeysRO = new UnmodifiableSetCollection( m_setKeys, super.keys()); } // ----- Selector interface ------------------------------------- @Override public Set keys() { Set setKeys = m_setKeysRO; ensureOpen(); return setKeys; } @Override public Set selectedKeys() { Set setReady = m_setReady; ensureOpen(); return setReady; } @Override public int selectNow() throws IOException { return select(-1); } @Override public synchronized int select(long cMillisTimeout) throws IOException { Set setKeys = m_setKeys; Set setKeysRO = m_setKeysRO; Set setReady = m_setReady; Set setPend = m_setPending; Selector delegate = m_delegate; ensureOpen(); synchronized (setKeysRO) // required by Selector doc { synchronized (setReady) // required by Selector doc { int cNew; synchronized (setPend) { if (!m_setCancelled.isEmpty()) { // we need to clear the canceled set, and ensure that any // canceled keys are removed from setKeys and setPend. Since // the canceled set can be updated concurrently, we must do // this a key at a time rather then via setCancel.clear(), this // way we ensure we've don't leave orphans in setKeys or setPend for (Iterator iter = m_setCancelled.iterator(); iter.hasNext(); ) { SelectionKey key = iter.next(); setKeys.remove(key); setPend.remove(key); iter.remove(); } } cNew = processPendingKeys(); } try { if (cMillisTimeout >= 0 && cNew == 0) { Blocking.select(delegate, cMillisTimeout); synchronized (setPend) { cNew = processPendingKeys(); } } else { delegate.selectNow(); } } finally { cleanupCancelledKeys(); } Set setClient = delegate.selectedKeys(); for (SelectionKey key : setClient) { if (setReady.add((SelectionKey) key.attachment())) { ++cNew; } } setClient.clear(); return cNew; } } } @Override public int select() throws IOException { return select(0); } // ----- AbstractSelector interface ----------------------------- @Override protected void implCloseSelector() throws IOException { Set setKeys = m_setKeys; Set setKeysRO = m_setKeysRO; Set setReady = m_setReady; super.implCloseSelector(); //let the thread in select return and release the locks synchronized(this) { synchronized (setKeysRO) { synchronized (setReady) { synchronized (setKeys) { for (Iterator iter = setKeys.iterator(); iter.hasNext(); ) { SelectionKey key = iter.next(); if (key.isValid()) { key.cancel(); } iter.remove(); } } } } } } @Override protected SelectionKey register(final AbstractSelectableChannel ch, int ops, Object att) { Set setKeys = m_setKeys; synchronized (setKeys) // ensures key can't be returned from selector without being visible in setKeys() { SelectionKey key; if (ch instanceof MultiplexedServerSocketChannel) { key = ((MultiplexedServerSocketChannel) ch).makeKey(this); key.interestOps(ops); key.attach(att); setKeys.add(key); } else { key = super.register(ch, ops, att); } if (((MultiplexedChannel) ch).readyOps() != 0) { addPendingKey(key); } return key; } } // ----- Object interface -------------------------------------- /** * {@inheritDoc} */ public String toString() { return "MultiplexedSelector(" + m_delegate + ")"; } // ----- helper methods ---------------------------------------- /** * Ensure that the Selector is open. */ protected void ensureOpen() { if (!isOpen()) { throw new ClosedSelectorException(); } } /** * Add the ready SelectionKey to the pending set. * * @param key the ready key */ protected void addPendingKey(SelectionKey key) { Set set = m_setPending; synchronized (set) { set.add(key); } } /** * Update Selector ready set with ready keys added to the pending set * from the underlying Selector. * * @return number of new keys added to the readySet. */ protected int processPendingKeys() { Set setPend = m_setPending; Set setReady = m_setReady; int cNew = 0; if (!setPend.isEmpty()) { for (Iterator iter = setPend.iterator(); iter.hasNext();) { SelectionKey key = iter.next(); int nOps = ((MultiplexedChannel) key.channel()).readyOps(); if (nOps == 0) { iter.remove(); } else if ((key.interestOps() & nOps) != 0 && setReady.add(key)) { ++cNew; } } } return cNew; } // ----- data members ------------------------------------------- /** * The registered key set. */ protected Set m_setKeys = new HashSet(); /** * The exposed registered key set. */ protected Set m_setKeysRO; /** * The ready key set. */ protected Set m_setReady = new HashSet(); /** * The pending ready key set. */ protected Set m_setPending = new HashSet(); /** * The cancelled key set. */ protected Set m_setCancelled = Collections.newSetFromMap(new ConcurrentHashMap()); } // ----- inner class: MultiplexedChannel -------------------------------- /** * Common interface implemented by all channels serviced by this provider. */ protected interface MultiplexedChannel { /** * Return the operations that can be satisfied by already buffered data. * * @return the operations that can be satisfied by already buffered data. */ public int readyOps(); } // ----- inner class: MultiplexedServerSocketChannel -------------------- /** * MultiplexedServerSocketChannel is an implementation of a * ServerSocketChannel which shares an underlying ServerSocketChannel with * a number of other MultiplexedServerSocketChannels. */ protected static class MultiplexedServerSocketChannel extends ServerSocketChannel implements MultiplexedChannel { // ----- constructors ------------------------------------------- public MultiplexedServerSocketChannel(MultiplexedSocketProvider provider) throws IOException { super(new MultiplexedSelectorProvider(provider.getDependencies().getDelegateProvider())); m_provider = provider; m_socket = createServerSocket(); } protected ServerSocket createServerSocket() throws IOException { return new ServerSocket() { @Override public void bind(SocketAddress endpoint) throws IOException { bind(endpoint, 0); } @Override public void bind(SocketAddress endpoint, int backlog) throws IOException { if (isBound()) { throw new IOException("already bound"); } else if (endpoint == null || endpoint instanceof InetSocketAddress32) { InetSocketAddress32 addr = (InetSocketAddress32) endpoint; m_address = m_provider.open(addr, MultiplexedServerSocketChannel.this, null); } else { throw new IllegalArgumentException("unsupported SocketAddress type"); } } @Override public InetAddress getInetAddress() { return m_address.getAddress(); } @Override public int getLocalPort() { return m_address.getPort(); } @Override public SocketAddress getLocalSocketAddress() { return m_address; } @Override public ServerSocketChannel getChannel() { return MultiplexedServerSocketChannel.this; } @Override public boolean isBound() { return m_address != null; } @Override public boolean isClosed() { return m_fClosed; } @Override public void close() throws IOException { BlockingQueue queue = m_queue; boolean fClosed; synchronized (queue) { super.close(); // just to free underlying FD fClosed = m_fClosed; m_fClosed = true; } if (!fClosed) { InetSocketAddress32 addr = (InetSocketAddress32) getLocalSocketAddress(); if (addr != null) { m_provider.close(addr); } for (ServerSelectionKey key = m_keyHead; key != null; key = key.m_next) { key.cancel(); } // close all pending client channels in the queue, otherwise they will // be leaked and could live a long time before bing GC'd and the other // side sees the socket get closed for (SocketChannel chan = queue.poll(); chan != null; chan = queue.poll()) { try { chan.close(); } catch (IOException ioe) {} } // Its possible that other threads might be doing blocking accept on the // ServerSocketChannel. This marker channel allows them to unblock and // identify that the ServerChannel has been closed. queue.add(SERVER_CHANNEL_CLOSED_MARKER); } } @Override public Socket accept() throws IOException { ServerSocketChannel chanServer = getChannel(); if (chanServer.isBlocking()) { SocketChannel channel = chanServer.accept(); long cMillis = chanServer.socket().getSoTimeout(); if (channel == null && cMillis > 0) { throw new SocketTimeoutException("SocketTimeout after configured SO_TIMEOUT " + cMillis + "ms"); } return channel.socket(); } throw new IllegalBlockingModeException(); } @Override public String toString() { if (isBound()) { return "MultiplexedServerSocket[addr=" + m_address.getAddress() + ",port=" + MultiplexedSocketProvider.getBasePort(m_address.getPort()) + ",subport=" + MultiplexedSocketProvider.getSubPort(m_address.getPort()) + "]"; } return "MultiplexedServerSocket[unbound]"; } InetSocketAddress32 m_address; }; } protected ServerSocketChannel getChannel() { return this; } // ----- ServerSocketChannel interface -------------------------- @Override public ServerSocket socket() { return m_socket; } @Override public SocketChannel accept() throws IOException { if (!socket().isBound()) { throw new IOException("not bound"); } try { BlockingQueue queue = m_queue; SocketChannel chan; if (isBlocking()) { long cMillis = socket().getSoTimeout(); chan = cMillis == 0 ? queue.take() : queue.poll(cMillis, TimeUnit.MILLISECONDS); } else { chan = queue.poll(); } if (chan == null) { return chan; } else if (chan == SERVER_CHANNEL_CLOSED_MARKER) { //requeue to unblock other threads that might be blocked in accept() queue.add(SERVER_CHANNEL_CLOSED_MARKER); throw new IOException("socket closed"); } else { try { chan.socket().setReceiveBufferSize(socket().getReceiveBufferSize()); } catch (IOException e) { // apparently the accepted socket has been closed; while we could try to // pull another from the queue that would increase the complexity of this // method, especially in the case of a timed wait. So instead we return // a closed socket, which is perfectly allowable } } return chan; } catch (InterruptedException e) { throw new InterruptedIOException(e.getMessage()); } } @Override protected void implCloseSelectableChannel() throws IOException { socket().close(); } @Override protected void implConfigureBlocking(boolean block) throws IOException { // nothing to do } @Override public ServerSocketChannel bind(SocketAddress local, int backlog) throws IOException { m_socket.bind(local, backlog); return this; } @Override public ServerSocketChannel setOption(SocketOption name, T value) throws IOException { if (name == StandardSocketOptions.SO_RCVBUF) { socket().setReceiveBufferSize(((Integer) value).intValue()); } else if (name == StandardSocketOptions.SO_REUSEADDR) { socket().setReuseAddress(((Boolean) value).booleanValue()); } else { throw new UnsupportedOperationException(name.toString()); } return this; } // ----- NetworkChannel methods -------------------------------- @Override public SocketAddress getLocalAddress() throws IOException { return socket().getLocalSocketAddress(); } @Override public T getOption(SocketOption name) throws IOException { if (name == StandardSocketOptions.SO_RCVBUF) { return (T) Integer.valueOf(socket().getReceiveBufferSize()); } else if (name == StandardSocketOptions.SO_REUSEADDR) { return (T) Boolean.valueOf(socket().getReuseAddress()); } else { throw new UnsupportedOperationException(name.toString()); } } @Override public Set> supportedOptions() { return SERVER_OPTIONS; } // ----- helpers ------------------------------------------------ /** * Add an SocketChannel to the accept queue * * @param chan the channel * * @return true iff the channel was queued */ protected boolean add(SocketChannel chan) { BlockingQueue queue = m_queue; synchronized (queue) { if (!m_fClosed && queue.offer(chan)) { for (ServerSelectionKey key = m_keyHead; key != null; key = key.m_next) { MultiplexedSelector selectorMultiplexed = (MultiplexedSelector) key.selector(); selectorMultiplexed.addPendingKey(key); selectorMultiplexed.wakeup(); } return true; } } return false; } /** * Register this channel with specified selector. * * @param selector the selector to register with * * @return the selection key */ protected SelectionKey makeKey(Selector selector) { ServerSelectionKey key = new ServerSelectionKey(selector); synchronized (m_queue) { key.m_next = m_keyHead; m_keyHead = key; } return key; } @Override public int readyOps() { return m_queue.isEmpty() ? 0 : SelectionKey.OP_ACCEPT; } @Override public String toString() { return "MultiplexedServerSocketChannel(" + socket() + ")"; } // ---- inner class: ServerSelectionKey ------------------------- class ServerSelectionKey extends SelectionKey { ServerSelectionKey(Selector selector) { m_selector = selector; } @Override public SelectableChannel channel() { return getChannel(); } @Override public Selector selector() { return m_selector; } @Override public boolean isValid() { return !m_fCanceled; } @Override public void cancel() { m_fCanceled = true; ((MultiplexedSelector) m_selector).m_setCancelled.add(this); synchronized (MultiplexedServerSocketChannel.this.m_queue) { ServerSelectionKey keyLast = null; for (ServerSelectionKey key = m_keyHead; key != null; key = key.m_next) { if (key == this) { if (keyLast == null) { m_keyHead = m_next; } else { keyLast.m_next = m_next; } break; } keyLast = key; } } } @Override public int interestOps() { ensureValid(); return m_nInterest; } @Override public SelectionKey interestOps(int ops) { ensureValid(); if (ops == 0 || ops == OP_ACCEPT) { m_nInterest = ops; return this; } throw new IllegalArgumentException(); } @Override public int readyOps() { // The only valid op for ServerSelectionKey is OP_ACCEPT. // The key will be returned in the MultiplexedSelector selected set // only if there is a pending socket for the MultiplexedServerSocketChannel // In that case, OP_ACCEPT is the readyOps. return OP_ACCEPT; } protected void ensureValid() { if (m_fCanceled) { throw new CancelledKeyException(); } } /** * The associated selector. */ protected Selector m_selector; /** * True iff the key has been canceled. */ protected boolean m_fCanceled; /** * The registered interest set. */ protected int m_nInterest; /** * The next key associated with this channel. */ protected ServerSelectionKey m_next; } // ----- data members ------------------------------------------- /** * The queue of ready client channels. */ protected final BlockingQueue m_queue = new LinkedBlockingDeque(); /** * The ServerSocket representation of this channel. */ protected ServerSocket m_socket; /** * The head of the SelectionKey linked-list. */ protected ServerSelectionKey m_keyHead; /** * MultiplexedSocketProvider associated with this ServerSocketChannel */ protected MultiplexedSocketProvider m_provider; /** * Flag indicating if the channel is closed. */ protected boolean m_fClosed; /** * Special SocketChannel that is added to the client channel queue to * indicate that this ServerSocketChannel is closed. This is needed to * unblock threads waiting for client sockets in accept(). */ protected static final SocketChannel SERVER_CHANNEL_CLOSED_MARKER = new SocketChannel(null) { @Override public Socket socket() { return null; } @Override public boolean isConnected() { return false; } @Override public boolean isConnectionPending() { return false; } @Override public boolean connect(SocketAddress remote) throws IOException { return false; } @Override public boolean finishConnect() throws IOException { return false; } @Override public int read(ByteBuffer dst) throws IOException { return 0; } @Override public long read(ByteBuffer[] dsts, int offset, int length) throws IOException { return 0; } @Override public int write(ByteBuffer src) throws IOException { return 0; } @Override public long write(ByteBuffer[] srcs, int offset, int length) throws IOException { return 0; } @Override protected void implCloseSelectableChannel() throws IOException { } @Override protected void implConfigureBlocking(boolean block) throws IOException { } @Override public SocketChannel bind(SocketAddress local) throws IOException { return null; } @Override public SocketChannel setOption(SocketOption name, T value) throws IOException { return null; } @Override public SocketChannel shutdownInput() throws IOException { return null; } @Override public SocketChannel shutdownOutput() throws IOException { return null; } @Override public SocketAddress getRemoteAddress() throws IOException { return null; } @Override public SocketAddress getLocalAddress() throws IOException { return null; } @Override public T getOption(SocketOption name) throws IOException { return null; } @Override public Set> supportedOptions() { return null; } }; } // ----- inner class: MultiplexedSocketChannel -------------------------- /** * MultiplexedSocketChannel */ protected static class MultiplexedSocketChannel extends WrapperSocketChannel implements MultiplexedChannel { // ----- constructors ------------------------------------------- /** * Create a MultiplexedSocketChannel for an incoming SocketChannel * * @param delegate incoming socket channel delegate * @param addrLocal the local address associated with this socket, or null * @param bufIn initial bytes to be returned from read calls before socket data, or null */ public MultiplexedSocketChannel(SocketChannel delegate, SocketAddress addrLocal, ByteBuffer bufIn) { super(delegate, new MultiplexedSelectorProvider(delegate.provider())); // Note: addrLocal is generally an InetSocketAddress32, the only exception is in the case that // we are accepting from a DemultiplexedServerSocket in which case the supplied address is just // an InetSocketAddress, which we ignore m_addrLocal = addrLocal instanceof InetSocketAddress32 ? (InetSocketAddress32) addrLocal : null; m_bufHeaderIn = bufIn; } // ----- WrapperSocketChannel methods --------------------------- /** * Return the delegate channel * * @return the delegate channel */ protected SocketChannel delegate() { return f_delegate; } /** * Produce a wrapper around the specified socket. * * @param socket the socket to wrap * @return the wrapper socket */ protected Socket wrapSocket(Socket socket) { return new MultiplexedSocket(socket, this); } // ----- SocketChannel methods ---------------------------------- @Override public boolean isConnected() { return super.isConnected() && m_bufHeaderOut == null; } @Override public boolean isConnectionPending() { return super.isConnectionPending() || m_bufHeaderOut != null; } @Override public boolean connect(SocketAddress remote) throws IOException { if (!(remote instanceof InetSocketAddress32)) { throw new IllegalArgumentException("unsupported SocketAddress type"); } InetSocketAddress32 addrPeer = (InetSocketAddress32) remote; if (addrPeer.isUnresolved()) { throw new UnresolvedAddressException(); } int nPort = addrPeer.getPort(); boolean fConnected = super.connect(getTransportAddress(addrPeer)); m_addrPeer = addrPeer; if (isPortExtended(nPort)) { ByteBuffer buf = m_bufHeaderOut = ByteBuffer.allocate(8); buf.putInt(PROTOCOL_ID).putInt(getSubPort(nPort)).flip(); return finishConnect(); } else { return fConnected; } } @Override public boolean finishConnect() throws IOException { boolean fResult = super.finishConnect(); if (fResult) { ByteBuffer buf = m_bufHeaderOut; if (buf != null) { delegate().write(buf); if (buf.hasRemaining()) { LOGGER.log(Level.FINEST, "{0} physical connection established, {2} of multiplexed" + " protocol header pending for logical connection to be established", new Object[]{this, buf.remaining()}); return false; } LOGGER.log(Level.FINEST, "{0} multiplexed connection established", new Object[]{this}); m_bufHeaderOut = null; } } return fResult; } @Override public int write(ByteBuffer src) throws IOException { return m_bufHeaderOut == null || finishConnect() ? super.write(src) : 0; } @Override public long write(ByteBuffer[] srcs, int offset, int length) throws IOException { return m_bufHeaderOut == null || finishConnect() ? super.write(srcs, offset, length) : 0; } @Override public int read(ByteBuffer dst) throws IOException { if (m_bufHeaderIn == null) { return super.read(dst); } else if (socket().isClosed()) { throw new ClosedChannelException(); } else { return readHeader(new ByteBuffer[]{dst}, 0, 1); } } @Override public long read(ByteBuffer[] dsts, int offset, int length) throws IOException { if (m_bufHeaderIn == null) { return super.read(dsts, offset, length); } else if (socket().isClosed()) { throw new ClosedChannelException(); } else { return readHeader(dsts, offset, length); } } @Override protected void implCloseSelectableChannel() throws IOException { super.implCloseSelectableChannel(); m_bufHeaderIn = null; } @Override public int readyOps() { return m_bufHeaderIn == null ? 0 : SelectionKey.OP_READ; } @Override public String toString() { return "MultiplexedSocketChannel(" + socket() + ")"; } // ----- helpers ----------------------------------------------- /** * Transfer as many bytes as possible from the inbound header buffer to the supplied buffer * * @param aBufDst the destination buffers * @param offset the starting offset to write into * @param length the maximum number of output buffers to access * * @return the number of bytes transferred */ protected int readHeader(ByteBuffer[] aBufDst, int offset, int length) { ByteBuffer bufIn = m_bufHeaderIn; int cbIn = bufIn.remaining(); int cb = 0; for (int i = 0; i < length && cbIn > 0; ++i) { ByteBuffer bufOut = aBufDst[offset + i]; int cbOut = bufOut.remaining(); for (int j = 0, c = Math.min(cbOut, cbIn); j < c; ++j) { bufOut.put(bufIn.get()); ++cb; --cbIn; } } if (cbIn == 0) { m_bufHeaderIn = null; // header has been transferred } return cb; } @Override public WrapperSelector.WrapperSelectionKey registerInternal(WrapperSelector selector, int ops, Object att) throws IOException { WrapperSelector.WrapperSelectionKey key = new SocketSelectionKey(selector, f_delegate .register(selector.getDelegate(), 0), att); key.interestOps(ops); return key; } /** * SelectionKey which is aware of the state of the channel's inbound buffer. */ protected class SocketSelectionKey extends WrapperSelector.WrapperSelectionKey { public SocketSelectionKey(WrapperSelector selector, SelectionKey key, Object att) { super(selector, key, att); } @Override public SelectableChannel channel() { return MultiplexedSocketChannel.this; } @Override public SelectionKey interestOps(int ops) { // handle the case where we've connected the underlying socket but not yet pushed the protocol // header. In such as case we tell the user that we're not connected, thus they express OP_CONNECT // interest, but what we really need is OP_WRITE interest boolean fConnecting = m_bufHeaderOut != null && // logical connection is still pending !delegate().isConnectionPending() && // physical connection is complete (ops & OP_CONNECT) != 0 && // app expressed interest in OP_CONNECT (ops & OP_WRITE) == 0; // didn't ask for OP_WRITE super.interestOps(fConnecting ? (ops | OP_WRITE) & ~OP_CONNECT // add in OP_WRITE and remove OP_CONNECT, since physical connect is complete : ops); m_nOpsInterest = ops; return this; } @Override public int interestOps() { return m_nOpsInterest; } @Override public int readyOps() { // TODO: this is a little broken, as it relies on the current value of // interestOps rather then the value used during the last call into the // selector. return (super.readyOps() | MultiplexedSocketChannel.this.readyOps()) & interestOps(); } @Override public String toString() { return "MultiplexedSocketChannel{" + delegate() + "}"; } /** * The cached interest ops. */ protected int m_nOpsInterest; } // ----- data members ------------------------------------------ /** * The peer's address. */ protected InetSocketAddress32 m_addrPeer; /** * The socket's local address. */ protected InetSocketAddress32 m_addrLocal; /** * The outbound protocol header. */ protected ByteBuffer m_bufHeaderOut; /** * The inbound protocol header, or more specifically bytes which were read looking for the header but * need to be returned from socket read calls before any further socket data. */ protected ByteBuffer m_bufHeaderIn; } // ----- inner class: MultiplexedSocket ------------------------------ /** * MultiplexedSocket is an implementation of a Socket that works with * multiplexed socket addresses represented by InetSocketAddress32. */ protected static class MultiplexedSocket extends WrapperSocket { // ----- constructors ------------------------------------------- /** * Construct a MultiplexedSocket * * @param delegate underlying delegate socket * @param channel SocketChannel to be associated with the MultiplexedSocket. * Could be null for an outbound socket. */ public MultiplexedSocket(Socket delegate, MultiplexedSocketChannel channel) { super(delegate); f_channel = channel; if (channel == null) { f_out = null; f_in = null; } else { f_out = new SocketChannelOutputStream(f_channel); f_in = new SocketChannelInputStream(f_channel); } } /** * {@inheritDoc} */ @Override public SocketChannel getChannel() { return f_channel; } /** * {@inheritDoc} */ @Override public void connect(SocketAddress addr) throws IOException { connect(addr, 0); } /** * {@inheritDoc} */ @Override public void connect(SocketAddress addr, int cMillis) throws IOException { if (!(addr instanceof InetSocketAddress32)) { throw new IllegalArgumentException("unsupported SocketAddress type"); } InetSocketAddress32 addr32 = (InetSocketAddress32) addr; super.connect(getTransportAddress(addr32), cMillis); m_addrPeer = addr32; if (isPortExtended(addr32.getPort())) { ByteBuffer buff = ByteBuffer.allocate(8); buff.putInt(PROTOCOL_ID).putInt(getSubPort(addr32.getPort())).flip(); getOutputStream().write(buff.array()); getOutputStream().flush(); } } /** * {@inheritDoc} */ @Override public void bind(SocketAddress addr) throws IOException { if (addr == null || addr instanceof InetSocketAddress32) { InetSocketAddress32 addrBind = (InetSocketAddress32) addr; if (addrBind != null) { int nSub = getSubPort(addrBind.getPort()); if (nSub != 0 && nSub != -1) { throw new IOException("cannot bind client sockets to non-zero sub-ports"); } } if (addrBind == null) { super.bind(null); addrBind = new InetSocketAddress32(super.getLocalAddress(), super.getLocalPort()); } else { super.bind(getTransportAddress(addrBind)); } m_addrLocal = addrBind; } else { throw new IllegalArgumentException("unsupported SocketAddress type"); } } /** * {@inheritDoc} */ @Override public SocketAddress getLocalSocketAddress() { InetSocketAddress32 addr = m_addrLocal; if (addr == null) { if (f_channel != null) { // in the case of an accepted channel we need to use the server socket's address addr = m_addrLocal = f_channel.m_addrLocal; } if (addr == null) { InetSocketAddress addrReal = (InetSocketAddress) super.getLocalSocketAddress(); if (addrReal != null) { addr = m_addrLocal = new InetSocketAddress32(addrReal.getAddress(), addrReal.getPort()); } } } return addr; } @Override public InetAddress getLocalAddress() { InetSocketAddress32 addr = (InetSocketAddress32) getLocalSocketAddress(); return addr == null ? InetAddresses.ADDR_ANY : addr.getAddress(); } /** * {@inheritDoc} */ @Override public int getLocalPort() { InetSocketAddress32 addr = (InetSocketAddress32) getLocalSocketAddress(); return addr == null ? -1 : addr.getPort(); } /** * {@inheritDoc} */ @Override public SocketAddress getRemoteSocketAddress() { InetSocketAddress32 addr = m_addrPeer; if (addr == null) { // compute from delegate socket if (f_channel != null) { // in the case this is a channel associated socket, use its address info addr = m_addrPeer = f_channel.m_addrPeer; } if (addr == null) { // compute from delegate socket InetSocketAddress addrReal = (InetSocketAddress) super.getRemoteSocketAddress(); if (addrReal != null) { addr = m_addrPeer = new InetSocketAddress32(addrReal.getAddress(), addrReal.getPort()); } } } return addr; } /** * {@inheritDoc} */ @Override public int getPort() { InetSocketAddress32 addr = (InetSocketAddress32) getRemoteSocketAddress(); return addr == null ? 0 : addr.getPort(); } /** * {@inheritDoc} */ @Override public boolean isInputShutdown() { MultiplexedSocketChannel chan = f_channel; return chan == null ? super.isInputShutdown() : chan.m_bufHeaderIn == null && super.isInputShutdown(); } /** * {@inheritDoc} */ @Override public void shutdownInput() throws IOException { super.shutdownInput(); MultiplexedSocketChannel chan = f_channel; if (chan != null) { chan.m_bufHeaderIn = null; } } @Override public InputStream getInputStream() throws IOException { return f_in == null ? super.getInputStream() : f_in; } @Override public OutputStream getOutputStream() throws IOException { return f_out == null ? super.getOutputStream() : f_out; } /** * {@inheritDoc} */ @Override public void close() throws IOException { super.close(); MultiplexedSocketChannel chan = f_channel; if (chan != null) { chan.m_bufHeaderIn = null; } } @Override public String toString() { return "MultiplexedSocket{" + super.toString() + "}"; } /** * Associated Socket channel. Could be null for an outbound socket. */ protected final MultiplexedSocketChannel f_channel; /** * OutuputStream for channel based sockets. */ protected final SocketChannelOutputStream f_out; /** * InputStream for channel based sockets. */ protected final SocketChannelInputStream f_in; /** * Local address */ protected InetSocketAddress32 m_addrLocal; /** * Peer address */ protected InetSocketAddress32 m_addrPeer; } // ----- helpers -------------------------------------------------------- /** * Start listening for connections on the specified address. * * @param addr the address to listen on * @param server the server socket * @param setBaseExclude the set of base ports to exclude during ephemeral bind, or null * * @return the bound address * * @throws IOException on an I/O error */ protected InetSocketAddress32 open(InetSocketAddress32 addr, MultiplexedServerSocketChannel server, Set setBaseExclude) throws IOException { if (addr == null) { addr = new InetSocketAddress32(InetAddress.getLocalHost(), 0); } else if (addr.isUnresolved()) { throw new SocketException(addr.getHostName()); } InetSocketAddress addrListen = getTransportAddress(addr); InetAddress addrIp = addr.getAddress(); int nPort = addr.getPort(); ServerSocket socket = server.socket(); int nPortSub = getSubPort(nPort); if (nPortSub > 0 && nPortSub <= WELL_KNOWN_SUB_PORT_END) { // ensure that the sub port has been registered as a WellKnownSubPort. This is meant to prevent // accidental usage of the space by apps which have not had a well known port assigned to them. // this is not a security feature. boolean fKnown = false; for (WellKnownSubPorts port : WellKnownSubPorts.values()) { if (port.getSubPort() == nPortSub) { fKnown = true; break; } } if (!fKnown) { throw new IOException( "attempt to bind to unassigned sub-port " + nPortSub + " in well known subport range"); } } // handle NAT addresses if (addrIp != null && InetAddresses.isNatLocalAddress(addrIp, nPort)) { addrIp = InetAddresses.ADDR_ANY; } // handle the case where doing a multiplexed binding to all IPs if (addrIp != null && addrIp.isAnyLocalAddress()) { boolean fEphemeral = getSubPort(nPort) == 0 /*ephemeral sub*/ || getBasePort(nPort) == 0 /*ephemeral base*/; // manually add a listener for each IP in the system Set setAddrAcquired = new HashSet<>(); Set setIpAll = new TreeSet<>(InetAddressComparator.INSTANCE); // ordered to avoid "deadlock" in case of concurrent process bind // COH-12612: the IPv6 zone ID can be mangled on some operating systems (e.g. OS X). // skip over duplicate addresses to avoid "address already in use" errors. for (Enumeration iter = NetworkInterface.getNetworkInterfaces(); iter .hasMoreElements(); ) { NetworkInterface nic = iter.nextElement(); for (Enumeration iterAddr = nic.getInetAddresses(); iterAddr.hasMoreElements(); ) { setIpAll.add(iterAddr.nextElement()); } for (Enumeration iterSub = nic.getSubInterfaces(); iterSub.hasMoreElements(); ) { for (Enumeration iterAddr = iterSub.nextElement().getInetAddresses(); iterAddr .hasMoreElements(); ) { setIpAll.add(iterAddr.nextElement()); } } } try { for (Iterator iter = setIpAll.iterator(); iter.hasNext(); ) { InetAddress addrNext = iter.next(); if (!addrNext.isAnyLocalAddress()) // Solaris lists wildcard as one of the local IPs { try { // re-enter for a single IP InetSocketAddress32 addrAdd = open(new InetSocketAddress32(addrNext, nPort), server, setBaseExclude); setAddrAcquired.add(addrAdd); if (getSubPort(nPort) == 0 || getBasePort(nPort) == 0) { // first acquisition from ephemeral; switch to // actual for the remainder of this pass nPort = addrAdd.getPort(); } } catch (IOException e) { // this may be a non-bindable IP, such as IPv6 temporary IPs rather then a port // conflict. Validate by seeing by testing for port conflict try { close(open(new InetSocketAddress32(addrNext, 0), server, setBaseExclude)); } catch (IOException e2) { // this must be an outbound only IP so we shouldn't have attempted to bind it continue; } // if we get here then apparently the original port was simply not available if (fEphemeral && nPort != addr.getPort()) { // since we're emulating a ephemeral binding on wildcard it's possible that the // actual ephemeral allocation on the first real IP isn't actually available // on one of the subsequent IPs // Note: we hold onto the ports we've already acquired, not because we'll use // them but because we don't want to get them again and keep failing nPort = addr.getPort(); // try again iter = setIpAll.iterator(); // from the beginning if (setBaseExclude == null) { setBaseExclude = new HashSet<>(); } // avoid this port on future binds, this is necessary in case of a double ephemeral // binding, we'd endlessly reslect the same base port and fail forever setBaseExclude.add(getBasePort(nPort)); } else { throw e; } } } } // clear out the addresses we're going to keep so they don't get freed in the finally // leaving just those temporary ephemeral bindings which we were not able to acquire // on all IPs for (Iterator iter = setAddrAcquired.iterator(); iter.hasNext(); ) { InetSocketAddress32 addr32 = iter.next(); if (addr32.getPort() == nPort) { iter.remove(); } } return fEphemeral ? new InetSocketAddress32(addr.getAddress(), nPort) : addr; } finally // clean up any bindings we aren't going to keep { for (InetSocketAddress32 addrDrop : setAddrAcquired) { try { close(addrDrop); } catch (IOException e) {} } } } int nPortEphemeralLow = m_nPortEphemeralLow; int nPortEphemeralHi = m_nPortEphemeralHi; // handle the case where it is a double ephemeral address ConcurrentMap mapListener = m_mapListener; if (getBasePort(nPort) == 0 && !mapListener.isEmpty()) { // select any multiplexed listener for (Map.Entry entry : mapListener.entrySet()) { int nPortUsed = entry.getKey().getPort(); if ((setBaseExclude == null || setBaseExclude.contains(nPortUsed)) && nPortUsed >= nPortEphemeralLow && nPortUsed <= nPortEphemeralHi && entry.getKey().getAddress().equals(addrIp)) { try { // re-enter for each possible port return open(new InetSocketAddress32(addrIp, getPort(nPortUsed, getSubPort(nPort))), server, null); } catch (IOException e) { } } } } // handle the basic case for a single IP ServerSocketChannel chanGarbage = null; while (true) { Listener listener = mapListener.get(addrListen); if (listener == null) { // try to bind to the address ServerSocketChannel chanListen = new ListenChannel( getDependencies().getDelegateProvider().openServerSocketChannel()); try { ServerSocket socketListen = chanListen.socket(); socketListen.setReceiveBufferSize(socket.getReceiveBufferSize()); socketListen.bind(addrListen, getDependencies().getBacklog()); if (getBasePort(nPort) == 0) { // "learn" the native ephemeral base port range as we bind to real ephemeral ports int nPortBind = chanListen.socket().getLocalPort(); if (nPortBind == 65535) { // we don't want this ephemeral port since we would not be able to bind sub-ports to it; // retry while holding it to obtain a different ephemeral base. chanGarbage = chanListen; // will be closed after next bind continue; } else if (nPortBind < nPortEphemeralLow) { m_nPortEphemeralLow = nPortBind; } if (nPortBind > nPortEphemeralHi) { m_nPortEphemeralHi = nPortBind; } addrListen = (InetSocketAddress) chanListen.socket().getLocalSocketAddress(); } chanListen.configureBlocking(false); listener = new Listener(chanListen); getDependencies().getSelectionService().register(chanListen, listener); mapListener.put(addrListen, listener); } catch (IOException e) { chanListen.close(); if (chanGarbage != null) { chanGarbage.close(); } throw e; } } if (chanGarbage != null) { chanGarbage.close(); chanGarbage = null; } synchronized (listener) { ServerSocketChannel chanListen = listener.getChannel(); if (chanListen.isOpen()) { return listener.register(getSubPort(nPort), server); } } } } /** * Stop listening for connections on the specified address. * * @param addr the address to stop listening on * * @throws IOException on an I/O error */ protected void close(InetSocketAddress32 addr) throws IOException { InetAddress addrIp = addr.getAddress(); int nPort = addr.getPort(); if (addrIp != null && addrIp.isAnyLocalAddress()) { // manually remove a listener for each IP in the system for (Enumeration iter = NetworkInterface.getNetworkInterfaces(); iter.hasMoreElements(); ) { for (Enumeration iterAddr = iter.nextElement().getInetAddresses(); iterAddr .hasMoreElements(); ) { try { InetAddress addrIface = iterAddr.nextElement(); // COH-17162: uniquely Solaris IPMP permits having an any local address // within the network interface if (!addrIface.isAnyLocalAddress()) { close(new InetSocketAddress32(addrIface, addr.getPort())); } } catch (IOException e) { } } } return; } ConcurrentMap mapListener = m_mapListener; SocketAddress addrListen = getTransportAddress(addr); Listener listener = mapListener.get(addrListen); if (listener == null) { throw new IOException("not bound"); } else { synchronized (listener) { if (listener.deregister(getSubPort(nPort))) { listener.getChannel().close(); mapListener.remove(addrListen); } } } } // ----- inner class: Listener ------------------------------------------ /** * Listener is a SelectionHandler which waits on the real * ServerSocketChannel for new connections. */ protected class Listener extends SafeSelectionHandler { public Listener(ServerSocketChannel chan) throws IOException { super(chan); } @Override protected int onReadySafe(int nOps) throws IOException { SelectionService svc = getDependencies().getSelectionService(); SocketChannel chan; while ((chan = getChannel().accept()) != null) { try { chan.configureBlocking(false); svc.register(chan, new Switcher(chan)); } catch (IOException e) { // accepted chan is no longer usable try { chan.close(); } catch (IOException e2) { } } } return OP_ACCEPT; } @Override protected int onException(Throwable t) { if (getChannel().isOpen()) { LogRecord rec = new LogRecord(Level.WARNING, "unhandled exception; continuing"); rec.setThrown(t); getDependencies().getLogger().log(rec); return OP_ACCEPT; } // else in shutdown; ignore return 0; } /** * Register an acceptor queue with a sub-port on this listener * * @param nPortSub the sub-port, or -1 for standard port binding * @param server the server socket * * @throws IOException on registration failure * * @return the bound address */ protected InetSocketAddress32 register(int nPortSub, MultiplexedServerSocketChannel server) throws IOException { ConcurrentNavigableMap map = m_mapBindings; ServerSocket socket = getChannel().socket(); if (nPortSub == 0) { // first try a "random" port nPortSub = EPHEMERAL_SUB_PORT_START + (System .identityHashCode(server) % (0xFFFF - EPHEMERAL_SUB_PORT_START)); if (map.putIfAbsent(nPortSub, server) == null) { // port acquired return new InetSocketAddress32(socket.getInetAddress(), getPort(socket.getLocalPort(), nPortSub)); } // scan through and find a free port nPortSub = 0xFFFF; for (Iterator iter = m_setEphemeral.iterator(); nPortSub >= EPHEMERAL_SUB_PORT_START; ) { int nPortUsed = Math.max(EPHEMERAL_SUB_PORT_START - 1, iter.hasNext() ? iter.next() : 0); for (; nPortSub > nPortUsed; --nPortSub) { // nPortSub is free if (map.putIfAbsent(nPortSub, server) == null) { // port acquired return new InetSocketAddress32(socket.getInetAddress(), getPort(socket.getLocalPort(), nPortSub)); } } nPortSub = nPortUsed - 1; } throw new IOException("no available ephemeral sub-ports within base port " + socket.getLocalPort()); } else if (map.putIfAbsent(nPortSub, server) != null) { throw new IOException("address already in use: " + getAddressString(socket) + '.' + nPortSub); } return new InetSocketAddress32(socket.getInetAddress(), getPort(socket.getLocalPort(), nPortSub)); } /** * Deregister an acceptor. * * @param nPortSub the sub-port * * @return true iff this was the last registered port * * @throws IOException on deregistration failure */ protected boolean deregister(int nPortSub) throws IOException { ConcurrentMap map = m_mapBindings; if (map.remove(nPortSub) == null) { throw new IOException("not bound"); } return map.isEmpty(); } // ----- inner class: Switcher ---------------------------------- /** * Switcher handles the initial protocol header from new connections. */ public class Switcher extends SafeSelectionHandler { public Switcher(SocketChannel chan) { super(chan); } @Override protected int onReadySafe(int nOps) throws IOException { final SocketChannel chan = getChannel(); final ByteBuffer buf = m_buf; if (chan.read(buf) < 0) { // socket closed before, process based on what has been read thus far accept(); return 0; } // check for multiplexed protocol header boolean fStandard = false; switch (buf.position()) { default: fStandard |= buf.get(3) != (byte) PROTOCOL_ID; case 3: fStandard |= buf.get(2) != (byte) (PROTOCOL_ID >>> 8); case 2: fStandard |= buf.get(1) != (byte) (PROTOCOL_ID >>> 16); case 1: fStandard |= buf.get(0) != (byte) (PROTOCOL_ID >>> 24); case 0: break; } Map mapBindings = m_mapBindings; if (fStandard || // non-multiplexed header bytes !buf.hasRemaining() || // enough bytes to accept if multiplexed mapBindings.containsKey(-1) && mapBindings.size() == 1) // no sub-port listeners { accept(); return 0; } else if (m_timer == null) { // this only occurs on our initial registration, i.e. first pass // if we failed to accept on first pass setup a timer to route this // as a "standard" socket if we fail to accept within timeout // Note: we do this here to avoid registering the non-cancelable timer // if we don't have to final Dependencies deps = getDependencies(); Runnable timer = m_timer = new Runnable() { public void run() { if (isPending()) { // timeout; to be here we either receive nothing at all on the connection; or we've received // only a partial protocol header try { // perform one final read attempt switch (chan.read(buf)) { case -1: // socket had actually been closed, but we hadn't detected it LOGGER.log(Level.WARNING, "{0} handling delayed close of accepted connection from {1} after {2} ms", new Object[]{Listener.this.getChannel().socket() .getLocalSocketAddress(), chan.socket() .getRemoteSocketAddress(), deps .getIdentificationTimeoutMillis()}); break; case 0: // "expected" result LOGGER.log(Level.WARNING, "{0} failed to identify protocol from {1} bytes for connection from {2} after {3} ms, " + "handling as non-multiplexed", new Object[]{Listener.this.getChannel().socket() .getLocalSocketAddress(), buf.position(), chan.socket() .getRemoteSocketAddress(), deps .getIdentificationTimeoutMillis()}); break; default: // more data was available, but we hadn't detected it LOGGER.log(Level.WARNING, "{0} handling delayed read on accepted connection from {1} after {2} ms", new Object[] {Listener.this.getChannel().socket().getLocalSocketAddress(), chan.socket().getRemoteSocketAddress(), deps.getIdentificationTimeoutMillis()}); break; } accept(); } catch (IOException e) { onException(e); } } } }; deps.getSelectionService().invoke(chan, timer, deps.getIdentificationTimeoutMillis()); } return OP_READ; } @Override protected int onException(Throwable t) { if (LOGGER.isLoggable(Level.FINEST)) { LogRecord record = new LogRecord(Level.FINEST, "{0} exception while waiting for multiplexed header on {1}"); record.setParameters(new Object[]{ Listener.this.getChannel().socket().getLocalSocketAddress(), getChannel().socket().getRemoteSocketAddress()}); record.setThrown(t); LOGGER.log(record); } return super.onException(t); } /** * Accept the chanel and queue it to the appropriate MultiplexedServerSocketChannel * * @throws IOException if an I/O error occurs */ protected void accept() throws IOException { SocketChannel chan = getChannel(); ByteBuffer buf = m_buf; // identify if the protocol header is present int nSubPort; if (!buf.hasRemaining() && buf.getInt(0) == PROTOCOL_ID) { // multiplexed client is connecting nSubPort = buf.getInt(4); buf = null; } else { // standard client connecting nSubPort = -1; if (!buf.flip().hasRemaining()) { buf = null; } // else give the bytes back to the channel } // find SeverSocketChannel final SelectionService svc = getDependencies().getSelectionService(); svc.register(chan, /*handler*/ null); // use invocation to ensure that the channel has been unregistered before continuing, specifically we // must be unregisterd before setting chan to blocking mode final int nFinSubPort = nSubPort; final ByteBuffer bufFin = buf; final SocketChannel chanFin = chan; svc.invoke(chan, new Runnable() { @Override public void run() { SocketChannel chan = chanFin; try { try { chan.configureBlocking(true); // look like every accepted socket } catch (IllegalBlockingModeException e) { // deregistration hasn't occurred yet (there is no order between register and invoke) svc.invoke(chan, this, 0); return; } MultiplexedServerSocketChannel server = m_mapBindings.get(nFinSubPort); if (server != null) { // The MultiplexedChannel is replaced with another MultiplexedChannel to address a deficiency in // the WrapperChannel infrastructure which prevents a channel from being registered, canceled, and // re-registered with the same Selector. This is caused by the fact that WrapperSocketChannel extends // AbstractSocketChannel which in turn hides any reasonable way of removing formerly registered // SelectionKeys. So here we work-around the issue by producing a new channel. // Also we take this opportunity to swap the local address for the accepted connection if // it appears to be a NAT based connection. SocketAddress addrSrv = server.getLocalAddress(); InetAddress addrSrvIP = InetAddresses.getAddress(addrSrv); InetAddress addrLocal = InetAddresses.isNatLocalAddress(addrSrv) ? addrSrvIP // substitute the local NAT address : addrSrvIP.isAnyLocalAddress() && InetAddresses.hasNatLocalAddress() ? InetAddresses.getRoutes(InetAddresses.getLocalBindableAddresses(), Collections.singleton(chan.socket().getInetAddress())).iterator().next() // find best (possibly NAT) address : chan.socket().getLocalAddress(); // don't change chan = new MultiplexedSocketChannel(((MultiplexedSocketChannel) chan).delegate(), new InetSocketAddress32(addrLocal, chan.socket().getLocalPort()), bufFin); } if (server == null || !server.add(chan)) { // no registered ServerSocketChannel, or server queue was full LogRecord record = new LogRecord(Level.FINE, "{0} rejecting connection from {1} to subport {2} due to {3}, header {4}"); record.setParameters(new Object[] { Listener.this.getChannel().socket().getLocalSocketAddress(), chan.socket().getRemoteSocketAddress(), nFinSubPort, server == null ? "absence of corresponding MultiplexedServerSocket" : "backlogged MultiplexedServerSocket", bufFin == null ? null : Buffers.toString(bufFin)}); LOGGER.log(record); // TODO: it would be nice to be able to send back some indicator that this is meant to appear // as a reject rather then an EOS. As is a multiplexed client would only see a rejected connection // if there was no listener for the base port, but no listener for a sub-port looks like a connect/eos // the problem with doing this would be that we couldn't cover all cases, such as ones where we // don't yet know if the client is multiplexed. So while we could add the feature we still couldn't // cover all cases, thus any user of multiplexing must be able to handle a connect/eos case as if // it was a reject. chan.close(); } } catch (IOException e) { try { chan.close(); } catch (IOException e2) {} } } }, 0); m_buf = null; // mark as accepted } /** * Return true iff the channel has yet to be "accepted". * * @return true iff the channel has yet to be "accepted" */ protected boolean isPending() { return getChannel().isOpen() && m_buf != null; } /** * Holder for protocol header. */ protected ByteBuffer m_buf = ByteBuffer.allocate(8); /** * Timeout task watching for non-multiplexed connections. */ protected Runnable m_timer; } /** * Map of port to servers. */ protected final ConcurrentNavigableMap m_mapBindings = new ConcurrentSkipListMap(); /** * The Set of allocated sub-ports in the ephemeral range. */ protected final SortedSet m_setEphemeral = m_mapBindings.descendingKeySet(); } // ----- helpers -------------------------------------------------------- /** * Return the underlying transport address for the specified address. * * @param addr the multiplexed address * * @return the transport address */ public static InetSocketAddress getTransportAddress(InetSocketAddress32 addr) { if (addr == null) { return null; } return new InetSocketAddress(addr.getAddress(), getBasePort(addr.getPort())); } /** * Return true iff the specified port represents an extended port. * * @param nPort the port to test * * @return true iff the specified port represents a extended port */ public static boolean isPortExtended(int nPort) { return (nPort & 0xFFFF0000) != 0; } /** * Return the base (transport) port for a given 32b port. * * @param nPort the port * * @return the base port */ public static int getBasePort(int nPort) { return isPortExtended(nPort) ? ~nPort >>> 16 : nPort; } /** * Return the sub-port for a given 32b port. * * @param nPort the port * * @return the sub-port, or -1 if none */ public static int getSubPort(int nPort) { return isPortExtended(nPort) ? ~nPort & 0x0FFFF : -1; } /** * Return the 32 bit port for the specified base and sub port * * @param nPortBase the base port * @param nPortSub the sub port, or -1 for none * * @return the 32 bit port version */ public static int getPort(int nPortBase, int nPortSub) { if (nPortBase < 0 || nPortBase > 0xFFFF) { throw new IllegalArgumentException("base port " + nPortBase + " is out of range"); } if (nPortSub < -1 || nPortSub > 0xFFFF) { throw new IllegalArgumentException("sub port " + nPortSub + " is out of range"); } if (nPortBase == 0xFFFF && nPortSub >= 0) { throw new IllegalArgumentException("base port of 65535 does not support sub ports"); } return nPortSub == -1 ? nPortBase : ~(nPortBase << 16 | nPortSub); } // ----- inner class: ListenChannel --------------------------------------------------- /** * Helper wrapper for the real ServerSocketChannel to allow it to be managed by the * multiplexed SelectionService. */ protected class ListenChannel extends WrapperServerSocketChannel implements MultiplexedChannel { public ListenChannel(ServerSocketChannel delegate) throws IOException { super(delegate, new MultiplexedSelectorProvider(delegate.provider())); } @Override public SocketChannel accept() throws IOException { SocketChannel chan = f_delegate.accept(); return chan == null ? null : new MultiplexedSocketChannel(chan, this.socket().getLocalSocketAddress(), null); } @Override public int readyOps() { return 0; } } // ----- interface: Dependencies ---------------------------------------- /** * Dependencies describes the MultiplexedSocketProvider's dependencies. */ public interface Dependencies { /** * Return the underlying SocketProvider to use. * * @return the SocketProvider */ public SocketProvider getDelegateProvider(); /** * Return the SelectionService to utilize for processing IO. * * @return the SelectionService */ public SelectionService getSelectionService(); /** * Return the backlog setting for the underlying SocketProvider. * * @return the backlog setting */ public int getBacklog(); /** * Return the number of milliseconds an accepted connection has to provide a multiplexed protocol header * before it is considered to be a standard (non-multiplexed) connection. *

* A high timeout will only negatively impact the arguably rare use-case of a non-multiplexed clients which * connects, sends nothing, and waits for the server-side to perform the first transmission. This initial server * transmission to a "quiet" non-multiplexed client will be artificially delayed by the identification timeout. * All other usage patterns will not be negatively impacted by a high timeout though would be negatively * impacted by a low timeout as even minimal packet loss could cause them to be incorrectly identified, * resulting in improper connection routing or socket closure. *

*

* See Tuning TCP Parameters for the 21st Century * for details on TCP's initial retransmission timeout. With even minimal packet loss the initial transmission * could easily take tens of seconds. It is this initial retransmission timeout which makes a low identification * timeout unsafe. *

*

* The default value may be controlled by the com.oracle.coherence.common.internal.net.MultiplexedSocketProvider.server.identification.timeout * system property and defaults to one minute. *

* * @return the timeout in milliseconds */ public long getIdentificationTimeoutMillis(); /** * Return the Logger to use. * * @return the logger */ public Logger getLogger(); } // ----- inner class: DefaultDependencies ------------------------------- /** * Produce a copy of the specified Dependencies object. * * @param deps the dependencies to copy * * @return the copied Dependencies as a DefaultDependencies object. */ protected DefaultDependencies copyDependencies(Dependencies deps) { return new DefaultDependencies(deps); } /** * DefaultDependencies provides a default implementation of the Dependencies * interface. */ public static class DefaultDependencies implements Dependencies { /** * Produce a DefaultDependencies object initialized with all defaults. */ public DefaultDependencies() { this (null); } /** * Produce a copy based on the supplied Dependencies. * * @param deps the Dependencies to copy */ public DefaultDependencies(Dependencies deps) { if (deps != null) { m_provider = deps.getDelegateProvider(); m_service = deps.getSelectionService(); m_nBacklog = deps.getBacklog(); m_cMillisIdentify = deps.getIdentificationTimeoutMillis(); m_logger = deps.getLogger(); } } // ----- DefaultDependencies methods ---------------------------- /** * Validate the dependencies object. * * @return this object */ protected DefaultDependencies validate() { return this; } // ----- Dependencies methods ----------------------------------- @Override public SocketProvider getDelegateProvider() { SocketProvider provider = m_provider; if (provider == null) { m_provider = provider = TcpSocketProvider.INSTANCE; } return provider; } /** * Specify the SocketProvider to which the MultiplexedSocketProvider * will delegate to. * * @param provider the provider to delegate to * * @return this object */ public DefaultDependencies setDelegateProvider(SocketProvider provider) { m_provider = provider; return this; } @Override public SelectionService getSelectionService() { SelectionService service = m_service; if (service == null) { m_service = service = SelectionServices.getDefaultService(); } return service; } /** * Specify the SelectionService to use for IO processing. * * @param service the SelectionService to use * * @return this object */ public DefaultDependencies setSelectionService(SelectionService service) { m_service = service; return this; } @Override public int getBacklog() { return m_nBacklog; } /** * Specify the backlog to use when binding the underlying ServerSocket. * * @param nBacklog the backlog * * @return this object */ public DefaultDependencies setBacklog(int nBacklog) { m_nBacklog = nBacklog; return this; } /** * {@inheritDoc} */ @Override public long getIdentificationTimeoutMillis() { return m_cMillisIdentify; } /** * Specify the identification timeout in milliseconds. * * @param cMillis the identification timeout * * @return this object */ public DefaultDependencies setIdentificationTimeoutMillis(long cMillis) { m_cMillisIdentify = cMillis; return this; } /** * {@inheritDoc} */ @Override public Logger getLogger() { Logger logger = m_logger; return logger == null ? LOGGER : logger; } /** * Specify the Logger to use. * * @param logger the logger * * @return this object */ public DefaultDependencies setLogger(Logger logger) { m_logger = logger; return this; } // ----- constants ---------------------------------------------- /** * The default backlog value. */ protected static final int s_nBacklogDefault; /** * The default accept timeout. */ protected static final long s_cMillisIdentifyDefault; static { // we default the backlog to as high as the underlying OS will allow, as multiplexed sockets // may see a significant connection rate, note that the Java translates a value of 0 to 50, and // thus relying on the Java default is simply insufficient for many multiplexed cases // on Linux the maximum allowed backlog is /proc/sys/net/core/somaxconn // on Windows a value of SOMAXCONN will allow the winsock impl to choose a reasonable value // while SOMAXCONN is not available in Java, the Windows value happens to be Integer.MAX_VALUE int nBacklog = Integer.MAX_VALUE; try { nBacklog = Integer.parseInt(System.getProperty( MultiplexedSocketProvider.class.getName() + ".server.backlog", Integer.toString(nBacklog))); } catch (Throwable t) {} s_nBacklogDefault = nBacklog; // Set the default identification timeout. This value governs how long we will wait for an accepted // connection to identify itself as multiplexed before assuming it is not. To identify itself // as multiplexed it only needs to send the 8-byte protocol header, and thus it would seem // that a very low timeout would be sufficient. Unfortunately though we have to account for // the possibility that this initial packet gets dropped and must wait on the TCP retransmit // timeout. This timeout is implementation dependent but appears to start at 3s in most OSs, // and increase from there in response to packet loss, i.e. 3, 6, 12,... Based on how the TCP // 3-way handshake works the client should always see the connection as being established // before the server does. So the only dropped packet we need to worry about is the first // user-data packet, i.e. the one with our protocol header. Given the above timings and assuming // near-zero delivery time if this packet were to be dropped once it would take the server about // 3s to see it, or 9s if it were dropped twice, or 21s if dropped thrice. Since we can't possibly // rely on zero packet loss, it would seem our minimum safe default is going to be >3s, with >9s // being the next safe option. Based on this we reluctantly have a default to a large timeout. // This should only impose a negative impact on non-multiplexed "polite" clients, i.e. clients // which connect and wait for the server to speak first. // // For more details on these timings see: // http://www.ietf.org/proceedings/75/slides/tcpm-1.pdf // http://us.generation-nt.com/answer/patch-tcp-expose-initial-rto-via-new-sysctl-help-203365542.html long cMillisId = 0; try { cMillisId = new Duration(System.getProperty( MultiplexedSocketProvider.class.getName() + ".server.identification.timeout", "1m")).as(Duration.Magnitude.MILLI); // default doc'd on getIdentificationTimeoutMillis } catch (Throwable t) {} s_cMillisIdentifyDefault = cMillisId; } // ----- data members ------------------------------------------- /** * The SocketProvider to utilize. */ protected SocketProvider m_provider; /** * The SelectionService to utilize. */ protected SelectionService m_service; /** * The backlog. */ protected int m_nBacklog = s_nBacklogDefault; /** * The accept timeout. */ protected long m_cMillisIdentify = s_cMillisIdentifyDefault; /** * The Logger. */ protected Logger m_logger; } // ----- constants ------------------------------------------------------ /** * WellKnownSubports are sub-ports that are reserved for use by components. */ public static enum WellKnownSubPorts { COHERENCE_TCP_RING (1), COHERENCE_TCMP_DATAGRAM (2), COHERENCE_NAME_SERVICE (3); /** * Construct a well known subport for MultiplexedSocketProvider * @param subPort */ WellKnownSubPorts(int subPort) { m_nSubPort = subPort; } /** * Return the sub-port. * * @return the sub-port */ public int getSubPort() { return m_nSubPort; } /** * Return the 32 bit port number consisting of the supplied base-port and this sub-port. * * @param nPortBase the base port * * @return the port */ public int getPort(int nPortBase) { return MultiplexedSocketProvider.getPort(nPortBase, m_nSubPort); } // ----- data members ------------------------------------------- private final int m_nSubPort; } /** * The protocol identifier. */ protected static final int PROTOCOL_ID = ProtocolIdentifiers.MULTIPLEXED_SOCKET; /** * The end of the well-known sub-port range. */ public static final int WELL_KNOWN_SUB_PORT_END = 1023; /** * The start of the ephemeral sub-port range. */ protected static final int EPHEMERAL_SUB_PORT_START = 32768; /** * The default Logger for the provider. */ private static Logger LOGGER = Logger.getLogger(MultiplexedSocketProvider.class.getName()); static final Set> SERVER_OPTIONS; static { Set> setOpt = new HashSet>(); setOpt.add(StandardSocketOptions.SO_RCVBUF); setOpt.add(StandardSocketOptions.SO_REUSEADDR); SERVER_OPTIONS = Collections.unmodifiableSet(setOpt); } // ----- data members --------------------------------------------------- /** * The provider's dependencies. */ protected final Dependencies m_dependencies; /** * Map of Listener addresses to their corresponding Listener object */ protected final ConcurrentMap m_mapListener = new ConcurrentHashMap(); /** * The minimum base ephemeral port number which has at some point been * allocated. */ protected int m_nPortEphemeralLow = Integer.MAX_VALUE; /** * The maximum base ephemeral port number which has at some point been * allocated. */ protected int m_nPortEphemeralHi = Integer.MIN_VALUE; }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy