Merge branch 'bluetooth-connection-limiter' into 'master'

Don't make or accept Bluetooth contact connections during key agreement

See merge request akwizgran/briar!770
This commit is contained in:
Torsten Grote
2018-04-29 14:42:00 +00:00
11 changed files with 239 additions and 20 deletions

View File

@@ -55,10 +55,12 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
// Non-null if the plugin started successfully
private volatile BluetoothAdapter adapter = null;
AndroidBluetoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor,
AndroidBluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
Executor ioExecutor, AndroidExecutor androidExecutor,
Context appContext, SecureRandom secureRandom, Backoff backoff,
DuplexPluginCallback callback, int maxLatency) {
super(ioExecutor, secureRandom, backoff, callback, maxLatency);
super(connectionLimiter, ioExecutor, secureRandom, backoff, callback,
maxLatency);
this.androidExecutor = androidExecutor;
this.appContext = appContext;
}
@@ -154,7 +156,8 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
}
private DuplexTransportConnection wrapSocket(BluetoothSocket s) {
return new AndroidBluetoothTransportConnection(this, s);
return new AndroidBluetoothTransportConnection(this,
connectionLimiter, s);
}
@Override

View File

@@ -59,11 +59,13 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
@Override
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
BluetoothConnectionLimiter connectionLimiter =
new BluetoothConnectionLimiterImpl();
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(ioExecutor,
androidExecutor, appContext, secureRandom, backoff, callback,
MAX_LATENCY);
AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(
connectionLimiter, ioExecutor, androidExecutor, appContext,
secureRandom, backoff, callback, MAX_LATENCY);
eventBus.addListener(plugin);
return plugin;
}

View File

@@ -14,10 +14,14 @@ import java.io.OutputStream;
class AndroidBluetoothTransportConnection
extends AbstractDuplexTransportConnection {
private final BluetoothConnectionLimiter connectionManager;
private final BluetoothSocket socket;
AndroidBluetoothTransportConnection(Plugin plugin, BluetoothSocket socket) {
AndroidBluetoothTransportConnection(Plugin plugin,
BluetoothConnectionLimiter connectionManager,
BluetoothSocket socket) {
super(plugin);
this.connectionManager = connectionManager;
this.socket = socket;
}
@@ -33,6 +37,10 @@ class AndroidBluetoothTransportConnection
@Override
protected void closeConnection(boolean exception) throws IOException {
socket.close();
try {
socket.close();
} finally {
connectionManager.connectionClosed(this);
}
}
}

View File

@@ -0,0 +1,9 @@
package org.briarproject.bramble.api.keyagreement.event;
import org.briarproject.bramble.api.event.Event;
/**
* An event that is broadcast when a BQP task stops listening.
*/
public class KeyAgreementStoppedListeningEvent extends Event {
}

View File

@@ -14,6 +14,7 @@ import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFailedEvent;
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFinishedEvent;
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementListeningEvent;
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementStartedEvent;
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementStoppedListeningEvent;
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementWaitingEvent;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
@@ -71,6 +72,7 @@ class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
if (localPayload != null) {
if (remotePayload == null) connector.stopListening();
else interrupt();
eventBus.broadcast(new KeyAgreementStoppedListeningEvent());
}
}

View File

@@ -0,0 +1,47 @@
package org.briarproject.bramble.plugin.bluetooth;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
@NotNullByDefault
interface BluetoothConnectionLimiter {
/**
* Informs the limiter that key agreement has started.
*/
void keyAgreementStarted();
/**
* Informs the limiter that key agreement has ended.
*/
void keyAgreementEnded();
/**
* Returns true if a contact connection can be opened. This method does not
* need to be called for key agreement connections.
*/
boolean canOpenContactConnection();
/**
* Informs the limiter that a contact connection has been opened. The
* limiter may close the new connection if key agreement is in progress.
* <p/>
* Returns false if the limiter has closed the new connection.
*/
boolean contactConnectionOpened(DuplexTransportConnection conn);
/**
* Informs the limiter that a key agreement connection has been opened.
*/
void keyAgreementConnectionOpened(DuplexTransportConnection conn);
/**
* Informs the limiter that the given connection has been closed.
*/
void connectionClosed(DuplexTransportConnection conn);
/**
* Informs the limiter that all connections have been closed.
*/
void allConnectionsClosed();
}

View File

@@ -0,0 +1,115 @@
package org.briarproject.bramble.plugin.bluetooth;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
@NotNullByDefault
@ThreadSafe
class BluetoothConnectionLimiterImpl implements BluetoothConnectionLimiter {
private static final Logger LOG =
Logger.getLogger(BluetoothConnectionLimiterImpl.class.getName());
private final Object lock = new Object();
// The following are locking: lock
private final LinkedList<DuplexTransportConnection> connections =
new LinkedList<>();
private boolean keyAgreementInProgress = false;
@Override
public void keyAgreementStarted() {
List<DuplexTransportConnection> close;
synchronized (lock) {
keyAgreementInProgress = true;
close = new ArrayList<>(connections);
connections.clear();
}
if (LOG.isLoggable(INFO)) {
LOG.info("Key agreement started, closing " + close.size() +
" connections");
}
for (DuplexTransportConnection conn : close) tryToClose(conn);
}
@Override
public void keyAgreementEnded() {
synchronized (lock) {
keyAgreementInProgress = false;
}
LOG.info("Key agreement ended");
}
@Override
public boolean canOpenContactConnection() {
synchronized (lock) {
if (keyAgreementInProgress) {
LOG.info("Can't open contact connection during key agreement");
return false;
} else {
LOG.info("Can open contact connection");
return true;
}
}
}
@Override
public boolean contactConnectionOpened(DuplexTransportConnection conn) {
boolean accept = true;
synchronized (lock) {
if (keyAgreementInProgress) {
LOG.info("Refusing contact connection during key agreement");
accept = false;
} else {
LOG.info("Accepting contact connection");
connections.add(conn);
}
}
if (!accept) tryToClose(conn);
return accept;
}
@Override
public void keyAgreementConnectionOpened(DuplexTransportConnection conn) {
synchronized (lock) {
LOG.info("Accepting key agreement connection");
connections.add(conn);
}
}
private void tryToClose(DuplexTransportConnection conn) {
try {
conn.getWriter().dispose(false);
conn.getReader().dispose(false, false);
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
@Override
public void connectionClosed(DuplexTransportConnection conn) {
synchronized (lock) {
connections.remove(conn);
if (LOG.isLoggable(INFO))
LOG.info("Connection closed, " + connections.size() + " open");
}
}
@Override
public void allConnectionsClosed() {
synchronized (lock) {
connections.clear();
LOG.info("All connections closed");
}
}
}

View File

@@ -7,6 +7,8 @@ import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementListeningEvent;
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementStoppedListeningEvent;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
@@ -51,6 +53,8 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
private static final Logger LOG =
Logger.getLogger(BluetoothPlugin.class.getName());
final BluetoothConnectionLimiter connectionLimiter;
private final Executor ioExecutor;
private final SecureRandom secureRandom;
private final Backoff backoff;
@@ -91,8 +95,10 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
abstract DuplexTransportConnection connectTo(String address, String uuid)
throws IOException;
BluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom,
BluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
Executor ioExecutor, SecureRandom secureRandom,
Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
this.connectionLimiter = connectionLimiter;
this.ioExecutor = ioExecutor;
this.secureRandom = secureRandom;
this.backoff = backoff;
@@ -110,6 +116,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
void onAdapterDisabled() {
LOG.info("Bluetooth disabled");
tryToClose(socket);
connectionLimiter.allConnectionsClosed();
callback.transportDisabled();
}
@@ -213,7 +220,8 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
return;
}
backoff.reset();
callback.incomingConnectionCreated(conn);
if (connectionLimiter.contactConnectionOpened(conn))
callback.incomingConnectionCreated(conn);
if (!running) return;
}
}
@@ -257,10 +265,12 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
if (StringUtils.isNullOrEmpty(uuid)) continue;
ioExecutor.execute(() -> {
if (!isRunning() || !shouldAllowContactConnections()) return;
if (!connectionLimiter.canOpenContactConnection()) return;
DuplexTransportConnection conn = connect(address, uuid);
if (conn != null) {
backoff.reset();
callback.outgoingConnectionCreated(c, conn);
if (connectionLimiter.contactConnectionOpened(conn))
callback.outgoingConnectionCreated(c, conn);
}
});
}
@@ -300,12 +310,16 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
@Override
public DuplexTransportConnection createConnection(ContactId c) {
if (!isRunning() || !shouldAllowContactConnections()) return null;
if (!connectionLimiter.canOpenContactConnection()) return null;
TransportProperties p = callback.getRemoteProperties(c);
String address = p.get(PROP_ADDRESS);
if (StringUtils.isNullOrEmpty(address)) return null;
String uuid = p.get(PROP_UUID);
if (StringUtils.isNullOrEmpty(uuid)) return null;
return connect(address, uuid);
DuplexTransportConnection conn = connect(address, uuid);
if (conn == null) return null;
// TODO: Why don't we reset the backoff here?
return connectionLimiter.contactConnectionOpened(conn) ? conn : null;
}
@Override
@@ -355,7 +369,9 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
if (LOG.isLoggable(INFO))
LOG.info("Connecting to key agreement UUID " + uuid);
return connect(address, uuid);
DuplexTransportConnection conn = connect(address, uuid);
if (conn != null) connectionLimiter.keyAgreementConnectionOpened(conn);
return conn;
}
private String parseAddress(BdfList descriptor) throws FormatException {
@@ -376,6 +392,10 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
SettingsUpdatedEvent s = (SettingsUpdatedEvent) e;
if (s.getNamespace().equals(ID.getString()))
ioExecutor.execute(this::onSettingsUpdated);
} else if (e instanceof KeyAgreementListeningEvent) {
ioExecutor.execute(connectionLimiter::keyAgreementStarted);
} else if (e instanceof KeyAgreementStoppedListeningEvent) {
ioExecutor.execute(connectionLimiter::keyAgreementEnded);
}
}
@@ -408,6 +428,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
public KeyAgreementConnection accept() throws IOException {
DuplexTransportConnection conn = acceptConnection(ss);
if (LOG.isLoggable(INFO)) LOG.info(ID + ": Incoming connection");
connectionLimiter.keyAgreementConnectionOpened(conn);
return new KeyAgreementConnection(conn, ID);
}

View File

@@ -31,9 +31,11 @@ class JavaBluetoothPlugin extends BluetoothPlugin<StreamConnectionNotifier> {
// Non-null if the plugin started successfully
private volatile LocalDevice localDevice = null;
JavaBluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom,
JavaBluetoothPlugin(BluetoothConnectionLimiter connectionManager,
Executor ioExecutor, SecureRandom secureRandom,
Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
super(ioExecutor, secureRandom, backoff, callback, maxLatency);
super(connectionManager, ioExecutor, secureRandom, backoff, callback,
maxLatency);
}
@Override
@@ -110,6 +112,6 @@ class JavaBluetoothPlugin extends BluetoothPlugin<StreamConnectionNotifier> {
}
private DuplexTransportConnection wrapSocket(StreamConnection s) {
return new JavaBluetoothTransportConnection(this, s);
return new JavaBluetoothTransportConnection(this, connectionLimiter, s);
}
}

View File

@@ -51,10 +51,12 @@ public class JavaBluetoothPluginFactory implements DuplexPluginFactory {
@Override
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
BluetoothConnectionLimiter connectionLimiter =
new BluetoothConnectionLimiterImpl();
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
JavaBluetoothPlugin plugin = new JavaBluetoothPlugin(ioExecutor,
secureRandom, backoff, callback, MAX_LATENCY);
JavaBluetoothPlugin plugin = new JavaBluetoothPlugin(connectionLimiter,
ioExecutor, secureRandom, backoff, callback, MAX_LATENCY);
eventBus.addListener(plugin);
return plugin;
}

View File

@@ -14,11 +14,15 @@ import javax.microedition.io.StreamConnection;
class JavaBluetoothTransportConnection
extends AbstractDuplexTransportConnection {
private final BluetoothConnectionLimiter connectionManager;
private final StreamConnection stream;
JavaBluetoothTransportConnection(Plugin plugin, StreamConnection stream) {
JavaBluetoothTransportConnection(Plugin plugin,
BluetoothConnectionLimiter connectionManager,
StreamConnection stream) {
super(plugin);
this.stream = stream;
this.connectionManager = connectionManager;
}
@Override
@@ -33,6 +37,10 @@ class JavaBluetoothTransportConnection
@Override
protected void closeConnection(boolean exception) throws IOException {
stream.close();
try {
stream.close();
} finally {
connectionManager.connectionClosed(this);
}
}
}