Skip to content

Commit

Permalink
IPv6 improvements:
Browse files Browse the repository at this point in the history
- use IPv6 by default if available on Windows with APR
- new option to configure whether APR with IPv6 also uses IPv4 on dual stack systems
- improve unlock accept logic to better handle cases where only IPv4 or only IPv6 are configured

git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@1772170 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information
markt-asf committed Dec 1, 2016
1 parent 508e046 commit 52e87a1
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 76 deletions.
74 changes: 54 additions & 20 deletions java/org/apache/tomcat/util/net/AbstractEndpoint.java
Expand Up @@ -16,6 +16,7 @@
*/
package org.apache.tomcat.util.net;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.InetSocketAddress;
Expand Down Expand Up @@ -378,7 +379,19 @@ public void setExecutor(Executor executor) {
public int getPort() { return port; }
public void setPort(int port ) { this.port=port; }

public abstract int getLocalPort();

public final int getLocalPort() {
try {
InetSocketAddress localAddress = getLocalAddress();
if (localAddress == null) {
return -1;
}
return localAddress.getPort();
} catch (IOException ioe) {
return -1;
}
}


/**
* Address for the server socket.
Expand All @@ -387,6 +400,22 @@ public void setExecutor(Executor executor) {
public InetAddress getAddress() { return address; }
public void setAddress(InetAddress address) { this.address = address; }


/**
* Obtain the network address the server socket is bound to. This primarily
* exists to enable the correct address to be used when unlocking the server
* socket since it removes the guess-work involved if no address is
* specifically set.
*
* @return The network address that the server socket is listening on or
* null if the server socket is not currently bound.
*
* @throws IOException If there is a problem determining the currently bound
* socket
*/
protected abstract InetSocketAddress getLocalAddress() throws IOException;


/**
* Allows the server developer to specify the acceptCount (backlog) that
* should be used for server sockets. By default, this value
Expand Down Expand Up @@ -761,35 +790,40 @@ protected void unlockAccept() {
return;
}

InetSocketAddress saddr = null;
InetSocketAddress unlockAddress = null;
InetSocketAddress localAddress = null;
try {
// Need to create a connection to unlock the accept();
if (address == null) {
saddr = new InetSocketAddress("localhost", getLocalPort());
} else if (address.isAnyLocalAddress()) {
localAddress = getLocalAddress();
} catch (IOException ioe) {
// TODO i18n
getLog().debug("Unable to determine local address for " + getName(), ioe);
}
if (localAddress == null) {
// TODO i18n
getLog().warn("Failed to unlock acceptor for " + getName() + " because the local address was not available.");
return;
}

try {
if (localAddress.getAddress().isAnyLocalAddress()) {
// Need a local address of the same type (IPv4 or IPV6) as the
// configured bind address since the connector may be configured
// to not map between types.
InetAddress localAddress = null;
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
while (localAddress == null && networkInterfaces.hasMoreElements()) {
while (unlockAddress == null && networkInterfaces.hasMoreElements()) {
NetworkInterface networkInterface = networkInterfaces.nextElement();
Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();
while (localAddress == null && inetAddresses.hasMoreElements()) {
while (unlockAddress == null && inetAddresses.hasMoreElements()) {
InetAddress inetAddress = inetAddresses.nextElement();
if (address.getClass().isAssignableFrom(inetAddress.getClass())) {
localAddress = inetAddress;
if (localAddress.getAddress().getClass().isAssignableFrom(inetAddress.getClass())) {
unlockAddress = new InetSocketAddress(inetAddress, localAddress.getPort());
}
}
}
// Fall-back option
if (localAddress == null) {
saddr = new InetSocketAddress("localhost", getLocalPort());
}
saddr = new InetSocketAddress(localAddress, getLocalPort());
} else {
saddr = new InetSocketAddress(address, getLocalPort());
unlockAddress = localAddress;
}

try (java.net.Socket s = new java.net.Socket()) {
int stmo = 2 * 1000;
int utmo = 2 * 1000;
Expand All @@ -800,9 +834,9 @@ protected void unlockAccept() {
s.setSoTimeout(stmo);
s.setSoLinger(getSocketProperties().getSoLingerOn(),getSocketProperties().getSoLingerTime());
if (getLog().isDebugEnabled()) {
getLog().debug("About to unlock socket for:"+saddr);
getLog().debug("About to unlock socket for:" + unlockAddress);
}
s.connect(saddr,utmo);
s.connect(unlockAddress,utmo);
if (getDeferAccept()) {
/*
* In the case of a deferred accept / accept filters we need to
Expand All @@ -817,7 +851,7 @@ protected void unlockAccept() {
sw.flush();
}
if (getLog().isDebugEnabled()) {
getLog().debug("Socket unlock completed for:"+saddr);
getLog().debug("Socket unlock completed for:" + unlockAddress);
}

// Wait for upto 1000ms acceptor threads to unlock
Expand Down
21 changes: 21 additions & 0 deletions java/org/apache/tomcat/util/net/AbstractJsseEndpoint.java
Expand Up @@ -16,6 +16,10 @@
*/
package org.apache.tomcat.util.net;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.channels.NetworkChannel;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashSet;
Expand Down Expand Up @@ -235,4 +239,21 @@ public void unbind() throws Exception {
}
}
}


protected abstract NetworkChannel getServerSocket();


@Override
protected final InetSocketAddress getLocalAddress() throws IOException {
NetworkChannel serverSock = getServerSocket();
if (serverSock == null) {
return null;
}
SocketAddress sa = serverSock.getLocalAddress();
if (sa instanceof InetSocketAddress) {
return (InetSocketAddress) sa;
}
return null;
}
}
41 changes: 32 additions & 9 deletions java/org/apache/tomcat/util/net/AprEndpoint.java
Expand Up @@ -18,6 +18,7 @@

import java.io.EOFException;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
Expand Down Expand Up @@ -127,6 +128,11 @@ public AprEndpoint() {
public boolean getDeferAccept() { return deferAccept; }


private boolean ipv6v6only = false;
public void setIpv6v6only(boolean ipv6v6only) { this.ipv6v6only = ipv6v6only; }
public boolean getIpv6v6only() { return ipv6v6only; }


/**
* Size of the sendfile (= concurrent files which can be served).
*/
Expand Down Expand Up @@ -189,23 +195,32 @@ protected Type getSslConfigType() {
}


/**
* Port in use.
*/
@Override
public int getLocalPort() {
public InetSocketAddress getLocalAddress() throws IOException {
long s = serverSock;
if (s == 0) {
return -1;
return null;
} else {
long sa;
try {
sa = Address.get(Socket.APR_LOCAL, s);
Sockaddr addr = Address.getInfo(sa);
return addr.port;
} catch (IOException ioe) {
// re-throw
throw ioe;
} catch (Exception e) {
return -1;
// wrap
throw new IOException(e);
}
Sockaddr addr = Address.getInfo(sa);
if (addr.hostname == null) {
// any local address
if (addr.family == Socket.APR_INET6) {
return new InetSocketAddress("::", addr.port);
} else {
return new InetSocketAddress("0.0.0.0", addr.port);
}
}
return new InetSocketAddress(addr.hostname, addr.port);
}
}

Expand Down Expand Up @@ -288,8 +303,9 @@ public void bind() throws Exception {
int family = Socket.APR_INET;
if (Library.APR_HAVE_IPV6) {
if (addressStr == null) {
if (!OS.IS_BSD && !OS.IS_WIN32 && !OS.IS_WIN64)
if (!OS.IS_BSD) {
family = Socket.APR_UNSPEC;
}
} else if (addressStr.indexOf(':') >= 0) {
family = Socket.APR_UNSPEC;
}
Expand All @@ -304,6 +320,13 @@ public void bind() throws Exception {
if (OS.IS_UNIX) {
Socket.optSet(serverSock, Socket.APR_SO_REUSEADDR, 1);
}
if (Library.APR_HAVE_IPV6) {
if (getIpv6v6only()) {
Socket.optSet(serverSock, Socket.APR_IPV6_V6ONLY, 1);
} else {
Socket.optSet(serverSock, Socket.APR_IPV6_V6ONLY, 0);
}
}
// Deal with the firewalls that tend to drop the inactive sockets
Socket.optSet(serverSock, Socket.APR_SO_KEEPALIVE, 1);
// Bind the server socket
Expand Down
30 changes: 7 additions & 23 deletions java/org/apache/tomcat/util/net/Nio2Endpoint.java
Expand Up @@ -30,6 +30,7 @@
import java.nio.channels.ClosedChannelException;
import java.nio.channels.CompletionHandler;
import java.nio.channels.FileChannel;
import java.nio.channels.NetworkChannel;
import java.nio.channels.ReadPendingException;
import java.nio.channels.WritePendingException;
import java.nio.file.StandardOpenOption;
Expand Down Expand Up @@ -113,29 +114,6 @@ public boolean getDeferAccept() {
}


/**
* Port in use.
*/
@Override
public int getLocalPort() {
AsynchronousServerSocketChannel ssc = serverSock;
if (ssc == null) {
return -1;
} else {
try {
SocketAddress sa = ssc.getLocalAddress();
if (sa instanceof InetSocketAddress) {
return ((InetSocketAddress) sa).getPort();
} else {
return -1;
}
} catch (IOException e) {
return -1;
}
}
}


// --------------------------------------------------------- Public Methods

/**
Expand Down Expand Up @@ -405,6 +383,12 @@ protected Log getLog() {
}


@Override
protected NetworkChannel getServerSocket() {
return serverSock;
}


// --------------------------------------------------- Acceptor Inner Class

/**
Expand Down
27 changes: 7 additions & 20 deletions java/org/apache/tomcat/util/net/NioEndpoint.java
Expand Up @@ -22,12 +22,12 @@
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.FileChannel;
import java.nio.channels.NetworkChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
Expand Down Expand Up @@ -176,25 +176,6 @@ public boolean getDeferAccept() {
}


/**
* Port in use.
*/
@Override
public int getLocalPort() {
ServerSocketChannel ssc = serverSock;
if (ssc == null) {
return -1;
} else {
ServerSocket s = ssc.socket();
if (s == null) {
return -1;
} else {
return s.getLocalPort();
}
}
}


// --------------------------------------------------------- Public Methods
/**
* Number of keep-alive sockets.
Expand Down Expand Up @@ -416,6 +397,12 @@ protected Log getLog() {
}


@Override
protected NetworkChannel getServerSocket() {
return serverSock;
}


// --------------------------------------------------- Acceptor Inner Class
/**
* The background thread that listens for incoming TCP/IP connections and
Expand Down
13 changes: 13 additions & 0 deletions webapps/docs/changelog.xml
Expand Up @@ -177,6 +177,19 @@
of a specific type such as <code>0.0.0.0</code> or <code>::</code>.
(markt)
</fix>
<add>
Add a new configuration option, <code>ipv6v6only</code> to the APR
connectors that allows them to be configure to only accept IPv6
connections when configured with an IPv6 address rather than the
default which is to accept IPv4 connections as well if the operating
system uses a dual network stack. (markt)
</add>
<fix>
Improve the logic that unlocks the acceptor thread so a better choice is
made for the address to connect to when a connector is configured for
any local port. This reduces the likelihood of the unlock failing.
(markt)
</fix>
</changelog>
</subsection>
<subsection name="Web applications">
Expand Down

0 comments on commit 52e87a1

Please sign in to comment.