Skip to content

ui: support DNS SRV lookup & show resolved inetendpoint #70

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jsr305 = "com.google.code.findbugs:jsr305:3.0.2"
junit = "junit:junit:4.13.2"
kotlinx-coroutines-android = "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.0"
zxing-android-embedded = "com.journeyapps:zxing-android-embedded:4.3.0"
dnsjava = "dnsjava:dnsjava:3.4.2"

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
Expand Down
1 change: 1 addition & 0 deletions tunnel/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ android {
dependencies {
implementation(libs.androidx.annotation)
implementation(libs.androidx.collection)
implementation(libs.dnsjava)
compileOnly(libs.jsr305)
testImplementation(libs.junit)
}
Expand Down
86 changes: 72 additions & 14 deletions tunnel/src/main/java/com/wireguard/config/InetEndpoint.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@

import com.wireguard.util.NonNullForAll;

import org.xbill.DNS.DClass;
import org.xbill.DNS.ExtendedResolver;
import org.xbill.DNS.Lookup;
import org.xbill.DNS.Record;
import org.xbill.DNS.Resolver;
import org.xbill.DNS.SRVRecord;
import org.xbill.DNS.SimpleResolver;
import org.xbill.DNS.TextParseException;
import org.xbill.DNS.Type;

import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.URI;
Expand All @@ -15,6 +25,7 @@
import java.time.Duration;
import java.time.Instant;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

import androidx.annotation.Nullable;
Expand Down Expand Up @@ -46,6 +57,11 @@ private InetEndpoint(final String host, final boolean isResolved, final int port
public static InetEndpoint parse(final String endpoint) throws ParseException {
if (FORBIDDEN_CHARACTERS.matcher(endpoint).find())
throw new ParseException(InetEndpoint.class, endpoint, "Forbidden characters");
if (endpoint.contains("_")) {
// SRV records
final String host = endpoint.split(":")[0];
return new InetEndpoint(host, false, 0);
}
final URI uri;
try {
uri = new URI("wg://" + endpoint);
Expand Down Expand Up @@ -92,21 +108,60 @@ public Optional<InetEndpoint> getResolved() {
return Optional.of(this);
synchronized (lock) {
//TODO(zx2c4): Implement a real timeout mechanism using DNS TTL
if (Duration.between(lastResolution, Instant.now()).toMinutes() > 1) {
try {
// Prefer v4 endpoints over v6 to work around DNS64 and IPv6 NAT issues.
final InetAddress[] candidates = InetAddress.getAllByName(host);
InetAddress address = candidates[0];
for (final InetAddress candidate : candidates) {
if (candidate instanceof Inet4Address) {
address = candidate;
break;
final long ttlTimeout = Duration.between(lastResolution, Instant.now()).toSeconds();
if (ttlTimeout > 60) {
resolved = null;
final String[] target = {host};
final int[] targetPort = {port};
if (host.contains("_")) {
// SRV records
try {
final Lookup lookup = new Lookup(host, Type.SRV, DClass.IN);
final Resolver resolver1 = new SimpleResolver("223.5.5.5");
final Resolver resolver2 = new SimpleResolver("223.6.6.6");
final Resolver[] resolvers = {resolver1, resolver2};
final Resolver extendedResolver = new ExtendedResolver(resolvers);
lookup.setResolver(extendedResolver);
lookup.setCache(null);
final Record[] records = lookup.run();
if (lookup.getResult() == Lookup.SUCCESSFUL) {
for (final Record record : records) {
final SRVRecord srv = (SRVRecord) record;
try {
target[0] = srv.getTarget().toString(true);
targetPort[0] = srv.getPort();
InetAddresses.parse(target[0]);
// Parsing ths host as a numeric address worked, so we don't need to do DNS lookups.
resolved = new InetEndpoint(target[0], true, targetPort[0]);
} catch (final ParseException ignored) {
// Failed to parse the host as a numeric address, so it must be a DNS hostname/FQDN.
}
// use the first SRV record and break out of loop
break;
}
} else {
System.out.println("SRV lookup failed: " + lookup.getErrorString());
}
} catch (final TextParseException | UnknownHostException e) {
System.out.println("SRV lookup failed: " + e.getMessage());
}
}
if (resolved == null) {
try {
// Prefer v4 endpoints over v6 to work around DNS64 and IPv6 NAT issues.
final InetAddress[] candidates = InetAddress.getAllByName(target[0]);
InetAddress address = candidates[0];
for (final InetAddress candidate : candidates) {
if (candidate instanceof Inet4Address) {
address = candidate;
break;
}
}
resolved = new InetEndpoint(address.getHostAddress(), true, targetPort[0]);
lastResolution = Instant.now();
} catch (final UnknownHostException e) {
System.out.println("DNS lookup failed: " + e.getMessage());
}
resolved = new InetEndpoint(address.getHostAddress(), true, port);
lastResolution = Instant.now();
} catch (final UnknownHostException e) {
resolved = null;
}
}
return Optional.ofNullable(resolved);
Expand All @@ -121,6 +176,9 @@ public int hashCode() {
@Override
public String toString() {
final boolean isBareIpv6 = isResolved && BARE_IPV6.matcher(host).matches();
return (isBareIpv6 ? '[' + host + ']' : host) + ':' + port;
// Only show the port if it's non-zero
if (port > 0)
return (isBareIpv6 ? '[' + host + ']' : host) + ':' + port;
return (isBareIpv6 ? '[' + host + ']' : host);
}
}
8 changes: 4 additions & 4 deletions tunnel/tools/libwg-go/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ module golang.zx2c4.com/wireguard/android
go 1.20

require (
golang.org/x/sys v0.6.0
golang.zx2c4.com/wireguard v0.0.0-20230223181233-21636207a675
golang.org/x/sys v0.13.0
golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb
)

require (
golang.org/x/crypto v0.7.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
)
19 changes: 11 additions & 8 deletions tunnel/tools/libwg-go/go.sum
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20230223181233-21636207a675 h1:/J/RVnr7ng4fWPRH3xa4WtBJ1Jp+Auu4YNLmGiPv5QU=
golang.zx2c4.com/wireguard v0.0.0-20230223181233-21636207a675/go.mod h1:whfbyDBt09xhCYQWtO2+3UVjlaq6/9hDZrjg2ZE6SyA=
golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb h1:c5tyN8sSp8jSDxdCCDXVOpJwYXXhmTkNMt+g0zTSOic=
golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,17 @@ class TunnelDetailFragment : BaseFragment(), MenuProvider {
for (i in 0 until binding.peersLayout.childCount) {
val peer: TunnelDetailPeerBinding = DataBindingUtil.getBinding(binding.peersLayout.getChildAt(i))
?: continue
if (binding.config != null && i < binding.config!!.peers.size) {
val endpoint = binding.config!!.peers[i].endpoint.get()
val resolved = endpoint.resolved.get()
if (resolved.host != endpoint.host) {
if (endpoint.port != 0) {
peer.endpointText.text = "${endpoint.host}:${endpoint.port}\n${resolved.host}:${resolved.port}"
} else {
peer.endpointText.text = "${endpoint.host}\n${resolved.host}:${resolved.port}"
}
}
}
val publicKey = peer.item!!.publicKey
val peerStats = statistics.peer(publicKey)
if (peerStats == null || (peerStats.rxBytes == 0L && peerStats.txBytes == 0L)) {
Expand Down