Merge branch '302-lan-plugin-privacy' into 'master'

Store a fixed number of recent IP addresses, padded with fakes

Closes #302

See merge request !159
This commit is contained in:
akwizgran
2016-05-06 16:00:36 +00:00
4 changed files with 170 additions and 71 deletions

View File

@@ -1,14 +1,19 @@
package org.briarproject.plugins.tcp;
import org.briarproject.api.TransportId;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.plugins.Backoff;
import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
import org.briarproject.api.properties.TransportProperties;
import org.briarproject.api.settings.Settings;
import org.briarproject.util.StringUtils;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Executor;
@@ -17,6 +22,10 @@ class LanTcpPlugin extends TcpPlugin {
static final TransportId ID = new TransportId("lan");
private static final int MAX_ADDRESSES = 5;
private static final String PROP_IP_PORTS = "ipPorts";
private static final String SEPARATOR = ",";
LanTcpPlugin(Executor ioExecutor, Backoff backoff,
DuplexPluginCallback callback, int maxLatency, int maxIdleTime) {
super(ioExecutor, backoff, callback, maxLatency, maxIdleTime);
@@ -30,18 +39,74 @@ class LanTcpPlugin extends TcpPlugin {
protected List<SocketAddress> getLocalSocketAddresses() {
// Use the same address and port as last time if available
TransportProperties p = callback.getLocalProperties();
String oldAddress = p.get("address"), oldPort = p.get("port");
InetSocketAddress old = parseSocketAddress(oldAddress, oldPort);
List<SocketAddress> addrs = new LinkedList<SocketAddress>();
for (InetAddress a : getLocalIpAddresses()) {
if (isAcceptableAddress(a)) {
String oldIpPorts = p.get(PROP_IP_PORTS);
List<InetSocketAddress> olds = parseSocketAddresses(oldIpPorts);
List<SocketAddress> locals = new LinkedList<SocketAddress>();
for (InetAddress local : getLocalIpAddresses()) {
if (isAcceptableAddress(local)) {
// If this is the old address, try to use the same port
if (old != null && old.getAddress().equals(a))
addrs.add(0, new InetSocketAddress(a, old.getPort()));
addrs.add(new InetSocketAddress(a, 0));
for (InetSocketAddress old : olds) {
if (old.getAddress().equals(local)) {
int port = old.getPort();
locals.add(0, new InetSocketAddress(local, port));
}
}
locals.add(new InetSocketAddress(local, 0));
}
}
return addrs;
return locals;
}
private List<InetSocketAddress> parseSocketAddresses(String ipPorts) {
if (StringUtils.isNullOrEmpty(ipPorts)) return Collections.emptyList();
String[] split = ipPorts.split(SEPARATOR);
List<InetSocketAddress> remotes = new ArrayList<InetSocketAddress>();
for (String ipPort : split) {
InetSocketAddress a = parseSocketAddress(ipPort);
if (a != null) remotes.add(a);
}
return remotes;
}
@Override
protected void setLocalSocketAddress(InetSocketAddress a) {
String ipPort = getIpPortString(a);
// Get the list of recently used addresses
String setting = callback.getSettings().get(PROP_IP_PORTS);
List<String> recent = new ArrayList<String>();
if (!StringUtils.isNullOrEmpty(setting))
Collections.addAll(recent, setting.split(SEPARATOR));
// Is the address already in the list?
if (recent.remove(ipPort)) {
// Move the address to the start of the list
recent.add(0, ipPort);
setting = StringUtils.join(recent, SEPARATOR);
} else {
// Add the address to the start of the list
recent.add(0, ipPort);
// Drop the least recently used address if the list is full
if (recent.size() > MAX_ADDRESSES)
recent = recent.subList(0, MAX_ADDRESSES);
setting = StringUtils.join(recent, SEPARATOR);
// Update the list of addresses shared with contacts
List<String> shared = new ArrayList<String>(recent);
Collections.sort(shared);
String property = StringUtils.join(shared, SEPARATOR);
TransportProperties properties = new TransportProperties();
properties.put(PROP_IP_PORTS, property);
callback.mergeLocalProperties(properties);
}
// Save the setting
Settings settings = new Settings();
settings.put(PROP_IP_PORTS, setting);
callback.mergeSettings(settings);
}
@Override
protected List<InetSocketAddress> getRemoteSocketAddresses(ContactId c) {
TransportProperties p = callback.getRemoteProperties().get(c);
if (p == null) return Collections.emptyList();
return parseSocketAddresses(p.get(PROP_IP_PORTS));
}
private boolean isAcceptableAddress(InetAddress a) {

View File

@@ -8,7 +8,6 @@ import org.briarproject.api.plugins.Backoff;
import org.briarproject.api.plugins.duplex.DuplexPlugin;
import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
import org.briarproject.api.properties.TransportProperties;
import org.briarproject.util.StringUtils;
import java.io.IOException;
@@ -52,7 +51,22 @@ abstract class TcpPlugin implements DuplexPlugin {
*/
protected abstract List<SocketAddress> getLocalSocketAddresses();
/** Returns true if connections to the given address can be attempted. */
/**
* Adds the address on which the plugin is listening to the transport
* properties.
*/
protected abstract void setLocalSocketAddress(InetSocketAddress a);
/**
* Returns zero or more socket addresses for connecting to the given
* contact.
*/
protected abstract List<InetSocketAddress> getRemoteSocketAddresses(
ContactId c);
/**
* Returns true if connections to the given address can be attempted.
*/
protected abstract boolean isConnectable(InetSocketAddress remote);
protected TcpPlugin(Executor ioExecutor, Backoff backoff,
@@ -126,17 +140,11 @@ abstract class TcpPlugin implements DuplexPlugin {
}
}
protected String getHostAddress(InetAddress a) {
String addr = a.getHostAddress();
protected String getIpPortString(InetSocketAddress a) {
String addr = a.getAddress().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);
if (percent != -1) addr = addr.substring(0, percent);
return addr + ":" + a.getPort();
}
private void acceptContactConnections() {
@@ -197,37 +205,34 @@ abstract class TcpPlugin implements DuplexPlugin {
public DuplexTransportConnection createConnection(ContactId c) {
if (!isRunning()) return null;
InetSocketAddress remote = getRemoteSocketAddress(c);
if (remote == null) return null;
if (!isConnectable(remote)) {
if (LOG.isLoggable(INFO)) {
SocketAddress local = socket.getLocalSocketAddress();
LOG.info(remote + " is not connectable from " + local);
for (InetSocketAddress remote : getRemoteSocketAddresses(c)) {
if (!isConnectable(remote)) {
if (LOG.isLoggable(INFO)) {
SocketAddress local = socket.getLocalSocketAddress();
LOG.info(remote + " is not connectable from " + local);
}
continue;
}
Socket s = new Socket();
try {
if (LOG.isLoggable(INFO)) LOG.info("Connecting to " + remote);
s.connect(remote);
s.setSoTimeout(socketTimeout);
if (LOG.isLoggable(INFO)) LOG.info("Connected to " + remote);
return new TcpTransportConnection(this, s);
} catch (IOException e) {
if (LOG.isLoggable(INFO))
LOG.info("Could not connect to " + remote);
}
return null;
}
Socket s = new Socket();
try {
if (LOG.isLoggable(INFO)) LOG.info("Connecting to " + remote);
s.connect(remote);
s.setSoTimeout(socketTimeout);
if (LOG.isLoggable(INFO)) LOG.info("Connected to " + remote);
return new TcpTransportConnection(this, s);
} catch (IOException e) {
if (LOG.isLoggable(INFO)) LOG.info("Could not connect to " + remote);
return null;
}
return null;
}
private InetSocketAddress getRemoteSocketAddress(ContactId c) {
TransportProperties p = callback.getRemoteProperties().get(c);
if (p == null) return null;
return parseSocketAddress(p.get("address"), p.get("port"));
}
protected InetSocketAddress parseSocketAddress(String addr, String port) {
if (StringUtils.isNullOrEmpty(addr)) return null;
if (StringUtils.isNullOrEmpty(port)) return null;
protected InetSocketAddress parseSocketAddress(String ipPort) {
if (StringUtils.isNullOrEmpty(ipPort)) return null;
String[] split = ipPort.split(":");
if (split.length != 2) return null;
String addr = split[0], port = split[1];
// Ensure getByName() won't perform a DNS lookup
if (!DOTTED_QUAD.matcher(addr).matches()) return null;
try {
@@ -235,10 +240,12 @@ abstract class TcpPlugin implements DuplexPlugin {
int p = Integer.parseInt(port);
return new InetSocketAddress(a, p);
} catch (UnknownHostException e) {
if (LOG.isLoggable(WARNING)) LOG.warning("Invalid address: " + addr);
if (LOG.isLoggable(WARNING))
LOG.warning("Invalid address: " + addr);
return null;
} catch (NumberFormatException e) {
if (LOG.isLoggable(WARNING)) LOG.warning("Invalid port: " + port);
if (LOG.isLoggable(WARNING))
LOG.warning("Invalid port: " + port);
return null;
}
}

View File

@@ -1,6 +1,7 @@
package org.briarproject.plugins.tcp;
import org.briarproject.api.TransportId;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.plugins.Backoff;
import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
import org.briarproject.api.properties.TransportProperties;
@@ -9,6 +10,7 @@ import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Executor;
@@ -17,6 +19,8 @@ class WanTcpPlugin extends TcpPlugin {
static final TransportId ID = new TransportId("wan");
private static final String PROP_IP_PORT = "ipPort";
private final PortMapper portMapper;
private volatile MappingResult mappingResult;
@@ -35,8 +39,7 @@ class WanTcpPlugin extends TcpPlugin {
protected List<SocketAddress> getLocalSocketAddresses() {
// Use the same address and port as last time if available
TransportProperties p = callback.getLocalProperties();
String oldAddress = p.get("address"), oldPort = p.get("port");
InetSocketAddress old = parseSocketAddress(oldAddress, oldPort);
InetSocketAddress old = parseSocketAddress(p.get(PROP_IP_PORT));
List<SocketAddress> addrs = new LinkedList<SocketAddress>();
for (InetAddress a : getLocalIpAddresses()) {
if (isAcceptableAddress(a)) {
@@ -69,6 +72,15 @@ class WanTcpPlugin extends TcpPlugin {
return 32768 + (int) (Math.random() * 32768);
}
@Override
protected List<InetSocketAddress> getRemoteSocketAddresses(ContactId c) {
TransportProperties p = callback.getRemoteProperties().get(c);
if (p == null) return Collections.emptyList();
InetSocketAddress parsed = parseSocketAddress(p.get(PROP_IP_PORT));
if (parsed == null) return Collections.emptyList();
return Collections.singletonList(parsed);
}
@Override
protected boolean isConnectable(InetSocketAddress remote) {
if (remote.getPort() == 0) return false;
@@ -83,8 +95,7 @@ class WanTcpPlugin extends TcpPlugin {
a = mappingResult.getExternal();
}
TransportProperties p = new TransportProperties();
p.put("address", getHostAddress(a.getAddress()));
p.put("port", String.valueOf(a.getPort()));
p.put(PROP_IP_PORT, getIpPortString(a));
callback.mergeLocalProperties(p);
}
}

View File

@@ -26,6 +26,7 @@ import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -91,14 +92,17 @@ public class LanTcpPluginTest extends BriarTestCase {
plugin.start();
// The plugin should have bound a socket and stored the port number
assertTrue(callback.propertiesLatch.await(5, SECONDS));
String addrString = callback.local.get("address");
assertNotNull(addrString);
String ipPorts = callback.local.get("ipPorts");
assertNotNull(ipPorts);
String[] split = ipPorts.split(",");
assertEquals(1, split.length);
split = split[0].split(":");
assertEquals(2, split.length);
String addrString = split[0], portString = split[1];
InetAddress addr = InetAddress.getByName(addrString);
assertTrue(addr instanceof Inet4Address);
assertFalse(addr.isLoopbackAddress());
assertTrue(addr.isLinkLocalAddress() || addr.isSiteLocalAddress());
String portString = callback.local.get("port");
assertNotNull(portString);
int port = Integer.parseInt(portString);
assertTrue(port > 0 && port < 65536);
// The plugin should be listening on the port
@@ -124,11 +128,17 @@ public class LanTcpPluginTest extends BriarTestCase {
plugin.start();
// The plugin should have bound a socket and stored the port number
assertTrue(callback.propertiesLatch.await(5, SECONDS));
String addr = callback.local.get("address");
assertNotNull(addr);
assertTrue(callback.propertiesLatch.await(5, SECONDS));
String ipPorts = callback.local.get("ipPorts");
assertNotNull(ipPorts);
String[] split = ipPorts.split(",");
assertEquals(1, split.length);
split = split[0].split(":");
assertEquals(2, split.length);
String addrString = split[0];
// Listen on the same interface as the plugin
final ServerSocket ss = new ServerSocket();
ss.bind(new InetSocketAddress(addr, 0), 10);
ss.bind(new InetSocketAddress(addrString, 0), 10);
int port = ss.getLocalPort();
final CountDownLatch latch = new CountDownLatch(1);
final AtomicBoolean error = new AtomicBoolean(false);
@@ -145,8 +155,7 @@ public class LanTcpPluginTest extends BriarTestCase {
}.start();
// Tell the plugin about the port
TransportProperties p = new TransportProperties();
p.put("address", addr);
p.put("port", String.valueOf(port));
p.put("ipPorts", addrString + ":" + port);
callback.remote.put(contactId, p);
// Connect to the port
DuplexTransportConnection d = plugin.createConnection(contactId);
@@ -175,7 +184,7 @@ public class LanTcpPluginTest extends BriarTestCase {
private static class Callback implements DuplexPluginCallback {
private final Map<ContactId, TransportProperties> remote =
new Hashtable<ContactId, TransportProperties>();
new Hashtable<>();
private final CountDownLatch propertiesLatch = new CountDownLatch(1);
private final CountDownLatch connectionsLatch = new CountDownLatch(1);
private final TransportProperties local = new TransportProperties();
@@ -192,7 +201,8 @@ public class LanTcpPluginTest extends BriarTestCase {
return remote;
}
public void mergeSettings(Settings s) {}
public void mergeSettings(Settings s) {
}
public void mergeLocalProperties(TransportProperties p) {
local.putAll(p);
@@ -207,18 +217,22 @@ public class LanTcpPluginTest extends BriarTestCase {
return false;
}
public void showMessage(String... message) {}
public void showMessage(String... message) {
}
public void incomingConnectionCreated(DuplexTransportConnection d) {
connectionsLatch.countDown();
}
public void outgoingConnectionCreated(ContactId c,
DuplexTransportConnection d) {}
DuplexTransportConnection d) {
}
public void transportEnabled() {}
public void transportEnabled() {
}
public void transportDisabled() {}
public void transportDisabled() {
}
}
private static class TestBackoff implements Backoff {
@@ -227,8 +241,10 @@ public class LanTcpPluginTest extends BriarTestCase {
return 60 * 1000;
}
public void increment() {}
public void increment() {
}
public void reset() {}
public void reset() {
}
}
}