Compare commits

..

3 Commits

6 changed files with 96 additions and 42 deletions
+2 -2
View File
@@ -1,5 +1,5 @@
amneziawgVersionCode=11
amneziawgVersionName=2.0.1
amneziawgVersionCode=10
amneziawgVersionName=2.0.0
amneziawgPackageName=org.amnezia.awg
# When configured, Gradle will run in incubating parallel mode.
@@ -54,13 +54,19 @@ public final class InetEndpoint {
}
if (uri.getPort() < 0 || uri.getPort() > 65535)
throw new ParseException(InetEndpoint.class, endpoint, "Missing/invalid port number");
String host = uri.getHost();
if (host == null)
throw new ParseException(InetEndpoint.class, endpoint, "Missing host");
// URI.getHost() wraps IPv6 literals in brackets; strip them so the numeric parser accepts it.
if (host.length() > 1 && host.charAt(0) == '[' && host.charAt(host.length() - 1) == ']')
host = host.substring(1, host.length() - 1);
try {
InetAddresses.parse(uri.getHost());
InetAddresses.parse(host);
// Parsing ths host as a numeric address worked, so we don't need to do DNS lookups.
return new InetEndpoint(uri.getHost(), true, uri.getPort());
return new InetEndpoint(host, true, uri.getPort());
} catch (final ParseException ignored) {
// Failed to parse the host as a numeric address, so it must be a DNS hostname/FQDN.
return new InetEndpoint(uri.getHost(), false, uri.getPort());
return new InetEndpoint(host, false, uri.getPort());
}
}
@@ -0,0 +1,51 @@
/*
* Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package org.amnezia.awg.config;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class InetEndpointTest {
@Test
public void ipv6_literal_is_parsed_as_canonical_numeric_host() throws ParseException {
final InetEndpoint endpoint = InetEndpoint.parse("[2001:db8::1]:51820");
// URI.getHost() yields the bracketed form; the stored host must be the bare, canonical one.
assertEquals("Host is stored without brackets", "2001:db8::1", endpoint.getHost());
assertEquals("Port is parsed", 51820, endpoint.getPort());
// toString() must re-add brackets so the userspace string is valid for amneziawg-go.
assertEquals("toString re-brackets the literal", "[2001:db8::1]:51820", endpoint.toString());
}
@Test
public void ipv4_endpoint_is_unchanged() throws ParseException {
final InetEndpoint endpoint = InetEndpoint.parse("192.0.2.1:51820");
assertEquals("192.0.2.1", endpoint.getHost());
assertEquals(51820, endpoint.getPort());
assertEquals("192.0.2.1:51820", endpoint.toString());
}
@Test
public void hostname_endpoint_is_preserved() throws ParseException {
final InetEndpoint endpoint = InetEndpoint.parse("vpn.example.com:51820");
assertEquals("vpn.example.com", endpoint.getHost());
assertEquals(51820, endpoint.getPort());
}
@Test
public void ipv6_endpoint_round_trips_to_userspace_string() throws BadConfigException {
final Peer peer = new Peer.Builder()
.parsePublicKey("vBN7qyUTb5lJtWYJ8LhbPio1Z4RcyBPGnqFBGn6O6Qg=")
.parseEndpoint("[2001:db8::1]:51820")
.build();
assertTrue(
"Userspace string keeps the bracketed IPv6 endpoint",
peer.toAwgUserspaceString().contains("endpoint=[2001:db8::1]:51820")
);
}
}
+4 -4
View File
@@ -3,12 +3,12 @@ module github.com/amnezia-vpn/amneziawg-android
go 1.24.4
require (
github.com/amnezia-vpn/amneziawg-go v0.2.18
golang.org/x/sys v0.36.0
github.com/amnezia-vpn/amneziawg-go v0.2.16
golang.org/x/sys v0.33.0
)
require (
golang.org/x/crypto v0.42.0 // indirect
golang.org/x/net v0.44.0 // indirect
golang.org/x/crypto v0.39.0 // indirect
golang.org/x/net v0.41.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
)
-8
View File
@@ -1,23 +1,15 @@
github.com/amnezia-vpn/amneziawg-go v0.2.16 h1:XY6HOq/xtqH8ZXMncRWkjFs85EKdN10NLNnw23kTpE0=
github.com/amnezia-vpn/amneziawg-go v0.2.16/go.mod h1:nRkPpIzjCxMW8pZKXTRkpqAQVlmFJdVOGkeQSC7wbms=
github.com/amnezia-vpn/amneziawg-go v0.2.18 h1:pUn7/P8qdGmHd6JmE3bCQXPblZs3vruWR98nLODQLJg=
github.com/amnezia-vpn/amneziawg-go v0.2.18/go.mod h1:aMgOk9MuX0xI7b5TKAYp8pLM54RlXcOPzDvYw3YEO5A=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
@@ -63,11 +63,11 @@ class PeerProxy : BaseObservable, Parcelable {
@get:Bindable
val isAbleToExcludePrivateIps: Boolean
get() = allowedIpsState == AllowedIpsState.CONTAINS_IPV4_PUBLIC_NETWORKS || allowedIpsState == AllowedIpsState.CONTAINS_IPV4_WILDCARD
get() = allowedIpsState == AllowedIpsState.CONTAINS_PUBLIC_NETWORKS || allowedIpsState == AllowedIpsState.CONTAINS_WILDCARD
@get:Bindable
val isExcludingPrivateIps: Boolean
get() = allowedIpsState == AllowedIpsState.CONTAINS_IPV4_PUBLIC_NETWORKS
get() = allowedIpsState == AllowedIpsState.CONTAINS_PUBLIC_NETWORKS
private constructor(parcel: Parcel) {
allowedIps = parcel.readString() ?: ""
@@ -106,12 +106,11 @@ class PeerProxy : BaseObservable, Parcelable {
// 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
// A wildcard in either family means private IPs can still be excluded.
if (networkStrings.containsAll(IPV4_WILDCARD) || networkStrings.containsAll(IPV6_WILDCARD))
AllowedIpsState.CONTAINS_WILDCARD
else if (networkStrings.containsAll(IPV4_PUBLIC_NETWORKS) || networkStrings.containsAll(IPV6_PUBLIC_NETWORKS))
AllowedIpsState.CONTAINS_PUBLIC_NETWORKS
else
AllowedIpsState.OTHER
} else {
@@ -128,31 +127,29 @@ class PeerProxy : BaseObservable, Parcelable {
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.
// For each family, replace the first instance of the wildcard with its public network list, or vice versa.
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 replacements = listOf(IPV4_WILDCARD to IPV4_PUBLIC_NETWORKS, IPV6_WILDCARD to IPV6_PUBLIC_NETWORKS)
.map { (wildcard, public) -> if (excludingPrivateIps) wildcard to public else public to 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.
val output: MutableCollection<String?> = LinkedHashSet(input.size)
val replaced = BooleanArray(replacements.size)
for (network in input) {
if (oldNetworks.contains(network)) {
if (!replaced) {
for (replacement in newNetworks) if (!output.contains(replacement)) output.add(replacement)
replaced = true
val family = replacements.indexOfFirst { it.first.contains(network) }
if (family >= 0) {
if (!replaced[family]) {
for (replacement in replacements[family].second) if (!output.contains(replacement)) output.add(replacement)
replaced[family] = true
}
} else if (!output.contains(network)) {
output.add(network)
}
}
// DNS servers only need to handled specially when we're excluding private IPs.
// DNS servers only need to be 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
allowedIpsState = if (excludingPrivateIps) AllowedIpsState.CONTAINS_PUBLIC_NETWORKS else AllowedIpsState.CONTAINS_WILDCARD
notifyPropertyChanged(BR.allowedIps)
notifyPropertyChanged(BR.excludingPrivateIps)
}
@@ -169,8 +166,10 @@ class PeerProxy : BaseObservable, Parcelable {
}
private fun setInterfaceDns(dnsServers: CharSequence) {
val newDnsRoutes = Attribute.split(dnsServers).filter { !it.contains(":") }.map { "$it/32" }
if (allowedIpsState == AllowedIpsState.CONTAINS_IPV4_PUBLIC_NETWORKS) {
val newDnsRoutes = Attribute.split(dnsServers)
.filter { it.contains(":") || it.matches(Regex("""^\d{1,3}(\.\d{1,3}){3}$""")) }
.map { if (it.contains(":")) "$it/128" else "$it/32" }
if (allowedIpsState == AllowedIpsState.CONTAINS_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()
@@ -209,7 +208,7 @@ class PeerProxy : BaseObservable, Parcelable {
}
private enum class AllowedIpsState {
CONTAINS_IPV4_PUBLIC_NETWORKS, CONTAINS_IPV4_WILDCARD, INVALID, OTHER
CONTAINS_PUBLIC_NETWORKS, CONTAINS_WILDCARD, INVALID, OTHER
}
private class InterfaceDnsListener constructor(peerProxy: PeerProxy) : OnPropertyChangedCallback() {
@@ -290,5 +289,11 @@ class PeerProxy : BaseObservable, Parcelable {
"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")
// ::/0 minus the private ranges fc00::/7 (unique local) and fe80::/10 (link-local).
private val IPV6_PUBLIC_NETWORKS = setOf(
"::/1", "8000::/2", "c000::/3", "e000::/4", "f000::/5", "f800::/6",
"fe00::/9", "fec0::/10", "ff00::/8"
)
private val IPV6_WILDCARD = setOf("::/0")
}
}