/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.tomcat.jni;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.util.Enumeration;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
/**
* Tests for server-side sockets using any local address (0.0.0.0 or ::).
*/
public class TestSocketServerAnyLocalAddress extends AbstractJniTest {
private long serverSocket = 0;
private long clientSocket = 0;
@Before
public void init() throws Exception {
long serverPool = Pool.create(0);
long inetAddress = Address.info(null, Socket.APR_UNSPEC,
0, 0, serverPool);
serverSocket = Socket.create(Address.getInfo(inetAddress).family, Socket.SOCK_STREAM,
Socket.APR_PROTO_TCP, serverPool);
if (OS.IS_UNIX) {
Socket.optSet(serverSocket, Socket.APR_SO_REUSEADDR, 1);
}
int rc = Socket.bind(serverSocket, inetAddress);
Assert.assertEquals("Can't bind: " + Error.strerror(rc), 0, rc);
Socket.listen(serverSocket, 5);
if (!OS.IS_UNIX) {
Socket.optSet(serverSocket, Socket.APR_SO_REUSEADDR, 1);
}
}
@After
public void destroy() {
if (clientSocket != 0) {
Socket.close(clientSocket);
Socket.destroy(clientSocket);
}
if (serverSocket != 0) {
Socket.close(serverSocket);
Socket.destroy(serverSocket);
}
}
@Test
public void testWithClient() throws Exception {
/* Start the client that connects to the server */
Client client = new Client(serverSocket);
client.start();
boolean running = true;
while (running) {
/* Accept the client connection */
clientSocket = Socket.accept(serverSocket);
/* Configure a 2ms timeout for reading from client */
Socket.timeoutSet(clientSocket, 10000);
byte [] buf = new byte[1];
while (Socket.recv(clientSocket, buf, 0, 1) == 1) {
// If 'A' was read, echo back 'Z'
if (buf[0] == 'A') {
buf[0] = 'Z';
Socket.send(clientSocket, buf, 0, 1);
}
}
if (buf[0] == 'E') {
running = false;
} else if (buf[0] == 'Z') {
// NO-OP - connection closing
} else {
Assert.fail("Unexpected data");
}
}
client.join();
}
/**
* Simple client that connects, sends a single byte then closes the
* connection.
*/
private static class Client extends java.lang.Thread {
private final long serverSocket;
public Client(long serverSocket) throws Exception {
this.serverSocket = serverSocket;
}
@Override
public void run() {
try {
InetSocketAddress connectAddress = getConnectAddress(serverSocket);
java.net.Socket sock = new java.net.Socket();
sock.connect(connectAddress, 10000);
sock.setSoTimeout(10000);
OutputStream ou = sock.getOutputStream();
InputStream in = sock.getInputStream();
ou.write('A');
ou.flush();
int rep = in.read();
sock.close();
if (rep != 'Z') {
throw new Exception("Read wrong data");
}
sock = new java.net.Socket();
sock.connect(connectAddress, 10000);
sock.setSoTimeout(10000);
ou = sock.getOutputStream();
ou.write('E');
ou.flush();
sock.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/*
* Assumes server is listening on any local address
*/
private static InetSocketAddress getConnectAddress(long serverSocket) throws Exception {
long sa = Address.get(Socket.APR_LOCAL, serverSocket);
Sockaddr addr = Address.getInfo(sa);
InetSocketAddress localAddress;
if (addr.family == Socket.APR_INET6) {
localAddress = new InetSocketAddress("::", addr.port);
} else {
localAddress = new InetSocketAddress("0.0.0.0", addr.port);
}
// 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 loopbackConnectAddress = null;
InetAddress linkLocalConnectAddress = null;
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
while (networkInterfaces.hasMoreElements()) {
NetworkInterface networkInterface = networkInterfaces.nextElement();
Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();
while (inetAddresses.hasMoreElements()) {
InetAddress inetAddress = inetAddresses.nextElement();
if (localAddress.getAddress().getClass().isAssignableFrom(inetAddress.getClass())) {
if (inetAddress.isLoopbackAddress()) {
if (loopbackConnectAddress == null) {
loopbackConnectAddress = inetAddress;
}
} else if (inetAddress.isLinkLocalAddress()) {
if (linkLocalConnectAddress == null) {
linkLocalConnectAddress = inetAddress;
}
} else {
// Use a non-link local, non-loop back address by default
return new InetSocketAddress(inetAddress, localAddress.getPort());
}
}
}
}
// Prefer loop back over link local since on some platforms (e.g.
// OSX) some link local addresses are not included when listening on
// all local addresses.
if (loopbackConnectAddress != null) {
return new InetSocketAddress(loopbackConnectAddress, localAddress.getPort());
}
if (linkLocalConnectAddress != null) {
return new InetSocketAddress(linkLocalConnectAddress, localAddress.getPort());
}
// Fallback
return new InetSocketAddress("localhost", localAddress.getPort());
}
}
}