org.lastbamboo.common.ice.MappedTcpOffererServerPool Maven / Gradle / Ivy
package org.lastbamboo.common.ice;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import javax.net.ServerSocketFactory;
import org.apache.commons.io.IOExceptionWithCause;
import org.lastbamboo.common.portmapping.NatPmpService;
import org.lastbamboo.common.portmapping.PortMappingProtocol;
import org.lastbamboo.common.portmapping.UpnpService;
import org.littleshoot.util.CommonUtils;
import org.littleshoot.util.NetworkUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is a single server socket for all ICE offerers. It works by
* keeping track of which IP addresses it's expecting incoming sockets from
* and then associating those with existing ICE negotiations. Users of this
* class should first check if there are already associated mappings for
* expected incoming addresses and use alternative means if there are, such
* as creating a dynamically generated server socket.
*
* This class is useful to avoid continually opening and closing server sockets
* and to avoid constant UPnP and NAP-PMP churn.
*/
public class MappedTcpOffererServerPool {
private final Logger log = LoggerFactory.getLogger(getClass());
private final Queue servers =
new ConcurrentLinkedQueue();
private final NatPmpService natPmpService;
private final UpnpService upnpService;
private final ServerSocketFactory serverSocketFactory;
/**
* Creates a new mapped server for the answerer.
*
* @param natPmpService The NAT PMP mapper.
* @param upnpService The UPnP mapper.
* @param serverAddress The address of the server.
* @param serverSocketFactory The factory for creating server sockets --
* could be SSL sockets, for example.
* @throws IOException If there's an error starting the server.
*/
public MappedTcpOffererServerPool(final NatPmpService natPmpService,
final UpnpService upnpService,
final ServerSocketFactory serverSocketFactory) {
this.natPmpService = natPmpService;
this.upnpService = upnpService;
this.serverSocketFactory = serverSocketFactory;
// We add a couple of cached server sockets right at the beginning
// for later use.
final Runnable runner = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 6; i++) {
try {
addServerSocket(serverSocket());
} catch (final IOException e) {
log.error("Could not create server socket!");
}
}
}
};
final Thread t =
new Thread(runner, "Mapped-Offerer-Server-Socket-Creation-Thread");
t.setDaemon(true);
t.start();
}
/**
* Accessor for a port-mapped server socket. If there are not cached sockets
* around, this will create a new one and return it on demand.
*/
public PortMappedServerSocket serverSocket() throws IOException {
synchronized (servers) {
if (servers.isEmpty()) {
return randomPortServer();
}
else {
return servers.remove();
}
}
}
public void addServerSocket(final PortMappedServerSocket ss) {
synchronized (servers) {
// This guards against bugs in external code that might add
// the same server socket twice.
if (servers.contains(ss)) {
log.warn("We already have this server socket -- " +
"bug in calling code?", ss);
}
else {
servers.add(ss);
}
}
}
private PortMappedServerSocket randomPortServer() throws IOException {
IOException ioe = null;
final InetAddress lh = NetworkUtils.getLocalHost();
for (int i = 0; i < 20; i++) {
try {
final ServerSocket ss =
this.serverSocketFactory.createServerSocket();
final int port = CommonUtils.randomPort();
final InetSocketAddress endpoint =
new InetSocketAddress(lh, port);
ss.bind(endpoint);
// With this set, calls to accept will timeout after the
// specified interval.
ss.setSoTimeout(30*1000);
final boolean isPublic = NetworkUtils.isPublicAddress(lh);
final PortMappedServerSocket pmss =
new PortMappedServerSocket(ss, isPublic);
log.info("Attempting to map port {} via UPnP", port);
upnpService.addUpnpMapping(PortMappingProtocol.TCP, port,
port, pmss);
log.info("Attempting to map port {} via NAT-PMP", port);
natPmpService.addNatPmpMapping(PortMappingProtocol.TCP, port,
port, pmss);
return pmss;
} catch (final IOException e) {
log.info("Error binding?", e);
ioe = e;
}
}
if (ioe == null) {
throw new IOException(
"Could not create server socket after many tries");
}
throw new IOExceptionWithCause(
"Could not create server socket after many tries", ioe);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy