From 0f04ea5496b4d4a3497cef827d15afed2cdc41c8 Mon Sep 17 00:00:00 2001 From: akwizgran <michael@briarproject.org> Date: Fri, 2 Nov 2012 19:38:57 +0000 Subject: [PATCH] UPnP port mapper using the Weupnp library (untested). --- libs/weupnp-0.1.1.jar | Bin 0 -> 25422 bytes src/net/sf/briar/HelloWorldModule.java | 7 +- src/net/sf/briar/HelloWorldService.java | 7 +- .../sf/briar/plugins/tcp/MappingResult.java | 32 +++++++ src/net/sf/briar/plugins/tcp/PortMapper.java | 10 +++ .../sf/briar/plugins/tcp/PortMapperImpl.java | 82 ++++++++++++++++++ src/net/sf/briar/plugins/tcp/TcpPlugin.java | 14 +-- .../sf/briar/plugins/tcp/WanTcpPlugin.java | 56 +++++++++++- .../plugins/tcp/WanTcpPluginFactory.java | 3 +- 9 files changed, 192 insertions(+), 19 deletions(-) create mode 100644 libs/weupnp-0.1.1.jar create mode 100644 src/net/sf/briar/plugins/tcp/MappingResult.java create mode 100644 src/net/sf/briar/plugins/tcp/PortMapper.java create mode 100644 src/net/sf/briar/plugins/tcp/PortMapperImpl.java diff --git a/libs/weupnp-0.1.1.jar b/libs/weupnp-0.1.1.jar new file mode 100644 index 0000000000000000000000000000000000000000..2626a76c0284e8ca9e42bf5ce111ba1afb7527ab GIT binary patch literal 25422 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 literal 0 HcmV?d00001 diff --git a/src/net/sf/briar/HelloWorldModule.java b/src/net/sf/briar/HelloWorldModule.java index 64a3d8fcf2..a0fa22c7f8 100644 --- a/src/net/sf/briar/HelloWorldModule.java +++ b/src/net/sf/briar/HelloWorldModule.java @@ -1,13 +1,10 @@ package net.sf.briar; -import static android.content.Context.MODE_PRIVATE; - import java.io.File; import net.sf.briar.api.crypto.Password; import net.sf.briar.api.db.DatabaseConfig; import net.sf.briar.api.ui.UiCallback; -import android.content.Context; import com.google.inject.AbstractModule; @@ -16,7 +13,7 @@ public class HelloWorldModule extends AbstractModule { private final DatabaseConfig config; private final UiCallback callback; - public HelloWorldModule(final Context appContext) { + public HelloWorldModule(final File dir) { final Password password = new Password() { public char[] getPassword() { @@ -26,7 +23,7 @@ public class HelloWorldModule extends AbstractModule { config = new DatabaseConfig() { public File getDataDirectory() { - return appContext.getDir("db", MODE_PRIVATE); + return dir; } public Password getPassword() { diff --git a/src/net/sf/briar/HelloWorldService.java b/src/net/sf/briar/HelloWorldService.java index 2eec307890..6dbbb30790 100644 --- a/src/net/sf/briar/HelloWorldService.java +++ b/src/net/sf/briar/HelloWorldService.java @@ -3,6 +3,7 @@ package net.sf.briar; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; +import java.io.File; import java.io.IOException; import java.util.logging.Logger; @@ -55,8 +56,8 @@ public class HelloWorldService extends Service implements Runnable { } public void run() { - Injector i = Guice.createInjector( - new HelloWorldModule(getApplicationContext()), + File dir = getApplicationContext().getDir("db", MODE_PRIVATE); + Injector i = Guice.createInjector(new HelloWorldModule(dir), new AndroidModule(), new ClockModule(), new CryptoModule(), new DatabaseModule(), new LifecycleModule(), new PluginsModule(), new ProtocolModule(), @@ -77,7 +78,7 @@ public class HelloWorldService extends Service implements Runnable { LOG.info(pluginsStarted + " plugins started"); // ...sleep... try { - Thread.sleep(1000); + Thread.sleep(30 * 1000); } catch(InterruptedException ignored) {} // ...and stop if(LOG.isLoggable(INFO)) LOG.info("Shutting down"); diff --git a/src/net/sf/briar/plugins/tcp/MappingResult.java b/src/net/sf/briar/plugins/tcp/MappingResult.java new file mode 100644 index 0000000000..f558a8d731 --- /dev/null +++ b/src/net/sf/briar/plugins/tcp/MappingResult.java @@ -0,0 +1,32 @@ +package net.sf.briar.plugins.tcp; + +import java.net.InetAddress; + +class MappingResult { + + private final InetAddress internal, external; + private final boolean succeeded; + + MappingResult(InetAddress internal, InetAddress external, + boolean succeeded) { + this.internal = internal; + this.external = external; + this.succeeded = succeeded; + } + + InetAddress getInternal() { + return internal; + } + + InetAddress getExternal() { + return external; + } + + boolean getSucceeded() { + return succeeded; + } + + boolean isUsable() { + return internal != null && external != null && succeeded; + } +} diff --git a/src/net/sf/briar/plugins/tcp/PortMapper.java b/src/net/sf/briar/plugins/tcp/PortMapper.java new file mode 100644 index 0000000000..5bd226329d --- /dev/null +++ b/src/net/sf/briar/plugins/tcp/PortMapper.java @@ -0,0 +1,10 @@ +package net.sf.briar.plugins.tcp; + +interface PortMapper { + + void start(); + + void stop(); + + MappingResult map(int port); +} diff --git a/src/net/sf/briar/plugins/tcp/PortMapperImpl.java b/src/net/sf/briar/plugins/tcp/PortMapperImpl.java new file mode 100644 index 0000000000..e990cb2e38 --- /dev/null +++ b/src/net/sf/briar/plugins/tcp/PortMapperImpl.java @@ -0,0 +1,82 @@ +package net.sf.briar.plugins.tcp; + +import static java.util.logging.Level.WARNING; + +import java.io.IOException; +import java.net.InetAddress; +import java.util.Collection; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.logging.Logger; + +import javax.xml.parsers.ParserConfigurationException; + +import org.wetorrent.upnp.GatewayDevice; +import org.wetorrent.upnp.GatewayDiscover; +import org.xml.sax.SAXException; + +class PortMapperImpl implements PortMapper { + + private static final Logger LOG = + Logger.getLogger(PortMapperImpl.class.getName()); + + private final CountDownLatch started = new CountDownLatch(1); + private final Collection<Integer> ports = + new CopyOnWriteArrayList<Integer>(); + + private volatile GatewayDevice gateway = null; + + public void start() { + GatewayDiscover d = new GatewayDiscover(); + try { + d.discover(); + } catch(IOException e) { + if(LOG.isLoggable(WARNING)) LOG.warning(e.toString()); + } catch(SAXException e) { + if(LOG.isLoggable(WARNING)) LOG.warning(e.toString()); + } catch(ParserConfigurationException e) { + if(LOG.isLoggable(WARNING)) LOG.warning(e.toString()); + } + gateway = d.getValidGateway(); + started.countDown(); + } + + public void stop() { + if(gateway == null) return; + try { + for(Integer port: ports) gateway.deletePortMapping(port, "TCP"); + } catch(IOException e) { + if(LOG.isLoggable(WARNING)) LOG.warning(e.toString()); + } catch(SAXException e) { + if(LOG.isLoggable(WARNING)) LOG.warning(e.toString()); + } + } + + public MappingResult map(int port) { + try { + started.await(); + } catch(InterruptedException e) { + if(LOG.isLoggable(WARNING)) LOG.warning(e.toString()); + Thread.currentThread().interrupt(); + return null; + } + if(gateway == null) return null; + InetAddress internal = gateway.getLocalAddress(); + if(internal == null) return null; + boolean succeeded = false; + InetAddress external = null; + try { + succeeded = gateway.addPortMapping(port, port, + internal.getHostAddress(), "TCP", "TCP"); + String externalString = gateway.getExternalIPAddress(); + if(externalString != null) + external = InetAddress.getByName(externalString); + } catch(IOException e) { + if(LOG.isLoggable(WARNING)) LOG.warning(e.toString()); + } catch(SAXException e) { + if(LOG.isLoggable(WARNING)) LOG.warning(e.toString()); + } + if(succeeded) ports.add(port); + return new MappingResult(internal, external, succeeded); + } +} diff --git a/src/net/sf/briar/plugins/tcp/TcpPlugin.java b/src/net/sf/briar/plugins/tcp/TcpPlugin.java index 603cba44e1..f476aad47b 100644 --- a/src/net/sf/briar/plugins/tcp/TcpPlugin.java +++ b/src/net/sf/briar/plugins/tcp/TcpPlugin.java @@ -91,10 +91,11 @@ abstract class TcpPlugin implements DuplexPlugin { socket = ss; } if(LOG.isLoggable(INFO)) { - LOG.info("Listening on " + ss.getInetAddress().getHostAddress() - + ":" + ss.getLocalPort()); + String addr = ss.getInetAddress().getHostAddress(); + int port = ss.getLocalPort(); + LOG.info("Listening on " + addr + " " + port); } - setLocalSocketAddress(ss.getLocalSocketAddress()); + setLocalSocketAddress((InetSocketAddress) ss.getLocalSocketAddress()); acceptContactConnections(ss); } @@ -106,12 +107,11 @@ abstract class TcpPlugin implements DuplexPlugin { } } - private void setLocalSocketAddress(SocketAddress s) { - InetSocketAddress i = (InetSocketAddress) s; - InetAddress addr = i.getAddress(); + protected void setLocalSocketAddress(InetSocketAddress a) { + InetAddress addr = a.getAddress(); TransportProperties p = new TransportProperties(); p.put("address", addr.getHostAddress()); - p.put("port", String.valueOf(i.getPort())); + p.put("port", String.valueOf(a.getPort())); callback.mergeLocalProperties(p); } diff --git a/src/net/sf/briar/plugins/tcp/WanTcpPlugin.java b/src/net/sf/briar/plugins/tcp/WanTcpPlugin.java index 69bd3074e0..9b2cd234fa 100644 --- a/src/net/sf/briar/plugins/tcp/WanTcpPlugin.java +++ b/src/net/sf/briar/plugins/tcp/WanTcpPlugin.java @@ -2,6 +2,7 @@ package net.sf.briar.plugins.tcp; import static java.util.logging.Level.WARNING; +import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.NetworkInterface; @@ -33,27 +34,54 @@ class WanTcpPlugin extends TcpPlugin { private static final Logger LOG = Logger.getLogger(WanTcpPlugin.class.getName()); + private final PortMapper portMapper; + + private volatile MappingResult mappingResult; + WanTcpPlugin(@PluginExecutor Executor pluginExecutor, - DuplexPluginCallback callback, long pollingInterval) { + DuplexPluginCallback callback, long pollingInterval, + PortMapper portMapper) { super(pluginExecutor, callback, pollingInterval); + this.portMapper = portMapper; } public TransportId getId() { return ID; } + @Override + public void start() throws IOException { + super.start(); + pluginExecutor.execute(new Runnable() { + public void run() { + portMapper.start(); + } + }); + } + + @Override + public void stop() throws IOException { + super.stop(); + pluginExecutor.execute(new Runnable() { + public void run() { + portMapper.stop(); + } + }); + } + @Override protected List<SocketAddress> getLocalSocketAddresses() { List<SocketAddress> addrs = new ArrayList<SocketAddress>(); - // Prefer a previously used address and port if available + // Prefer a previously used external 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(addrString != null && portString != null) { try { addr = InetAddress.getByName(addrString); - int port = Integer.valueOf(portString); + port = Integer.valueOf(portString); addrs.add(new InetSocketAddress(addr, port)); addrs.add(new InetSocketAddress(addr, 0)); } catch(NumberFormatException e) { @@ -79,9 +107,31 @@ class WanTcpPlugin extends TcpPlugin { if(!link && !site) addrs.add(new InetSocketAddress(a, 0)); } } + // Accept interfaces that can be port-mapped + if(port == 0) port = chooseEphemeralPort(); + mappingResult = portMapper.map(port); + if(mappingResult != null && mappingResult.isUsable()) + addrs.add(new InetSocketAddress(mappingResult.getInternal(), port)); return addrs; } + private int chooseEphemeralPort() { + return 32768 + (int) (Math.random() * 32768); + } + + @Override + protected void setLocalSocketAddress(InetSocketAddress a) { + InetAddress addr = a.getAddress(); + if(mappingResult != null && mappingResult.isUsable()) { + if(addr.equals(mappingResult.getInternal())) + addr = mappingResult.getExternal(); + } + TransportProperties p = new TransportProperties(); + p.put("address", addr.getHostAddress()); + p.put("port", String.valueOf(a.getPort())); + callback.mergeLocalProperties(p); + } + public boolean supportsInvitations() { return false; } diff --git a/src/net/sf/briar/plugins/tcp/WanTcpPluginFactory.java b/src/net/sf/briar/plugins/tcp/WanTcpPluginFactory.java index e4ed87284f..f976e22cf5 100644 --- a/src/net/sf/briar/plugins/tcp/WanTcpPluginFactory.java +++ b/src/net/sf/briar/plugins/tcp/WanTcpPluginFactory.java @@ -16,6 +16,7 @@ public class WanTcpPluginFactory implements DuplexPluginFactory { public DuplexPlugin createPlugin(@PluginExecutor Executor pluginExecutor, AndroidExecutor androidExecutor, Context appContext, DuplexPluginCallback callback) { - return new WanTcpPlugin(pluginExecutor, callback, POLLING_INTERVAL); + return new WanTcpPlugin(pluginExecutor, callback, POLLING_INTERVAL, + new PortMapperImpl()); } } -- GitLab