Provide more information about plugin states.

This commit is contained in:
akwizgran
2020-01-14 12:18:24 +00:00
parent ded1792213
commit 5d6ed1a724
13 changed files with 398 additions and 160 deletions

View File

@@ -33,6 +33,8 @@ import static android.os.Build.VERSION.SDK_INT;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.State.AVAILABLE;
import static org.briarproject.bramble.api.plugin.Plugin.State.UNAVAILABLE;
@NotNullByDefault
class AndroidLanTcpPlugin extends LanTcpPlugin implements EventListener {
@@ -79,16 +81,11 @@ class AndroidLanTcpPlugin extends LanTcpPlugin implements EventListener {
@Override
public void start() {
if (used.getAndSet(true)) throw new IllegalStateException();
running = true;
state.setStarted();
callback.pluginStateChanged(getState());
updateConnectionStatus();
}
@Override
public void stop() {
running = false;
tryToClose(socket);
}
@Override
protected Socket createSocket() throws IOException {
return socketFactory.createSocket();
@@ -143,7 +140,8 @@ class AndroidLanTcpPlugin extends LanTcpPlugin implements EventListener {
private void updateConnectionStatus() {
connectionStatusExecutor.execute(() -> {
if (!running) return;
State state = getState();
if (state != AVAILABLE && state != UNAVAILABLE) return;
Collection<InetAddress> addrs = getLocalIpAddresses();
if (addrs.contains(WIFI_AP_ADDRESS)) {
LOG.info("Providing wifi hotspot");
@@ -152,15 +150,15 @@ class AndroidLanTcpPlugin extends LanTcpPlugin implements EventListener {
// make outgoing connections on API 21+ if another network
// has internet access
socketFactory = SocketFactory.getDefault();
if (socket == null || socket.isClosed()) bind();
if (state == UNAVAILABLE) bind();
} else if (addrs.isEmpty()) {
LOG.info("Not connected to wifi");
socketFactory = SocketFactory.getDefault();
tryToClose(socket);
// TODO: Check that socket was closed when interface went down
} else {
LOG.info("Connected to wifi");
socketFactory = getSocketFactory();
if (socket == null || socket.isClosed()) bind();
if (state == UNAVAILABLE) bind();
}
});
}

View File

@@ -74,7 +74,7 @@ class AndroidTorPlugin extends TorPlugin {
@Override
protected void enableNetwork(boolean enable) throws IOException {
if (!running) return;
if (!state.isRunning()) return;
if (enable) wakeLock.acquire();
super.enableNetwork(enable);
if (!enable) wakeLock.release();

View File

@@ -9,6 +9,34 @@ import java.util.Collection;
@NotNullByDefault
public interface Plugin {
enum State {
/**
* The plugin has not been started, has been stopped, or is disabled by
* settings.
*/
DISABLED,
/**
* The plugin has been started, has not been stopped, is enabled by
* settings, but can't yet tell whether it can make or receive
* connections.
*/
ENABLING,
/**
* The plugin has been started, has not been stopped, is enabled by
* settings, and can make or receive connections.
*/
AVAILABLE,
/**
* The plugin has been started, has not been stopped, is enabled by
* settings, but can't make or receive connections
*/
UNAVAILABLE
}
/**
* Returns the plugin's transport identifier.
*/
@@ -35,9 +63,9 @@ public interface Plugin {
void stop() throws PluginException;
/**
* Returns true if the plugin is running.
* Returns the current state of the plugin.
*/
boolean isRunning();
State getState();
/**
* Returns true if the plugin should be polled periodically to attempt to

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.api.plugin;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin.State;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.settings.Settings;
@@ -32,12 +33,7 @@ public interface PluginCallback extends ConnectionHandler {
void mergeLocalProperties(TransportProperties p);
/**
* Signals that the transport is enabled.
* Signals that the transport's state may have changed.
*/
void transportEnabled();
/**
* Signals that the transport is disabled.
*/
void transportDisabled();
void pluginStateChanged(State state);
}

View File

@@ -8,6 +8,7 @@ import org.briarproject.bramble.api.lifecycle.ServiceException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionManager;
import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.Plugin.State;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.PluginConfig;
import org.briarproject.bramble.api.plugin.PluginException;
@@ -36,6 +37,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe;
@@ -45,6 +47,8 @@ import static java.util.logging.Level.FINE;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.State.AVAILABLE;
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now;
@@ -250,6 +254,8 @@ class PluginManagerImpl implements PluginManager, Service {
private class Callback implements PluginCallback {
private final TransportId id;
private final AtomicReference<State> state =
new AtomicReference<>(DISABLED);
private final AtomicBoolean enabled = new AtomicBoolean(false);
private Callback(TransportId id) {
@@ -295,15 +301,25 @@ class PluginManagerImpl implements PluginManager, Service {
}
@Override
public void transportEnabled() {
if (!enabled.getAndSet(true))
eventBus.broadcast(new TransportEnabledEvent(id));
}
@Override
public void transportDisabled() {
if (enabled.getAndSet(false))
eventBus.broadcast(new TransportDisabledEvent(id));
public void pluginStateChanged(State newState) {
State oldState = state.getAndSet(newState);
if (newState != oldState) {
if (LOG.isLoggable(INFO)) {
LOG.info(id + " changed from state " + oldState
+ " to " + newState);
}
if (newState == AVAILABLE) {
if (!enabled.getAndSet(true))
eventBus.broadcast(new TransportEnabledEvent(id));
} else {
if (enabled.getAndSet(false))
eventBus.broadcast(new TransportDisabledEvent(id));
}
} else {
// TODO: Remove
if (LOG.isLoggable(INFO))
LOG.info(id + " stayed in state " + oldState);
}
}
@Override

View File

@@ -10,6 +10,7 @@ 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.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.ConnectionHandler;
@@ -36,6 +37,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
@@ -46,6 +49,9 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_BT_ENA
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES;
import static org.briarproject.bramble.api.plugin.Plugin.State.AVAILABLE;
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.api.plugin.Plugin.State.UNAVAILABLE;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@@ -68,9 +74,10 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
private final int maxLatency;
private final AtomicBoolean used = new AtomicBoolean(false);
private volatile boolean running = false, contactConnections = false;
protected final PluginState state = new PluginState();
private volatile boolean contactConnections = false;
private volatile String contactConnectionsUuid = null;
private volatile SS socket = null;
abstract void initialiseAdapter() throws IOException;
@@ -120,13 +127,16 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
// We may not have been able to get the local address before
ioExecutor.execute(this::updateProperties);
if (shouldAllowContactConnections()) bind();
callback.pluginStateChanged(getState());
}
void onAdapterDisabled() {
LOG.info("Bluetooth disabled");
tryToClose(socket);
// TODO: Is this needed, or will the socket be closed automatically?
SS ss = state.clearServerSocket();
tryToClose(ss);
connectionLimiter.allConnectionsClosed();
callback.transportDisabled();
callback.pluginStateChanged(getState());
}
@Override
@@ -154,7 +164,8 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
throw new PluginException(e);
}
updateProperties();
running = true;
state.setStarted();
callback.pluginStateChanged(getState());
loadSettings(callback.getSettings());
if (shouldAllowContactConnections()) {
if (isAdapterEnabled()) bind();
@@ -172,7 +183,8 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
private void bind() {
ioExecutor.execute(() -> {
if (!isRunning() || !shouldAllowContactConnections()) return;
if (!shouldAllowContactConnections() || getState() != AVAILABLE)
return;
// Bind a server socket to accept connections from contacts
SS ss;
try {
@@ -181,14 +193,15 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
logException(LOG, WARNING, e);
return;
}
if (!isRunning() || !shouldAllowContactConnections()) {
if (!shouldAllowContactConnections() ||
!state.setServerSocket(ss)) {
LOG.info("Closing redundant server socket");
tryToClose(ss);
return;
}
socket = ss;
backoff.reset();
callback.transportEnabled();
acceptContactConnections();
callback.pluginStateChanged(getState());
acceptContactConnections(ss);
});
}
@@ -217,34 +230,36 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
if (changed) callback.mergeLocalProperties(p);
}
private void acceptContactConnections() {
private void acceptContactConnections(SS ss) {
while (true) {
DuplexTransportConnection conn;
try {
conn = acceptConnection(socket);
conn = acceptConnection(ss);
} catch (IOException e) {
// This is expected when the socket is closed
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
// TODO: Check that this is logged at shutdown/when BT disabled
LOG.info("Server socket closed");
state.clearServerSocket();
return;
}
LOG.info("Connection received");
backoff.reset();
if (connectionLimiter.contactConnectionOpened(conn))
callback.handleConnection(conn);
if (!running) return;
}
}
@Override
public void stop() {
running = false;
tryToClose(socket);
callback.transportDisabled();
SS ss = state.setStopped();
callback.pluginStateChanged(getState());
tryToClose(ss);
disableAdapterIfEnabledByUs();
}
@Override
public boolean isRunning() {
return running && isAdapterEnabled();
public State getState() {
return state.getState();
}
@Override
@@ -260,7 +275,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
@Override
public void poll(Collection<Pair<TransportProperties, ConnectionHandler>>
properties) {
if (!isRunning() || !shouldAllowContactConnections()) return;
if (!shouldAllowContactConnections() || getState() != AVAILABLE) return;
backoff.increment();
for (Pair<TransportProperties, ConnectionHandler> p : properties) {
connect(p.getFirst(), p.getSecond());
@@ -273,7 +288,8 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
String uuid = p.get(PROP_UUID);
if (isNullOrEmpty(uuid)) return;
ioExecutor.execute(() -> {
if (!isRunning() || !shouldAllowContactConnections()) return;
if (!shouldAllowContactConnections() || getState() != AVAILABLE)
return;
if (!connectionLimiter.canOpenContactConnection()) return;
DuplexTransportConnection d = createConnection(p);
if (d != null) {
@@ -317,7 +333,8 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
@Override
public DuplexTransportConnection createConnection(TransportProperties p) {
if (!isRunning() || !shouldAllowContactConnections()) return null;
if (!shouldAllowContactConnections() || getState() != AVAILABLE)
return null;
if (!connectionLimiter.canOpenContactConnection()) return null;
String address = p.get(PROP_ADDRESS);
if (isNullOrEmpty(address)) return null;
@@ -336,7 +353,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
@Override
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
if (!isRunning()) return null;
if (getState() != AVAILABLE) return null;
// No truncation necessary because COMMIT_LENGTH = 16
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
@@ -348,7 +365,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
logException(LOG, WARNING, e);
return null;
}
if (!isRunning()) {
if (getState() != AVAILABLE) {
tryToClose(ss);
return null;
}
@@ -362,7 +379,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
@Override
public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor) {
if (!isRunning()) return null;
if (getState() != AVAILABLE) return null;
// No truncation necessary because COMMIT_LENGTH = 16
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
DuplexTransportConnection conn;
@@ -428,8 +445,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
boolean isAllowed = shouldAllowContactConnections();
if (wasAllowed && !isAllowed) {
LOG.info("Contact connections disabled");
tryToClose(socket);
callback.transportDisabled();
tryToClose(state.clearServerSocket());
disableAdapterIfEnabledByUs();
} else if (!wasAllowed && isAllowed) {
LOG.info("Contact connections enabled");
@@ -460,4 +476,45 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
tryToClose(ss);
}
}
@ThreadSafe
@NotNullByDefault
protected class PluginState {
@GuardedBy("this")
private boolean started = false, stopped = false;
@GuardedBy("this")
@Nullable
private SS serverSocket = null;
synchronized void setStarted() {
started = true;
}
@Nullable
synchronized SS setStopped() {
stopped = true;
SS ss = serverSocket;
serverSocket = null;
return ss;
}
synchronized boolean setServerSocket(SS ss) {
if (stopped || serverSocket != null) return false;
serverSocket = ss;
return true;
}
@Nullable
synchronized SS clearServerSocket() {
SS ss = serverSocket;
serverSocket = null;
return ss;
}
synchronized State getState() {
if (!started || stopped) return DISABLED;
return isAdapterEnabled() ? AVAILABLE : UNAVAILABLE;
}
}
}

View File

@@ -16,6 +16,7 @@ import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.FileConstants.PROP_PATH;
import static org.briarproject.bramble.api.plugin.Plugin.State.AVAILABLE;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@@ -45,7 +46,7 @@ abstract class FilePlugin implements SimplexPlugin {
@Override
public TransportConnectionReader createReader(TransportProperties p) {
if (!isRunning()) return null;
if (getState() != AVAILABLE) return null;
String path = p.get(PROP_PATH);
if (isNullOrEmpty(path)) return null;
try {
@@ -60,7 +61,7 @@ abstract class FilePlugin implements SimplexPlugin {
@Override
public TransportConnectionWriter createWriter(TransportProperties p) {
if (!isRunning()) return null;
if (getState() != AVAILABLE) return null;
String path = p.get(PROP_PATH);
if (isNullOrEmpty(path)) return null;
try {

View File

@@ -11,7 +11,6 @@ import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.util.IoUtils;
import java.io.IOException;
import java.net.Inet4Address;
@@ -19,7 +18,6 @@ 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.ArrayList;
import java.util.Comparator;
@@ -38,6 +36,7 @@ import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID;
import static org.briarproject.bramble.api.plugin.LanTcpConstants.PREF_LAN_IP_PORTS;
import static org.briarproject.bramble.api.plugin.LanTcpConstants.PROP_IP_PORTS;
import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
import static org.briarproject.bramble.util.PrivacyUtils.scrubSocketAddress;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
import static org.briarproject.bramble.util.StringUtils.join;
@@ -149,8 +148,9 @@ class LanTcpPlugin extends TcpPlugin {
if (remote.getPort() == 0) return false;
if (!isAcceptableAddress(remote.getAddress())) return false;
// Try to determine whether the address is on the same LAN as us
if (socket == null) return false;
byte[] localIp = socket.getInetAddress().getAddress();
ServerSocket ss = state.getServerSocket();
if (ss == null) return false;
byte[] localIp = ss.getInetAddress().getAddress();
byte[] remoteIp = remote.getAddress().getAddress();
return addressesAreOnSameLan(localIp, remoteIp);
}
@@ -209,10 +209,10 @@ class LanTcpPlugin extends TcpPlugin {
} catch (IOException e) {
if (LOG.isLoggable(INFO))
LOG.info("Failed to bind " + scrubSocketAddress(addr));
tryToClose(ss);
tryToClose(ss, LOG, WARNING);
}
}
if (ss == null || !ss.isBound()) {
if (ss == null) {
LOG.info("Could not bind server socket for key agreement");
return null;
}
@@ -228,7 +228,8 @@ class LanTcpPlugin extends TcpPlugin {
@Override
public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor) {
if (!isRunning()) return null;
ServerSocket ss = state.getServerSocket();
if (ss == null) return null;
InetSocketAddress remote;
try {
remote = parseSocketAddress(descriptor);
@@ -238,10 +239,9 @@ class LanTcpPlugin extends TcpPlugin {
}
if (!isConnectable(remote)) {
if (LOG.isLoggable(INFO)) {
SocketAddress local = socket.getLocalSocketAddress();
LOG.info(scrubSocketAddress(remote) +
" is not connectable from " +
scrubSocketAddress(local));
scrubSocketAddress(ss.getLocalSocketAddress()));
}
return null;
}
@@ -249,7 +249,7 @@ class LanTcpPlugin extends TcpPlugin {
if (LOG.isLoggable(INFO))
LOG.info("Connecting to " + scrubSocketAddress(remote));
Socket s = createSocket();
s.bind(new InetSocketAddress(socket.getInetAddress(), 0));
s.bind(new InetSocketAddress(ss.getInetAddress(), 0));
s.connect(remote);
s.setSoTimeout(socketTimeout);
if (LOG.isLoggable(INFO))
@@ -296,7 +296,7 @@ class LanTcpPlugin extends TcpPlugin {
@Override
public void close() {
IoUtils.tryToClose(ss, LOG, WARNING);
tryToClose(ss, LOG, WARNING);
}
}

View File

@@ -5,6 +5,7 @@ import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.ConnectionHandler;
@@ -14,7 +15,6 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.rendezvous.KeyMaterialSource;
import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint;
import org.briarproject.bramble.util.IoUtils;
import java.io.IOException;
import java.net.InetAddress;
@@ -22,7 +22,6 @@ import java.net.InetSocketAddress;
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;
@@ -35,6 +34,8 @@ import java.util.logging.Logger;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import static java.net.NetworkInterface.getNetworkInterfaces;
import static java.util.Collections.emptyList;
@@ -42,6 +43,10 @@ import static java.util.Collections.list;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.State.AVAILABLE;
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.api.plugin.Plugin.State.UNAVAILABLE;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.PrivacyUtils.scrubSocketAddress;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@@ -60,9 +65,7 @@ abstract class TcpPlugin implements DuplexPlugin {
protected final PluginCallback callback;
protected final int maxLatency, maxIdleTime, socketTimeout;
protected final AtomicBoolean used = new AtomicBoolean(false);
protected volatile boolean running = false;
protected volatile ServerSocket socket = null;
protected final PluginState state = new PluginState();
/**
* Returns zero or more socket addresses on which the plugin should listen,
@@ -86,6 +89,7 @@ abstract class TcpPlugin implements DuplexPlugin {
/**
* Returns true if connections to the given address can be attempted.
*/
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
protected abstract boolean isConnectable(InetSocketAddress remote);
TcpPlugin(Executor ioExecutor, Backoff backoff, PluginCallback callback,
@@ -115,14 +119,14 @@ abstract class TcpPlugin implements DuplexPlugin {
@Override
public void start() {
if (used.getAndSet(true)) throw new IllegalStateException();
running = true;
state.setStarted();
callback.pluginStateChanged(getState());
bind();
}
protected void bind() {
bindExecutor.execute(() -> {
if (!running) return;
if (socket != null && !socket.isClosed()) return;
if (getState() != UNAVAILABLE) return;
ServerSocket ss = null;
for (InetSocketAddress addr : getLocalSocketAddresses()) {
try {
@@ -132,34 +136,29 @@ abstract class TcpPlugin implements DuplexPlugin {
} catch (IOException e) {
if (LOG.isLoggable(INFO))
LOG.info("Failed to bind " + scrubSocketAddress(addr));
tryToClose(ss);
tryToClose(ss, LOG, WARNING);
}
}
if (ss == null || !ss.isBound()) {
if (ss == null) {
LOG.info("Could not bind server socket");
return;
}
if (!running) {
tryToClose(ss);
if (!state.setServerSocket(ss)) {
LOG.info("Closing redundant server socket");
tryToClose(ss, LOG, WARNING);
return;
}
socket = ss;
backoff.reset();
InetSocketAddress local =
(InetSocketAddress) ss.getLocalSocketAddress();
setLocalSocketAddress(local);
callback.pluginStateChanged(getState());
if (LOG.isLoggable(INFO))
LOG.info("Listening on " + scrubSocketAddress(local));
callback.transportEnabled();
acceptContactConnections();
acceptContactConnections(ss);
});
}
protected void tryToClose(@Nullable ServerSocket ss) {
IoUtils.tryToClose(ss, LOG, WARNING);
callback.transportDisabled();
}
String getIpPortString(InetSocketAddress a) {
String addr = a.getAddress().getHostAddress();
int percent = addr.indexOf('%');
@@ -167,15 +166,18 @@ abstract class TcpPlugin implements DuplexPlugin {
return addr + ":" + a.getPort();
}
private void acceptContactConnections() {
while (isRunning()) {
private void acceptContactConnections(ServerSocket ss) {
while (true) {
Socket s;
try {
s = socket.accept();
s = ss.accept();
s.setSoTimeout(socketTimeout);
} catch (IOException e) {
// This is expected when the socket is closed
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
// This is expected when the server socket is closed
// TODO: Check that this is logged at shutdown/when LAN disabled
LOG.info("Server socket closed");
state.clearServerSocket(ss);
callback.pluginStateChanged(getState());
return;
}
if (LOG.isLoggable(INFO))
@@ -188,13 +190,14 @@ abstract class TcpPlugin implements DuplexPlugin {
@Override
public void stop() {
running = false;
tryToClose(socket);
ServerSocket ss = state.setStopped();
callback.pluginStateChanged(getState());
tryToClose(ss, LOG, WARNING);
}
@Override
public boolean isRunning() {
return running && socket != null && !socket.isClosed();
public State getState() {
return state.getState();
}
@Override
@@ -210,7 +213,7 @@ abstract class TcpPlugin implements DuplexPlugin {
@Override
public void poll(Collection<Pair<TransportProperties, ConnectionHandler>>
properties) {
if (!isRunning()) return;
if (getState() != AVAILABLE) return;
backoff.increment();
for (Pair<TransportProperties, ConnectionHandler> p : properties) {
connect(p.getFirst(), p.getSecond());
@@ -229,14 +232,14 @@ abstract class TcpPlugin implements DuplexPlugin {
@Override
public DuplexTransportConnection createConnection(TransportProperties p) {
if (!isRunning()) return null;
ServerSocket ss = state.getServerSocket();
if (ss == null) return null;
for (InetSocketAddress remote : getRemoteSocketAddresses(p)) {
if (!isConnectable(remote)) {
if (LOG.isLoggable(INFO)) {
SocketAddress local = socket.getLocalSocketAddress();
LOG.info(scrubSocketAddress(remote) +
" is not connectable from " +
scrubSocketAddress(local));
scrubSocketAddress(ss.getLocalSocketAddress()));
}
continue;
}
@@ -244,7 +247,7 @@ abstract class TcpPlugin implements DuplexPlugin {
if (LOG.isLoggable(INFO))
LOG.info("Connecting to " + scrubSocketAddress(remote));
Socket s = createSocket();
s.bind(new InetSocketAddress(socket.getInetAddress(), 0));
s.bind(new InetSocketAddress(ss.getInetAddress(), 0));
s.connect(remote);
s.setSoTimeout(socketTimeout);
if (LOG.isLoggable(INFO))
@@ -327,4 +330,47 @@ abstract class TcpPlugin implements DuplexPlugin {
return emptyList();
}
}
@ThreadSafe
@NotNullByDefault
protected static class PluginState {
@GuardedBy("this")
private boolean started = false, stopped = false;
@GuardedBy("this")
@Nullable
private ServerSocket serverSocket = null;
synchronized void setStarted() {
started = true;
}
@Nullable
synchronized ServerSocket setStopped() {
stopped = true;
ServerSocket ss = serverSocket;
serverSocket = null;
return ss;
}
@Nullable
synchronized ServerSocket getServerSocket() {
return serverSocket;
}
synchronized boolean setServerSocket(ServerSocket ss) {
if (stopped || serverSocket != null) return false;
serverSocket = ss;
return true;
}
synchronized void clearServerSocket(ServerSocket ss) {
if (serverSocket == ss) serverSocket = null;
}
synchronized State getState() {
if (!started || stopped) return DISABLED;
return serverSocket == null ? UNAVAILABLE : AVAILABLE;
}
}
}

View File

@@ -15,6 +15,7 @@ import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.network.NetworkStatus;
import org.briarproject.bramble.api.network.event.NetworkStatusEvent;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.ConnectionHandler;
@@ -54,6 +55,9 @@ import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.zip.ZipInputStream;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import javax.net.SocketFactory;
import static java.util.Arrays.asList;
@@ -65,6 +69,10 @@ import static java.util.logging.Logger.getLogger;
import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS;
import static net.freehaven.tor.control.TorControlCommands.HS_PRIVKEY;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.api.plugin.Plugin.State.AVAILABLE;
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING;
import static org.briarproject.bramble.api.plugin.Plugin.State.UNAVAILABLE;
import static org.briarproject.bramble.api.plugin.TorConstants.CONTROL_PORT;
import static org.briarproject.bramble.api.plugin.TorConstants.ID;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_MOBILE;
@@ -113,16 +121,14 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private final int maxLatency, maxIdleTime, socketTimeout;
private final File torDirectory, torFile, geoIpFile, obfs4File, configFile;
private final File doneFile, cookieFile;
private final ConnectionStatus connectionStatus;
private final AtomicBoolean used = new AtomicBoolean(false);
private volatile ServerSocket socket = null;
protected final PluginState state = new PluginState();
private volatile Socket controlSocket = null;
private volatile TorControlConnection controlConnection = null;
private volatile Settings settings = null;
protected volatile boolean running = false;
protected abstract int getProcessId();
protected abstract long getLastUpdateTime();
@@ -159,7 +165,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
configFile = new File(torDirectory, "torrc");
doneFile = new File(torDirectory, "done");
cookieFile = new File(torDirectory, ".tor/control_auth_cookie");
connectionStatus = new ConnectionStatus();
// Don't execute more than one connection status check at a time
connectionStatusExecutor =
new PoliteExecutor("TorPlugin", ioExecutor, 1);
@@ -258,7 +263,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
// Tell Tor to exit when the control connection is closed
controlConnection.takeOwnership();
controlConnection.resetConf(singletonList(OWNER));
running = true;
// Register to receive events from the Tor process
controlConnection.setEventHandler(this);
controlConnection.setEvents(asList(EVENTS));
@@ -266,11 +270,13 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
String phase = controlConnection.getInfo("status/bootstrap-phase");
if (phase != null && phase.contains("PROGRESS=100")) {
LOG.info("Tor has already bootstrapped");
connectionStatus.setBootstrapped();
state.setBootstrapped();
}
} catch (IOException e) {
throw new PluginException(e);
}
state.setStarted();
callback.pluginStateChanged(getState());
// Check whether we're online
updateConnectionStatus(networkManager.getNetworkStatus(),
batteryManager.isCharging());
@@ -393,11 +399,11 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
tryToClose(ss, LOG, WARNING);
return;
}
if (!running) {
if (!state.setServerSocket(ss)) {
LOG.info("Closing redundant server socket");
tryToClose(ss, LOG, WARNING);
return;
}
socket = ss;
// Store the port number
String localPort = String.valueOf(ss.getLocalPort());
Settings s = new Settings();
@@ -412,7 +418,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
private void publishHiddenService(String port) {
if (!running) return;
if (!state.isRunning()) return;
LOG.info("Creating hidden service");
String privKey = settings.get(HS_PRIVKEY);
Map<Integer, String> portLines = singletonMap(80, "127.0.0.1:" + port);
@@ -450,14 +456,16 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
private void acceptContactConnections(ServerSocket ss) {
while (running) {
while (true) {
Socket s;
try {
s = ss.accept();
s.setSoTimeout(socketTimeout);
} catch (IOException e) {
// This is expected when the socket is closed
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
// This is expected when the server socket is closed
// TODO: Check that this is logged at shutdown
LOG.info("Server socket closed");
state.clearServerSocket(ss);
return;
}
LOG.info("Connection received");
@@ -467,10 +475,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
protected void enableNetwork(boolean enable) throws IOException {
if (!running) return;
connectionStatus.enableNetwork(enable);
if (!state.isRunning()) return;
state.enableNetwork(enable);
callback.pluginStateChanged(getState());
controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
if (!enable) callback.transportDisabled();
}
private void enableBridges(boolean enable, boolean needsMeek)
@@ -494,9 +502,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override
public void stop() {
running = false;
tryToClose(socket, LOG, WARNING);
callback.transportDisabled();
ServerSocket ss = state.setStopped();
callback.pluginStateChanged(getState());
tryToClose(ss, LOG, WARNING);
if (controlSocket != null && controlConnection != null) {
try {
LOG.info("Stopping Tor");
@@ -510,8 +518,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
@Override
public boolean isRunning() {
return running && connectionStatus.isConnected();
public State getState() {
return state.getState();
}
@Override
@@ -527,7 +535,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override
public void poll(Collection<Pair<TransportProperties, ConnectionHandler>>
properties) {
if (!isRunning()) return;
if (getState() != AVAILABLE) return;
backoff.increment();
for (Pair<TransportProperties, ConnectionHandler> p : properties) {
connect(p.getFirst(), p.getSecond());
@@ -546,7 +554,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override
public DuplexTransportConnection createConnection(TransportProperties p) {
if (!isRunning()) return null;
if (getState() != AVAILABLE) return null;
String bestOnion = null;
String onion2 = p.get(PROP_ONION_V2);
String onion3 = p.get(PROP_ONION_V3);
@@ -663,10 +671,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override
public void circuitStatus(String status, String id, String path) {
if (status.equals("BUILT") &&
connectionStatus.getAndSetCircuitBuilt()) {
state.getAndSetCircuitBuilt()) {
callback.pluginStateChanged(getState());
LOG.info("First circuit built");
backoff.reset();
if (isRunning()) callback.transportEnabled();
}
}
@@ -697,9 +705,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
public void message(String severity, String msg) {
if (LOG.isLoggable(INFO)) LOG.info(severity + " " + msg);
if (severity.equals("NOTICE") && msg.startsWith("Bootstrapped 100%")) {
connectionStatus.setBootstrapped();
state.setBootstrapped();
callback.pluginStateChanged(getState());
backoff.reset();
if (isRunning()) callback.transportEnabled();
}
}
@@ -746,7 +754,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private void updateConnectionStatus(NetworkStatus status,
boolean charging) {
connectionStatusExecutor.execute(() -> {
if (!running) return;
if (!state.isRunning()) return;
boolean online = status.isConnected();
boolean wifi = status.isWifi();
String country = locationUtils.getCurrentCountry();
@@ -762,7 +770,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (LOG.isLoggable(INFO)) {
LOG.info("Online: " + online + ", wifi: " + wifi);
if ("".equals(country)) LOG.info("Country code unknown");
if (country.isEmpty()) LOG.info("Country code unknown");
else LOG.info("Country code: " + country);
LOG.info("Charging: " + charging);
}
@@ -810,33 +818,73 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
private void enableConnectionPadding(boolean enable) throws IOException {
if (!running) return;
if (!state.isRunning()) return;
controlConnection.setConf("ConnectionPadding", enable ? "1" : "0");
}
private static class ConnectionStatus {
@ThreadSafe
@NotNullByDefault
protected static class PluginState {
// All of the following are locking: this
private boolean networkEnabled = false;
private boolean bootstrapped = false, circuitBuilt = false;
@GuardedBy("this")
private boolean started = false,
stopped = false,
networkInitialised = false,
networkEnabled = false,
bootstrapped = false,
circuitBuilt = false;
private synchronized void setBootstrapped() {
@GuardedBy("this")
@Nullable
private ServerSocket serverSocket = null;
synchronized void setStarted() {
started = true;
}
synchronized boolean isRunning() {
return started && !stopped;
}
@Nullable
synchronized ServerSocket setStopped() {
stopped = true;
ServerSocket ss = serverSocket;
serverSocket = null;
return ss;
}
synchronized void setBootstrapped() {
bootstrapped = true;
}
private synchronized boolean getAndSetCircuitBuilt() {
synchronized boolean getAndSetCircuitBuilt() {
boolean firstCircuit = !circuitBuilt;
circuitBuilt = true;
return firstCircuit;
}
private synchronized void enableNetwork(boolean enable) {
synchronized void enableNetwork(boolean enable) {
networkInitialised = true;
networkEnabled = enable;
if (!enable) circuitBuilt = false;
}
private synchronized boolean isConnected() {
return networkEnabled && bootstrapped && circuitBuilt;
synchronized boolean setServerSocket(ServerSocket ss) {
if (stopped || serverSocket != null) return false;
serverSocket = ss;
return true;
}
synchronized void clearServerSocket(ServerSocket ss) {
if (serverSocket == ss) serverSocket = null;
}
synchronized State getState() {
if (!started || stopped) return DISABLED;
if (!networkInitialised) return ENABLING;
if (!networkEnabled) return UNAVAILABLE;
return bootstrapped && circuitBuilt ? AVAILABLE : ENABLING;
}
}
}

View File

@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.Plugin.State;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
@@ -327,6 +328,7 @@ public class LanTcpPluginTest extends BrambleTestCase {
assertEquals(0, comparator.compare(linkLocal, linkLocal));
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean systemHasLocalIpv4Address() throws Exception {
for (NetworkInterface i : list(getNetworkInterfaces())) {
for (InetAddress a : list(i.getInetAddresses())) {
@@ -365,11 +367,7 @@ public class LanTcpPluginTest extends BrambleTestCase {
}
@Override
public void transportEnabled() {
}
@Override
public void transportDisabled() {
public void pluginStateChanged(State newState) {
}
@Override

View File

@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionHandler;
import org.briarproject.bramble.api.plugin.PluginCallback;
@@ -23,9 +24,16 @@ import java.util.Collection;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.State.AVAILABLE;
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING;
import static org.briarproject.bramble.api.plugin.Plugin.State.UNAVAILABLE;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@@ -44,8 +52,8 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
private final PluginCallback callback;
private final int maxLatency;
private final AtomicBoolean used = new AtomicBoolean(false);
private final PluginState state = new PluginState();
private volatile boolean running = false;
private volatile Modem modem = null;
ModemPlugin(ModemFactory modemFactory, SerialPortList serialPortList,
@@ -75,6 +83,8 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
@Override
public void start() throws PluginException {
if (used.getAndSet(true)) throw new IllegalStateException();
state.setStarted();
callback.pluginStateChanged(getState());
for (String portName : serialPortList.getPortNames()) {
if (LOG.isLoggable(INFO))
LOG.info("Trying to initialise modem on " + portName);
@@ -83,18 +93,23 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
if (!modem.start()) continue;
if (LOG.isLoggable(INFO))
LOG.info("Initialised modem on " + portName);
running = true;
state.setInitialised();
callback.pluginStateChanged(getState());
return;
} catch (IOException e) {
logException(LOG, WARNING, e);
}
}
LOG.warning("Failed to initialised modem");
state.setFailed();
callback.pluginStateChanged(getState());
throw new PluginException();
}
@Override
public void stop() {
running = false;
state.setStopped();
callback.pluginStateChanged(getState());
if (modem != null) {
try {
modem.stop();
@@ -105,8 +120,8 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
}
@Override
public boolean isRunning() {
return running;
public State getState() {
return state.getState();
}
@Override
@@ -125,8 +140,8 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
throw new UnsupportedOperationException();
}
private boolean resetModem() {
if (!running) return false;
private void resetModem() {
if (getState() != AVAILABLE) return;
for (String portName : serialPortList.getPortNames()) {
if (LOG.isLoggable(INFO))
LOG.info("Trying to initialise modem on " + portName);
@@ -135,18 +150,19 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
if (!modem.start()) continue;
if (LOG.isLoggable(INFO))
LOG.info("Initialised modem on " + portName);
return true;
return;
} catch (IOException e) {
logException(LOG, WARNING, e);
}
}
running = false;
return false;
LOG.warning("Failed to initialise modem");
state.setFailed();
callback.pluginStateChanged(getState());
}
@Override
public DuplexTransportConnection createConnection(TransportProperties p) {
if (!running) return null;
if (getState() != AVAILABLE) return null;
// Get the ISO 3166 code for the caller's country
String fromIso = callback.getLocalProperties().get("iso3166");
if (isNullOrEmpty(fromIso)) return null;
@@ -232,4 +248,37 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
if (exception) resetModem();
}
}
@ThreadSafe
@NotNullByDefault
private static class PluginState {
@GuardedBy("this")
private boolean started = false,
stopped = false,
initialised = false,
failed = false;
private synchronized void setStarted() {
started = true;
}
private synchronized void setStopped() {
stopped = true;
}
private synchronized void setInitialised() {
initialised = true;
}
private synchronized void setFailed() {
failed = true;
}
private State getState() {
if (!started || stopped) return DISABLED;
if (failed) return UNAVAILABLE;
return initialised ? AVAILABLE : ENABLING;
}
}
}

View File

@@ -30,6 +30,7 @@ import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.State.AVAILABLE;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE;
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
@@ -176,7 +177,7 @@ public class NavDrawerControllerImpl extends DbControllerImpl
@Override
public boolean isTransportRunning(TransportId transportId) {
Plugin plugin = pluginManager.getPlugin(transportId);
return plugin != null && plugin.isRunning();
return plugin != null && plugin.getState() == AVAILABLE;
}
}