diff options
author | Harsh Shandilya <me@msfjarvis.dev> | 2020-03-09 19:00:14 +0530 |
---|---|---|
committer | Harsh Shandilya <me@msfjarvis.dev> | 2020-03-09 19:24:26 +0530 |
commit | adc613d8011af7c508050badb1272e8326554c39 (patch) | |
tree | 4eadedc0767e1f4f482b7c22ec905329acab62a6 /tunnel/src/main/java/com/wireguard/config/InetEndpoint.java | |
parent | fd573f6c1c37bcfcd09237dfcd55e08b1e0eaff9 (diff) |
Migrate tunnel related classes to tunnel/ Gradle module
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
Diffstat (limited to 'tunnel/src/main/java/com/wireguard/config/InetEndpoint.java')
-rw-r--r-- | tunnel/src/main/java/com/wireguard/config/InetEndpoint.java | 125 |
1 files changed, 125 insertions, 0 deletions
diff --git a/tunnel/src/main/java/com/wireguard/config/InetEndpoint.java b/tunnel/src/main/java/com/wireguard/config/InetEndpoint.java new file mode 100644 index 00000000..a442258e --- /dev/null +++ b/tunnel/src/main/java/com/wireguard/config/InetEndpoint.java @@ -0,0 +1,125 @@ +/* + * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.wireguard.config; + +import androidx.annotation.Nullable; + +import org.threeten.bp.Duration; +import org.threeten.bp.Instant; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.UnknownHostException; +import java.util.regex.Pattern; + +import java9.util.Optional; + + +/** + * An external endpoint (host and port) used to connect to a WireGuard {@link Peer}. + * <p> + * Instances of this class are externally immutable. + */ +public final class InetEndpoint { + private static final Pattern BARE_IPV6 = Pattern.compile("^[^\\[\\]]*:[^\\[\\]]*"); + private static final Pattern FORBIDDEN_CHARACTERS = Pattern.compile("[/?#]"); + + private final String host; + private final boolean isResolved; + private final Object lock = new Object(); + private final int port; + private Instant lastResolution = Instant.EPOCH; + @Nullable private InetEndpoint resolved; + + private InetEndpoint(final String host, final boolean isResolved, final int port) { + this.host = host; + this.isResolved = isResolved; + this.port = port; + } + + public static InetEndpoint parse(final String endpoint) throws ParseException { + if (FORBIDDEN_CHARACTERS.matcher(endpoint).find()) + throw new ParseException(InetEndpoint.class, endpoint, "Forbidden characters"); + final URI uri; + try { + uri = new URI("wg://" + endpoint); + } catch (final URISyntaxException e) { + throw new IllegalArgumentException(e); + } + if (uri.getPort() < 0 || uri.getPort() > 65535) + throw new ParseException(InetEndpoint.class, endpoint, "Missing/invalid port number"); + try { + InetAddresses.parse(uri.getHost()); + // 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()); + } 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()); + } + } + + @Override + public boolean equals(final Object obj) { + if (!(obj instanceof InetEndpoint)) + return false; + final InetEndpoint other = (InetEndpoint) obj; + return host.equals(other.host) && port == other.port; + } + + public String getHost() { + return host; + } + + public int getPort() { + return port; + } + + /** + * Generate an {@code InetEndpoint} instance with the same port and the host resolved using DNS + * to a numeric address. If the host is already numeric, the existing instance may be returned. + * Because this function may perform network I/O, it must not be called from the main thread. + * + * @return the resolved endpoint, or {@link Optional#empty()} + */ + public Optional<InetEndpoint> getResolved() { + if (isResolved) + 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; + } + } + resolved = new InetEndpoint(address.getHostAddress(), true, port); + lastResolution = Instant.now(); + } catch (final UnknownHostException e) { + resolved = null; + } + } + return Optional.ofNullable(resolved); + } + } + + @Override + public int hashCode() { + return host.hashCode() ^ port; + } + + @Override + public String toString() { + final boolean isBareIpv6 = isResolved && BARE_IPV6.matcher(host).matches(); + return (isBareIpv6 ? '[' + host + ']' : host) + ':' + port; + } +} |