diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d8527046b..1a2d86681 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" } diff --git a/tunnel/build.gradle.kts b/tunnel/build.gradle.kts index 589d72dac..13035ba05 100644 --- a/tunnel/build.gradle.kts +++ b/tunnel/build.gradle.kts @@ -67,6 +67,7 @@ android { dependencies { implementation(libs.androidx.annotation) implementation(libs.androidx.collection) + implementation(libs.dnsjava) compileOnly(libs.jsr305) testImplementation(libs.junit) } diff --git a/tunnel/src/main/java/com/wireguard/config/InetEndpoint.java b/tunnel/src/main/java/com/wireguard/config/InetEndpoint.java index d1db432be..18ac9f004 100644 --- a/tunnel/src/main/java/com/wireguard/config/InetEndpoint.java +++ b/tunnel/src/main/java/com/wireguard/config/InetEndpoint.java @@ -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; @@ -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; @@ -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); @@ -92,21 +108,60 @@ public Optional 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); @@ -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); } } diff --git a/tunnel/tools/libwg-go/go.mod b/tunnel/tools/libwg-go/go.mod index 9318ebcf1..9f9381fff 100644 --- a/tunnel/tools/libwg-go/go.mod +++ b/tunnel/tools/libwg-go/go.mod @@ -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 ) diff --git a/tunnel/tools/libwg-go/go.sum b/tunnel/tools/libwg-go/go.sum index 3b49b497d..e5f8fc830 100644 --- a/tunnel/tools/libwg-go/go.sum +++ b/tunnel/tools/libwg-go/go.sum @@ -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= diff --git a/ui/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.kt b/ui/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.kt index 81d8e8c6c..a415ed3a6 100644 --- a/ui/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.kt +++ b/ui/src/main/java/com/wireguard/android/fragment/TunnelDetailFragment.kt @@ -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)) {