Support IPv6 SLAAC addresses.

This commit is contained in:
akwizgran
2020-02-17 11:07:14 +00:00
parent 2bd2f67693
commit e1084ffadd
8 changed files with 279 additions and 145 deletions

View File

@@ -83,7 +83,12 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
} }
@Override @Override
protected List<InetAddress> getUsableLocalInetAddresses() { protected List<InetAddress> getUsableLocalInetAddresses(boolean ipv4) {
if (ipv4) return getUsableLocalIpv4Addresses();
else return getUsableLocalIpv6Addresses();
}
private List<InetAddress> getUsableLocalIpv4Addresses() {
// If the device doesn't have wifi, don't open any sockets // If the device doesn't have wifi, don't open any sockets
if (wifiManager == null) return emptyList(); if (wifiManager == null) return emptyList();
// If we're connected to a wifi network, return its address // If we're connected to a wifi network, return its address
@@ -100,6 +105,17 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
return emptyList(); return emptyList();
} }
private List<InetAddress> getUsableLocalIpv6Addresses() {
// If the device doesn't have wifi, don't open any sockets
if (wifiManager == null) return emptyList();
// If we have a SLAAC address, return it
for (InetAddress addr : getLocalInetAddresses()) {
if (isSlaacAddress(addr)) return singletonList(addr);
}
// No suitable addresses
return emptyList();
}
private InetAddress intToInetAddress(int ip) { private InetAddress intToInetAddress(int ip) {
byte[] ipBytes = new byte[4]; byte[] ipBytes = new byte[4];
ipBytes[0] = (byte) (ip & 0xFF); ipBytes[0] = (byte) (ip & 0xFF);
@@ -150,12 +166,13 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
} else if (addrs.isEmpty()) { } else if (addrs.isEmpty()) {
LOG.info("Not connected to wifi"); LOG.info("Not connected to wifi");
socketFactory = SocketFactory.getDefault(); socketFactory = SocketFactory.getDefault();
// Server socket may not have been closed automatically when // Server sockets may not have been closed automatically when
// interface was taken down. Socket will be cleared and state // interface was taken down. Sockets will be cleared and state
// updated in acceptContactConnections() // updated in acceptContactConnections()
if (s == ACTIVE) { if (s == ACTIVE) {
LOG.info("Closing server socket"); LOG.info("Closing server sockets");
tryToClose(state.getServerSocket(), LOG, WARNING); tryToClose(state.getServerSocket(true), LOG, WARNING);
tryToClose(state.getServerSocket(false), LOG, WARNING);
} }
} else { } else {
LOG.info("Connected to wifi"); LOG.info("Connected to wifi");

View File

@@ -7,6 +7,7 @@ public interface LanTcpConstants {
// Transport properties (shared with contacts) // Transport properties (shared with contacts)
String PROP_IP_PORTS = "ipPorts"; String PROP_IP_PORTS = "ipPorts";
String PROP_PORT = "port"; String PROP_PORT = "port";
String PROP_SLAAC = "slaac";
// A local setting // A local setting
String PREF_LAN_IP_PORTS = "ipPorts"; String PREF_LAN_IP_PORTS = "ipPorts";

View File

@@ -2,13 +2,17 @@ package org.briarproject.bramble.util;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.net.Inet6Address; import java.net.Inet4Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
import static org.briarproject.bramble.util.StringUtils.isValidMac;
import static org.briarproject.bramble.util.StringUtils.toHexString;
@NotNullByDefault @NotNullByDefault
public class PrivacyUtils { public class PrivacyUtils {
@@ -19,7 +23,7 @@ public class PrivacyUtils {
@Nullable @Nullable
public static String scrubMacAddress(@Nullable String address) { public static String scrubMacAddress(@Nullable String address) {
if (address == null || address.length() == 0) return null; if (isNullOrEmpty(address) || !isValidMac(address)) return address;
// this is a fake address we need to know about // this is a fake address we need to know about
if (address.equals("02:00:00:00:00:00")) return address; if (address.equals("02:00:00:00:00:00")) return address;
// keep first and last octet of MAC address // keep first and last octet of MAC address
@@ -27,39 +31,37 @@ public class PrivacyUtils {
+ address.substring(14, 17); + address.substring(14, 17);
} }
@Nullable
public static String scrubInetAddress(InetAddress address) { public static String scrubInetAddress(InetAddress address) {
// don't scrub link and site local addresses if (address instanceof Inet4Address) {
if (address.isLinkLocalAddress() || address.isSiteLocalAddress()) // Don't scrub local IPv4 addresses
return address.toString(); if (address.isLoopbackAddress() || address.isLinkLocalAddress() ||
// completely scrub IPv6 addresses address.isSiteLocalAddress()) {
if (address instanceof Inet6Address) return "[scrubbed]"; return address.getHostAddress();
// keep first and last octet of IPv4 addresses }
return scrubInetAddress(address.toString()); // Keep first and last octet of non-local IPv4 addresses
return scrubIpv4Address(address.getAddress());
} else {
// Keep first and last octet of IPv6 addresses
return scrubIpv6Address(address.getAddress());
}
} }
@Nullable private static String scrubIpv4Address(byte[] ipv4) {
public static String scrubInetAddress(@Nullable String address) { return (ipv4[0] & 0xFF) + ".[scrubbed]." + (ipv4[3] & 0xFF);
if (address == null) return null; }
int firstDot = address.indexOf("."); private static String scrubIpv6Address(byte[] ipv6) {
if (firstDot == -1) return "[scrubbed]"; String hex = toHexString(ipv6).toLowerCase();
String prefix = address.substring(0, firstDot + 1); return hex.substring(0, 2) + "[scrubbed]" + hex.substring(30);
int lastDot = address.lastIndexOf(".");
String suffix = address.substring(lastDot, address.length());
return prefix + "[scrubbed]" + suffix;
} }
@Nullable
public static String scrubSocketAddress(InetSocketAddress address) { public static String scrubSocketAddress(InetSocketAddress address) {
InetAddress inetAddress = address.getAddress(); return scrubInetAddress(address.getAddress());
return scrubInetAddress(inetAddress);
} }
@Nullable
public static String scrubSocketAddress(SocketAddress address) { public static String scrubSocketAddress(SocketAddress address) {
if (address instanceof InetSocketAddress) if (address instanceof InetSocketAddress)
return scrubSocketAddress((InetSocketAddress) address); return scrubSocketAddress((InetSocketAddress) address);
return scrubInetAddress(address.toString()); return "[scrubbed]";
} }
} }

View File

@@ -14,6 +14,7 @@ import org.briarproject.bramble.api.settings.Settings;
import java.io.IOException; import java.io.IOException;
import java.net.Inet4Address; import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.InterfaceAddress; import java.net.InterfaceAddress;
@@ -22,7 +23,6 @@ import java.net.Socket;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Random;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -30,6 +30,8 @@ import javax.annotation.Nullable;
import static java.lang.Integer.parseInt; import static java.lang.Integer.parseInt;
import static java.util.Collections.addAll; import static java.util.Collections.addAll;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.Collections.sort; import static java.util.Collections.sort;
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;
@@ -39,11 +41,14 @@ 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.PREF_LAN_IP_PORTS;
import static org.briarproject.bramble.api.plugin.LanTcpConstants.PROP_IP_PORTS; import static org.briarproject.bramble.api.plugin.LanTcpConstants.PROP_IP_PORTS;
import static org.briarproject.bramble.api.plugin.LanTcpConstants.PROP_PORT; import static org.briarproject.bramble.api.plugin.LanTcpConstants.PROP_PORT;
import static org.briarproject.bramble.api.plugin.LanTcpConstants.PROP_SLAAC;
import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED; 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.IoUtils.tryToClose;
import static org.briarproject.bramble.util.PrivacyUtils.scrubSocketAddress; import static org.briarproject.bramble.util.PrivacyUtils.scrubSocketAddress;
import static org.briarproject.bramble.util.StringUtils.fromHexString;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
import static org.briarproject.bramble.util.StringUtils.join; import static org.briarproject.bramble.util.StringUtils.join;
import static org.briarproject.bramble.util.StringUtils.toHexString;
@NotNullByDefault @NotNullByDefault
class LanTcpPlugin extends TcpPlugin { class LanTcpPlugin extends TcpPlugin {
@@ -53,6 +58,13 @@ class LanTcpPlugin extends TcpPlugin {
private static final int MAX_ADDRESSES = 4; private static final int MAX_ADDRESSES = 4;
private static final String SEPARATOR = ","; private static final String SEPARATOR = ",";
/**
* The network prefix of a SLAAC IPv6 address. See
* https://tools.ietf.org/html/rfc4862#section-5.3
*/
private static final byte[] SLAAC_PREFIX =
new byte[] {(byte) 0xFE, (byte) 0x80, 0, 0, 0, 0, 0, 0};
/** /**
* The IP address of an Android device providing a wifi access point. * The IP address of an Android device providing a wifi access point.
*/ */
@@ -99,22 +111,22 @@ class LanTcpPlugin extends TcpPlugin {
protected void initialisePortProperty() { protected void initialisePortProperty() {
TransportProperties p = callback.getLocalProperties(); TransportProperties p = callback.getLocalProperties();
if (isNullOrEmpty(p.get(PROP_PORT))) { if (isNullOrEmpty(p.get(PROP_PORT))) {
int port = new Random().nextInt(32768) + 32768; int port = chooseEphemeralPort();
p.put(PROP_PORT, String.valueOf(port)); p.put(PROP_PORT, String.valueOf(port));
callback.mergeLocalProperties(p); callback.mergeLocalProperties(p);
} }
} }
@Override @Override
protected List<InetSocketAddress> getLocalSocketAddresses() { protected List<InetSocketAddress> getLocalSocketAddresses(boolean ipv4) {
TransportProperties p = callback.getLocalProperties(); TransportProperties p = callback.getLocalProperties();
int preferredPort = parsePortProperty(p.get(PROP_PORT)); int preferredPort = parsePortProperty(p.get(PROP_PORT));
String oldIpPorts = p.get(PROP_IP_PORTS); String oldIpPorts = p.get(PROP_IP_PORTS);
List<InetSocketAddress> olds = parseSocketAddresses(oldIpPorts); List<InetSocketAddress> olds = parseIpv4SocketAddresses(oldIpPorts);
List<InetSocketAddress> locals = new ArrayList<>(); List<InetSocketAddress> locals = new ArrayList<>();
List<InetSocketAddress> fallbacks = new ArrayList<>(); List<InetSocketAddress> fallbacks = new ArrayList<>();
for (InetAddress local : getUsableLocalInetAddresses()) { for (InetAddress local : getUsableLocalInetAddresses(ipv4)) {
// If we've used this address before, try to use the same port // If we've used this address before, try to use the same port
int port = preferredPort; int port = preferredPort;
for (InetSocketAddress old : olds) { for (InetSocketAddress old : olds) {
@@ -140,17 +152,17 @@ class LanTcpPlugin extends TcpPlugin {
} }
} }
private List<InetSocketAddress> parseSocketAddresses(String ipPorts) { private List<InetSocketAddress> parseIpv4SocketAddresses(String ipPorts) {
List<InetSocketAddress> addresses = new ArrayList<>(); List<InetSocketAddress> addresses = new ArrayList<>();
if (isNullOrEmpty(ipPorts)) return addresses; if (isNullOrEmpty(ipPorts)) return addresses;
for (String ipPort : ipPorts.split(SEPARATOR)) { for (String ipPort : ipPorts.split(SEPARATOR)) {
InetSocketAddress a = parseSocketAddress(ipPort); InetSocketAddress a = parseIpv4SocketAddress(ipPort);
if (a != null) addresses.add(a); if (a != null) addresses.add(a);
} }
return addresses; return addresses;
} }
protected List<InetAddress> getUsableLocalInetAddresses() { protected List<InetAddress> getUsableLocalInetAddresses(boolean ipv4) {
List<InterfaceAddress> ifAddrs = List<InterfaceAddress> ifAddrs =
new ArrayList<>(getLocalInterfaceAddresses()); new ArrayList<>(getLocalInterfaceAddresses());
// Prefer longer network prefixes // Prefer longer network prefixes
@@ -159,13 +171,18 @@ class LanTcpPlugin extends TcpPlugin {
List<InetAddress> addrs = new ArrayList<>(); List<InetAddress> addrs = new ArrayList<>();
for (InterfaceAddress ifAddr : ifAddrs) { for (InterfaceAddress ifAddr : ifAddrs) {
InetAddress addr = ifAddr.getAddress(); InetAddress addr = ifAddr.getAddress();
if (isAcceptableAddress(addr)) addrs.add(addr); if (isAcceptableAddress(addr, ipv4)) addrs.add(addr);
} }
return addrs; return addrs;
} }
@Override @Override
protected void setLocalSocketAddress(InetSocketAddress a) { protected void setLocalSocketAddress(InetSocketAddress a, boolean ipv4) {
if (ipv4) setLocalIpv4SocketAddress(a);
else setLocalIpv6SocketAddress(a);
}
private void setLocalIpv4SocketAddress(InetSocketAddress a) {
String ipPort = getIpPortString(a); String ipPort = getIpPortString(a);
// Get the list of recently used addresses // Get the list of recently used addresses
String setting = callback.getSettings().get(PREF_LAN_IP_PORTS); String setting = callback.getSettings().get(PREF_LAN_IP_PORTS);
@@ -198,11 +215,38 @@ class LanTcpPlugin extends TcpPlugin {
callback.mergeSettings(settings); callback.mergeSettings(settings);
} }
private void setLocalIpv6SocketAddress(InetSocketAddress a) {
if (isSlaacAddress(a.getAddress())) {
String property = toHexString(a.getAddress().getAddress());
TransportProperties properties = new TransportProperties();
properties.put(PROP_SLAAC, property);
callback.mergeLocalProperties(properties);
}
}
// See https://tools.ietf.org/html/rfc4862#section-5.3
protected boolean isSlaacAddress(InetAddress a) {
if (!(a instanceof Inet6Address)) return false;
byte[] ip = a.getAddress();
for (int i = 0; i < 8; i++) {
if (ip[i] != SLAAC_PREFIX[i]) return false;
}
return (ip[8] & 0x02) == 0x02
&& ip[11] == (byte) 0xFF
&& ip[12] == (byte) 0xFE;
}
@Override @Override
protected List<InetSocketAddress> getRemoteSocketAddresses( protected List<InetSocketAddress> getRemoteSocketAddresses(
TransportProperties p, boolean ipv4) {
if (ipv4) return getRemoteIpv4SocketAddresses(p);
else return getRemoteIpv6SocketAddresses(p);
}
private List<InetSocketAddress> getRemoteIpv4SocketAddresses(
TransportProperties p) { TransportProperties p) {
String ipPorts = p.get(PROP_IP_PORTS); String ipPorts = p.get(PROP_IP_PORTS);
List<InetSocketAddress> remotes = parseSocketAddresses(ipPorts); List<InetSocketAddress> remotes = parseIpv4SocketAddresses(ipPorts);
int port = parsePortProperty(p.get(PROP_PORT)); int port = parsePortProperty(p.get(PROP_PORT));
// If the contact has a preferred port, we can guess their IP:port when // If the contact has a preferred port, we can guess their IP:port when
// they're providing a wifi access point // they're providing a wifi access point
@@ -217,20 +261,48 @@ class LanTcpPlugin extends TcpPlugin {
return remotes; return remotes;
} }
private boolean isAcceptableAddress(InetAddress a) { private List<InetSocketAddress> getRemoteIpv6SocketAddresses(
// Accept link-local and site-local IPv4 addresses TransportProperties p) {
boolean ipv4 = a instanceof Inet4Address; InetAddress slaac = parseSlaacProperty(p.get(PROP_SLAAC));
boolean loop = a.isLoopbackAddress(); int port = parsePortProperty(p.get(PROP_PORT));
boolean link = a.isLinkLocalAddress(); if (slaac == null || port == 0) return emptyList();
boolean site = a.isSiteLocalAddress(); return singletonList(new InetSocketAddress(slaac, port));
return ipv4 && !loop && (link || site); }
@Nullable
private InetAddress parseSlaacProperty(String slaacProperty) {
if (isNullOrEmpty(slaacProperty)) return null;
try {
byte[] ip = fromHexString(slaacProperty);
if (ip.length != 16) return null;
InetAddress a = InetAddress.getByAddress(ip);
return isSlaacAddress(a) ? a : null;
} catch (IllegalArgumentException | UnknownHostException e) {
return null;
}
}
private boolean isAcceptableAddress(InetAddress a, boolean ipv4) {
if (ipv4) {
// Accept link-local and site-local IPv4 addresses
boolean isIpv4 = a instanceof Inet4Address;
boolean loop = a.isLoopbackAddress();
boolean link = a.isLinkLocalAddress();
boolean site = a.isSiteLocalAddress();
return isIpv4 && !loop && (link || site);
} else {
// Accept IPv6 SLAAC addresses
return isSlaacAddress(a);
}
} }
@Override @Override
protected boolean isConnectable(InterfaceAddress local, protected boolean isConnectable(InterfaceAddress local,
InetSocketAddress remote) { InetSocketAddress remote) {
if (remote.getPort() == 0) return false; if (remote.getPort() == 0) return false;
if (!isAcceptableAddress(remote.getAddress())) return false; InetAddress remoteAddress = remote.getAddress();
boolean ipv4 = local.getAddress() instanceof Inet4Address;
if (!isAcceptableAddress(remoteAddress, ipv4)) return false;
// Try to determine whether the address is on the same LAN as us // Try to determine whether the address is on the same LAN as us
byte[] localIp = local.getAddress().getAddress(); byte[] localIp = local.getAddress().getAddress();
byte[] remoteIp = remote.getAddress().getAddress(); byte[] remoteIp = remote.getAddress().getAddress();
@@ -262,7 +334,7 @@ class LanTcpPlugin extends TcpPlugin {
@Override @Override
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) { public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
ServerSocket ss = null; ServerSocket ss = null;
for (InetSocketAddress addr : getLocalSocketAddresses()) { for (InetSocketAddress addr : getLocalSocketAddresses(true)) {
// Don't try to reuse the same port we use for contact connections // Don't try to reuse the same port we use for contact connections
addr = new InetSocketAddress(addr.getAddress(), 0); addr = new InetSocketAddress(addr.getAddress(), 0);
try { try {
@@ -291,7 +363,7 @@ class LanTcpPlugin extends TcpPlugin {
@Override @Override
public DuplexTransportConnection createKeyAgreementConnection( public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor) { byte[] commitment, BdfList descriptor) {
ServerSocket ss = state.getServerSocket(); ServerSocket ss = state.getServerSocket(true);
if (ss == null) return null; if (ss == null) return null;
InterfaceAddress local = getLocalInterfaceAddress(ss.getInetAddress()); InterfaceAddress local = getLocalInterfaceAddress(ss.getInetAddress());
if (local == null) { if (local == null) {

View File

@@ -54,11 +54,13 @@ class PortMapperImpl implements PortMapper {
shutdownManager.addShutdownHook(() -> deleteMapping(port)); shutdownManager.addShutdownHook(() -> deleteMapping(port));
} }
String externalString = gateway.getExternalIPAddress(); String externalString = gateway.getExternalIPAddress();
if (LOG.isLoggable(INFO)) if (externalString == null) {
LOG.info( LOG.info("External address not available");
"External address " + scrubInetAddress(externalString)); } else {
if (externalString != null)
external = InetAddress.getByName(externalString); external = InetAddress.getByName(externalString);
if (LOG.isLoggable(INFO))
LOG.info("External address " + scrubInetAddress(external));
}
} catch (IOException | SAXException e) { } catch (IOException | SAXException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }

View File

@@ -43,6 +43,7 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Collections.list; import static java.util.Collections.list;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
@@ -78,20 +79,22 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
* Returns zero or more socket addresses on which the plugin should listen, * Returns zero or more socket addresses on which the plugin should listen,
* in order of preference. At most one of the addresses will be bound. * in order of preference. At most one of the addresses will be bound.
*/ */
protected abstract List<InetSocketAddress> getLocalSocketAddresses(); protected abstract List<InetSocketAddress> getLocalSocketAddresses(
boolean ipv4);
/** /**
* Adds the address on which the plugin is listening to the transport * Adds the address on which the plugin is listening to the transport
* properties. * properties.
*/ */
protected abstract void setLocalSocketAddress(InetSocketAddress a); protected abstract void setLocalSocketAddress(InetSocketAddress a,
boolean ipv4);
/** /**
* Returns zero or more socket addresses for connecting to a contact with * Returns zero or more socket addresses for connecting to a contact with
* the given transport properties. * the given transport properties.
*/ */
protected abstract List<InetSocketAddress> getRemoteSocketAddresses( protected abstract List<InetSocketAddress> getRemoteSocketAddresses(
TransportProperties p); TransportProperties p, boolean ipv4);
/** /**
* Returns true if connections to the given address can be attempted. * Returns true if connections to the given address can be attempted.
@@ -136,37 +139,43 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
protected void bind() { protected void bind() {
bindExecutor.execute(() -> { bindExecutor.execute(() -> {
if (getState() != INACTIVE) return; if (getState() != INACTIVE) return;
ServerSocket ss = null; bind(true);
for (InetSocketAddress addr : getLocalSocketAddresses()) { bind(false);
try {
ss = new ServerSocket();
ss.bind(addr);
break;
} catch (IOException e) {
if (LOG.isLoggable(INFO))
LOG.info("Failed to bind " + scrubSocketAddress(addr));
tryToClose(ss, LOG, WARNING);
}
}
if (ss == null) {
LOG.info("Could not bind server socket");
return;
}
if (!state.setServerSocket(ss)) {
LOG.info("Closing redundant server socket");
tryToClose(ss, LOG, WARNING);
return;
}
backoff.reset();
InetSocketAddress local =
(InetSocketAddress) ss.getLocalSocketAddress();
setLocalSocketAddress(local);
if (LOG.isLoggable(INFO))
LOG.info("Listening on " + scrubSocketAddress(local));
acceptContactConnections(ss);
}); });
} }
private void bind(boolean ipv4) {
ServerSocket ss = null;
for (InetSocketAddress addr : getLocalSocketAddresses(ipv4)) {
try {
ss = new ServerSocket();
ss.bind(addr);
break;
} catch (IOException e) {
if (LOG.isLoggable(INFO))
LOG.info("Failed to bind " + scrubSocketAddress(addr));
tryToClose(ss, LOG, WARNING);
}
}
if (ss == null) {
LOG.info("Could not bind server socket");
return;
}
if (!state.setServerSocket(ss, ipv4)) {
LOG.info("Closing redundant server socket");
tryToClose(ss, LOG, WARNING);
return;
}
backoff.reset();
InetSocketAddress local =
(InetSocketAddress) ss.getLocalSocketAddress();
setLocalSocketAddress(local, ipv4);
if (LOG.isLoggable(INFO))
LOG.info("Listening on " + scrubSocketAddress(local));
ServerSocket finalSocket = ss;
ioExecutor.execute(() -> acceptContactConnections(finalSocket, ipv4));
}
String getIpPortString(InetSocketAddress a) { String getIpPortString(InetSocketAddress a) {
String addr = a.getAddress().getHostAddress(); String addr = a.getAddress().getHostAddress();
int percent = addr.indexOf('%'); int percent = addr.indexOf('%');
@@ -174,7 +183,7 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
return addr + ":" + a.getPort(); return addr + ":" + a.getPort();
} }
private void acceptContactConnections(ServerSocket ss) { private void acceptContactConnections(ServerSocket ss, boolean ipv4) {
while (true) { while (true) {
Socket s; Socket s;
try { try {
@@ -183,12 +192,13 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
} catch (IOException e) { } catch (IOException e) {
// This is expected when the server socket is closed // This is expected when the server socket is closed
LOG.info("Server socket closed"); LOG.info("Server socket closed");
state.clearServerSocket(ss); state.clearServerSocket(ss, ipv4);
return; return;
} }
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO)) {
LOG.info("Connection from " + LOG.info("Connection from " +
scrubSocketAddress(s.getRemoteSocketAddress())); scrubSocketAddress(s.getRemoteSocketAddress()));
}
backoff.reset(); backoff.reset();
callback.handleConnection(new TcpTransportConnection(this, s)); callback.handleConnection(new TcpTransportConnection(this, s));
} }
@@ -196,8 +206,9 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
@Override @Override
public void stop() { public void stop() {
ServerSocket ss = state.setStopped(); for (@Nullable ServerSocket ss : state.setStopped()) {
tryToClose(ss, LOG, WARNING); tryToClose(ss, LOG, WARNING);
}
} }
@Override @Override
@@ -242,14 +253,22 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
@Override @Override
public DuplexTransportConnection createConnection(TransportProperties p) { public DuplexTransportConnection createConnection(TransportProperties p) {
ServerSocket ss = state.getServerSocket(); DuplexTransportConnection c = createConnection(p, true);
if (c != null) return c;
return createConnection(p, false);
}
@Nullable
private DuplexTransportConnection createConnection(TransportProperties p,
boolean ipv4) {
ServerSocket ss = state.getServerSocket(ipv4);
if (ss == null) return null; if (ss == null) return null;
InterfaceAddress local = getLocalInterfaceAddress(ss.getInetAddress()); InterfaceAddress local = getLocalInterfaceAddress(ss.getInetAddress());
if (local == null) { if (local == null) {
LOG.warning("No interface for server socket"); LOG.warning("No interface for server socket");
return null; return null;
} }
for (InetSocketAddress remote : getRemoteSocketAddresses(p)) { for (InetSocketAddress remote : getRemoteSocketAddresses(p, ipv4)) {
// Don't try to connect to our own address // Don't try to connect to our own address
if (!canConnectToOwnAddress() && if (!canConnectToOwnAddress() &&
remote.getAddress().equals(ss.getInetAddress())) { remote.getAddress().equals(ss.getInetAddress())) {
@@ -274,9 +293,10 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
LOG.info("Connected to " + scrubSocketAddress(remote)); LOG.info("Connected to " + scrubSocketAddress(remote));
return new TcpTransportConnection(this, s); return new TcpTransportConnection(this, s);
} catch (IOException e) { } catch (IOException e) {
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO)) {
LOG.info("Could not connect to " + LOG.info("Could not connect to " +
scrubSocketAddress(remote)); scrubSocketAddress(remote));
}
} }
} }
return null; return null;
@@ -299,8 +319,12 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
return new Socket(); return new Socket();
} }
int chooseEphemeralPort() {
return 32768 + (int) (Math.random() * 32768);
}
@Nullable @Nullable
InetSocketAddress parseSocketAddress(String ipPort) { InetSocketAddress parseIpv4SocketAddress(String ipPort) {
if (isNullOrEmpty(ipPort)) return null; if (isNullOrEmpty(ipPort)) return null;
String[] split = ipPort.split(":"); String[] split = ipPort.split(":");
if (split.length != 2) return null; if (split.length != 2) return null;
@@ -311,14 +335,7 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
InetAddress a = InetAddress.getByName(addr); InetAddress a = InetAddress.getByName(addr);
int p = Integer.parseInt(port); int p = Integer.parseInt(port);
return new InetSocketAddress(a, p); return new InetSocketAddress(a, p);
} catch (UnknownHostException e) { } catch (UnknownHostException | NumberFormatException e) {
if (LOG.isLoggable(WARNING))
// not scrubbing to enable us to find the problem
LOG.warning("Invalid address: " + addr);
return null;
} catch (NumberFormatException e) {
if (LOG.isLoggable(WARNING))
LOG.warning("Invalid port: " + port);
return null; return null;
} }
} }
@@ -389,13 +406,15 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
@IoExecutor @IoExecutor
private void onSettingsUpdated(Settings settings) { private void onSettingsUpdated(Settings settings) {
boolean enabledByUser = settings.getBoolean(PREF_PLUGIN_ENABLE, false); boolean enabledByUser = settings.getBoolean(PREF_PLUGIN_ENABLE, false);
ServerSocket ss = state.setEnabledByUser(enabledByUser); List<ServerSocket> toClose = state.setEnabledByUser(enabledByUser);
State s = getState(); State s = getState();
if (ss != null) { if (!toClose.isEmpty()) {
LOG.info("Disabled by user, closing server socket"); LOG.info("Disabled by user, closing server sockets");
tryToClose(ss, LOG, WARNING); for (@Nullable ServerSocket ss : toClose) {
tryToClose(ss, LOG, WARNING);
}
} else if (s == INACTIVE) { } else if (s == INACTIVE) {
LOG.info("Enabled by user, opening server socket"); LOG.info("Enabled by user, opening server sockets");
bind(); bind();
} }
} }
@@ -409,7 +428,7 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
@GuardedBy("this") @GuardedBy("this")
@Nullable @Nullable
private ServerSocket serverSocket = null; private ServerSocket serverSocketV4 = null, serverSocketV6 = null;
synchronized void setStarted(boolean enabledByUser) { synchronized void setStarted(boolean enabledByUser) {
started = true; started = true;
@@ -417,48 +436,62 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
callback.pluginStateChanged(getState()); callback.pluginStateChanged(getState());
} }
@Nullable synchronized List<ServerSocket> setStopped() {
synchronized ServerSocket setStopped() {
stopped = true; stopped = true;
ServerSocket ss = serverSocket; List<ServerSocket> toClose = clearServerSockets();
serverSocket = null;
callback.pluginStateChanged(getState()); callback.pluginStateChanged(getState());
return ss; return toClose;
} }
@Nullable @GuardedBy("this")
synchronized ServerSocket setEnabledByUser(boolean enabledByUser) { private List<ServerSocket> clearServerSockets() {
List<ServerSocket> toClose = asList(serverSocketV4, serverSocketV6);
serverSocketV4 = null;
serverSocketV6 = null;
return toClose;
}
synchronized List<ServerSocket> setEnabledByUser(
boolean enabledByUser) {
this.enabledByUser = enabledByUser; this.enabledByUser = enabledByUser;
ServerSocket ss = null; List<ServerSocket> toClose = enabledByUser
if (!enabledByUser) { ? emptyList() : clearServerSockets();
ss = serverSocket;
serverSocket = null;
}
callback.pluginStateChanged(getState()); callback.pluginStateChanged(getState());
return ss; return toClose;
} }
@Nullable @Nullable
synchronized ServerSocket getServerSocket() { synchronized ServerSocket getServerSocket(boolean ipv4) {
return serverSocket; return ipv4 ? serverSocketV4 : serverSocketV6;
} }
synchronized boolean setServerSocket(ServerSocket ss) { synchronized boolean setServerSocket(ServerSocket ss, boolean ipv4) {
if (stopped || serverSocket != null) return false; if (stopped) return false;
serverSocket = ss; if (ipv4) {
if (serverSocketV4 != null) return false;
serverSocketV4 = ss;
} else {
if (serverSocketV6 != null) return false;
serverSocketV6 = ss;
}
callback.pluginStateChanged(getState()); callback.pluginStateChanged(getState());
return true; return true;
} }
synchronized void clearServerSocket(ServerSocket ss) { synchronized void clearServerSocket(ServerSocket ss, boolean ipv4) {
if (serverSocket == ss) serverSocket = null; if (ipv4) {
if (serverSocketV4 == ss) serverSocketV4 = null;
} else {
if (serverSocketV6 == ss) serverSocketV6 = null;
}
callback.pluginStateChanged(getState()); callback.pluginStateChanged(getState());
} }
synchronized State getState() { synchronized State getState() {
if (!started || stopped) return STARTING_STOPPING; if (!started || stopped) return STARTING_STOPPING;
if (!enabledByUser) return DISABLED; if (!enabledByUser) return DISABLED;
return serverSocket == null ? INACTIVE : ACTIVE; if (serverSocketV4 != null || serverSocketV6 != null) return ACTIVE;
return INACTIVE;
} }
synchronized int getReasonsDisabled() { synchronized int getReasonsDisabled() {

View File

@@ -43,10 +43,11 @@ class WanTcpPlugin extends TcpPlugin {
} }
@Override @Override
protected List<InetSocketAddress> getLocalSocketAddresses() { protected List<InetSocketAddress> getLocalSocketAddresses(boolean ipv4) {
if (!ipv4) return emptyList();
// Use the same address and port as last time if available // Use the same address and port as last time if available
TransportProperties p = callback.getLocalProperties(); TransportProperties p = callback.getLocalProperties();
InetSocketAddress old = parseSocketAddress(p.get(PROP_IP_PORT)); InetSocketAddress old = parseIpv4SocketAddress(p.get(PROP_IP_PORT));
List<InetSocketAddress> addrs = new LinkedList<>(); List<InetSocketAddress> addrs = new LinkedList<>();
for (InetAddress a : getLocalInetAddresses()) { for (InetAddress a : getLocalInetAddresses()) {
if (isAcceptableAddress(a)) { if (isAcceptableAddress(a)) {
@@ -76,14 +77,11 @@ class WanTcpPlugin extends TcpPlugin {
return ipv4 && !loop && !link && !site; return ipv4 && !loop && !link && !site;
} }
private int chooseEphemeralPort() {
return 32768 + (int) (Math.random() * 32768);
}
@Override @Override
protected List<InetSocketAddress> getRemoteSocketAddresses( protected List<InetSocketAddress> getRemoteSocketAddresses(
TransportProperties p) { TransportProperties p, boolean ipv4) {
InetSocketAddress parsed = parseSocketAddress(p.get(PROP_IP_PORT)); if (!ipv4) return emptyList();
InetSocketAddress parsed = parseIpv4SocketAddress(p.get(PROP_IP_PORT));
if (parsed == null) return emptyList(); if (parsed == null) return emptyList();
return singletonList(parsed); return singletonList(parsed);
} }
@@ -96,7 +94,8 @@ class WanTcpPlugin extends TcpPlugin {
} }
@Override @Override
protected void setLocalSocketAddress(InetSocketAddress a) { protected void setLocalSocketAddress(InetSocketAddress a, boolean ipv4) {
if (!ipv4) throw new AssertionError();
if (mappingResult != null && mappingResult.isUsable()) { if (mappingResult != null && mappingResult.isUsable()) {
// Advertise the external address to contacts // Advertise the external address to contacts
if (a.equals(mappingResult.getInternal())) { if (a.equals(mappingResult.getInternal())) {

View File

@@ -21,6 +21,8 @@ import org.briarproject.briar.android.logging.BriefLogFormatter;
import java.io.File; import java.io.File;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
@@ -185,12 +187,18 @@ public class BriarReportPrimer implements ReportPrimer {
WifiInfo wifiInfo = wm.getConnectionInfo(); WifiInfo wifiInfo = wm.getConnectionInfo();
if (wifiInfo != null) { if (wifiInfo != null) {
int ip = wifiInfo.getIpAddress(); // Nice API, Google int ip = wifiInfo.getIpAddress(); // Nice API, Google
int ip1 = ip & 0xFF; byte[] ipBytes = new byte[4];
int ip2 = (ip >> 8) & 0xFF; ipBytes[0] = (byte) (ip & 0xFF);
int ip3 = (ip >> 16) & 0xFF; ipBytes[1] = (byte) ((ip >> 8) & 0xFF);
int ip4 = (ip >> 24) & 0xFF; ipBytes[2] = (byte) ((ip >> 16) & 0xFF);
String address = ip1 + "." + ip2 + "." + ip3 + "." + ip4; ipBytes[3] = (byte) ((ip >> 24) & 0xFF);
customData.put("Wi-Fi address", scrubInetAddress(address)); try {
InetAddress address = InetAddress.getByAddress(ipBytes);
customData.put("Wi-Fi address",
scrubInetAddress(address));
} catch (UnknownHostException ignored) {
// Should only be thrown if address has illegal length
}
} }
} }