Raise the connection limit if connections are stable.

This commit is contained in:
akwizgran
2020-05-05 17:52:42 +01:00
parent 104a82aea9
commit bda3b2100a
6 changed files with 160 additions and 19 deletions

View File

@@ -67,7 +67,7 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
@Override @Override
public DuplexPlugin createPlugin(PluginCallback callback) { public DuplexPlugin createPlugin(PluginCallback callback) {
BluetoothConnectionLimiter connectionLimiter = BluetoothConnectionLimiter connectionLimiter =
new BluetoothConnectionLimiterImpl(); new BluetoothConnectionLimiterImpl(clock);
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE); MAX_POLLING_INTERVAL, BACKOFF_BASE);
AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin( AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(

View File

@@ -3,9 +3,16 @@ package org.briarproject.bramble.plugin.bluetooth;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import static java.util.concurrent.TimeUnit.SECONDS;
@NotNullByDefault @NotNullByDefault
interface BluetoothConnectionLimiter { interface BluetoothConnectionLimiter {
/**
* How long a connection must remain open before it's considered stable.
*/
long STABILITY_PERIOD_MS = SECONDS.toMillis(90);
/** /**
* Informs the limiter that key agreement has started. * Informs the limiter that key agreement has started.
*/ */

View File

@@ -2,15 +2,18 @@ package org.briarproject.bramble.plugin.bluetooth;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.system.Clock;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
@@ -24,21 +27,28 @@ class BluetoothConnectionLimiterImpl implements BluetoothConnectionLimiter {
private static final Logger LOG = private static final Logger LOG =
getLogger(BluetoothConnectionLimiterImpl.class.getName()); getLogger(BluetoothConnectionLimiterImpl.class.getName());
private final Clock clock;
private final Object lock = new Object(); private final Object lock = new Object();
@GuardedBy("lock") @GuardedBy("lock")
private final LinkedList<DuplexTransportConnection> connections = private final List<ConnectionRecord> connections = new LinkedList<>();
new LinkedList<>();
@GuardedBy("lock") @GuardedBy("lock")
private boolean keyAgreementInProgress = false; private boolean keyAgreementInProgress = false;
@GuardedBy("lock") @GuardedBy("lock")
private int connectionLimit = 1; private int connectionLimit = 1;
@Inject
BluetoothConnectionLimiterImpl(Clock clock) {
this.clock = clock;
}
@Override @Override
public void keyAgreementStarted() { public void keyAgreementStarted() {
List<DuplexTransportConnection> close; List<DuplexTransportConnection> close;
synchronized (lock) { synchronized (lock) {
keyAgreementInProgress = true; keyAgreementInProgress = true;
close = new ArrayList<>(connections); close = new ArrayList<>(connections.size());
for (ConnectionRecord rec : connections) close.add(rec.connection);
connections.clear(); connections.clear();
} }
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
@@ -62,7 +72,9 @@ class BluetoothConnectionLimiterImpl implements BluetoothConnectionLimiter {
if (keyAgreementInProgress) { if (keyAgreementInProgress) {
LOG.info("Can't open contact connection during key agreement"); LOG.info("Can't open contact connection during key agreement");
return false; return false;
} else if (connections.size() >= connectionLimit) { }
considerRaisingConnectionLimit(clock.currentTimeMillis());
if (connections.size() >= connectionLimit) {
LOG.info("Can't open contact connection due to limit"); LOG.info("Can't open contact connection due to limit");
return false; return false;
} else { } else {
@@ -79,12 +91,16 @@ class BluetoothConnectionLimiterImpl implements BluetoothConnectionLimiter {
if (keyAgreementInProgress) { if (keyAgreementInProgress) {
LOG.info("Refusing contact connection during key agreement"); LOG.info("Refusing contact connection during key agreement");
accept = false; accept = false;
} else if (connections.size() > connectionLimit) {
LOG.info("Refusing contact connection due to limit");
accept = false;
} else { } else {
LOG.info("Accepting contact connection"); long now = clock.currentTimeMillis();
connections.add(conn); considerRaisingConnectionLimit(now);
if (connections.size() > connectionLimit) {
LOG.info("Refusing contact connection due to limit");
accept = false;
} else {
LOG.info("Accepting contact connection");
connections.add(new ConnectionRecord(conn, now));
}
} }
} }
if (!accept) tryToClose(conn); if (!accept) tryToClose(conn);
@@ -95,7 +111,8 @@ class BluetoothConnectionLimiterImpl implements BluetoothConnectionLimiter {
public void keyAgreementConnectionOpened(DuplexTransportConnection conn) { public void keyAgreementConnectionOpened(DuplexTransportConnection conn) {
synchronized (lock) { synchronized (lock) {
LOG.info("Accepting key agreement connection"); LOG.info("Accepting key agreement connection");
connections.add(conn); connections.add(
new ConnectionRecord(conn, clock.currentTimeMillis()));
} }
} }
@@ -111,7 +128,13 @@ class BluetoothConnectionLimiterImpl implements BluetoothConnectionLimiter {
@Override @Override
public void connectionClosed(DuplexTransportConnection conn) { public void connectionClosed(DuplexTransportConnection conn) {
synchronized (lock) { synchronized (lock) {
connections.remove(conn); Iterator<ConnectionRecord> it = connections.iterator();
while (it.hasNext()) {
if (it.next().connection == conn) {
it.remove();
break;
}
}
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Connection closed, " + connections.size() + " open"); LOG.info("Connection closed, " + connections.size() + " open");
} }
@@ -124,4 +147,32 @@ class BluetoothConnectionLimiterImpl implements BluetoothConnectionLimiter {
LOG.info("All connections closed"); LOG.info("All connections closed");
} }
} }
@GuardedBy("lock")
private void considerRaisingConnectionLimit(long now) {
int stable = 0;
for (ConnectionRecord rec : connections) {
if (now - rec.timeOpened >= STABILITY_PERIOD_MS) stable++;
}
if (stable >= connectionLimit) {
LOG.info("Raising connection limit");
connectionLimit++;
}
if (LOG.isLoggable(INFO)) {
LOG.info(stable + " connections are stable, limit is "
+ connectionLimit);
}
}
private static final class ConnectionRecord {
private final DuplexTransportConnection connection;
private final long timeOpened;
private ConnectionRecord(DuplexTransportConnection connection,
long timeOpened) {
this.connection = connection;
this.timeOpened = timeOpened;
}
}
} }

View File

@@ -3,15 +3,18 @@ package org.briarproject.bramble.plugin.bluetooth;
import org.briarproject.bramble.api.plugin.TransportConnectionReader; import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter; import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations; import org.jmock.Expectations;
import org.junit.Test; import org.junit.Test;
import static org.briarproject.bramble.plugin.bluetooth.BluetoothConnectionLimiter.STABILITY_PERIOD_MS;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
public class BluetoothConnectionLimiterImplTest extends BrambleMockTestCase { public class BluetoothConnectionLimiterImplTest extends BrambleMockTestCase {
private final Clock clock = context.mock(Clock.class);
private final DuplexTransportConnection conn = private final DuplexTransportConnection conn =
context.mock(DuplexTransportConnection.class); context.mock(DuplexTransportConnection.class);
private final TransportConnectionReader reader = private final TransportConnectionReader reader =
@@ -19,29 +22,104 @@ public class BluetoothConnectionLimiterImplTest extends BrambleMockTestCase {
private final TransportConnectionWriter writer = private final TransportConnectionWriter writer =
context.mock(TransportConnectionWriter.class); context.mock(TransportConnectionWriter.class);
private final long now = System.currentTimeMillis();
@Test @Test
public void testLimiterAllowsOneOutgoingConnection() { public void testLimiterAllowsOneOutgoingConnection() {
BluetoothConnectionLimiter limiter = BluetoothConnectionLimiter limiter =
new BluetoothConnectionLimiterImpl(); new BluetoothConnectionLimiterImpl(clock);
expectGetCurrentTime(now);
assertTrue(limiter.canOpenContactConnection()); assertTrue(limiter.canOpenContactConnection());
expectGetCurrentTime(now);
assertTrue(limiter.contactConnectionOpened(conn)); assertTrue(limiter.contactConnectionOpened(conn));
expectGetCurrentTime(now);
assertFalse(limiter.canOpenContactConnection()); assertFalse(limiter.canOpenContactConnection());
} }
@Test @Test
public void testLimiterAllowsSecondIncomingConnection() throws Exception { public void testLimiterAllowsSecondIncomingConnection() throws Exception {
BluetoothConnectionLimiter limiter = BluetoothConnectionLimiter limiter =
new BluetoothConnectionLimiterImpl(); new BluetoothConnectionLimiterImpl(clock);
expectGetCurrentTime(now);
assertTrue(limiter.canOpenContactConnection()); assertTrue(limiter.canOpenContactConnection());
expectGetCurrentTime(now);
assertTrue(limiter.contactConnectionOpened(conn)); assertTrue(limiter.contactConnectionOpened(conn));
expectGetCurrentTime(now);
assertFalse(limiter.canOpenContactConnection()); assertFalse(limiter.canOpenContactConnection());
// The limiter allows a second incoming connection // The limiter allows a second incoming connection
expectGetCurrentTime(now);
assertTrue(limiter.contactConnectionOpened(conn)); assertTrue(limiter.contactConnectionOpened(conn));
// The limiter closes any further incoming connections
// The limiter does not allow a third incoming connection
expectGetCurrentTime(now);
expectCloseConnection(); expectCloseConnection();
assertFalse(limiter.contactConnectionOpened(conn)); assertFalse(limiter.contactConnectionOpened(conn));
} }
@Test
public void testLimiterAllowsSecondOutgoingConnectionWhenFirstIsStable()
throws Exception {
BluetoothConnectionLimiter limiter =
new BluetoothConnectionLimiterImpl(clock);
expectGetCurrentTime(now);
assertTrue(limiter.canOpenContactConnection());
expectGetCurrentTime(now);
assertTrue(limiter.contactConnectionOpened(conn));
// The first connection is not yet stable
expectGetCurrentTime(now + STABILITY_PERIOD_MS - 1);
assertFalse(limiter.canOpenContactConnection());
// The first connection is stable, so the limit is raised
expectGetCurrentTime(now + STABILITY_PERIOD_MS);
assertTrue(limiter.canOpenContactConnection());
}
@Test
public void testLimiterAllowsThirdIncomingConnectionWhenFirstTwoAreStable()
throws Exception {
BluetoothConnectionLimiter limiter =
new BluetoothConnectionLimiterImpl(clock);
expectGetCurrentTime(now);
assertTrue(limiter.canOpenContactConnection());
expectGetCurrentTime(now);
assertTrue(limiter.contactConnectionOpened(conn));
expectGetCurrentTime(now);
assertFalse(limiter.canOpenContactConnection());
// The limiter allows a second incoming connection
expectGetCurrentTime(now);
assertTrue(limiter.contactConnectionOpened(conn));
// The limiter does not allow a third incoming connection
expectGetCurrentTime(now + STABILITY_PERIOD_MS - 1);
expectCloseConnection();
assertFalse(limiter.contactConnectionOpened(conn));
// The first two connections are stable, so the limit is raised
expectGetCurrentTime(now + STABILITY_PERIOD_MS);
assertTrue(limiter.contactConnectionOpened(conn));
}
private void expectGetCurrentTime(long now) {
context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(now));
}});
}
private void expectCloseConnection() throws Exception { private void expectCloseConnection() throws Exception {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(conn).getReader(); oneOf(conn).getReader();

View File

@@ -10,6 +10,7 @@ import org.briarproject.bramble.api.plugin.PluginConfig;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory; import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory; import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import org.briarproject.bramble.api.reliability.ReliabilityLayerFactory; import org.briarproject.bramble.api.reliability.ReliabilityLayerFactory;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.plugin.bluetooth.JavaBluetoothPluginFactory; import org.briarproject.bramble.plugin.bluetooth.JavaBluetoothPluginFactory;
import org.briarproject.bramble.plugin.modem.ModemPluginFactory; import org.briarproject.bramble.plugin.modem.ModemPluginFactory;
import org.briarproject.bramble.plugin.tcp.LanTcpPluginFactory; import org.briarproject.bramble.plugin.tcp.LanTcpPluginFactory;
@@ -32,10 +33,11 @@ public class DesktopPluginModule extends PluginModule {
PluginConfig getPluginConfig(@IoExecutor Executor ioExecutor, PluginConfig getPluginConfig(@IoExecutor Executor ioExecutor,
SecureRandom random, BackoffFactory backoffFactory, SecureRandom random, BackoffFactory backoffFactory,
ReliabilityLayerFactory reliabilityFactory, ReliabilityLayerFactory reliabilityFactory,
ShutdownManager shutdownManager, EventBus eventBus, ShutdownManager shutdownManager, EventBus eventBus, Clock clock,
TimeoutMonitor timeoutMonitor) { TimeoutMonitor timeoutMonitor) {
DuplexPluginFactory bluetooth = new JavaBluetoothPluginFactory( DuplexPluginFactory bluetooth = new JavaBluetoothPluginFactory(
ioExecutor, random, eventBus, timeoutMonitor, backoffFactory); ioExecutor, random, eventBus, clock, timeoutMonitor,
backoffFactory);
DuplexPluginFactory modem = new ModemPluginFactory(ioExecutor, DuplexPluginFactory modem = new ModemPluginFactory(ioExecutor,
reliabilityFactory); reliabilityFactory);
DuplexPluginFactory lan = new LanTcpPluginFactory(ioExecutor, DuplexPluginFactory lan = new LanTcpPluginFactory(ioExecutor,

View File

@@ -9,6 +9,7 @@ import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin; import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory; import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.system.Clock;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -30,15 +31,17 @@ public class JavaBluetoothPluginFactory implements DuplexPluginFactory {
private final Executor ioExecutor; private final Executor ioExecutor;
private final SecureRandom secureRandom; private final SecureRandom secureRandom;
private final EventBus eventBus; private final EventBus eventBus;
private final Clock clock;
private final TimeoutMonitor timeoutMonitor; private final TimeoutMonitor timeoutMonitor;
private final BackoffFactory backoffFactory; private final BackoffFactory backoffFactory;
public JavaBluetoothPluginFactory(Executor ioExecutor, public JavaBluetoothPluginFactory(Executor ioExecutor,
SecureRandom secureRandom, EventBus eventBus, SecureRandom secureRandom, EventBus eventBus, Clock clock,
TimeoutMonitor timeoutMonitor, BackoffFactory backoffFactory) { TimeoutMonitor timeoutMonitor, BackoffFactory backoffFactory) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.secureRandom = secureRandom; this.secureRandom = secureRandom;
this.eventBus = eventBus; this.eventBus = eventBus;
this.clock = clock;
this.timeoutMonitor = timeoutMonitor; this.timeoutMonitor = timeoutMonitor;
this.backoffFactory = backoffFactory; this.backoffFactory = backoffFactory;
} }
@@ -56,7 +59,7 @@ public class JavaBluetoothPluginFactory implements DuplexPluginFactory {
@Override @Override
public DuplexPlugin createPlugin(PluginCallback callback) { public DuplexPlugin createPlugin(PluginCallback callback) {
BluetoothConnectionLimiter connectionLimiter = BluetoothConnectionLimiter connectionLimiter =
new BluetoothConnectionLimiterImpl(); new BluetoothConnectionLimiterImpl(clock);
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE); MAX_POLLING_INTERVAL, BACKOFF_BASE);
JavaBluetoothPlugin plugin = new JavaBluetoothPlugin(connectionLimiter, JavaBluetoothPlugin plugin = new JavaBluetoothPlugin(connectionLimiter,