Changed the root package from net.sf.briar to org.briarproject.

This commit is contained in:
akwizgran
2014-01-08 16:18:30 +00:00
parent dce70f487c
commit 832476412c
427 changed files with 2507 additions and 2507 deletions

View File

@@ -0,0 +1,362 @@
package org.briarproject.plugins;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import org.briarproject.api.ContactId;
import org.briarproject.api.TransportConfig;
import org.briarproject.api.TransportId;
import org.briarproject.api.TransportProperties;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException;
import org.briarproject.api.plugins.Plugin;
import org.briarproject.api.plugins.PluginCallback;
import org.briarproject.api.plugins.PluginExecutor;
import org.briarproject.api.plugins.PluginManager;
import org.briarproject.api.plugins.duplex.DuplexPlugin;
import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
import org.briarproject.api.plugins.duplex.DuplexPluginConfig;
import org.briarproject.api.plugins.duplex.DuplexPluginFactory;
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
import org.briarproject.api.plugins.simplex.SimplexPlugin;
import org.briarproject.api.plugins.simplex.SimplexPluginCallback;
import org.briarproject.api.plugins.simplex.SimplexPluginConfig;
import org.briarproject.api.plugins.simplex.SimplexPluginFactory;
import org.briarproject.api.plugins.simplex.SimplexTransportReader;
import org.briarproject.api.plugins.simplex.SimplexTransportWriter;
import org.briarproject.api.transport.ConnectionDispatcher;
import org.briarproject.api.ui.UiCallback;
// FIXME: Don't make alien calls with a lock held (that includes waiting on a
// latch that depends on an alien call)
class PluginManagerImpl implements PluginManager {
private static final Logger LOG =
Logger.getLogger(PluginManagerImpl.class.getName());
private final Executor pluginExecutor;
private final SimplexPluginConfig simplexPluginConfig;
private final DuplexPluginConfig duplexPluginConfig;
private final DatabaseComponent db;
private final Poller poller;
private final ConnectionDispatcher dispatcher;
private final UiCallback uiCallback;
private final List<SimplexPlugin> simplexPlugins;
private final List<DuplexPlugin> duplexPlugins;
@Inject
PluginManagerImpl(@PluginExecutor Executor pluginExecutor,
SimplexPluginConfig simplexPluginConfig,
DuplexPluginConfig duplexPluginConfig, DatabaseComponent db,
Poller poller, ConnectionDispatcher dispatcher,
UiCallback uiCallback) {
this.pluginExecutor = pluginExecutor;
this.simplexPluginConfig = simplexPluginConfig;
this.duplexPluginConfig = duplexPluginConfig;
this.db = db;
this.poller = poller;
this.dispatcher = dispatcher;
this.uiCallback = uiCallback;
simplexPlugins = new CopyOnWriteArrayList<SimplexPlugin>();
duplexPlugins = new CopyOnWriteArrayList<DuplexPlugin>();
}
public synchronized boolean start() {
// Instantiate and start the simplex plugins
if(LOG.isLoggable(INFO)) LOG.info("Starting simplex plugins");
Collection<SimplexPluginFactory> sFactories =
simplexPluginConfig.getFactories();
final CountDownLatch sLatch = new CountDownLatch(sFactories.size());
for(SimplexPluginFactory factory : sFactories)
pluginExecutor.execute(new SimplexPluginStarter(factory, sLatch));
// Instantiate and start the duplex plugins
if(LOG.isLoggable(INFO)) LOG.info("Starting duplex plugins");
Collection<DuplexPluginFactory> dFactories =
duplexPluginConfig.getFactories();
final CountDownLatch dLatch = new CountDownLatch(dFactories.size());
for(DuplexPluginFactory factory : dFactories)
pluginExecutor.execute(new DuplexPluginStarter(factory, dLatch));
// Wait for the plugins to start
try {
sLatch.await();
dLatch.await();
} catch(InterruptedException e) {
if(LOG.isLoggable(WARNING))
LOG.warning("Interrupted while starting plugins");
Thread.currentThread().interrupt();
return false;
}
// Start the poller
if(LOG.isLoggable(INFO)) LOG.info("Starting poller");
List<Plugin> plugins = new ArrayList<Plugin>();
plugins.addAll(simplexPlugins);
plugins.addAll(duplexPlugins);
poller.start(Collections.unmodifiableList(plugins));
return true;
}
public synchronized boolean stop() {
// Stop the poller
if(LOG.isLoggable(INFO)) LOG.info("Stopping poller");
poller.stop();
int plugins = simplexPlugins.size() + duplexPlugins.size();
final CountDownLatch latch = new CountDownLatch(plugins);
// Stop the simplex plugins
if(LOG.isLoggable(INFO)) LOG.info("Stopping simplex plugins");
for(SimplexPlugin plugin : simplexPlugins)
pluginExecutor.execute(new PluginStopper(plugin, latch));
// Stop the duplex plugins
if(LOG.isLoggable(INFO)) LOG.info("Stopping duplex plugins");
for(DuplexPlugin plugin : duplexPlugins)
pluginExecutor.execute(new PluginStopper(plugin, latch));
simplexPlugins.clear();
duplexPlugins.clear();
// Wait for all the plugins to stop
try {
latch.await();
} catch(InterruptedException e) {
if(LOG.isLoggable(WARNING))
LOG.warning("Interrupted while stopping plugins");
Thread.currentThread().interrupt();
return false;
}
return true;
}
public Collection<DuplexPlugin> getInvitationPlugins() {
List<DuplexPlugin> supported = new ArrayList<DuplexPlugin>();
for(DuplexPlugin d : duplexPlugins)
if(d.supportsInvitations()) supported.add(d);
return Collections.unmodifiableList(supported);
}
private class SimplexPluginStarter implements Runnable {
private final SimplexPluginFactory factory;
private final CountDownLatch latch;
private SimplexPluginStarter(SimplexPluginFactory factory,
CountDownLatch latch) {
this.factory = factory;
this.latch = latch;
}
public void run() {
try {
TransportId id = factory.getId();
SimplexCallback callback = new SimplexCallback(id);
SimplexPlugin plugin = factory.createPlugin(callback);
if(plugin == null) {
if(LOG.isLoggable(INFO)) {
String name = factory.getClass().getSimpleName();
LOG.info(name + " did not create a plugin");
}
return;
}
try {
db.addTransport(id, plugin.getMaxLatency());
} catch(DbException e) {
if(LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
return;
}
try {
if(plugin.start()) {
simplexPlugins.add(plugin);
} else {
if(LOG.isLoggable(INFO)) {
String name = plugin.getClass().getSimpleName();
LOG.info(name + " did not start");
}
}
} catch(IOException e) {
if(LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
} finally {
latch.countDown();
}
}
}
private class DuplexPluginStarter implements Runnable {
private final DuplexPluginFactory factory;
private final CountDownLatch latch;
private DuplexPluginStarter(DuplexPluginFactory factory,
CountDownLatch latch) {
this.factory = factory;
this.latch = latch;
}
public void run() {
try {
TransportId id = factory.getId();
DuplexCallback callback = new DuplexCallback(id);
DuplexPlugin plugin = factory.createPlugin(callback);
if(plugin == null) {
if(LOG.isLoggable(INFO)) {
String name = factory.getClass().getSimpleName();
LOG.info(name + " did not create a plugin");
}
return;
}
try {
db.addTransport(id, plugin.getMaxLatency());
} catch(DbException e) {
if(LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
return;
}
try {
if(plugin.start()) {
duplexPlugins.add(plugin);
} else {
if(LOG.isLoggable(INFO)) {
String name = plugin.getClass().getSimpleName();
LOG.info(name + " did not start");
}
}
} catch(IOException e) {
if(LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
} finally {
latch.countDown();
}
}
}
private class PluginStopper implements Runnable {
private final Plugin plugin;
private final CountDownLatch latch;
private PluginStopper(Plugin plugin, CountDownLatch latch) {
this.plugin = plugin;
this.latch = latch;
}
public void run() {
try {
plugin.stop();
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} finally {
latch.countDown();
}
}
}
private abstract class PluginCallbackImpl implements PluginCallback {
protected final TransportId id;
protected PluginCallbackImpl(TransportId id) {
this.id = id;
}
public TransportConfig getConfig() {
try {
return db.getConfig(id);
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return new TransportConfig();
}
}
public TransportProperties getLocalProperties() {
try {
TransportProperties p = db.getLocalProperties(id);
return p == null ? new TransportProperties() : p;
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return new TransportProperties();
}
}
public Map<ContactId, TransportProperties> getRemoteProperties() {
try {
return db.getRemoteProperties(id);
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return Collections.emptyMap();
}
}
public void mergeConfig(TransportConfig c) {
try {
db.mergeConfig(id, c);
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
public void mergeLocalProperties(TransportProperties p) {
try {
db.mergeLocalProperties(id, p);
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
public int showChoice(String[] options, String... message) {
return uiCallback.showChoice(options, message);
}
public boolean showConfirmationMessage(String... message) {
return uiCallback.showConfirmationMessage(message);
}
public void showMessage(String... message) {
uiCallback.showMessage(message);
}
}
private class SimplexCallback extends PluginCallbackImpl
implements SimplexPluginCallback {
private SimplexCallback(TransportId id) {
super(id);
}
public void readerCreated(SimplexTransportReader r) {
dispatcher.dispatchReader(id, r);
}
public void writerCreated(ContactId c, SimplexTransportWriter w) {
dispatcher.dispatchWriter(c, id, w);
}
}
private class DuplexCallback extends PluginCallbackImpl
implements DuplexPluginCallback {
private DuplexCallback(TransportId id) {
super(id);
}
public void incomingConnectionCreated(DuplexTransportConnection d) {
dispatcher.dispatchIncomingConnection(id, d);
}
public void outgoingConnectionCreated(ContactId c,
DuplexTransportConnection d) {
dispatcher.dispatchOutgoingConnection(c, id, d);
}
}
}

View File

@@ -0,0 +1,52 @@
package org.briarproject.plugins;
import static java.util.concurrent.TimeUnit.SECONDS;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import javax.inject.Singleton;
import org.briarproject.api.lifecycle.LifecycleManager;
import org.briarproject.api.plugins.PluginExecutor;
import org.briarproject.api.plugins.PluginManager;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
public class PluginsModule extends AbstractModule {
private final ExecutorService pluginExecutor;
public PluginsModule() {
// The thread pool is unbounded, so use direct handoff
BlockingQueue<Runnable> queue = new SynchronousQueue<Runnable>();
// Discard tasks that are submitted during shutdown
RejectedExecutionHandler policy =
new ThreadPoolExecutor.DiscardPolicy();
// Create threads as required and keep them in the pool for 60 seconds
pluginExecutor = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60, SECONDS, queue, policy);
}
protected void configure() {
bind(Poller.class).to(PollerImpl.class);
}
@Provides @Singleton
PluginManager getPluginManager(LifecycleManager lifecycleManager,
PluginManagerImpl pluginManager) {
lifecycleManager.register(pluginManager);
return pluginManager;
}
@Provides @Singleton @PluginExecutor
Executor getPluginExecutor(LifecycleManager lifecycleManager) {
lifecycleManager.registerForShutdown(pluginExecutor);
return pluginExecutor;
}
}

View File

@@ -0,0 +1,14 @@
package org.briarproject.plugins;
import java.util.Collection;
import org.briarproject.api.plugins.Plugin;
interface Poller {
/** Starts a new thread to poll the given collection of plugins. */
void start(Collection<Plugin> plugins);
/** Tells the poller thread to exit. */
void stop();
}

View File

@@ -0,0 +1,126 @@
package org.briarproject.plugins;
import static java.util.logging.Level.INFO;
import java.util.Collection;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import org.briarproject.api.ContactId;
import org.briarproject.api.plugins.Plugin;
import org.briarproject.api.plugins.PluginExecutor;
import org.briarproject.api.system.Clock;
import org.briarproject.api.transport.ConnectionRegistry;
class PollerImpl implements Poller, Runnable {
private static final Logger LOG =
Logger.getLogger(PollerImpl.class.getName());
private final Executor pluginExecutor;
private final ConnectionRegistry connRegistry;
private final Clock clock;
private final SortedSet<PollTime> pollTimes;
@Inject
PollerImpl(@PluginExecutor Executor pluginExecutor,
ConnectionRegistry connRegistry, Clock clock) {
this.pluginExecutor = pluginExecutor;
this.connRegistry = connRegistry;
this.clock = clock;
pollTimes = new TreeSet<PollTime>();
}
public synchronized void start(Collection<Plugin> plugins) {
for(Plugin plugin : plugins) schedule(plugin, true);
new Thread(this, "Poller").start();
}
private synchronized void schedule(Plugin plugin, boolean randomise) {
if(plugin.shouldPoll()) {
long now = clock.currentTimeMillis();
long interval = plugin.getPollingInterval();
// Randomise intervals at startup to spread out connection attempts
if(randomise) interval = (long) (interval * Math.random());
pollTimes.add(new PollTime(now + interval, plugin));
}
}
public synchronized void stop() {
pollTimes.clear();
notifyAll();
}
public void run() {
while(true) {
synchronized(this) {
if(pollTimes.isEmpty()) {
if(LOG.isLoggable(INFO)) LOG.info("Finished polling");
return;
}
long now = clock.currentTimeMillis();
final PollTime p = pollTimes.first();
if(now >= p.time) {
boolean removed = pollTimes.remove(p);
assert removed;
final Collection<ContactId> connected =
connRegistry.getConnectedContacts(p.plugin.getId());
if(LOG.isLoggable(INFO))
LOG.info("Polling " + p.plugin.getClass().getName());
pluginExecutor.execute(new Runnable() {
public void run() {
p.plugin.poll(connected);
}
});
schedule(p.plugin, false);
} else {
try {
wait(p.time - now);
} catch(InterruptedException e) {
if(LOG.isLoggable(INFO))
LOG.info("Interrupted while waiting to poll");
Thread.currentThread().interrupt();
return;
}
}
}
}
}
private static class PollTime implements Comparable<PollTime> {
private final long time;
private final Plugin plugin;
private PollTime(long time, Plugin plugin) {
this.time = time;
this.plugin = plugin;
}
// Must be consistent with equals()
public int compareTo(PollTime p) {
if(time < p.time) return -1;
if(time > p.time) return 1;
return 0;
}
// Must be consistent with equals()
@Override
public int hashCode() {
return (int) (time ^ (time >>> 32)) ^ plugin.hashCode();
}
@Override
public boolean equals(Object o) {
if(o instanceof PollTime) {
PollTime p = (PollTime) o;
return time == p.time && plugin == p.plugin;
}
return false;
}
}
}

View File

@@ -0,0 +1,122 @@
package org.briarproject.plugins.file;
import static java.util.logging.Level.WARNING;
import static org.briarproject.api.transport.TransportConstants.MIN_CONNECTION_LENGTH;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collection;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import org.briarproject.api.ContactId;
import org.briarproject.api.plugins.simplex.SimplexPlugin;
import org.briarproject.api.plugins.simplex.SimplexPluginCallback;
import org.briarproject.api.plugins.simplex.SimplexTransportReader;
import org.briarproject.api.plugins.simplex.SimplexTransportWriter;
import org.briarproject.api.system.FileUtils;
public abstract class FilePlugin implements SimplexPlugin {
private static final Logger LOG =
Logger.getLogger(FilePlugin.class.getName());
protected final Executor pluginExecutor;
protected final FileUtils fileUtils;
protected final SimplexPluginCallback callback;
protected final int maxFrameLength;
protected final long maxLatency;
protected volatile boolean running = false;
protected abstract File chooseOutputDirectory();
protected abstract Collection<File> findFilesByName(String filename);
protected abstract void writerFinished(File f);
protected abstract void readerFinished(File f);
protected FilePlugin(Executor pluginExecutor, FileUtils fileUtils,
SimplexPluginCallback callback, int maxFrameLength,
long maxLatency) {
this.pluginExecutor = pluginExecutor;
this.fileUtils = fileUtils;
this.callback = callback;
this.maxFrameLength = maxFrameLength;
this.maxLatency = maxLatency;
}
public int getMaxFrameLength() {
return maxFrameLength;
}
public long getMaxLatency() {
return maxLatency;
}
public SimplexTransportReader createReader(ContactId c) {
return null;
}
public SimplexTransportWriter createWriter(ContactId c) {
if(!running) return null;
return createWriter(createConnectionFilename());
}
private String createConnectionFilename() {
StringBuilder s = new StringBuilder(12);
for(int i = 0; i < 8; i++) s.append((char) ('a' + Math.random() * 26));
s.append(".dat");
return s.toString();
}
// Package access for testing
boolean isPossibleConnectionFilename(String filename) {
return filename.toLowerCase().matches("[a-z]{8}\\.dat");
}
private SimplexTransportWriter createWriter(String filename) {
if(!running) return null;
File dir = chooseOutputDirectory();
if(dir == null || !dir.exists() || !dir.isDirectory()) return null;
File f = new File(dir, filename);
try {
long capacity = fileUtils.getFreeSpace(dir);
if(capacity < MIN_CONNECTION_LENGTH) return null;
OutputStream out = new FileOutputStream(f);
return new FileTransportWriter(f, out, capacity, this);
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
f.delete();
return null;
}
}
protected void createReaderFromFile(final File f) {
if(!running) return;
pluginExecutor.execute(new ReaderCreator(f));
}
private class ReaderCreator implements Runnable {
private final File file;
private ReaderCreator(File file) {
this.file = file;
}
public void run() {
if(isPossibleConnectionFilename(file.getName())) {
try {
FileInputStream in = new FileInputStream(file);
callback.readerCreated(new FileTransportReader(file, in,
FilePlugin.this));
} catch(IOException e) {
if(LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
}
}
}

View File

@@ -0,0 +1,46 @@
package org.briarproject.plugins.file;
import static java.util.logging.Level.WARNING;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Logger;
import org.briarproject.api.plugins.simplex.SimplexTransportReader;
class FileTransportReader implements SimplexTransportReader {
private static final Logger LOG =
Logger.getLogger(FileTransportReader.class.getName());
private final File file;
private final InputStream in;
private final FilePlugin plugin;
FileTransportReader(File file, InputStream in, FilePlugin plugin) {
this.file = file;
this.in = in;
this.plugin = plugin;
}
public int getMaxFrameLength() {
return plugin.getMaxFrameLength();
}
public InputStream getInputStream() {
return in;
}
public void dispose(boolean exception, boolean recognised) {
try {
in.close();
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
if(recognised) {
file.delete();
plugin.readerFinished(file);
}
}
}

View File

@@ -0,0 +1,59 @@
package org.briarproject.plugins.file;
import static java.util.logging.Level.WARNING;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.logging.Logger;
import org.briarproject.api.plugins.simplex.SimplexTransportWriter;
class FileTransportWriter implements SimplexTransportWriter {
private static final Logger LOG =
Logger.getLogger(FileTransportWriter.class.getName());
private final File file;
private final OutputStream out;
private final long capacity;
private final FilePlugin plugin;
FileTransportWriter(File file, OutputStream out, long capacity,
FilePlugin plugin) {
this.file = file;
this.out = out;
this.capacity = capacity;
this.plugin = plugin;
}
public long getCapacity() {
return capacity;
}
public int getMaxFrameLength() {
return plugin.getMaxFrameLength();
}
public long getMaxLatency() {
return plugin.getMaxLatency();
}
public OutputStream getOutputStream() {
return out;
}
public boolean shouldFlush() {
return false;
}
public void dispose(boolean exception) {
try {
out.close();
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
if(exception) file.delete();
else plugin.writerFinished(file);
}
}

View File

@@ -0,0 +1,345 @@
package org.briarproject.plugins.tcp;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MulticastSocket;
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.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import org.briarproject.api.TransportId;
import org.briarproject.api.TransportProperties;
import org.briarproject.api.crypto.PseudoRandom;
import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
import org.briarproject.api.system.Clock;
import org.briarproject.util.ByteUtils;
import org.briarproject.util.LatchedReference;
import org.briarproject.util.StringUtils;
/** A socket plugin that supports exchanging invitations over a LAN. */
class LanTcpPlugin extends TcpPlugin {
static final byte[] TRANSPORT_ID =
StringUtils.fromHexString("0d79357fd7f74d66c2f6f6ad0f7fff81"
+ "d21c53a43b90b0507ed0683872d8e2fc"
+ "5a88e8f953638228dc26669639757bbf");
static final TransportId ID = new TransportId(TRANSPORT_ID);
private static final Logger LOG =
Logger.getLogger(LanTcpPlugin.class.getName());
private static final int MULTICAST_INTERVAL = 1000; // 1 second
private final Clock clock;
LanTcpPlugin(Executor pluginExecutor, Clock clock,
DuplexPluginCallback callback, int maxFrameLength, long maxLatency,
long pollingInterval) {
super(pluginExecutor, callback, maxFrameLength, maxLatency,
pollingInterval);
this.clock = clock;
}
public TransportId getId() {
return ID;
}
public String getName() {
return "LAN_TCP_PLUGIN_NAME";
}
@Override
protected List<SocketAddress> getLocalSocketAddresses() {
List<SocketAddress> addrs = new ArrayList<SocketAddress>();
// Prefer a previously used address and port if available
TransportProperties p = callback.getLocalProperties();
String addrString = p.get("address");
String portString = p.get("port");
InetAddress addr = null;
if(!StringUtils.isNullOrEmpty(addrString) &&
!StringUtils.isNullOrEmpty(portString)) {
try {
addr = InetAddress.getByName(addrString);
int port = Integer.parseInt(portString);
addrs.add(new InetSocketAddress(addr, port));
addrs.add(new InetSocketAddress(addr, 0));
} catch(NumberFormatException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} catch(UnknownHostException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
List<NetworkInterface> ifaces;
try {
ifaces = Collections.list(NetworkInterface.getNetworkInterfaces());
} catch(SocketException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return addrs;
}
// Prefer interfaces with link-local or site-local addresses
for(NetworkInterface iface : ifaces) {
for(InetAddress a : Collections.list(iface.getInetAddresses())) {
if(addr != null && a.equals(addr)) continue;
if(a instanceof Inet6Address) continue;
if(a.isLoopbackAddress()) continue;
boolean link = a.isLinkLocalAddress();
boolean site = a.isSiteLocalAddress();
if(link || site) addrs.add(new InetSocketAddress(a, 0));
}
}
// Accept interfaces without link-local or site-local addresses
for(NetworkInterface iface : ifaces) {
for(InetAddress a : Collections.list(iface.getInetAddresses())) {
if(addr != null && a.equals(addr)) continue;
if(a instanceof Inet6Address) continue;
if(a.isLoopbackAddress()) continue;
boolean link = a.isLinkLocalAddress();
boolean site = a.isSiteLocalAddress();
if(!link && !site) addrs.add(new InetSocketAddress(a, 0));
}
}
return addrs;
}
public boolean supportsInvitations() {
return true;
}
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
long timeout) {
if(!running) return null;
// Use the invitation codes to generate the group address and port
InetSocketAddress group = chooseMulticastGroup(r);
// Bind a multicast socket for sending and receiving packets
InetAddress iface = null;
MulticastSocket ms = null;
try {
iface = chooseInvitationInterface();
if(iface == null) return null;
ms = new MulticastSocket(group.getPort());
ms.setInterface(iface);
ms.joinGroup(group.getAddress());
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
if(ms != null) tryToClose(ms, group.getAddress());
return null;
}
// Bind a server socket for receiving invitation connections
ServerSocket ss = null;
try {
ss = new ServerSocket();
ss.bind(new InetSocketAddress(iface, 0));
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
if(ss != null) tryToClose(ss);
return null;
}
// Start the listener threads
LatchedReference<Socket> socketLatch = new LatchedReference<Socket>();
new MulticastListenerThread(socketLatch, ms, iface).start();
new TcpListenerThread(socketLatch, ss).start();
// Send packets until a connection is made or we run out of time
byte[] buffer = new byte[2];
ByteUtils.writeUint16(ss.getLocalPort(), buffer, 0);
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
packet.setAddress(group.getAddress());
packet.setPort(group.getPort());
long now = clock.currentTimeMillis();
long end = now + timeout;
try {
while(now < end && running) {
// Send a packet
if(LOG.isLoggable(INFO)) LOG.info("Sending multicast packet");
ms.send(packet);
// Wait for an incoming or outgoing connection
try {
Socket s = socketLatch.waitForReference(MULTICAST_INTERVAL);
if(s != null) return new TcpTransportConnection(this, s);
} catch(InterruptedException e) {
if(LOG.isLoggable(INFO))
LOG.info("Interrupted while exchanging invitations");
Thread.currentThread().interrupt();
return null;
}
now = clock.currentTimeMillis();
}
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} finally {
// Closing the sockets will terminate the listener threads
tryToClose(ms, group.getAddress());
tryToClose(ss);
}
return null;
}
private InetSocketAddress chooseMulticastGroup(PseudoRandom r) {
byte[] b = r.nextBytes(5);
// The group address is 239.random.random.random, excluding 0 and 255
byte[] group = new byte[4];
group[0] = (byte) 239;
group[1] = legalAddressByte(b[0]);
group[2] = legalAddressByte(b[1]);
group[3] = legalAddressByte(b[2]);
// The port is random in the range 32768 - 65535, inclusive
int port = ByteUtils.readUint16(b, 3);
if(port < 32768) port += 32768;
InetAddress address;
try {
address = InetAddress.getByAddress(group);
} catch(UnknownHostException badAddressLength) {
throw new RuntimeException(badAddressLength);
}
return new InetSocketAddress(address, port);
}
private byte legalAddressByte(byte b) {
if(b == 0) return 1;
if(b == (byte) 255) return (byte) 254;
return b;
}
private InetAddress chooseInvitationInterface() throws IOException {
List<NetworkInterface> ifaces =
Collections.list(NetworkInterface.getNetworkInterfaces());
// Prefer an interface with a link-local or site-local address
for(NetworkInterface iface : ifaces) {
for(InetAddress addr : Collections.list(iface.getInetAddresses())) {
if(addr.isLoopbackAddress()) continue;
boolean link = addr.isLinkLocalAddress();
boolean site = addr.isSiteLocalAddress();
if(link || site) return addr;
}
}
// Accept an interface without a link-local or site-local address
for(NetworkInterface iface : ifaces) {
for(InetAddress addr : Collections.list(iface.getInetAddresses())) {
if(!addr.isLoopbackAddress()) return addr;
}
}
// No suitable interfaces
return null;
}
private void tryToClose(MulticastSocket ms, InetAddress addr) {
try {
ms.leaveGroup(addr);
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
ms.close();
}
private class MulticastListenerThread extends Thread {
private final LatchedReference<Socket> socketLatch;
private final MulticastSocket multicastSocket;
private final InetAddress localAddress;
private MulticastListenerThread(LatchedReference<Socket> socketLatch,
MulticastSocket multicastSocket, InetAddress localAddress) {
this.socketLatch = socketLatch;
this.multicastSocket = multicastSocket;
this.localAddress = localAddress;
}
@Override
public void run() {
if(LOG.isLoggable(INFO))
LOG.info("Listening for multicast packets");
// Listen until a valid packet is received or the socket is closed
byte[] buffer = new byte[2];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
try {
while(running) {
multicastSocket.receive(packet);
if(LOG.isLoggable(INFO))
LOG.info("Received multicast packet");
parseAndConnectBack(packet);
}
} catch(IOException e) {
// This is expected when the socket is closed
if(LOG.isLoggable(INFO)) LOG.log(INFO, e.toString(), e);
}
}
private void parseAndConnectBack(DatagramPacket packet) {
InetAddress addr = packet.getAddress();
if(addr.equals(localAddress)) {
if(LOG.isLoggable(INFO)) LOG.info("Ignoring own packet");
return;
}
byte[] b = packet.getData();
int off = packet.getOffset();
int len = packet.getLength();
if(len != 2) {
if(LOG.isLoggable(INFO)) LOG.info("Invalid length: " + len);
return;
}
int port = ByteUtils.readUint16(b, off);
if(port < 32768 || port >= 65536) {
if(LOG.isLoggable(INFO)) LOG.info("Invalid port: " + port);
return;
}
if(LOG.isLoggable(INFO))
LOG.info("Packet from " + getHostAddress(addr) + ":" + port);
try {
// Connect back on the advertised TCP port
Socket s = new Socket(addr, port);
if(LOG.isLoggable(INFO)) LOG.info("Outgoing connection");
if(!socketLatch.set(s)) {
if(LOG.isLoggable(INFO))
LOG.info("Closing redundant connection");
s.close();
}
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}
private class TcpListenerThread extends Thread {
private final LatchedReference<Socket> socketLatch;
private final ServerSocket serverSocket;
private TcpListenerThread(LatchedReference<Socket> socketLatch,
ServerSocket serverSocket) {
this.socketLatch = socketLatch;
this.serverSocket = serverSocket;
}
@Override
public void run() {
if(LOG.isLoggable(INFO))
LOG.info("Listening for invitation connections");
// Listen until a connection is received or the socket is closed
try {
Socket s = serverSocket.accept();
if(LOG.isLoggable(INFO)) LOG.info("Incoming connection");
if(!socketLatch.set(s)) {
if(LOG.isLoggable(INFO))
LOG.info("Closing redundant connection");
s.close();
}
} catch(IOException e) {
// This is expected when the socket is closed
if(LOG.isLoggable(INFO)) LOG.log(INFO, e.toString(), e);
}
}
}
}

View File

@@ -0,0 +1,34 @@
package org.briarproject.plugins.tcp;
import java.util.concurrent.Executor;
import org.briarproject.api.TransportId;
import org.briarproject.api.plugins.duplex.DuplexPlugin;
import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
import org.briarproject.api.plugins.duplex.DuplexPluginFactory;
import org.briarproject.api.system.Clock;
import org.briarproject.api.system.SystemClock;
public class LanTcpPluginFactory implements DuplexPluginFactory {
private static final int MAX_FRAME_LENGTH = 1024;
private static final long MAX_LATENCY = 60 * 1000; // 1 minute
private static final long POLLING_INTERVAL = 60 * 1000; // 1 minute
private final Executor pluginExecutor;
private final Clock clock;
public LanTcpPluginFactory(Executor pluginExecutor) {
this.pluginExecutor = pluginExecutor;
clock = new SystemClock();
}
public TransportId getId() {
return LanTcpPlugin.ID;
}
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
return new LanTcpPlugin(pluginExecutor, clock, callback,
MAX_FRAME_LENGTH, MAX_LATENCY, POLLING_INTERVAL);
}
}

View File

@@ -0,0 +1,31 @@
package org.briarproject.plugins.tcp;
import java.net.InetAddress;
import java.net.InetSocketAddress;
class MappingResult {
private final InetAddress internal, external;
private final int port;
private final boolean succeeded;
MappingResult(InetAddress internal, InetAddress external, int port,
boolean succeeded) {
this.internal = internal;
this.external = external;
this.port = port;
this.succeeded = succeeded;
}
InetSocketAddress getInternal() {
return isUsable() ? new InetSocketAddress(internal, port) : null;
}
InetSocketAddress getExternal() {
return isUsable() ? new InetSocketAddress(external, port) : null;
}
boolean isUsable() {
return internal != null && external != null && port != 0 && succeeded;
}
}

View File

@@ -0,0 +1,6 @@
package org.briarproject.plugins.tcp;
interface PortMapper {
MappingResult map(int port);
}

View File

@@ -0,0 +1,97 @@
package org.briarproject.plugins.tcp;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import java.io.IOException;
import java.net.InetAddress;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import javax.xml.parsers.ParserConfigurationException;
import org.briarproject.api.lifecycle.ShutdownManager;
import org.bitlet.weupnp.GatewayDevice;
import org.bitlet.weupnp.GatewayDiscover;
import org.xml.sax.SAXException;
class PortMapperImpl implements PortMapper {
private static final Logger LOG =
Logger.getLogger(PortMapperImpl.class.getName());
private final ShutdownManager shutdownManager;
private final AtomicBoolean started = new AtomicBoolean(false);
private volatile GatewayDevice gateway = null;
PortMapperImpl(ShutdownManager shutdownManager) {
this.shutdownManager = shutdownManager;
}
public MappingResult map(final int port) {
if(!started.getAndSet(true)) start();
if(gateway == null) return null;
InetAddress internal = gateway.getLocalAddress();
if(internal == null) return null;
if(LOG.isLoggable(INFO))
LOG.info("Internal address " + getHostAddress(internal));
boolean succeeded = false;
InetAddress external = null;
try {
succeeded = gateway.addPortMapping(port, port,
getHostAddress(internal), "TCP", "TCP");
if(succeeded) {
shutdownManager.addShutdownHook(new Runnable() {
public void run() {
deleteMapping(port);
}
});
}
String externalString = gateway.getExternalIPAddress();
if(LOG.isLoggable(INFO))
LOG.info("External address " + externalString);
if(externalString != null)
external = InetAddress.getByName(externalString);
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} catch(SAXException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
return new MappingResult(internal, external, port, succeeded);
}
private String getHostAddress(InetAddress a) {
String addr = a.getHostAddress();
int percent = addr.indexOf('%');
if(percent == -1) return addr;
return addr.substring(0, percent);
}
private void start() {
GatewayDiscover d = new GatewayDiscover();
try {
d.discover();
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} catch(SAXException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} catch(ParserConfigurationException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
gateway = d.getValidGateway();
}
private void deleteMapping(int port) {
try {
gateway.deletePortMapping(port, "TCP");
if(LOG.isLoggable(INFO))
LOG.info("Deleted mapping for port " + port);
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} catch(SAXException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}

View File

@@ -0,0 +1,210 @@
package org.briarproject.plugins.tcp;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import org.briarproject.api.ContactId;
import org.briarproject.api.TransportProperties;
import org.briarproject.api.plugins.duplex.DuplexPlugin;
import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
import org.briarproject.util.StringUtils;
abstract class TcpPlugin implements DuplexPlugin {
private static final Logger LOG =
Logger.getLogger(TcpPlugin.class.getName());
protected final Executor pluginExecutor;
protected final DuplexPluginCallback callback;
protected final int maxFrameLength;
protected final long maxLatency, pollingInterval;
protected volatile boolean running = false;
private volatile ServerSocket socket = null;
/**
* Returns zero or more socket addresses on which the plugin should listen,
* in order of preference. At most one of the addresses will be bound.
*/
protected abstract List<SocketAddress> getLocalSocketAddresses();
protected TcpPlugin(Executor pluginExecutor, DuplexPluginCallback callback,
int maxFrameLength, long maxLatency, long pollingInterval) {
this.pluginExecutor = pluginExecutor;
this.callback = callback;
this.maxFrameLength = maxFrameLength;
this.maxLatency = maxLatency;
this.pollingInterval = pollingInterval;
}
public int getMaxFrameLength() {
return maxFrameLength;
}
public long getMaxLatency() {
return maxLatency;
}
public boolean start() {
running = true;
pluginExecutor.execute(new Runnable() {
public void run() {
bind();
}
});
return true;
}
private void bind() {
ServerSocket ss = null;
boolean found = false;
for(SocketAddress addr : getLocalSocketAddresses()) {
try {
ss = new ServerSocket();
ss.bind(addr);
found = true;
break;
} catch(IOException e) {
if(LOG.isLoggable(INFO)) LOG.info("Failed to bind " + addr);
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
tryToClose(ss);
continue;
}
}
if(!found) {
if(LOG.isLoggable(INFO)) LOG.info("Could not bind server socket");
return;
}
if(!running) {
tryToClose(ss);
return;
}
socket = ss;
if(LOG.isLoggable(INFO))
LOG.info("Listening on " + ss.getLocalSocketAddress());
setLocalSocketAddress((InetSocketAddress) ss.getLocalSocketAddress());
acceptContactConnections(ss);
}
protected void tryToClose(ServerSocket ss) {
try {
ss.close();
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
protected String getHostAddress(InetAddress a) {
String addr = a.getHostAddress();
int percent = addr.indexOf('%');
return percent == -1 ? addr : addr.substring(0, percent);
}
protected void setLocalSocketAddress(InetSocketAddress a) {
TransportProperties p = new TransportProperties();
p.put("address", getHostAddress(a.getAddress()));
p.put("port", String.valueOf(a.getPort()));
callback.mergeLocalProperties(p);
}
private void acceptContactConnections(ServerSocket ss) {
while(true) {
Socket s;
try {
s = ss.accept();
} catch(IOException e) {
// This is expected when the socket is closed
if(LOG.isLoggable(INFO)) LOG.log(INFO, e.toString(), e);
tryToClose(ss);
return;
}
if(LOG.isLoggable(INFO))
LOG.info("Connection from " + s.getRemoteSocketAddress());
TcpTransportConnection conn = new TcpTransportConnection(this, s);
callback.incomingConnectionCreated(conn);
if(!running) return;
}
}
public void stop() {
running = false;
if(socket != null) tryToClose(socket);
}
public boolean shouldPoll() {
return true;
}
public long getPollingInterval() {
return pollingInterval;
}
public void poll(Collection<ContactId> connected) {
if(!running) return;
Map<ContactId, TransportProperties> remote =
callback.getRemoteProperties();
for(final ContactId c : remote.keySet()) {
if(connected.contains(c)) continue;
pluginExecutor.execute(new Runnable() {
public void run() {
connectAndCallBack(c);
}
});
}
}
private void connectAndCallBack(ContactId c) {
DuplexTransportConnection d = createConnection(c);
if(d != null) callback.outgoingConnectionCreated(c, d);
}
public DuplexTransportConnection createConnection(ContactId c) {
if(!running) return null;
SocketAddress addr = getRemoteSocketAddress(c);
if(addr == null) return null;
Socket s = new Socket();
try {
if(LOG.isLoggable(INFO)) LOG.info("Connecting to " + addr);
s.connect(addr);
if(LOG.isLoggable(INFO)) LOG.info("Connected to " + addr);
return new TcpTransportConnection(this, s);
} catch(IOException e) {
if(LOG.isLoggable(INFO)) LOG.log(INFO, e.toString(), e);
return null;
}
}
private SocketAddress getRemoteSocketAddress(ContactId c) {
TransportProperties p = callback.getRemoteProperties().get(c);
if(p == null) return null;
String addrString = p.get("address");
if(StringUtils.isNullOrEmpty(addrString)) return null;
String portString = p.get("port");
if(StringUtils.isNullOrEmpty(portString)) return null;
try {
InetAddress addr = InetAddress.getByName(addrString);
int port = Integer.parseInt(portString);
return new InetSocketAddress(addr, port);
} catch(NumberFormatException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return null;
} catch(UnknownHostException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return null;
}
}
}

View File

@@ -0,0 +1,45 @@
package org.briarproject.plugins.tcp;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import org.briarproject.api.plugins.Plugin;
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
class TcpTransportConnection implements DuplexTransportConnection {
private final Plugin plugin;
private final Socket socket;
TcpTransportConnection(Plugin plugin, Socket socket) {
this.plugin = plugin;
this.socket = socket;
}
public int getMaxFrameLength() {
return plugin.getMaxFrameLength();
}
public long getMaxLatency() {
return plugin.getMaxLatency();
}
public InputStream getInputStream() throws IOException {
return socket.getInputStream();
}
public OutputStream getOutputStream() throws IOException {
return socket.getOutputStream();
}
public boolean shouldFlush() {
return true;
}
public void dispose(boolean exception, boolean recognised)
throws IOException {
socket.close();
}
}

View File

@@ -0,0 +1,132 @@
package org.briarproject.plugins.tcp;
import static java.util.logging.Level.WARNING;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import org.briarproject.api.TransportId;
import org.briarproject.api.TransportProperties;
import org.briarproject.api.crypto.PseudoRandom;
import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
import org.briarproject.util.StringUtils;
class WanTcpPlugin extends TcpPlugin {
static final byte[] TRANSPORT_ID =
StringUtils.fromHexString("58c66d999e492b85065924acfd739d80"
+ "c65a62f87e5a4fc6c284f95908b9007d"
+ "512a93ebf89bf68f50a29e96eebf97b6");
static final TransportId ID = new TransportId(TRANSPORT_ID);
private static final Logger LOG =
Logger.getLogger(WanTcpPlugin.class.getName());
private final PortMapper portMapper;
private volatile MappingResult mappingResult;
WanTcpPlugin(Executor pluginExecutor, DuplexPluginCallback callback,
int maxFrameLength, long maxLatency, long pollingInterval,
PortMapper portMapper) {
super(pluginExecutor, callback, maxFrameLength, maxLatency,
pollingInterval);
this.portMapper = portMapper;
}
public TransportId getId() {
return ID;
}
public String getName() {
return "WAN_TCP_PLUGIN_NAME";
}
@Override
protected List<SocketAddress> getLocalSocketAddresses() {
List<SocketAddress> addrs = new ArrayList<SocketAddress>();
// Prefer a previously used address and port if available
TransportProperties p = callback.getLocalProperties();
String addrString = p.get("address");
String portString = p.get("port");
InetAddress addr = null;
int port = 0;
if(!StringUtils.isNullOrEmpty(addrString) &&
!StringUtils.isNullOrEmpty(portString)) {
try {
addr = InetAddress.getByName(addrString);
port = Integer.parseInt(portString);
addrs.add(new InetSocketAddress(addr, port));
addrs.add(new InetSocketAddress(addr, 0));
} catch(NumberFormatException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} catch(UnknownHostException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
// Get a list of the device's network interfaces
List<NetworkInterface> ifaces;
try {
ifaces = Collections.list(NetworkInterface.getNetworkInterfaces());
} catch(SocketException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return addrs;
}
// Accept interfaces without link-local or site-local addresses
for(NetworkInterface iface : ifaces) {
for(InetAddress a : Collections.list(iface.getInetAddresses())) {
if(addr != null && a.equals(addr)) continue;
if(a instanceof Inet6Address) continue;
if(a.isLoopbackAddress()) continue;
boolean link = a.isLinkLocalAddress();
boolean site = a.isSiteLocalAddress();
if(!link && !site) addrs.add(new InetSocketAddress(a, 0));
}
}
// Accept interfaces with local addresses that can be port-mapped
if(port == 0) port = chooseEphemeralPort();
mappingResult = portMapper.map(port);
if(mappingResult != null && mappingResult.isUsable()) {
InetSocketAddress a = mappingResult.getInternal();
if(!(a.getAddress() instanceof Inet6Address)) addrs.add(a);
}
return addrs;
}
private int chooseEphemeralPort() {
return 32768 + (int) (Math.random() * 32768);
}
@Override
protected void setLocalSocketAddress(InetSocketAddress a) {
if(mappingResult != null && mappingResult.isUsable()) {
// Advertise the external address to contacts
if(a.equals(mappingResult.getInternal()))
a = mappingResult.getExternal();
}
TransportProperties p = new TransportProperties();
p.put("address", getHostAddress(a.getAddress()));
p.put("port", String.valueOf(a.getPort()));
callback.mergeLocalProperties(p);
}
public boolean supportsInvitations() {
return false;
}
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
long timeout) {
throw new UnsupportedOperationException();
}
}

View File

@@ -0,0 +1,35 @@
package org.briarproject.plugins.tcp;
import java.util.concurrent.Executor;
import org.briarproject.api.TransportId;
import org.briarproject.api.lifecycle.ShutdownManager;
import org.briarproject.api.plugins.duplex.DuplexPlugin;
import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
import org.briarproject.api.plugins.duplex.DuplexPluginFactory;
public class WanTcpPluginFactory implements DuplexPluginFactory {
private static final int MAX_FRAME_LENGTH = 1024;
private static final long MAX_LATENCY = 60 * 1000; // 1 minute
private static final long POLLING_INTERVAL = 5 * 60 * 1000; // 5 minutes
private final Executor pluginExecutor;
private final ShutdownManager shutdownManager;
public WanTcpPluginFactory(Executor pluginExecutor,
ShutdownManager shutdownManager) {
this.pluginExecutor = pluginExecutor;
this.shutdownManager = shutdownManager;
}
public TransportId getId() {
return WanTcpPlugin.ID;
}
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
return new WanTcpPlugin(pluginExecutor, callback, MAX_FRAME_LENGTH,
MAX_LATENCY, POLLING_INTERVAL,
new PortMapperImpl(shutdownManager));
}
}