Skip to content
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 client/android/protocolApi/src/main/kotlin/Protocol.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ abstract class Protocol {

for (i in 0 until splitTunnelSites.length()) {
val address = InetNetwork.parse(splitTunnelSites.getString(i))
if (address.isIpv6 && addresses.none { it.isIpv6 }) continue
addressHandlerFunc(address)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,17 @@ open class Wireguard : Protocol() {
}

protected fun WireguardConfig.Builder.configWireguard(config: JSONObject, configData: JSONObject) {
configData.getString("client_ip").split(",").map { address ->
InetNetwork.parse(address.trim())
}.forEach(::addAddress)
val addresses = sequenceOf(
configData.optStringOrNull("client_ip"),
configData.optStringOrNull("client_ipv6")
).filterNotNull()
.flatMap { it.split(",").asSequence() }
.map { it.trim() }
.filter { it.isNotEmpty() }
.map { InetNetwork.parse(it) }
.toList()
addresses.forEach(::addAddress)
val hasIpv6Address = addresses.any { it.isIpv6 }

config.optStringOrNull("dns1")?.let { dns ->
addDnsServer(parseInetAddress(dns.trim()))
Expand All @@ -95,7 +103,7 @@ open class Wireguard : Protocol() {
val routes = hashSetOf<InetNetwork>()
configData.getJSONArray("allowed_ips").asSequence<String>().map { route ->
InetNetwork.parse(route.trim())
}.forEach(routes::add)
}.filter { it.isIpv4 || hasIpv6Address }.forEach(routes::add)
// if the allowed IPs list contains at least one non-default route, disable global split tunneling
if (routes.any { it !in defRoutes }) disableSplitTunneling()
addRoutes(routes)
Expand Down
1 change: 1 addition & 0 deletions client/core/configurators/awgConfigurator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ ProtocolConfig AwgConfigurator::createConfig(const ServerCredentials &credential
newClientConfig.hostName = wgConfig->clientConfig->hostName;
newClientConfig.port = wgConfig->clientConfig->port;
newClientConfig.clientIp = wgConfig->clientConfig->clientIp;
newClientConfig.clientIpv6 = wgConfig->clientConfig->clientIpv6;
newClientConfig.clientPrivateKey = wgConfig->clientConfig->clientPrivateKey;
newClientConfig.clientPublicKey = wgConfig->clientConfig->clientPublicKey;
newClientConfig.serverPublicKey = wgConfig->clientConfig->serverPublicKey;
Expand Down
151 changes: 138 additions & 13 deletions client/core/configurators/wireguardConfigurator.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "wireguardConfigurator.h"

#include <QDebug>
#include <QAbstractSocket>
#include <QJsonDocument>
#include <QProcess>
#include <QRegularExpression>
Expand Down Expand Up @@ -30,6 +31,56 @@

using namespace amnezia;

namespace
{

QString stripPrefixLength(const QString &address)
{
return address.trimmed().section("/", 0, 0).trimmed();
}

QString withPrefixLength(const QString &address, const QString &prefixLength)
{
if (address.contains("/")) {
return address.trimmed();
}
return QString("%1/%2").arg(address.trimmed(), prefixLength);
}

QString formatEndpointHost(const QString &host)
{
const QHostAddress hostAddress(host);
if (hostAddress.protocol() == QAbstractSocket::IPv6Protocol) {
return QString("[%1]").arg(host);
}
return host;
}

QHostAddress nextIpv6Address(const QHostAddress &address)
{
Q_IPV6ADDR bytes = address.toIPv6Address();
for (int i = 15; i >= 0; --i) {
++bytes[i];
if (bytes[i] != 0) {
break;
}
}
return QHostAddress(bytes);
}

QList<QHostAddress> filterAddressesByFamily(const QList<QHostAddress> &addresses, QAbstractSocket::NetworkLayerProtocol family)
{
QList<QHostAddress> result;
for (const QHostAddress &address : addresses) {
if (address.protocol() == family) {
result << address;
}
}
return result;
}

}

WireguardConfigurator::WireguardConfigurator(SshSession* sshSession, bool isAwg,
QObject *parent)
: ConfiguratorBase(sshSession, parent), m_isAwg(isAwg)
Expand Down Expand Up @@ -79,25 +130,54 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys()

QList<QHostAddress> WireguardConfigurator::getIpsFromConf(const QString &input)
{
QRegularExpression regex("AllowedIPs = (\\d+\\.\\d+\\.\\d+\\.\\d+)");
QRegularExpression regex("AllowedIPs\\s*=\\s*([^\\r\\n]+)");
QRegularExpressionMatchIterator matchIterator = regex.globalMatch(input);

QList<QHostAddress> ips;

while (matchIterator.hasNext()) {
QRegularExpressionMatch match = matchIterator.next();
const QString address_string { match.captured(1) };
const QHostAddress address { address_string };
if (address.isNull()) {
qWarning() << "Couldn't recognize the ip address: " << address_string;
} else {
ips << address;
const QStringList allowedIps = match.captured(1).split(",", Qt::SkipEmptyParts);
for (const QString &allowedIp : allowedIps) {
const QString addressString = stripPrefixLength(allowedIp);
const QHostAddress address { addressString };
if (address.isNull()) {
qWarning() << "Couldn't recognize the ip address: " << addressString;
} else {
ips << address;
}
}
}

return ips;
}

bool WireguardConfigurator::hasServerIpv6Egress(const ServerCredentials &credentials, DockerContainer container)
{
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};

const QString probeScript =
"(ping -6 -c 1 -W 2 2001:4860:4860::8888 >/dev/null 2>&1 || "
"ping6 -c 1 -W 2 2001:4860:4860::8888 >/dev/null 2>&1) && "
"echo AMNEZIA_IPV6_OK || true";

const auto errorCode = m_sshSession->runContainerScript(credentials, container, probeScript, cbReadStdOut);
if (errorCode != ErrorCode::NoError) {
qWarning() << "Unable to probe IPv6 egress in container, generating IPv4-only client config";
return false;
}

const bool hasIpv6Egress = stdOut.contains("AMNEZIA_IPV6_OK");
if (!hasIpv6Egress) {
qInfo() << "Container IPv6 egress is unavailable, generating IPv4-only WG/AWG client config";
}
return hasIpv6Egress;
}

WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardConfig(const ServerCredentials &credentials,
DockerContainer container,
const WireGuardServerConfig* serverConfig,
Expand Down Expand Up @@ -136,7 +216,9 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
if (errorCode != ErrorCode::NoError) {
return connData;
}
auto ips = getIpsFromConf(stdOut);
const auto ips = getIpsFromConf(stdOut);
const auto ipv4Addresses = filterAddressesByFamily(ips, QAbstractSocket::IPv4Protocol);
const auto ipv6Addresses = filterAddressesByFamily(ips, QAbstractSocket::IPv6Protocol);

QHostAddress nextIp = [&] {
QHostAddress result;
Expand All @@ -147,10 +229,10 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
} else if (awgServerConfig && !awgServerConfig->subnetAddress.isEmpty()) {
subnetAddress = awgServerConfig->subnetAddress;
}
if (ips.empty()) {
if (ipv4Addresses.empty()) {
lastIp.setAddress(subnetAddress);
} else {
lastIp = ips.last();
lastIp = ipv4Addresses.last();
}
quint8 lastOctet = static_cast<quint8>(lastIp.toIPv4Address());
switch (lastOctet) {
Expand All @@ -164,6 +246,28 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon

connData.clientIP = nextIp.toString();

if (hasServerIpv6Egress(credentials, container)) {
QHostAddress lastIpv6;
QString subnetIpv6Address = m_isAwg ? protocols::awg::defaultSubnetIpv6Address
: protocols::wireguard::defaultSubnetIpv6Address;
if (serverConfig && !serverConfig->subnetIpv6Address.isEmpty()) {
subnetIpv6Address = serverConfig->subnetIpv6Address;
} else if (awgServerConfig && !awgServerConfig->subnetIpv6Address.isEmpty()) {
subnetIpv6Address = awgServerConfig->subnetIpv6Address;
}

if (ipv6Addresses.empty()) {
lastIpv6.setAddress(stripPrefixLength(subnetIpv6Address));
} else {
lastIpv6 = ipv6Addresses.last();
}

const QHostAddress nextIpv6 = nextIpv6Address(lastIpv6);
if (!nextIpv6.isNull()) {
connData.clientIPv6 = nextIpv6.toString();
}
}

// Get keys
connData.serverPubKey =
m_sshSession->getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, errorCode);
Expand All @@ -180,11 +284,16 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
}

// Add client to config
QStringList peerAllowedIps { withPrefixLength(connData.clientIP, "32") };
if (!connData.clientIPv6.isEmpty()) {
peerAllowedIps << withPrefixLength(connData.clientIPv6, protocols::wireguard::defaultClientIpv6Cidr);
}

QString configPart = QString("[Peer]\n"
"PublicKey = %1\n"
"PresharedKey = %2\n"
"AllowedIPs = %3/32\n\n")
.arg(connData.clientPubKey, connData.pskKey, connData.clientIP);
"AllowedIPs = %3\n\n")
.arg(connData.clientPubKey, connData.pskKey, peerAllowedIps.join(", "));

errorCode = m_sshSession->uploadTextFileToContainer(container, credentials, configPart, configPath,
libssh::ScpOverwriteMode::ScpAppendToExisting);
Expand Down Expand Up @@ -238,8 +347,23 @@ ProtocolConfig WireguardConfigurator::createConfig(const ServerCredentials &cred
return WireGuardProtocolConfig{};
}

const QStringList clientAddresses = [&] {
QStringList addresses { withPrefixLength(connData.clientIP, "32") };
if (!connData.clientIPv6.isEmpty()) {
addresses << withPrefixLength(connData.clientIPv6, protocols::wireguard::defaultClientIpv6Cidr);
}
return addresses;
}();
QStringList allowedIps { "0.0.0.0/0" };
if (!connData.clientIPv6.isEmpty()) {
allowedIps << "::/0";
}

config.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", connData.clientPrivKey);
config.replace("$WIREGUARD_CLIENT_ADDRESS", clientAddresses.join(", "));
config.replace("$WIREGUARD_CLIENT_IP", connData.clientIP);
config.replace("$WIREGUARD_ALLOWED_IPS", allowedIps.join(", "));
config.replace("$WIREGUARD_ENDPOINT_HOST", formatEndpointHost(connData.host));
config.replace("$WIREGUARD_SERVER_PUBLIC_KEY", connData.serverPubKey);
config.replace("$WIREGUARD_PSK", connData.pskKey);

Expand All @@ -260,12 +384,13 @@ ProtocolConfig WireguardConfigurator::createConfig(const ServerCredentials &cred
clientConfig.hostName = connData.host;
clientConfig.port = connData.port.toInt();
clientConfig.clientIp = connData.clientIP;
clientConfig.clientIpv6 = connData.clientIPv6;
clientConfig.clientPrivateKey = connData.clientPrivKey;
clientConfig.clientPublicKey = connData.clientPubKey;
clientConfig.serverPublicKey = connData.serverPubKey;
clientConfig.presharedKey = connData.pskKey;
clientConfig.clientId = connData.clientPubKey;
clientConfig.allowedIps = QStringList { "0.0.0.0/0", "::/0" };
clientConfig.allowedIps = allowedIps;
clientConfig.persistentKeepAlive = "25";
clientConfig.mtu = mtu;
clientConfig.isObfuscationEnabled = false;
Expand Down
2 changes: 2 additions & 0 deletions client/core/configurators/wireguardConfigurator.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class WireguardConfigurator : public ConfiguratorBase
QString clientPrivKey; // client private key
QString clientPubKey; // client public key
QString clientIP; // internal client IP address
QString clientIPv6; // internal client IPv6 address
QString serverPubKey; // tls-auth key
QString pskKey; // preshared key
QString host; // host ip
Expand All @@ -43,6 +44,7 @@ class WireguardConfigurator : public ConfiguratorBase

private:
QList<QHostAddress> getIpsFromConf(const QString &input);
bool hasServerIpv6Egress(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container);
ConnectionData prepareWireguardConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container,
const amnezia::WireGuardServerConfig* serverConfig,
const amnezia::AwgServerConfig* awgServerConfig,
Expand Down
Loading