amnezia-client/client/android/src/com/wireguard/config/Interface.java
2023-10-09 10:29:42 -04:00

666 lines
23 KiB
Java

/*
* Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.config;
import com.wireguard.config.BadConfigException.Location;
import com.wireguard.config.BadConfigException.Reason;
import com.wireguard.config.BadConfigException.Section;
import com.wireguard.crypto.Key;
import com.wireguard.crypto.KeyFormatException;
import com.wireguard.crypto.KeyPair;
import java.net.InetAddress;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import androidx.annotation.Nullable;
/**
* Represents the configuration for a WireGuard interface (an [Interface] block). Interfaces must
* have a private key (used to initialize a {@code KeyPair}), and may optionally have several other
* attributes.
* <p>
* Instances of this class are immutable.
*/
public final class Interface {
private static final int MAX_UDP_PORT = 65535;
private static final int MIN_UDP_PORT = 0;
private final Set<InetNetwork> addresses;
private final Set<InetAddress> dnsServers;
private final Set<String> excludedApplications;
private final Set<String> includedApplications;
private final KeyPair keyPair;
private final Optional<Integer> listenPort;
private final Optional<Integer> mtu;
private final Optional<Integer> jc;
private final Optional<Integer> jmin;
private final Optional<Integer> jmax;
private final Optional<Integer> s1;
private final Optional<Integer> s2;
private final Optional<Long> h1;
private final Optional<Long> h2;
private final Optional<Long> h3;
private final Optional<Long> h4;
private Interface(final Builder builder) {
// Defensively copy to ensure immutability even if the Builder is reused.
addresses = Collections.unmodifiableSet(new LinkedHashSet<>(builder.addresses));
dnsServers = Collections.unmodifiableSet(new LinkedHashSet<>(builder.dnsServers));
excludedApplications =
Collections.unmodifiableSet(new LinkedHashSet<>(builder.excludedApplications));
includedApplications =
Collections.unmodifiableSet(new LinkedHashSet<>(builder.includedApplications));
keyPair = Objects.requireNonNull(builder.keyPair, "Interfaces must have a private key");
listenPort = builder.listenPort;
mtu = builder.mtu;
jc = builder.jc;
jmax = builder.jmax;
jmin = builder.jmin;
s1 = builder.s1;
s2 = builder.s2;
h1 = builder.h1;
h2 = builder.h2;
h3 = builder.h3;
h4 = builder.h4;
}
/**
* Parses an series of "KEY = VALUE" lines into an {@code Interface}. Throws
* {@link ParseException} if the input is not well-formed or contains unknown attributes.
*
* @param lines An iterable sequence of lines, containing at least a private key attribute
* @return An {@code Interface} with all of the attributes from {@code lines} set
*/
public static Interface parse(final Iterable<? extends CharSequence> lines)
throws BadConfigException {
final Builder builder = new Builder();
for (final CharSequence line : lines) {
final Attribute attribute =
Attribute.parse(line).orElseThrow(()
-> new BadConfigException(Section.INTERFACE,
Location.TOP_LEVEL, Reason.SYNTAX_ERROR, line));
switch (attribute.getKey().toLowerCase(Locale.ENGLISH)) {
case "address":
builder.parseAddresses(attribute.getValue());
break;
case "dns":
builder.parseDnsServers(attribute.getValue());
break;
case "excludedapplications":
builder.parseExcludedApplications(attribute.getValue());
break;
case "includedapplications":
builder.parseIncludedApplications(attribute.getValue());
break;
case "listenport":
builder.parseListenPort(attribute.getValue());
break;
case "mtu":
builder.parseMtu(attribute.getValue());
break;
case "privatekey":
builder.parsePrivateKey(attribute.getValue());
break;
case "jc":
builder.parseJc(attribute.getValue());
break;
case "jmin":
builder.parseJmin(attribute.getValue());
break;
case "jmax":
builder.parseJmax(attribute.getValue());
break;
case "s1":
builder.parseS1(attribute.getValue());
break;
case "s2":
builder.parseS2(attribute.getValue());
break;
case "h1":
builder.parseH1(attribute.getValue());
break;
case "h2":
builder.parseH2(attribute.getValue());
break;
case "h3":
builder.parseH3(attribute.getValue());
break;
case "h4":
builder.parseH4(attribute.getValue());
break;
default:
throw new BadConfigException(
Section.INTERFACE, Location.TOP_LEVEL, Reason.UNKNOWN_ATTRIBUTE, attribute.getKey());
}
}
return builder.build();
}
@Override
public boolean equals(final Object obj) {
if (!(obj instanceof Interface))
return false;
final Interface other = (Interface) obj;
return addresses.equals(other.addresses) && dnsServers.equals(other.dnsServers)
&& excludedApplications.equals(other.excludedApplications)
&& includedApplications.equals(other.includedApplications) && keyPair.equals(other.keyPair)
&& listenPort.equals(other.listenPort) && mtu.equals(other.mtu) && jc.equals(other.jc) && jmin.equals(other.jmin)
&& jmax.equals(other.jmax) && s1.equals(other.s1) && s2.equals(other.s2) && h1.equals(other.h1) && h2.equals(other.h2)
&& h3.equals(other.h3) && h4.equals(other.h4);
}
/**
* Returns the set of IP addresses assigned to the interface.
*
* @return a set of {@link InetNetwork}s
*/
public Set<InetNetwork> getAddresses() {
// The collection is already immutable.
return addresses;
}
/**
* Returns the set of DNS servers associated with the interface.
*
* @return a set of {@link InetAddress}es
*/
public Set<InetAddress> getDnsServers() {
// The collection is already immutable.
return dnsServers;
}
/**
* Returns the set of applications excluded from using the interface.
*
* @return a set of package names
*/
public Set<String> getExcludedApplications() {
// The collection is already immutable.
return excludedApplications;
}
/**
* Returns the set of applications included exclusively for using the interface.
*
* @return a set of package names
*/
public Set<String> getIncludedApplications() {
// The collection is already immutable.
return includedApplications;
}
/**
* Returns the public/private key pair used by the interface.
*
* @return a key pair
*/
public KeyPair getKeyPair() {
return keyPair;
}
/**
* Returns the UDP port number that the WireGuard interface will listen on.
*
* @return a UDP port number, or {@code Optional.empty()} if none is configured
*/
public Optional<Integer> getListenPort() {
return listenPort;
}
/**
* Returns the MTU used for the WireGuard interface.
*
* @return the MTU, or {@code Optional.empty()} if none is configured
*/
public Optional<Integer> getMtu() {
return mtu;
}
public Optional<Integer> getJc() {
return jc;
}
public Optional<Integer> getJmin() {
return jmin;
}
public Optional<Integer> getJmax() {
return jmax;
}
public Optional<Integer> getS1() {
return s1;
}
public Optional<Integer> getS2() {
return s2;
}
public Optional<Long> getH1() {
return h1;
}
public Optional<Long> getH2() {
return h2;
}
public Optional<Long> getH3() {
return h3;
}
public Optional<Long> getH4() {
return h4;
}
@Override
public int hashCode() {
int hash = 1;
hash = 31 * hash + addresses.hashCode();
hash = 31 * hash + dnsServers.hashCode();
hash = 31 * hash + excludedApplications.hashCode();
hash = 31 * hash + includedApplications.hashCode();
hash = 31 * hash + keyPair.hashCode();
hash = 31 * hash + listenPort.hashCode();
hash = 31 * hash + mtu.hashCode();
hash = 31 * hash + jc.hashCode();
hash = 31 * hash + jmin.hashCode();
hash = 31 * hash + jmax.hashCode();
hash = 31 * hash + s1.hashCode();
hash = 31 * hash + s2.hashCode();
hash = 31 * hash + h1.hashCode();
hash = 31 * hash + h2.hashCode();
hash = 31 * hash + h3.hashCode();
hash = 31 * hash + h4.hashCode();
return hash;
}
/**
* Converts the {@code Interface} into a string suitable for debugging purposes. The {@code
* Interface} is identified by its public key and (if set) the port used for its UDP socket.
*
* @return A concise single-line identifier for the {@code Interface}
*/
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("(Interface ");
sb.append(keyPair.getPublicKey().toBase64());
listenPort.ifPresent(lp -> sb.append(" @").append(lp));
sb.append(')');
return sb.toString();
}
/**
* Converts the {@code Interface} into a string suitable for inclusion in a {@code wg-quick}
* configuration file.
*
* @return The {@code Interface} represented as a series of "Key = Value" lines
*/
public String toWgQuickString() {
final StringBuilder sb = new StringBuilder();
if (!addresses.isEmpty())
sb.append("Address = ").append(Attribute.join(addresses)).append('\n');
if (!dnsServers.isEmpty()) {
final List<String> dnsServerStrings =
dnsServers.stream().map(InetAddress::getHostAddress).collect(Collectors.toList());
sb.append("DNS = ").append(Attribute.join(dnsServerStrings)).append('\n');
}
if (!excludedApplications.isEmpty())
sb.append("ExcludedApplications = ")
.append(Attribute.join(excludedApplications))
.append('\n');
if (!includedApplications.isEmpty())
sb.append("IncludedApplications = ")
.append(Attribute.join(includedApplications))
.append('\n');
listenPort.ifPresent(lp -> sb.append("ListenPort = ").append(lp).append('\n'));
mtu.ifPresent(m -> sb.append("MTU = ").append(m).append('\n'));
jc.ifPresent(t_jc -> sb.append("Jc = ").append(t_jc).append('\n'));
jmin.ifPresent(t_jmin -> sb.append("Jmin = ").append(t_jmin).append('\n'));
jmax.ifPresent(t_jmax -> sb.append("Jmax = ").append(t_jmax).append('\n'));
s1.ifPresent(t_s1 -> sb.append("S1 = ").append(t_s1).append('\n'));
s2.ifPresent(t_s2 -> sb.append("S2 = ").append(t_s2).append('\n'));
h1.ifPresent(t_h1 -> sb.append("H1 = ").append(t_h1).append('\n'));
h2.ifPresent(t_h2 -> sb.append("H2 = ").append(t_h2).append('\n'));
h3.ifPresent(t_h3 -> sb.append("H3 = ").append(t_h3).append('\n'));
h4.ifPresent(t_h4 -> sb.append("H4 = ").append(t_h4).append('\n'));
sb.append("PrivateKey = ").append(keyPair.getPrivateKey().toBase64()).append('\n');
return sb.toString();
}
/**
* Serializes the {@code Interface} for use with the WireGuard cross-platform userspace API.
* Note that not all attributes are included in this representation.
*
* @return the {@code Interface} represented as a series of "KEY=VALUE" lines
*/
public String toWgUserspaceString() {
final StringBuilder sb = new StringBuilder();
sb.append("private_key=").append(keyPair.getPrivateKey().toHex()).append('\n');
listenPort.ifPresent(lp -> sb.append("listen_port=").append(lp).append('\n'));
jc.ifPresent(t_jc -> sb.append("jc=").append(t_jc).append('\n'));
jmin.ifPresent(t_jmin -> sb.append("jmin=").append(t_jmin).append('\n'));
jmax.ifPresent(t_jmax -> sb.append("jmax=").append(t_jmax).append('\n'));
s1.ifPresent(t_s1 -> sb.append("s1=").append(t_s1).append('\n'));
s2.ifPresent(t_s2 -> sb.append("s2=").append(t_s2).append('\n'));
h1.ifPresent(t_h1 -> sb.append("h1=").append(t_h1).append('\n'));
h2.ifPresent(t_h2 -> sb.append("h2=").append(t_h2).append('\n'));
h3.ifPresent(t_h3 -> sb.append("h3=").append(t_h3).append('\n'));
h4.ifPresent(t_h4 -> sb.append("h4=").append(t_h4).append('\n'));
return sb.toString();
}
@SuppressWarnings("UnusedReturnValue")
public static final class Builder {
// Defaults to an empty set.
private final Set<InetNetwork> addresses = new LinkedHashSet<>();
// Defaults to an empty set.
private final Set<InetAddress> dnsServers = new LinkedHashSet<>();
// Defaults to an empty set.
private final Set<String> excludedApplications = new LinkedHashSet<>();
// Defaults to an empty set.
private final Set<String> includedApplications = new LinkedHashSet<>();
// No default; must be provided before building.
@Nullable private KeyPair keyPair;
// Defaults to not present.
private Optional<Integer> listenPort = Optional.empty();
// Defaults to not present.
private Optional<Integer> mtu = Optional.empty();
private Optional<Integer> jc = Optional.empty();
private Optional<Integer> jmin = Optional.empty();
private Optional<Integer> jmax = Optional.empty();
private Optional<Integer> s1 = Optional.empty();
private Optional<Integer> s2 = Optional.empty();
private Optional<Long> h1 = Optional.empty();
private Optional<Long> h2 = Optional.empty();
private Optional<Long> h3 = Optional.empty();
private Optional<Long> h4 = Optional.empty();
public Builder addAddress(final InetNetwork address) {
addresses.add(address);
return this;
}
public Builder addAddresses(final Collection<InetNetwork> addresses) {
this.addresses.addAll(addresses);
return this;
}
public Builder addDnsServer(final InetAddress dnsServer) {
dnsServers.add(dnsServer);
return this;
}
public Builder addDnsServers(final Collection<? extends InetAddress> dnsServers) {
this.dnsServers.addAll(dnsServers);
return this;
}
public Interface build() throws BadConfigException {
if (keyPair == null)
throw new BadConfigException(
Section.INTERFACE, Location.PRIVATE_KEY, Reason.MISSING_ATTRIBUTE, null);
if (!includedApplications.isEmpty() && !excludedApplications.isEmpty())
throw new BadConfigException(
Section.INTERFACE, Location.INCLUDED_APPLICATIONS, Reason.INVALID_KEY, null);
return new Interface(this);
}
public Builder excludeApplication(final String application) {
excludedApplications.add(application);
return this;
}
public Builder excludeApplications(final Collection<String> applications) {
excludedApplications.addAll(applications);
return this;
}
public Builder includeApplication(final String application) {
includedApplications.add(application);
return this;
}
public Builder includeApplications(final Collection<String> applications) {
includedApplications.addAll(applications);
return this;
}
public Builder parseAddresses(final CharSequence addresses) throws BadConfigException {
try {
for (final String address : Attribute.split(addresses))
addAddress(InetNetwork.parse(address));
return this;
} catch (final ParseException e) {
throw new BadConfigException(Section.INTERFACE, Location.ADDRESS, e);
}
}
public Builder parseDnsServers(final CharSequence dnsServers) throws BadConfigException {
try {
for (final String dnsServer : Attribute.split(dnsServers))
addDnsServer(InetAddresses.parse(dnsServer));
return this;
} catch (final ParseException e) {
throw new BadConfigException(Section.INTERFACE, Location.DNS, e);
}
}
public Builder parseExcludedApplications(final CharSequence apps) {
return excludeApplications(List.of(Attribute.split(apps)));
}
public Builder parseIncludedApplications(final CharSequence apps) {
return includeApplications(List.of(Attribute.split(apps)));
}
public Builder parseListenPort(final String listenPort) throws BadConfigException {
try {
return setListenPort(Integer.parseInt(listenPort));
} catch (final NumberFormatException e) {
throw new BadConfigException(Section.INTERFACE, Location.LISTEN_PORT, listenPort, e);
}
}
public Builder parseMtu(final String mtu) throws BadConfigException {
try {
return setMtu(Integer.parseInt(mtu));
} catch (final NumberFormatException e) {
throw new BadConfigException(Section.INTERFACE, Location.MTU, mtu, e);
}
}
public Builder parseJc(final String jc) throws BadConfigException {
try {
return setJc(Integer.parseInt(jc));
} catch (final NumberFormatException e) {
throw new BadConfigException(Section.INTERFACE, Location.JC, jc, e);
}
}
public Builder parseJmax(final String jmax) throws BadConfigException {
try {
return setJmax(Integer.parseInt(jmax));
} catch (final NumberFormatException e) {
throw new BadConfigException(Section.INTERFACE, Location.JMAX, jmax, e);
}
}
public Builder parseJmin(final String jmin) throws BadConfigException {
try {
return setJmin(Integer.parseInt(jmin));
} catch (final NumberFormatException e) {
throw new BadConfigException(Section.INTERFACE, Location.JMIN, jmin, e);
}
}
public Builder parseS1(final String s1) throws BadConfigException {
try {
return setS1(Integer.parseInt(s1));
} catch (final NumberFormatException e) {
throw new BadConfigException(Section.INTERFACE, Location.S1, s1, e);
}
}
public Builder parseS2(final String s2) throws BadConfigException {
try {
return setS2(Integer.parseInt(s2));
} catch (final NumberFormatException e) {
throw new BadConfigException(Section.INTERFACE, Location.S2, s2, e);
}
}
public Builder parseH1(final String h1) throws BadConfigException {
try {
return setH1(Long.parseLong(h1));
} catch (final NumberFormatException e) {
throw new BadConfigException(Section.INTERFACE, Location.H1, h1, e);
}
}
public Builder parseH2(final String h2) throws BadConfigException {
try {
return setH2(Long.parseLong(h2));
} catch (final NumberFormatException e) {
throw new BadConfigException(Section.INTERFACE, Location.H2, h2, e);
}
}
public Builder parseH3(final String h3) throws BadConfigException {
try {
return setH3(Long.parseLong(h3));
} catch (final NumberFormatException e) {
throw new BadConfigException(Section.INTERFACE, Location.H3, h3, e);
}
}
public Builder parseH4(final String h4) throws BadConfigException {
try {
return setH4(Long.parseLong(h4));
} catch (final NumberFormatException e) {
throw new BadConfigException(Section.INTERFACE, Location.H4, h4, e);
}
}
public Builder parsePrivateKey(final String privateKey) throws BadConfigException {
try {
return setKeyPair(new KeyPair(Key.fromBase64(privateKey)));
} catch (final KeyFormatException e) {
throw new BadConfigException(Section.INTERFACE, Location.PRIVATE_KEY, e);
}
}
public Builder setKeyPair(final KeyPair keyPair) {
this.keyPair = keyPair;
return this;
}
public Builder setListenPort(final int listenPort) throws BadConfigException {
if (listenPort < MIN_UDP_PORT || listenPort > MAX_UDP_PORT)
throw new BadConfigException(Section.INTERFACE, Location.LISTEN_PORT, Reason.INVALID_VALUE,
String.valueOf(listenPort));
this.listenPort = listenPort == 0 ? Optional.empty() : Optional.of(listenPort);
return this;
}
public Builder setMtu(final int mtu) throws BadConfigException {
if (mtu < 0)
throw new BadConfigException(
Section.INTERFACE, Location.MTU, Reason.INVALID_VALUE, String.valueOf(mtu));
this.mtu = mtu == 0 ? Optional.empty() : Optional.of(mtu);
return this;
}
public Builder setJc(final int jc) throws BadConfigException {
if (jc < 0)
throw new BadConfigException(
Section.INTERFACE, Location.JC, Reason.INVALID_VALUE, String.valueOf(jc));
this.jc = Optional.of(jc);
return this;
}
public Builder setJmin(final int jmin) throws BadConfigException {
if (jmin < 0)
throw new BadConfigException(
Section.INTERFACE, Location.JMIN, Reason.INVALID_VALUE, String.valueOf(jmin));
this.jmin = Optional.of(jmin);
return this;
}
public Builder setJmax(final int jmax) throws BadConfigException {
if (jmax < 0)
throw new BadConfigException(
Section.INTERFACE, Location.JMAX, Reason.INVALID_VALUE, String.valueOf(jmax));
this.jmax = Optional.of(jmax);
return this;
}
public Builder setS1(final int s1) throws BadConfigException {
if (s1 < 0)
throw new BadConfigException(
Section.INTERFACE, Location.S1, Reason.INVALID_VALUE, String.valueOf(s1));
this.s1 = Optional.of(s1);
return this;
}
public Builder setS2(final int s2) throws BadConfigException {
if (s2 < 0)
throw new BadConfigException(
Section.INTERFACE, Location.S2, Reason.INVALID_VALUE, String.valueOf(s2));
this.s2 = Optional.of(s2);
return this;
}
public Builder setH1(final long h1) throws BadConfigException {
if (h1 < 0)
throw new BadConfigException(
Section.INTERFACE, Location.H1, Reason.INVALID_VALUE, String.valueOf(h1));
this.h1 = Optional.of(h1);
return this;
}
public Builder setH2(final long h2) throws BadConfigException {
if (h2 < 0)
throw new BadConfigException(
Section.INTERFACE, Location.H2, Reason.INVALID_VALUE, String.valueOf(h2));
this.h2 = Optional.of(h2);
return this;
}
public Builder setH3(final long h3) throws BadConfigException {
if (h3 < 0)
throw new BadConfigException(
Section.INTERFACE, Location.H3, Reason.INVALID_VALUE, String.valueOf(h3));
this.h3 = Optional.of(h3);
return this;
}
public Builder setH4(final long h4) throws BadConfigException {
if (h4 < 0)
throw new BadConfigException(
Section.INTERFACE, Location.H4, Reason.INVALID_VALUE, String.valueOf(h4));
this.h4 = Optional.of(h4);
return this;
}
}
}