Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>master
@@ -1,5 +1,6 @@ | |||
apply plugin: 'com.android.application' | |||
apply plugin: 'kotlin-android' | |||
apply plugin: 'kotlin-kapt' | |||
version wireguardVersionName | |||
group groupName | |||
@@ -83,7 +83,7 @@ class TunnelEditorFragment : BaseFragment(), AppExclusionListener { | |||
override fun onExcludedAppsSelected(excludedApps: List<String>) { | |||
requireNotNull(binding) { "Tried to set excluded apps while no view was loaded" } | |||
binding!!.config!!.getInterface().excludedApplications.apply { | |||
binding!!.config!!.`interface`.excludedApplications.apply { | |||
clear() | |||
addAll(excludedApps) | |||
} | |||
@@ -125,12 +125,12 @@ class TunnelEditorFragment : BaseFragment(), AppExclusionListener { | |||
tunnel == null -> { | |||
Log.d(TAG, "Attempting to create new tunnel " + binding!!.name) | |||
val manager = Application.getTunnelManager() | |||
manager.create(binding!!.name, newConfig) | |||
manager.create(binding!!.name!!, newConfig) | |||
.whenComplete(this::onTunnelCreated) | |||
} | |||
tunnel!!.name != binding!!.name -> { | |||
Log.d(TAG, "Attempting to rename tunnel to " + binding!!.name) | |||
tunnel!!.setName(binding!!.name) | |||
tunnel!!.setName(binding!!.name!!) | |||
.whenComplete { _, t -> onTunnelRenamed(tunnel!!, newConfig, t) } | |||
} | |||
else -> { | |||
@@ -147,7 +147,7 @@ class TunnelEditorFragment : BaseFragment(), AppExclusionListener { | |||
@Suppress("UNUSED_PARAMETER") | |||
fun onRequestSetExcludedApplications(view: View?) { | |||
if (binding != null) { | |||
val excludedApps = ArrayList(binding!!.config!!.getInterface().excludedApplications) | |||
val excludedApps = ArrayList(binding!!.config!!.`interface`.excludedApplications) | |||
val fragment = AppListDialogFragment.newInstance(excludedApps, this) | |||
fragment.show(parentFragmentManager, null) | |||
} | |||
@@ -1,96 +0,0 @@ | |||
/* | |||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. | |||
* SPDX-License-Identifier: Apache-2.0 | |||
*/ | |||
package com.wireguard.android.viewmodel; | |||
import android.os.Parcel; | |||
import android.os.Parcelable; | |||
import com.wireguard.config.BadConfigException; | |||
import com.wireguard.config.Config; | |||
import com.wireguard.config.Peer; | |||
import com.wireguard.util.NonNullForAll; | |||
import java.util.ArrayList; | |||
import java.util.Collection; | |||
import androidx.databinding.ObservableArrayList; | |||
import androidx.databinding.ObservableList; | |||
@NonNullForAll | |||
public class ConfigProxy implements Parcelable { | |||
public static final Parcelable.Creator<ConfigProxy> CREATOR = new ConfigProxyCreator(); | |||
private final InterfaceProxy interfaze; | |||
private final ObservableList<PeerProxy> peers = new ObservableArrayList<>(); | |||
private ConfigProxy(final Parcel in) { | |||
interfaze = in.readParcelable(InterfaceProxy.class.getClassLoader()); | |||
in.readTypedList(peers, PeerProxy.CREATOR); | |||
for (final PeerProxy proxy : peers) | |||
proxy.bind(this); | |||
} | |||
public ConfigProxy(final Config other) { | |||
interfaze = new InterfaceProxy(other.getInterface()); | |||
for (final Peer peer : other.getPeers()) { | |||
final PeerProxy proxy = new PeerProxy(peer); | |||
peers.add(proxy); | |||
proxy.bind(this); | |||
} | |||
} | |||
public ConfigProxy() { | |||
interfaze = new InterfaceProxy(); | |||
} | |||
public PeerProxy addPeer() { | |||
final PeerProxy proxy = new PeerProxy(); | |||
peers.add(proxy); | |||
proxy.bind(this); | |||
return proxy; | |||
} | |||
@Override | |||
public int describeContents() { | |||
return 0; | |||
} | |||
public InterfaceProxy getInterface() { | |||
return interfaze; | |||
} | |||
public ObservableList<PeerProxy> getPeers() { | |||
return peers; | |||
} | |||
public Config resolve() throws BadConfigException { | |||
final Collection<Peer> resolvedPeers = new ArrayList<>(); | |||
for (final PeerProxy proxy : peers) | |||
resolvedPeers.add(proxy.resolve()); | |||
return new Config.Builder() | |||
.setInterface(interfaze.resolve()) | |||
.addPeers(resolvedPeers) | |||
.build(); | |||
} | |||
@Override | |||
public void writeToParcel(final Parcel dest, final int flags) { | |||
dest.writeParcelable(interfaze, flags); | |||
dest.writeTypedList(peers); | |||
} | |||
private static class ConfigProxyCreator implements Parcelable.Creator<ConfigProxy> { | |||
@Override | |||
public ConfigProxy createFromParcel(final Parcel in) { | |||
return new ConfigProxy(in); | |||
} | |||
@Override | |||
public ConfigProxy[] newArray(final int size) { | |||
return new ConfigProxy[size]; | |||
} | |||
} | |||
} |
@@ -0,0 +1,77 @@ | |||
/* | |||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. | |||
* SPDX-License-Identifier: Apache-2.0 | |||
*/ | |||
package com.wireguard.android.viewmodel | |||
import android.os.Parcel | |||
import android.os.Parcelable | |||
import androidx.databinding.ObservableArrayList | |||
import androidx.databinding.ObservableList | |||
import com.wireguard.config.BadConfigException | |||
import com.wireguard.config.Config | |||
import com.wireguard.config.Peer | |||
import java.util.ArrayList | |||
class ConfigProxy : Parcelable { | |||
val `interface`: InterfaceProxy | |||
val peers: ObservableList<PeerProxy> = ObservableArrayList() | |||
private constructor(parcel: Parcel) { | |||
`interface` = parcel.readParcelable(InterfaceProxy::class.java.classLoader)!! | |||
parcel.readTypedList(peers, PeerProxy.CREATOR) | |||
peers.forEach { it.bind(this) } | |||
} | |||
constructor(other: Config) { | |||
`interface` = InterfaceProxy(other.getInterface()) | |||
other.peers.forEach { | |||
val proxy = PeerProxy(it) | |||
peers.add(proxy) | |||
proxy.bind(this) | |||
} | |||
} | |||
constructor() { | |||
`interface` = InterfaceProxy() | |||
} | |||
fun addPeer(): PeerProxy { | |||
val proxy = PeerProxy() | |||
peers.add(proxy) | |||
proxy.bind(this) | |||
return proxy | |||
} | |||
override fun describeContents() = 0 | |||
@Throws(BadConfigException::class) | |||
fun resolve(): Config { | |||
val resolvedPeers: MutableCollection<Peer> = ArrayList() | |||
peers.forEach { resolvedPeers.add(it.resolve()) } | |||
return Config.Builder() | |||
.setInterface(`interface`.resolve()) | |||
.addPeers(resolvedPeers) | |||
.build() | |||
} | |||
override fun writeToParcel(dest: Parcel, flags: Int) { | |||
dest.writeParcelable(`interface`, flags) | |||
dest.writeTypedList(peers) | |||
} | |||
private class ConfigProxyCreator : Parcelable.Creator<ConfigProxy> { | |||
override fun createFromParcel(parcel: Parcel): ConfigProxy { | |||
return ConfigProxy(parcel) | |||
} | |||
override fun newArray(size: Int): Array<ConfigProxy?> { | |||
return arrayOfNulls(size) | |||
} | |||
} | |||
companion object { | |||
@JvmField | |||
val CREATOR: Parcelable.Creator<ConfigProxy> = ConfigProxyCreator() | |||
} | |||
} |
@@ -1,192 +0,0 @@ | |||
/* | |||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. | |||
* SPDX-License-Identifier: Apache-2.0 | |||
*/ | |||
package com.wireguard.android.viewmodel; | |||
import android.os.Parcel; | |||
import android.os.Parcelable; | |||
import com.wireguard.android.BR; | |||
import com.wireguard.config.Attribute; | |||
import com.wireguard.config.BadConfigException; | |||
import com.wireguard.config.Interface; | |||
import com.wireguard.crypto.Key; | |||
import com.wireguard.crypto.KeyFormatException; | |||
import com.wireguard.crypto.KeyPair; | |||
import com.wireguard.util.NonNullForAll; | |||
import java.net.InetAddress; | |||
import java.util.List; | |||
import androidx.databinding.BaseObservable; | |||
import androidx.databinding.Bindable; | |||
import androidx.databinding.ObservableArrayList; | |||
import androidx.databinding.ObservableList; | |||
import java9.util.stream.Collectors; | |||
import java9.util.stream.StreamSupport; | |||
@NonNullForAll | |||
public class InterfaceProxy extends BaseObservable implements Parcelable { | |||
public static final Parcelable.Creator<InterfaceProxy> CREATOR = new InterfaceProxyCreator(); | |||
private final ObservableList<String> excludedApplications = new ObservableArrayList<>(); | |||
private String addresses; | |||
private String dnsServers; | |||
private String listenPort; | |||
private String mtu; | |||
private String privateKey; | |||
private String publicKey; | |||
private InterfaceProxy(final Parcel in) { | |||
addresses = in.readString(); | |||
dnsServers = in.readString(); | |||
in.readStringList(excludedApplications); | |||
listenPort = in.readString(); | |||
mtu = in.readString(); | |||
privateKey = in.readString(); | |||
publicKey = in.readString(); | |||
} | |||
public InterfaceProxy(final Interface other) { | |||
addresses = Attribute.join(other.getAddresses()); | |||
final List<String> dnsServerStrings = StreamSupport.stream(other.getDnsServers()) | |||
.map(InetAddress::getHostAddress) | |||
.collect(Collectors.toUnmodifiableList()); | |||
dnsServers = Attribute.join(dnsServerStrings); | |||
excludedApplications.addAll(other.getExcludedApplications()); | |||
listenPort = other.getListenPort().map(String::valueOf).orElse(""); | |||
mtu = other.getMtu().map(String::valueOf).orElse(""); | |||
final KeyPair keyPair = other.getKeyPair(); | |||
privateKey = keyPair.getPrivateKey().toBase64(); | |||
publicKey = keyPair.getPublicKey().toBase64(); | |||
} | |||
public InterfaceProxy() { | |||
addresses = ""; | |||
dnsServers = ""; | |||
listenPort = ""; | |||
mtu = ""; | |||
privateKey = ""; | |||
publicKey = ""; | |||
} | |||
@Override | |||
public int describeContents() { | |||
return 0; | |||
} | |||
public void generateKeyPair() { | |||
final KeyPair keyPair = new KeyPair(); | |||
privateKey = keyPair.getPrivateKey().toBase64(); | |||
publicKey = keyPair.getPublicKey().toBase64(); | |||
notifyPropertyChanged(BR.privateKey); | |||
notifyPropertyChanged(BR.publicKey); | |||
} | |||
@Bindable | |||
public String getAddresses() { | |||
return addresses; | |||
} | |||
@Bindable | |||
public String getDnsServers() { | |||
return dnsServers; | |||
} | |||
public ObservableList<String> getExcludedApplications() { | |||
return excludedApplications; | |||
} | |||
@Bindable | |||
public String getListenPort() { | |||
return listenPort; | |||
} | |||
@Bindable | |||
public String getMtu() { | |||
return mtu; | |||
} | |||
@Bindable | |||
public String getPrivateKey() { | |||
return privateKey; | |||
} | |||
@Bindable | |||
public String getPublicKey() { | |||
return publicKey; | |||
} | |||
public Interface resolve() throws BadConfigException { | |||
final Interface.Builder builder = new Interface.Builder(); | |||
if (!addresses.isEmpty()) | |||
builder.parseAddresses(addresses); | |||
if (!dnsServers.isEmpty()) | |||
builder.parseDnsServers(dnsServers); | |||
if (!excludedApplications.isEmpty()) | |||
builder.excludeApplications(excludedApplications); | |||
if (!listenPort.isEmpty()) | |||
builder.parseListenPort(listenPort); | |||
if (!mtu.isEmpty()) | |||
builder.parseMtu(mtu); | |||
if (!privateKey.isEmpty()) | |||
builder.parsePrivateKey(privateKey); | |||
return builder.build(); | |||
} | |||
public void setAddresses(final String addresses) { | |||
this.addresses = addresses; | |||
notifyPropertyChanged(BR.addresses); | |||
} | |||
public void setDnsServers(final String dnsServers) { | |||
this.dnsServers = dnsServers; | |||
notifyPropertyChanged(BR.dnsServers); | |||
} | |||
public void setListenPort(final String listenPort) { | |||
this.listenPort = listenPort; | |||
notifyPropertyChanged(BR.listenPort); | |||
} | |||
public void setMtu(final String mtu) { | |||
this.mtu = mtu; | |||
notifyPropertyChanged(BR.mtu); | |||
} | |||
public void setPrivateKey(final String privateKey) { | |||
this.privateKey = privateKey; | |||
try { | |||
publicKey = new KeyPair(Key.fromBase64(privateKey)).getPublicKey().toBase64(); | |||
} catch (final KeyFormatException ignored) { | |||
publicKey = ""; | |||
} | |||
notifyPropertyChanged(BR.privateKey); | |||
notifyPropertyChanged(BR.publicKey); | |||
} | |||
@Override | |||
public void writeToParcel(final Parcel dest, final int flags) { | |||
dest.writeString(addresses); | |||
dest.writeString(dnsServers); | |||
dest.writeStringList(excludedApplications); | |||
dest.writeString(listenPort); | |||
dest.writeString(mtu); | |||
dest.writeString(privateKey); | |||
dest.writeString(publicKey); | |||
} | |||
private static class InterfaceProxyCreator implements Parcelable.Creator<InterfaceProxy> { | |||
@Override | |||
public InterfaceProxy createFromParcel(final Parcel in) { | |||
return new InterfaceProxy(in); | |||
} | |||
@Override | |||
public InterfaceProxy[] newArray(final int size) { | |||
return new InterfaceProxy[size]; | |||
} | |||
} | |||
} |
@@ -0,0 +1,135 @@ | |||
/* | |||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. | |||
* SPDX-License-Identifier: Apache-2.0 | |||
*/ | |||
package com.wireguard.android.viewmodel | |||
import android.os.Parcel | |||
import android.os.Parcelable | |||
import androidx.databinding.BaseObservable | |||
import androidx.databinding.Bindable | |||
import androidx.databinding.ObservableArrayList | |||
import androidx.databinding.ObservableList | |||
import com.wireguard.android.BR | |||
import com.wireguard.config.Attribute | |||
import com.wireguard.config.BadConfigException | |||
import com.wireguard.config.Interface | |||
import com.wireguard.crypto.Key | |||
import com.wireguard.crypto.KeyFormatException | |||
import com.wireguard.crypto.KeyPair | |||
class InterfaceProxy : BaseObservable, Parcelable { | |||
@get:Bindable | |||
val excludedApplications: ObservableList<String> = ObservableArrayList() | |||
@get:Bindable | |||
var addresses: String = "" | |||
set(value) { | |||
field = value | |||
notifyPropertyChanged(BR.addresses) | |||
} | |||
@get:Bindable | |||
var dnsServers: String = "" | |||
set(value) { | |||
field = value | |||
notifyPropertyChanged(BR.dnsServers) | |||
} | |||
@get:Bindable | |||
var listenPort: String = "" | |||
set(value) { | |||
field = value | |||
notifyPropertyChanged(BR.listenPort) | |||
} | |||
@get:Bindable | |||
var mtu: String = "" | |||
set(value) { | |||
field = value | |||
notifyPropertyChanged(BR.mtu) | |||
} | |||
@get:Bindable | |||
var privateKey: String = "" | |||
set(value) { | |||
field = value | |||
notifyPropertyChanged(BR.privateKey) | |||
notifyPropertyChanged(BR.publicKey) | |||
} | |||
@get:Bindable | |||
val publicKey: String | |||
get() = try { | |||
KeyPair(Key.fromBase64(privateKey)).publicKey.toBase64() | |||
} catch (ignored: KeyFormatException) { | |||
"" | |||
} | |||
private constructor(parcel: Parcel) { | |||
addresses = parcel.readString() ?: "" | |||
dnsServers = parcel.readString() ?: "" | |||
parcel.readStringList(excludedApplications) | |||
listenPort = parcel.readString() ?: "" | |||
mtu = parcel.readString() ?: "" | |||
privateKey = parcel.readString() ?: "" | |||
} | |||
constructor(other: Interface) { | |||
addresses = Attribute.join(other.addresses) | |||
val dnsServerStrings = other.dnsServers.map { it.hostAddress } | |||
dnsServers = Attribute.join(dnsServerStrings) | |||
excludedApplications.addAll(other.excludedApplications) | |||
listenPort = other.listenPort.map { it.toString() }.orElse("") | |||
mtu = other.mtu.map { it.toString() }.orElse("") | |||
val keyPair = other.keyPair | |||
privateKey = keyPair.privateKey.toBase64() | |||
} | |||
constructor() | |||
override fun describeContents() = 0 | |||
fun generateKeyPair() { | |||
val keyPair = KeyPair() | |||
privateKey = keyPair.privateKey.toBase64() | |||
notifyPropertyChanged(BR.privateKey) | |||
notifyPropertyChanged(BR.publicKey) | |||
} | |||
@Throws(BadConfigException::class) | |||
fun resolve(): Interface { | |||
val builder = Interface.Builder() | |||
if (addresses.isNotEmpty()) builder.parseAddresses(addresses) | |||
if (dnsServers.isNotEmpty()) builder.parseDnsServers(dnsServers) | |||
if (excludedApplications.isNotEmpty()) builder.excludeApplications(excludedApplications) | |||
if (listenPort.isNotEmpty()) builder.parseListenPort(listenPort) | |||
if (mtu.isNotEmpty()) builder.parseMtu(mtu) | |||
if (privateKey.isNotEmpty()) builder.parsePrivateKey(privateKey) | |||
return builder.build() | |||
} | |||
override fun writeToParcel(dest: Parcel, flags: Int) { | |||
dest.writeString(addresses) | |||
dest.writeString(dnsServers) | |||
dest.writeStringList(excludedApplications) | |||
dest.writeString(listenPort) | |||
dest.writeString(mtu) | |||
dest.writeString(privateKey) | |||
} | |||
private class InterfaceProxyCreator : Parcelable.Creator<InterfaceProxy> { | |||
override fun createFromParcel(parcel: Parcel): InterfaceProxy { | |||
return InterfaceProxy(parcel) | |||
} | |||
override fun newArray(size: Int): Array<InterfaceProxy?> { | |||
return arrayOfNulls(size) | |||
} | |||
} | |||
companion object { | |||
@JvmField | |||
val CREATOR: Parcelable.Creator<InterfaceProxy> = InterfaceProxyCreator() | |||
} | |||
} |
@@ -1,382 +0,0 @@ | |||
/* | |||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. | |||
* SPDX-License-Identifier: Apache-2.0 | |||
*/ | |||
package com.wireguard.android.viewmodel; | |||
import android.os.Parcel; | |||
import android.os.Parcelable; | |||
import com.wireguard.android.BR; | |||
import com.wireguard.config.Attribute; | |||
import com.wireguard.config.BadConfigException; | |||
import com.wireguard.config.InetEndpoint; | |||
import com.wireguard.config.Peer; | |||
import com.wireguard.crypto.Key; | |||
import com.wireguard.util.NonNullForAll; | |||
import java.lang.ref.WeakReference; | |||
import java.util.ArrayList; | |||
import java.util.Collection; | |||
import java.util.LinkedHashSet; | |||
import java.util.List; | |||
import java.util.Set; | |||
import androidx.annotation.Nullable; | |||
import androidx.databinding.BaseObservable; | |||
import androidx.databinding.Bindable; | |||
import androidx.databinding.Observable; | |||
import androidx.databinding.ObservableList; | |||
import java9.util.Lists; | |||
import java9.util.Sets; | |||
import java9.util.stream.Collectors; | |||
import java9.util.stream.Stream; | |||
@NonNullForAll | |||
public class PeerProxy extends BaseObservable implements Parcelable { | |||
public static final Parcelable.Creator<PeerProxy> CREATOR = new PeerProxyCreator(); | |||
private static final Set<String> IPV4_PUBLIC_NETWORKS = new LinkedHashSet<>(Lists.of( | |||
"0.0.0.0/5", "8.0.0.0/7", "11.0.0.0/8", "12.0.0.0/6", "16.0.0.0/4", "32.0.0.0/3", | |||
"64.0.0.0/2", "128.0.0.0/3", "160.0.0.0/5", "168.0.0.0/6", "172.0.0.0/12", | |||
"172.32.0.0/11", "172.64.0.0/10", "172.128.0.0/9", "173.0.0.0/8", "174.0.0.0/7", | |||
"176.0.0.0/4", "192.0.0.0/9", "192.128.0.0/11", "192.160.0.0/13", "192.169.0.0/16", | |||
"192.170.0.0/15", "192.172.0.0/14", "192.176.0.0/12", "192.192.0.0/10", | |||
"193.0.0.0/8", "194.0.0.0/7", "196.0.0.0/6", "200.0.0.0/5", "208.0.0.0/4" | |||
)); | |||
private static final Set<String> IPV4_WILDCARD = Sets.of("0.0.0.0/0"); | |||
private final List<String> dnsRoutes = new ArrayList<>(); | |||
private String allowedIps; | |||
private AllowedIpsState allowedIpsState = AllowedIpsState.INVALID; | |||
private String endpoint; | |||
@Nullable private InterfaceDnsListener interfaceDnsListener; | |||
@Nullable private ConfigProxy owner; | |||
@Nullable private PeerListListener peerListListener; | |||
private String persistentKeepalive; | |||
private String preSharedKey; | |||
private String publicKey; | |||
private int totalPeers; | |||
private PeerProxy(final Parcel in) { | |||
allowedIps = in.readString(); | |||
endpoint = in.readString(); | |||
persistentKeepalive = in.readString(); | |||
preSharedKey = in.readString(); | |||
publicKey = in.readString(); | |||
} | |||
public PeerProxy(final Peer other) { | |||
allowedIps = Attribute.join(other.getAllowedIps()); | |||
endpoint = other.getEndpoint().map(InetEndpoint::toString).orElse(""); | |||
persistentKeepalive = other.getPersistentKeepalive().map(String::valueOf).orElse(""); | |||
preSharedKey = other.getPreSharedKey().map(Key::toBase64).orElse(""); | |||
publicKey = other.getPublicKey().toBase64(); | |||
} | |||
public PeerProxy() { | |||
allowedIps = ""; | |||
endpoint = ""; | |||
persistentKeepalive = ""; | |||
preSharedKey = ""; | |||
publicKey = ""; | |||
} | |||
public void bind(final ConfigProxy owner) { | |||
final InterfaceProxy interfaze = owner.getInterface(); | |||
final ObservableList<PeerProxy> peers = owner.getPeers(); | |||
if (interfaceDnsListener == null) | |||
interfaceDnsListener = new InterfaceDnsListener(this); | |||
interfaze.addOnPropertyChangedCallback(interfaceDnsListener); | |||
setInterfaceDns(interfaze.getDnsServers()); | |||
if (peerListListener == null) | |||
peerListListener = new PeerListListener(this); | |||
peers.addOnListChangedCallback(peerListListener); | |||
setTotalPeers(peers.size()); | |||
this.owner = owner; | |||
} | |||
private void calculateAllowedIpsState() { | |||
final AllowedIpsState newState; | |||
if (totalPeers == 1) { | |||
// String comparison works because we only care if allowedIps is a superset of one of | |||
// the above sets of (valid) *networks*. We are not checking for a superset based on | |||
// the individual addresses in each set. | |||
final Collection<String> networkStrings = getAllowedIpsSet(); | |||
// If allowedIps contains both the wildcard and the public networks, then private | |||
// networks aren't excluded! | |||
if (networkStrings.containsAll(IPV4_WILDCARD)) | |||
newState = AllowedIpsState.CONTAINS_IPV4_WILDCARD; | |||
else if (networkStrings.containsAll(IPV4_PUBLIC_NETWORKS)) | |||
newState = AllowedIpsState.CONTAINS_IPV4_PUBLIC_NETWORKS; | |||
else | |||
newState = AllowedIpsState.OTHER; | |||
} else { | |||
newState = AllowedIpsState.INVALID; | |||
} | |||
if (newState != allowedIpsState) { | |||
allowedIpsState = newState; | |||
notifyPropertyChanged(BR.ableToExcludePrivateIps); | |||
notifyPropertyChanged(BR.excludingPrivateIps); | |||
} | |||
} | |||
@Override | |||
public int describeContents() { | |||
return 0; | |||
} | |||
@Bindable | |||
public String getAllowedIps() { | |||
return allowedIps; | |||
} | |||
private Set<String> getAllowedIpsSet() { | |||
return new LinkedHashSet<>(Lists.of(Attribute.split(allowedIps))); | |||
} | |||
@Bindable | |||
public String getEndpoint() { | |||
return endpoint; | |||
} | |||
@Bindable | |||
public String getPersistentKeepalive() { | |||
return persistentKeepalive; | |||
} | |||
@Bindable | |||
public String getPreSharedKey() { | |||
return preSharedKey; | |||
} | |||
@Bindable | |||
public String getPublicKey() { | |||
return publicKey; | |||
} | |||
@Bindable | |||
public boolean isAbleToExcludePrivateIps() { | |||
return allowedIpsState == AllowedIpsState.CONTAINS_IPV4_PUBLIC_NETWORKS | |||
|| allowedIpsState == AllowedIpsState.CONTAINS_IPV4_WILDCARD; | |||
} | |||
@Bindable | |||
public boolean isExcludingPrivateIps() { | |||
return allowedIpsState == AllowedIpsState.CONTAINS_IPV4_PUBLIC_NETWORKS; | |||
} | |||
public Peer resolve() throws BadConfigException { | |||
final Peer.Builder builder = new Peer.Builder(); | |||
if (!allowedIps.isEmpty()) | |||
builder.parseAllowedIPs(allowedIps); | |||
if (!endpoint.isEmpty()) | |||
builder.parseEndpoint(endpoint); | |||
if (!persistentKeepalive.isEmpty()) | |||
builder.parsePersistentKeepalive(persistentKeepalive); | |||
if (!preSharedKey.isEmpty()) | |||
builder.parsePreSharedKey(preSharedKey); | |||
if (!publicKey.isEmpty()) | |||
builder.parsePublicKey(publicKey); | |||
return builder.build(); | |||
} | |||
public void setAllowedIps(final String allowedIps) { | |||
this.allowedIps = allowedIps; | |||
notifyPropertyChanged(BR.allowedIps); | |||
calculateAllowedIpsState(); | |||
} | |||
public void setEndpoint(final String endpoint) { | |||
this.endpoint = endpoint; | |||
notifyPropertyChanged(BR.endpoint); | |||
} | |||
public void setExcludingPrivateIps(final boolean excludingPrivateIps) { | |||
if (!isAbleToExcludePrivateIps() || isExcludingPrivateIps() == excludingPrivateIps) | |||
return; | |||
final Set<String> oldNetworks = excludingPrivateIps ? IPV4_WILDCARD : IPV4_PUBLIC_NETWORKS; | |||
final Set<String> newNetworks = excludingPrivateIps ? IPV4_PUBLIC_NETWORKS : IPV4_WILDCARD; | |||
final Collection<String> input = getAllowedIpsSet(); | |||
final int outputSize = input.size() - oldNetworks.size() + newNetworks.size(); | |||
final Collection<String> output = new LinkedHashSet<>(outputSize); | |||
boolean replaced = false; | |||
// Replace the first instance of the wildcard with the public network list, or vice versa. | |||
for (final String network : input) { | |||
if (oldNetworks.contains(network)) { | |||
if (!replaced) { | |||
for (final String replacement : newNetworks) | |||
if (!output.contains(replacement)) | |||
output.add(replacement); | |||
replaced = true; | |||
} | |||
} else if (!output.contains(network)) { | |||
output.add(network); | |||
} | |||
} | |||
// DNS servers only need to handled specially when we're excluding private IPs. | |||
if (excludingPrivateIps) | |||
output.addAll(dnsRoutes); | |||
else | |||
output.removeAll(dnsRoutes); | |||
allowedIps = Attribute.join(output); | |||
allowedIpsState = excludingPrivateIps ? | |||
AllowedIpsState.CONTAINS_IPV4_PUBLIC_NETWORKS : AllowedIpsState.CONTAINS_IPV4_WILDCARD; | |||
notifyPropertyChanged(BR.allowedIps); | |||
notifyPropertyChanged(BR.excludingPrivateIps); | |||
} | |||
private void setInterfaceDns(final CharSequence dnsServers) { | |||
final List<String> newDnsRoutes = Stream.of(Attribute.split(dnsServers)) | |||
.filter(server -> !server.contains(":")) | |||
.map(server -> server + "/32") | |||
.collect(Collectors.toUnmodifiableList()); | |||
if (allowedIpsState == AllowedIpsState.CONTAINS_IPV4_PUBLIC_NETWORKS) { | |||
final Collection<String> input = getAllowedIpsSet(); | |||
final Collection<String> output = new LinkedHashSet<>(input.size() + 1); | |||
// Yes, this is quadratic in the number of DNS servers, but most users have 1 or 2. | |||
for (final String network : input) | |||
if (!dnsRoutes.contains(network) || newDnsRoutes.contains(network)) | |||
output.add(network); | |||
// Since output is a Set, this does the Right Thing™ (it does not duplicate networks). | |||
output.addAll(newDnsRoutes); | |||
// None of the public networks are /32s, so this cannot change the AllowedIPs state. | |||
allowedIps = Attribute.join(output); | |||
notifyPropertyChanged(BR.allowedIps); | |||
} | |||
dnsRoutes.clear(); | |||
dnsRoutes.addAll(newDnsRoutes); | |||
} | |||
public void setPersistentKeepalive(final String persistentKeepalive) { | |||
this.persistentKeepalive = persistentKeepalive; | |||
notifyPropertyChanged(BR.persistentKeepalive); | |||
} | |||
public void setPreSharedKey(final String preSharedKey) { | |||
this.preSharedKey = preSharedKey; | |||
notifyPropertyChanged(BR.preSharedKey); | |||
} | |||
public void setPublicKey(final String publicKey) { | |||
this.publicKey = publicKey; | |||
notifyPropertyChanged(BR.publicKey); | |||
} | |||
private void setTotalPeers(final int totalPeers) { | |||
if (this.totalPeers == totalPeers) | |||
return; | |||
this.totalPeers = totalPeers; | |||
calculateAllowedIpsState(); | |||
} | |||
public void unbind() { | |||
if (owner == null) | |||
return; | |||
final InterfaceProxy interfaze = owner.getInterface(); | |||
final ObservableList<PeerProxy> peers = owner.getPeers(); | |||
if (interfaceDnsListener != null) | |||
interfaze.removeOnPropertyChangedCallback(interfaceDnsListener); | |||
if (peerListListener != null) | |||
peers.removeOnListChangedCallback(peerListListener); | |||
peers.remove(this); | |||
setInterfaceDns(""); | |||
setTotalPeers(0); | |||
owner = null; | |||
} | |||
@Override | |||
public void writeToParcel(final Parcel dest, final int flags) { | |||
dest.writeString(allowedIps); | |||
dest.writeString(endpoint); | |||
dest.writeString(persistentKeepalive); | |||
dest.writeString(preSharedKey); | |||
dest.writeString(publicKey); | |||
} | |||
private enum AllowedIpsState { | |||
CONTAINS_IPV4_PUBLIC_NETWORKS, | |||
CONTAINS_IPV4_WILDCARD, | |||
INVALID, | |||
OTHER | |||
} | |||
private static final class InterfaceDnsListener extends Observable.OnPropertyChangedCallback { | |||
private final WeakReference<PeerProxy> weakPeerProxy; | |||
private InterfaceDnsListener(final PeerProxy peerProxy) { | |||
weakPeerProxy = new WeakReference<>(peerProxy); | |||
} | |||
@Override | |||
public void onPropertyChanged(final Observable sender, final int propertyId) { | |||
@Nullable final PeerProxy peerProxy = weakPeerProxy.get(); | |||
if (peerProxy == null) { | |||
sender.removeOnPropertyChangedCallback(this); | |||
return; | |||
} | |||
// This shouldn't be possible, but try to avoid a ClassCastException anyway. | |||
if (!(sender instanceof InterfaceProxy)) | |||
return; | |||
if (!(propertyId == BR._all || propertyId == BR.dnsServers)) | |||
return; | |||
peerProxy.setInterfaceDns(((InterfaceProxy) sender).getDnsServers()); | |||
} | |||
} | |||
private static final class PeerListListener | |||
extends ObservableList.OnListChangedCallback<ObservableList<PeerProxy>> { | |||
private final WeakReference<PeerProxy> weakPeerProxy; | |||
private PeerListListener(final PeerProxy peerProxy) { | |||
weakPeerProxy = new WeakReference<>(peerProxy); | |||
} | |||
@Override | |||
public void onChanged(final ObservableList<PeerProxy> sender) { | |||
@Nullable final PeerProxy peerProxy = weakPeerProxy.get(); | |||
if (peerProxy == null) { | |||
sender.removeOnListChangedCallback(this); | |||
return; | |||
} | |||
peerProxy.setTotalPeers(sender.size()); | |||
} | |||
@Override | |||
public void onItemRangeChanged(final ObservableList<PeerProxy> sender, | |||
final int positionStart, final int itemCount) { | |||
// Do nothing. | |||
} | |||
@Override | |||
public void onItemRangeInserted(final ObservableList<PeerProxy> sender, | |||
final int positionStart, final int itemCount) { | |||
onChanged(sender); | |||
} | |||
@Override | |||
public void onItemRangeMoved(final ObservableList<PeerProxy> sender, | |||
final int fromPosition, final int toPosition, | |||
final int itemCount) { | |||
// Do nothing. | |||
} | |||
@Override | |||
public void onItemRangeRemoved(final ObservableList<PeerProxy> sender, | |||
final int positionStart, final int itemCount) { | |||
onChanged(sender); | |||
} | |||
} | |||
private static class PeerProxyCreator implements Parcelable.Creator<PeerProxy> { | |||
@Override | |||
public PeerProxy createFromParcel(final Parcel in) { | |||
return new PeerProxy(in); | |||
} | |||
@Override | |||
public PeerProxy[] newArray(final int size) { | |||
return new PeerProxy[size]; | |||
} | |||
} | |||
} |
@@ -0,0 +1,288 @@ | |||
/* | |||
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. | |||
* SPDX-License-Identifier: Apache-2.0 | |||
*/ | |||
package com.wireguard.android.viewmodel | |||
import android.os.Parcel | |||
import android.os.Parcelable | |||
import androidx.databinding.BaseObservable | |||
import androidx.databinding.Bindable | |||
import androidx.databinding.Observable | |||
import androidx.databinding.Observable.OnPropertyChangedCallback | |||
import androidx.databinding.ObservableList | |||
import com.wireguard.android.BR | |||
import com.wireguard.config.Attribute | |||
import com.wireguard.config.BadConfigException | |||
import com.wireguard.config.Peer | |||
import java.lang.ref.WeakReference | |||
import java.util.ArrayList | |||
import java.util.LinkedHashSet | |||
class PeerProxy : BaseObservable, Parcelable { | |||
private val dnsRoutes: MutableList<String?> = ArrayList() | |||
private var allowedIpsState = AllowedIpsState.INVALID | |||
private var interfaceDnsListener: InterfaceDnsListener? = null | |||
private var peerListListener: PeerListListener? = null | |||
private var owner: ConfigProxy? = null | |||
private var totalPeers = 0 | |||
@get:Bindable | |||
var allowedIps: String = "" | |||
set(value) { | |||
field = value | |||
notifyPropertyChanged(BR.allowedIps) | |||
calculateAllowedIpsState() | |||
} | |||
@get:Bindable | |||
var endpoint: String = "" | |||
set(value) { | |||
field = value | |||
notifyPropertyChanged(BR.endpoint) | |||
} | |||
@get:Bindable | |||
var persistentKeepalive: String = "" | |||
set(value) { | |||
field = value | |||
notifyPropertyChanged(BR.persistentKeepalive) | |||
} | |||
@get:Bindable | |||
var preSharedKey: String = "" | |||
set(value) { | |||
field = value | |||
notifyPropertyChanged(BR.preSharedKey) | |||
} | |||
@get:Bindable | |||
var publicKey: String = "" | |||
set(value) { | |||
field = value | |||
notifyPropertyChanged(BR.publicKey) | |||
} | |||
@get:Bindable | |||
val isAbleToExcludePrivateIps: Boolean | |||
get() = allowedIpsState == AllowedIpsState.CONTAINS_IPV4_PUBLIC_NETWORKS || allowedIpsState == AllowedIpsState.CONTAINS_IPV4_WILDCARD | |||
@get:Bindable | |||
val isExcludingPrivateIps: Boolean | |||
get() = allowedIpsState == AllowedIpsState.CONTAINS_IPV4_PUBLIC_NETWORKS | |||
private constructor(parcel: Parcel) { | |||
allowedIps = parcel.readString() ?: "" | |||
endpoint = parcel.readString() ?: "" | |||
persistentKeepalive = parcel.readString() ?: "" | |||
preSharedKey = parcel.readString() ?: "" | |||
publicKey = parcel.readString() ?: "" | |||
} | |||
constructor(other: Peer) { | |||
allowedIps = Attribute.join(other.allowedIps) | |||
endpoint = other.endpoint.map { it.toString() }.orElse("") | |||
persistentKeepalive = other.persistentKeepalive.map { it.toString() }.orElse("") | |||
preSharedKey = other.preSharedKey.map { it.toBase64() }.orElse("") | |||
publicKey = other.publicKey.toBase64() | |||
} | |||
constructor() | |||
fun bind(owner: ConfigProxy) { | |||
val interfaze: InterfaceProxy = owner.`interface` | |||
val peers = owner.peers | |||
if (interfaceDnsListener == null) interfaceDnsListener = InterfaceDnsListener(this) | |||
interfaze.addOnPropertyChangedCallback(interfaceDnsListener!!) | |||
setInterfaceDns(interfaze.dnsServers) | |||
if (peerListListener == null) peerListListener = PeerListListener(this) | |||
peers.addOnListChangedCallback(peerListListener) | |||
setTotalPeers(peers.size) | |||
this.owner = owner | |||
} | |||
private fun calculateAllowedIpsState() { | |||
val newState: AllowedIpsState | |||
newState = if (totalPeers == 1) { | |||
// String comparison works because we only care if allowedIps is a superset of one of | |||
// the above sets of (valid) *networks*. We are not checking for a superset based on | |||
// the individual addresses in each set. | |||
val networkStrings: Collection<String> = getAllowedIpsSet() | |||
// If allowedIps contains both the wildcard and the public networks, then private | |||
// networks aren't excluded! | |||
if (networkStrings.containsAll(IPV4_WILDCARD)) | |||
AllowedIpsState.CONTAINS_IPV4_WILDCARD | |||
else if (networkStrings.containsAll(IPV4_PUBLIC_NETWORKS)) | |||
AllowedIpsState.CONTAINS_IPV4_PUBLIC_NETWORKS | |||
else | |||
AllowedIpsState.OTHER | |||
} else { | |||
AllowedIpsState.INVALID | |||
} | |||
if (newState != allowedIpsState) { | |||
allowedIpsState = newState | |||
notifyPropertyChanged(BR.ableToExcludePrivateIps) | |||
notifyPropertyChanged(BR.excludingPrivateIps) | |||
} | |||
} | |||
override fun describeContents() = 0 | |||
private fun getAllowedIpsSet() = setOf(*Attribute.split(allowedIps)) | |||
// Replace the first instance of the wildcard with the public network list, or vice versa. | |||
// DNS servers only need to handled specially when we're excluding private IPs. | |||
fun setExcludingPrivateIps(excludingPrivateIps: Boolean) { | |||
if (!isAbleToExcludePrivateIps || isExcludingPrivateIps == excludingPrivateIps) return | |||
val oldNetworks = if (excludingPrivateIps) IPV4_WILDCARD else IPV4_PUBLIC_NETWORKS | |||
val newNetworks = if (excludingPrivateIps) IPV4_PUBLIC_NETWORKS else IPV4_WILDCARD | |||
val input: Collection<String> = getAllowedIpsSet() | |||
val outputSize = input.size - oldNetworks.size + newNetworks.size | |||
val output: MutableCollection<String?> = LinkedHashSet(outputSize) | |||
var replaced = false | |||
// Replace the first instance of the wildcard with the public network list, or vice versa. | |||
for (network in input) { | |||
if (oldNetworks.contains(network)) { | |||
if (!replaced) { | |||
for (replacement in newNetworks) if (!output.contains(replacement)) output.add(replacement) | |||
replaced = true | |||
} | |||
} else if (!output.contains(network)) { | |||
output.add(network) | |||
} | |||
} | |||
// DNS servers only need to handled specially when we're excluding private IPs. | |||
if (excludingPrivateIps) output.addAll(dnsRoutes) else output.removeAll(dnsRoutes) | |||
allowedIps = Attribute.join(output) | |||
allowedIpsState = if (excludingPrivateIps) AllowedIpsState.CONTAINS_IPV4_PUBLIC_NETWORKS else AllowedIpsState.CONTAINS_IPV4_WILDCARD | |||
notifyPropertyChanged(BR.allowedIps) | |||
notifyPropertyChanged(BR.excludingPrivateIps) | |||
} | |||
@Throws(BadConfigException::class) | |||
fun resolve(): Peer { | |||
val builder = Peer.Builder() | |||
if (allowedIps.isNotEmpty()) builder.parseAllowedIPs(allowedIps) | |||
if (endpoint.isNotEmpty()) builder.parseEndpoint(endpoint) | |||
if (persistentKeepalive.isNotEmpty()) builder.parsePersistentKeepalive(persistentKeepalive) | |||
if (preSharedKey.isNotEmpty()) builder.parsePreSharedKey(preSharedKey) | |||
if (publicKey.isNotEmpty()) builder.parsePublicKey(publicKey) | |||
return builder.build() | |||
} | |||
private fun setInterfaceDns(dnsServers: CharSequence) { | |||
val newDnsRoutes = Attribute.split(dnsServers).filter { !it.contains(":") }.map { "$it/32" } | |||
if (allowedIpsState == AllowedIpsState.CONTAINS_IPV4_PUBLIC_NETWORKS) { | |||
val input = getAllowedIpsSet() | |||
// Yes, this is quadratic in the number of DNS servers, but most users have 1 or 2. | |||
val output = input.filter { !dnsRoutes.contains(it) || newDnsRoutes.contains(it) }.plus(newDnsRoutes).distinct() | |||
// None of the public networks are /32s, so this cannot change the AllowedIPs state. | |||
allowedIps = Attribute.join(output) | |||
notifyPropertyChanged(BR.allowedIps) | |||
} | |||
dnsRoutes.clear() | |||
dnsRoutes.addAll(newDnsRoutes) | |||
} | |||
private fun setTotalPeers(totalPeers: Int) { | |||
if (this.totalPeers == totalPeers) return | |||
this.totalPeers = totalPeers | |||
calculateAllowedIpsState() | |||
} | |||
fun unbind() { | |||
if (owner == null) return | |||
val interfaze: InterfaceProxy = owner!!.`interface` | |||
val peers = owner!!.peers | |||
if (interfaceDnsListener != null) interfaze.removeOnPropertyChangedCallback(interfaceDnsListener!!) | |||
if (peerListListener != null) peers.removeOnListChangedCallback(peerListListener) | |||
peers.remove(this) | |||
setInterfaceDns("") | |||
setTotalPeers(0) | |||
owner = null | |||
} | |||
override fun writeToParcel(dest: Parcel, flags: Int) { | |||
dest.writeString(allowedIps) | |||
dest.writeString(endpoint) | |||
dest.writeString(persistentKeepalive) | |||
dest.writeString(preSharedKey) | |||
dest.writeString(publicKey) | |||
} | |||
private enum class AllowedIpsState { | |||
CONTAINS_IPV4_PUBLIC_NETWORKS, CONTAINS_IPV4_WILDCARD, INVALID, OTHER | |||
} | |||
private class InterfaceDnsListener constructor(peerProxy: PeerProxy) : OnPropertyChangedCallback() { | |||
private val weakPeerProxy: WeakReference<PeerProxy> = WeakReference(peerProxy) | |||
override fun onPropertyChanged(sender: Observable, propertyId: Int) { | |||
val peerProxy = weakPeerProxy.get() | |||
if (peerProxy == null) { | |||
sender.removeOnPropertyChangedCallback(this) | |||
return | |||
} | |||
// This shouldn't be possible, but try to avoid a ClassCastException anyway. | |||
if (sender !is InterfaceProxy) return | |||
if (!(propertyId == BR._all || propertyId == BR.dnsServers)) return | |||
peerProxy.setInterfaceDns(sender.dnsServers) | |||
} | |||
} | |||
private class PeerListListener(peerProxy: PeerProxy) : ObservableList.OnListChangedCallback<ObservableList<PeerProxy?>>() { | |||
private val weakPeerProxy: WeakReference<PeerProxy> = WeakReference(peerProxy) | |||
override fun onChanged(sender: ObservableList<PeerProxy?>) { | |||
val peerProxy = weakPeerProxy.get() | |||
if (peerProxy == null) { | |||
sender.removeOnListChangedCallback(this) | |||
return | |||
} | |||
peerProxy.setTotalPeers(sender.size) | |||
} | |||
override fun onItemRangeChanged(sender: ObservableList<PeerProxy?>, | |||
positionStart: Int, itemCount: Int) { | |||
// Do nothing. | |||
} | |||
override fun onItemRangeInserted(sender: ObservableList<PeerProxy?>, | |||
positionStart: Int, itemCount: Int) { | |||
onChanged(sender) | |||
} | |||
override fun onItemRangeMoved(sender: ObservableList<PeerProxy?>, | |||
fromPosition: Int, toPosition: Int, | |||
itemCount: Int) { | |||
// Do nothing. | |||
} | |||
override fun onItemRangeRemoved(sender: ObservableList<PeerProxy?>, | |||
positionStart: Int, itemCount: Int) { | |||
onChanged(sender) | |||
} | |||
} | |||
private class PeerProxyCreator : Parcelable.Creator<PeerProxy> { | |||
override fun createFromParcel(parcel: Parcel): PeerProxy { | |||
return PeerProxy(parcel) | |||
} | |||
override fun newArray(size: Int): Array<PeerProxy?> { | |||
return arrayOfNulls(size) | |||
} | |||
} | |||
companion object { | |||
@JvmField | |||
val CREATOR: Parcelable.Creator<PeerProxy> = PeerProxyCreator() | |||
private val IPV4_PUBLIC_NETWORKS = setOf( | |||
"0.0.0.0/5", "8.0.0.0/7", "11.0.0.0/8", "12.0.0.0/6", "16.0.0.0/4", "32.0.0.0/3", | |||
"64.0.0.0/2", "128.0.0.0/3", "160.0.0.0/5", "168.0.0.0/6", "172.0.0.0/12", | |||
"172.32.0.0/11", "172.64.0.0/10", "172.128.0.0/9", "173.0.0.0/8", "174.0.0.0/7", | |||
"176.0.0.0/4", "192.0.0.0/9", "192.128.0.0/11", "192.160.0.0/13", "192.169.0.0/16", | |||
"192.170.0.0/15", "192.172.0.0/14", "192.176.0.0/12", "192.192.0.0/10", | |||
"193.0.0.0/8", "194.0.0.0/7", "196.0.0.0/6", "200.0.0.0/5", "208.0.0.0/4" | |||
) | |||
private val IPV4_WILDCARD = setOf("0.0.0.0/0") | |||
} | |||
} |