mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-05-19 01:15:56 +03:00
Compare commits
3 Commits
dev
...
feat/suppo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f250abc628 | ||
|
|
910f3d5d97 | ||
|
|
10755aca19 |
@@ -88,33 +88,68 @@ open class Wireguard : Protocol() {
|
||||
addDnsServer(parseInetAddress(dns.trim()))
|
||||
}
|
||||
|
||||
val defRoutes = hashSetOf(
|
||||
InetNetwork("0.0.0.0", 0),
|
||||
InetNetwork("::", 0)
|
||||
)
|
||||
val routes = hashSetOf<InetNetwork>()
|
||||
configData.getJSONArray("allowed_ips").asSequence<String>().map { route ->
|
||||
InetNetwork.parse(route.trim())
|
||||
}.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)
|
||||
|
||||
configData.optStringOrNull("mtu")?.let { setMtu(it.toInt()) }
|
||||
|
||||
val host = configData.getString("hostName").let { parseInetAddress(it.trim()) }
|
||||
val port = configData.getInt("port")
|
||||
setEndpoint(InetEndpoint(host, port))
|
||||
configData.getString("client_priv_key").let { setPrivateKeyHex(it.base64ToHex()) }
|
||||
|
||||
if (configData.optBoolean("isObfuscationEnabled")) {
|
||||
setUseProtocolExtension(true)
|
||||
configExtensionParameters(configData)
|
||||
}
|
||||
|
||||
configData.optStringOrNull("persistent_keep_alive")?.let { setPersistentKeepalive(it.toInt()) }
|
||||
configData.getString("client_priv_key").let { setPrivateKeyHex(it.base64ToHex()) }
|
||||
configData.getString("server_pub_key").let { setPublicKeyHex(it.base64ToHex()) }
|
||||
configData.optStringOrNull("psk_key")?.let { setPreSharedKeyHex(it.base64ToHex()) }
|
||||
val defRoutes = hashSetOf(InetNetwork("0.0.0.0", 0), InetNetwork("::", 0))
|
||||
val peersArray = configData.optJSONArray("peers")
|
||||
|
||||
if (peersArray != null && peersArray.length() > 0) {
|
||||
// Multi-peer: collect union of all peers' allowed IPs for the VPN interface routing table
|
||||
val allRoutes = hashSetOf<InetNetwork>()
|
||||
for (i in 0 until peersArray.length()) {
|
||||
peersArray.getJSONObject(i).getJSONArray("allowed_ips").asSequence<String>()
|
||||
.map { InetNetwork.parse(it.trim()) }.forEach(allRoutes::add)
|
||||
}
|
||||
if (allRoutes.any { it !in defRoutes }) disableSplitTunneling()
|
||||
addRoutes(allRoutes)
|
||||
|
||||
// Primary peer from first entry
|
||||
val firstPeer = peersArray.getJSONObject(0)
|
||||
val firstAllowedIps = firstPeer.getJSONArray("allowed_ips").asSequence<String>()
|
||||
.map { InetNetwork.parse(it.trim()) }.toList()
|
||||
setPeerAllowedIps(firstAllowedIps)
|
||||
setEndpoint(InetEndpoint(parseInetAddress(firstPeer.getString("hostName").trim()), firstPeer.getInt("port")))
|
||||
firstPeer.optStringOrNull("persistent_keep_alive")?.let { setPersistentKeepalive(it.toInt()) }
|
||||
firstPeer.getString("server_pub_key").let { setPublicKeyHex(it.base64ToHex()) }
|
||||
firstPeer.optStringOrNull("psk_key")?.let { setPreSharedKeyHex(it.base64ToHex()) }
|
||||
|
||||
// Additional peers
|
||||
for (i in 1 until peersArray.length()) {
|
||||
val peerData = peersArray.getJSONObject(i)
|
||||
val peerAllowedIps = peerData.getJSONArray("allowed_ips").asSequence<String>()
|
||||
.map { InetNetwork.parse(it.trim()) }.toList()
|
||||
addPeer(
|
||||
PeerConfig(
|
||||
publicKeyHex = peerData.getString("server_pub_key").base64ToHex(),
|
||||
preSharedKeyHex = peerData.optStringOrNull("psk_key")?.base64ToHex(),
|
||||
persistentKeepalive = peerData.optStringOrNull("persistent_keep_alive")?.toInt() ?: 0,
|
||||
endpoint = InetEndpoint(parseInetAddress(peerData.getString("hostName").trim()), peerData.getInt("port")),
|
||||
allowedIps = peerAllowedIps
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// Single peer (original behavior)
|
||||
val routes = hashSetOf<InetNetwork>()
|
||||
configData.getJSONArray("allowed_ips").asSequence<String>().map { route ->
|
||||
InetNetwork.parse(route.trim())
|
||||
}.forEach(routes::add)
|
||||
if (routes.any { it !in defRoutes }) disableSplitTunneling()
|
||||
addRoutes(routes)
|
||||
|
||||
val host = configData.getString("hostName").let { parseInetAddress(it.trim()) }
|
||||
val port = configData.getInt("port")
|
||||
setEndpoint(InetEndpoint(host, port))
|
||||
configData.optStringOrNull("persistent_keep_alive")?.let { setPersistentKeepalive(it.toInt()) }
|
||||
configData.getString("server_pub_key").let { setPublicKeyHex(it.base64ToHex()) }
|
||||
configData.optStringOrNull("psk_key")?.let { setPreSharedKeyHex(it.base64ToHex()) }
|
||||
}
|
||||
}
|
||||
|
||||
protected fun WireguardConfig.Builder.configExtensionParameters(configData: JSONObject) {
|
||||
@@ -201,7 +236,11 @@ open class Wireguard : Protocol() {
|
||||
Log.e(TAG, "Failed to get tunnel config")
|
||||
return -2
|
||||
}
|
||||
val lastHandshake = config.lines().find { it.startsWith("last_handshake_time_sec=") }?.substring(24)?.toLong()
|
||||
// For multi-peer: take the max handshake time across all peers (any connected peer = tunnel active)
|
||||
val lastHandshake = config.lines()
|
||||
.filter { it.startsWith("last_handshake_time_sec=") }
|
||||
.mapNotNull { it.substring(24).toLongOrNull() }
|
||||
.maxOrNull()
|
||||
if (lastHandshake == null) {
|
||||
Log.e(TAG, "Failed to get last_handshake_time_sec")
|
||||
return -2
|
||||
|
||||
@@ -4,9 +4,18 @@ import android.util.Base64
|
||||
import org.amnezia.vpn.protocol.BadConfigException
|
||||
import org.amnezia.vpn.protocol.ProtocolConfig
|
||||
import org.amnezia.vpn.util.net.InetEndpoint
|
||||
import org.amnezia.vpn.util.net.InetNetwork
|
||||
|
||||
private const val WIREGUARD_DEFAULT_MTU = 1280
|
||||
|
||||
data class PeerConfig(
|
||||
val publicKeyHex: String,
|
||||
val preSharedKeyHex: String?,
|
||||
val persistentKeepalive: Int,
|
||||
val endpoint: InetEndpoint,
|
||||
val allowedIps: List<InetNetwork>
|
||||
)
|
||||
|
||||
open class WireguardConfig protected constructor(
|
||||
protocolConfigBuilder: ProtocolConfig.Builder,
|
||||
val endpoint: InetEndpoint,
|
||||
@@ -31,6 +40,8 @@ open class WireguardConfig protected constructor(
|
||||
var i3: String?,
|
||||
var i4: String?,
|
||||
var i5: String?,
|
||||
val peerAllowedIps: List<InetNetwork>?,
|
||||
val additionalPeers: List<PeerConfig>,
|
||||
) : ProtocolConfig(protocolConfigBuilder) {
|
||||
|
||||
protected constructor(builder: Builder) : this(
|
||||
@@ -57,6 +68,8 @@ open class WireguardConfig protected constructor(
|
||||
builder.i3,
|
||||
builder.i4,
|
||||
builder.i5,
|
||||
builder.peerAllowedIps,
|
||||
builder.additionalPeers.toList(),
|
||||
)
|
||||
|
||||
fun toWgUserspaceString(): String = with(StringBuilder()) {
|
||||
@@ -103,14 +116,22 @@ open class WireguardConfig protected constructor(
|
||||
|
||||
open fun appendPeerLine(sb: StringBuilder) = with(sb) {
|
||||
appendLine("public_key=$publicKeyHex")
|
||||
routes.filter { it.include }.forEach { route ->
|
||||
appendLine("allowed_ip=${route.inetNetwork}")
|
||||
}
|
||||
val primaryIps = peerAllowedIps ?: routes.filter { it.include }.map { it.inetNetwork }
|
||||
primaryIps.forEach { net -> appendLine("allowed_ip=$net") }
|
||||
appendLine("endpoint=$endpoint")
|
||||
if (persistentKeepalive != 0)
|
||||
appendLine("persistent_keepalive_interval=$persistentKeepalive")
|
||||
if (preSharedKeyHex != null)
|
||||
appendLine("preshared_key=$preSharedKeyHex")
|
||||
for (peer in additionalPeers) {
|
||||
appendLine("public_key=${peer.publicKeyHex}")
|
||||
peer.allowedIps.forEach { net -> appendLine("allowed_ip=$net") }
|
||||
appendLine("endpoint=${peer.endpoint}")
|
||||
if (peer.persistentKeepalive != 0)
|
||||
appendLine("persistent_keepalive_interval=${peer.persistentKeepalive}")
|
||||
if (peer.preSharedKeyHex != null)
|
||||
appendLine("preshared_key=${peer.preSharedKeyHex}")
|
||||
}
|
||||
}
|
||||
|
||||
open class Builder : ProtocolConfig.Builder(true) {
|
||||
@@ -150,6 +171,9 @@ open class WireguardConfig protected constructor(
|
||||
internal var i4: String? = null
|
||||
internal var i5: String? = null
|
||||
|
||||
internal var peerAllowedIps: List<InetNetwork>? = null
|
||||
internal val additionalPeers: MutableList<PeerConfig> = mutableListOf()
|
||||
|
||||
fun setEndpoint(endpoint: InetEndpoint) = apply { this.endpoint = endpoint }
|
||||
|
||||
fun setPersistentKeepalive(persistentKeepalive: Int) = apply { this.persistentKeepalive = persistentKeepalive }
|
||||
@@ -179,6 +203,9 @@ open class WireguardConfig protected constructor(
|
||||
fun setI4(i4: String) = apply { this.i4 = i4 }
|
||||
fun setI5(i5: String) = apply { this.i5 = i5 }
|
||||
|
||||
fun setPeerAllowedIps(ips: List<InetNetwork>) = apply { this.peerAllowedIps = ips }
|
||||
fun addPeer(peer: PeerConfig) = apply { this.additionalPeers += peer }
|
||||
|
||||
override fun build(): WireguardConfig = configBuild().run { WireguardConfig(this@Builder) }
|
||||
}
|
||||
|
||||
|
||||
@@ -488,24 +488,45 @@ QJsonObject ImportController::extractOpenVpnConfig(const QString &data) const
|
||||
|
||||
QJsonObject ImportController::extractWireGuardConfig(const QString &data, ConfigTypes &configType) const
|
||||
{
|
||||
QMap<QString, QString> configMap;
|
||||
auto configByLines = data.split("\n");
|
||||
QMap<QString, QString> interfaceMap;
|
||||
QList<QMap<QString, QString>> peerList;
|
||||
|
||||
enum class WgSection { None, Interface, Peer };
|
||||
WgSection currentSection = WgSection::None;
|
||||
|
||||
const auto configByLines = data.split("\n");
|
||||
for (const QString &line : configByLines) {
|
||||
QString trimmedLine = line.trimmed();
|
||||
if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) {
|
||||
continue;
|
||||
} else {
|
||||
QStringList parts = trimmedLine.split(" = ");
|
||||
const QString trimmedLine = line.trimmed();
|
||||
if (trimmedLine == "[Interface]") {
|
||||
currentSection = WgSection::Interface;
|
||||
} else if (trimmedLine == "[Peer]") {
|
||||
currentSection = WgSection::Peer;
|
||||
peerList.append(QMap<QString, QString>());
|
||||
} else if (!trimmedLine.isEmpty() && !trimmedLine.startsWith("#")) {
|
||||
const QStringList parts = trimmedLine.split(" = ");
|
||||
if (parts.count() == 2) {
|
||||
configMap[parts.at(0).trimmed()] = parts.at(1).trimmed();
|
||||
const QString key = parts.at(0).trimmed();
|
||||
const QString value = parts.at(1).trimmed();
|
||||
if (currentSection == WgSection::Interface) {
|
||||
interfaceMap[key] = value;
|
||||
} else if (currentSection == WgSection::Peer && !peerList.isEmpty()) {
|
||||
peerList.last()[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (peerList.isEmpty()) {
|
||||
qDebug() << "No [Peer] section found in WireGuard config";
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
const QMap<QString, QString> &firstPeerMap = peerList.first();
|
||||
|
||||
QJsonObject lastConfig;
|
||||
lastConfig[configKey::config] = data;
|
||||
|
||||
auto url { QUrl::fromUserInput(configMap.value(protocols::wireguard::Endpoint)) };
|
||||
auto url { QUrl::fromUserInput(firstPeerMap.value(protocols::wireguard::Endpoint)) };
|
||||
QString hostName;
|
||||
QString port;
|
||||
if (!url.host().isEmpty()) {
|
||||
@@ -524,37 +545,55 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data, Config
|
||||
lastConfig[configKey::hostName] = hostName;
|
||||
lastConfig[configKey::port] = port.toInt();
|
||||
|
||||
if (!configMap.value(protocols::wireguard::PrivateKey).isEmpty()
|
||||
&& !configMap.value(protocols::wireguard::Address).isEmpty()
|
||||
&& !configMap.value(protocols::wireguard::PublicKey).isEmpty()) {
|
||||
lastConfig[configKey::clientPrivKey] = configMap.value(protocols::wireguard::PrivateKey);
|
||||
lastConfig[configKey::clientIp] = configMap.value(protocols::wireguard::Address);
|
||||
if (!interfaceMap.value(protocols::wireguard::PrivateKey).isEmpty()
|
||||
&& !interfaceMap.value(protocols::wireguard::Address).isEmpty()
|
||||
&& !firstPeerMap.value(protocols::wireguard::PublicKey).isEmpty()) {
|
||||
lastConfig[configKey::clientPrivKey] = interfaceMap.value(protocols::wireguard::PrivateKey);
|
||||
lastConfig[configKey::clientIp] = interfaceMap.value(protocols::wireguard::Address);
|
||||
|
||||
if (!configMap.value(protocols::wireguard::PresharedKey).isEmpty()) {
|
||||
lastConfig[configKey::pskKey] = configMap.value(protocols::wireguard::PresharedKey);
|
||||
} else if (!configMap.value(protocols::wireguard::PreSharedKey).isEmpty()) {
|
||||
lastConfig[configKey::pskKey] = configMap.value(protocols::wireguard::PreSharedKey);
|
||||
if (!firstPeerMap.value(protocols::wireguard::PresharedKey).isEmpty()) {
|
||||
lastConfig[configKey::pskKey] = firstPeerMap.value(protocols::wireguard::PresharedKey);
|
||||
} else if (!firstPeerMap.value(protocols::wireguard::PreSharedKey).isEmpty()) {
|
||||
lastConfig[configKey::pskKey] = firstPeerMap.value(protocols::wireguard::PreSharedKey);
|
||||
}
|
||||
|
||||
lastConfig[configKey::serverPubKey] = configMap.value(protocols::wireguard::PublicKey);
|
||||
lastConfig[configKey::serverPubKey] = firstPeerMap.value(protocols::wireguard::PublicKey);
|
||||
} else {
|
||||
qDebug() << "One of the key parameters is missing (PrivateKey, Address, PublicKey)";
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
if (!configMap.value(protocols::wireguard::MTU).isEmpty()) {
|
||||
lastConfig[configKey::mtu] = configMap.value(protocols::wireguard::MTU);
|
||||
}
|
||||
|
||||
if (!configMap.value(protocols::wireguard::PersistentKeepalive).isEmpty()) {
|
||||
lastConfig[configKey::persistentKeepAlive] = configMap.value(protocols::wireguard::PersistentKeepalive);
|
||||
if (!firstPeerMap.value(protocols::wireguard::PersistentKeepalive).isEmpty()) {
|
||||
lastConfig[configKey::persistentKeepAlive] = firstPeerMap.value(protocols::wireguard::PersistentKeepalive);
|
||||
}
|
||||
|
||||
QJsonArray allowedIpsJsonArray = QJsonArray::fromStringList(
|
||||
configMap.value(protocols::wireguard::AllowedIPs).split(", "));
|
||||
firstPeerMap.value(protocols::wireguard::AllowedIPs).split(", "));
|
||||
|
||||
lastConfig[configKey::allowedIps] = allowedIpsJsonArray;
|
||||
|
||||
if (peerList.size() > 1) {
|
||||
QJsonArray peersArray;
|
||||
for (const auto &peerMap : std::as_const(peerList)) {
|
||||
QJsonObject peerObj;
|
||||
const auto peerUrl = QUrl::fromUserInput(peerMap.value(protocols::wireguard::Endpoint));
|
||||
peerObj[configKey::serverPubKey] = peerMap.value(protocols::wireguard::PublicKey);
|
||||
if (!peerMap.value(protocols::wireguard::PresharedKey).isEmpty()) {
|
||||
peerObj[configKey::pskKey] = peerMap.value(protocols::wireguard::PresharedKey);
|
||||
} else if (!peerMap.value(protocols::wireguard::PreSharedKey).isEmpty()) {
|
||||
peerObj[configKey::pskKey] = peerMap.value(protocols::wireguard::PreSharedKey);
|
||||
}
|
||||
peerObj[configKey::hostName] = peerUrl.host();
|
||||
peerObj[configKey::port] = peerUrl.port() != -1 ? peerUrl.port() : QString(protocols::wireguard::defaultPort).toInt();
|
||||
peerObj[configKey::allowedIps] = QJsonArray::fromStringList(peerMap.value(protocols::wireguard::AllowedIPs).split(", "));
|
||||
if (!peerMap.value(protocols::wireguard::PersistentKeepalive).isEmpty()) {
|
||||
peerObj[configKey::persistentKeepAlive] = peerMap.value(protocols::wireguard::PersistentKeepalive);
|
||||
}
|
||||
peersArray.append(peerObj);
|
||||
}
|
||||
lastConfig["peers"] = peersArray;
|
||||
}
|
||||
|
||||
QString protocolName = configKey::wireguard;
|
||||
QString protocolVersion;
|
||||
ConfigTypes detectedType = ConfigTypes::WireGuard;
|
||||
@@ -572,25 +611,25 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data, Config
|
||||
};
|
||||
|
||||
bool hasAllRequiredFields = std::all_of(requiredJunkFields.begin(), requiredJunkFields.end(),
|
||||
[&configMap](const QString &field) { return !configMap.value(field).isEmpty(); });
|
||||
[&interfaceMap](const QString &field) { return !interfaceMap.value(field).isEmpty(); });
|
||||
if (hasAllRequiredFields) {
|
||||
for (const QString &field : requiredJunkFields) {
|
||||
lastConfig[field] = configMap.value(field);
|
||||
lastConfig[field] = interfaceMap.value(field);
|
||||
}
|
||||
|
||||
for (const QString &field : optionalJunkFields) {
|
||||
if (!configMap.value(field).isEmpty()) {
|
||||
lastConfig[field] = configMap.value(field);
|
||||
if (!interfaceMap.value(field).isEmpty()) {
|
||||
lastConfig[field] = interfaceMap.value(field);
|
||||
}
|
||||
}
|
||||
|
||||
bool hasCookieReplyPacketJunkSize = !configMap.value(configKey::cookieReplyPacketJunkSize).isEmpty();
|
||||
bool hasTransportPacketJunkSize = !configMap.value(configKey::transportPacketJunkSize).isEmpty();
|
||||
bool hasSpecialJunk = !configMap.value(configKey::specialJunk1).isEmpty() ||
|
||||
!configMap.value(configKey::specialJunk2).isEmpty() ||
|
||||
!configMap.value(configKey::specialJunk3).isEmpty() ||
|
||||
!configMap.value(configKey::specialJunk4).isEmpty() ||
|
||||
!configMap.value(configKey::specialJunk5).isEmpty();
|
||||
bool hasCookieReplyPacketJunkSize = !interfaceMap.value(configKey::cookieReplyPacketJunkSize).isEmpty();
|
||||
bool hasTransportPacketJunkSize = !interfaceMap.value(configKey::transportPacketJunkSize).isEmpty();
|
||||
bool hasSpecialJunk = !interfaceMap.value(configKey::specialJunk1).isEmpty() ||
|
||||
!interfaceMap.value(configKey::specialJunk2).isEmpty() ||
|
||||
!interfaceMap.value(configKey::specialJunk3).isEmpty() ||
|
||||
!interfaceMap.value(configKey::specialJunk4).isEmpty() ||
|
||||
!interfaceMap.value(configKey::specialJunk5).isEmpty();
|
||||
|
||||
if (hasCookieReplyPacketJunkSize && hasTransportPacketJunkSize) {
|
||||
protocolVersion = "2";
|
||||
@@ -601,8 +640,8 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data, Config
|
||||
detectedType = ConfigTypes::Awg;
|
||||
}
|
||||
|
||||
if (!configMap.value(protocols::wireguard::MTU).isEmpty()) {
|
||||
lastConfig[configKey::mtu] = configMap.value(protocols::wireguard::MTU);
|
||||
if (!interfaceMap.value(protocols::wireguard::MTU).isEmpty()) {
|
||||
lastConfig[configKey::mtu] = interfaceMap.value(protocols::wireguard::MTU);
|
||||
} else {
|
||||
lastConfig[configKey::mtu] = (protocolName == configKey::awg)
|
||||
? protocols::awg::defaultMtu
|
||||
|
||||
@@ -205,7 +205,11 @@ QJsonObject AwgClientConfig::toJson() const
|
||||
if (isObfuscationEnabled) {
|
||||
obj[configKey::isObfuscationEnabled] = isObfuscationEnabled;
|
||||
}
|
||||
|
||||
|
||||
if (!peers.isEmpty()) {
|
||||
obj["peers"] = peers;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -250,7 +254,9 @@ AwgClientConfig AwgClientConfig::fromJson(const QJsonObject& json)
|
||||
config.specialJunk5 = json.value(configKey::specialJunk5).toString();
|
||||
|
||||
config.isObfuscationEnabled = json.value(configKey::isObfuscationEnabled).toBool(false);
|
||||
|
||||
|
||||
config.peers = json.value("peers").toArray();
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef AWGPROTOCOLCONFIG_H
|
||||
#define AWGPROTOCOLCONFIG_H
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
@@ -60,6 +61,7 @@ struct AwgClientConfig {
|
||||
QStringList allowedIps;
|
||||
QString persistentKeepAlive;
|
||||
QString mtu;
|
||||
QJsonArray peers;
|
||||
QString junkPacketCount;
|
||||
QString junkPacketMinSize;
|
||||
QString junkPacketMaxSize;
|
||||
|
||||
@@ -441,6 +441,37 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
||||
config.m_specialJunk["I5"] = obj.value("I5").toString();
|
||||
}
|
||||
|
||||
if (obj.contains("primaryPeerAllowedIPAddressRanges") &&
|
||||
obj.value("primaryPeerAllowedIPAddressRanges").isArray()) {
|
||||
for (const QJsonValue& ipVal : obj.value("primaryPeerAllowedIPAddressRanges").toArray()) {
|
||||
if (!ipVal.isObject()) continue;
|
||||
QJsonObject ipObj = ipVal.toObject();
|
||||
config.m_primaryPeerAllowedIPRanges.append(
|
||||
IPAddress(QHostAddress(ipObj.value("address").toString()),
|
||||
ipObj.value("range").toInt()));
|
||||
}
|
||||
}
|
||||
|
||||
if (obj.contains("additionalPeers") && obj.value("additionalPeers").isArray()) {
|
||||
for (const QJsonValue& peerVal : obj.value("additionalPeers").toArray()) {
|
||||
if (!peerVal.isObject()) continue;
|
||||
QJsonObject peerObj = peerVal.toObject();
|
||||
InterfaceConfig::AdditionalPeerConfig peer;
|
||||
peer.m_serverPublicKey = peerObj.value("serverPublicKey").toString();
|
||||
peer.m_serverPskKey = peerObj.value("serverPskKey").toString();
|
||||
peer.m_serverIpv4AddrIn = peerObj.value("serverIpv4AddrIn").toString();
|
||||
peer.m_serverPort = peerObj.value("serverPort").toInt();
|
||||
for (const QJsonValue& ipVal : peerObj.value("allowedIPAddressRanges").toArray()) {
|
||||
if (!ipVal.isObject()) continue;
|
||||
QJsonObject ipObj = ipVal.toObject();
|
||||
peer.m_allowedIPAddressRanges.append(
|
||||
IPAddress(QHostAddress(ipObj.value("address").toString()),
|
||||
ipObj.value("range").toInt()));
|
||||
}
|
||||
config.m_additionalPeers.append(peer);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,9 @@ class InterfaceConfig {
|
||||
int m_serverPort = 0;
|
||||
int m_deviceMTU = 1420;
|
||||
QList<IPAddress> m_allowedIPAddressRanges;
|
||||
// For multi-peer: primary peer's own IPs only (used for UAPI allowed_ips).
|
||||
// Empty for single-peer (falls back to m_allowedIPAddressRanges).
|
||||
QList<IPAddress> m_primaryPeerAllowedIPRanges;
|
||||
QStringList m_excludedAddresses;
|
||||
QStringList m_vpnDisabledApps;
|
||||
QStringList m_allowedDnsServers;
|
||||
@@ -58,6 +61,15 @@ class InterfaceConfig {
|
||||
QString m_transportPacketMagicHeader;
|
||||
QMap<QString, QString> m_specialJunk;
|
||||
|
||||
struct AdditionalPeerConfig {
|
||||
QString m_serverPublicKey;
|
||||
QString m_serverPskKey;
|
||||
QString m_serverIpv4AddrIn;
|
||||
int m_serverPort = 0;
|
||||
QList<IPAddress> m_allowedIPAddressRanges;
|
||||
};
|
||||
QList<AdditionalPeerConfig> m_additionalPeers;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
QString toWgConf(
|
||||
const QMap<QString, QString>& extra = QMap<QString, QString>()) const;
|
||||
|
||||
@@ -169,68 +169,96 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||
|
||||
QJsonArray jsAllowedIPAddesses;
|
||||
|
||||
QJsonArray plainAllowedIP = wgConfig.value(amnezia::configKey::allowedIps).toArray();
|
||||
QJsonArray defaultAllowedIP = { "0.0.0.0/0", "::/0" };
|
||||
auto ipRangeToJson = [](const QString& ipRange) -> QJsonObject {
|
||||
QJsonObject range;
|
||||
const QStringList parts = ipRange.split('/');
|
||||
range.insert("address", parts[0]);
|
||||
range.insert("range", parts.size() > 1 ? parts[1].toInt() : 32);
|
||||
range.insert("isIpv6", ipRange.contains(':'));
|
||||
return range;
|
||||
};
|
||||
|
||||
if (plainAllowedIP != defaultAllowedIP && !plainAllowedIP.isEmpty()) {
|
||||
// Use AllowedIP list from WG config because of higher priority
|
||||
for (auto v : plainAllowedIP) {
|
||||
QString ipRange = v.toString();
|
||||
if (ipRange.split('/').size() > 1){
|
||||
QJsonObject range;
|
||||
range.insert("address", ipRange.split('/')[0]);
|
||||
range.insert("range", atoi(ipRange.split('/')[1].toLocal8Bit()));
|
||||
range.insert("isIpv6", false);
|
||||
jsAllowedIPAddesses.append(range);
|
||||
} else {
|
||||
QJsonObject range;
|
||||
range.insert("address",ipRange);
|
||||
range.insert("range", 32);
|
||||
range.insert("isIpv6", false);
|
||||
jsAllowedIPAddesses.append(range);
|
||||
QJsonArray peersArray = wgConfig.value("peers").toArray();
|
||||
bool isMultiPeer = peersArray.size() > 1;
|
||||
|
||||
if (isMultiPeer) {
|
||||
// Union of all peers' IPs goes into allowedIPAddressRanges (used for route setup).
|
||||
QSet<QString> seenIps;
|
||||
for (const QJsonValue& peerVal : std::as_const(peersArray)) {
|
||||
for (const QJsonValue& ipVal : peerVal.toObject().value(amnezia::configKey::allowedIps).toArray()) {
|
||||
const QString ipRange = ipVal.toString().trimmed();
|
||||
if (seenIps.contains(ipRange)) continue;
|
||||
seenIps.insert(ipRange);
|
||||
jsAllowedIPAddesses.append(ipRangeToJson(ipRange));
|
||||
}
|
||||
}
|
||||
|
||||
// Primary peer's own IPs only — used for UAPI allowed_ips to avoid trie conflicts.
|
||||
QJsonArray primaryPeerIpsJson;
|
||||
for (const QJsonValue& ipVal : peersArray[0].toObject().value(amnezia::configKey::allowedIps).toArray()) {
|
||||
primaryPeerIpsJson.append(ipRangeToJson(ipVal.toString().trimmed()));
|
||||
}
|
||||
json.insert("primaryPeerAllowedIPAddressRanges", primaryPeerIpsJson);
|
||||
|
||||
QJsonArray additionalPeersJson;
|
||||
for (int i = 1; i < peersArray.size(); ++i) {
|
||||
const QJsonObject peerObj = peersArray[i].toObject();
|
||||
QJsonObject additionalPeer;
|
||||
additionalPeer.insert("serverPublicKey", peerObj.value(amnezia::configKey::serverPubKey));
|
||||
additionalPeer.insert("serverPskKey", peerObj.value(amnezia::configKey::pskKey));
|
||||
additionalPeer.insert("serverIpv4AddrIn", peerObj.value(amnezia::configKey::hostName));
|
||||
additionalPeer.insert("serverPort", peerObj.value(amnezia::configKey::port).toInt());
|
||||
QJsonArray additionalPeerIps;
|
||||
for (const QJsonValue& ipVal : peerObj.value(amnezia::configKey::allowedIps).toArray()) {
|
||||
additionalPeerIps.append(ipRangeToJson(ipVal.toString().trimmed()));
|
||||
}
|
||||
additionalPeer.insert("allowedIPAddressRanges", additionalPeerIps);
|
||||
additionalPeersJson.append(additionalPeer);
|
||||
}
|
||||
json.insert("additionalPeers", additionalPeersJson);
|
||||
} else {
|
||||
QJsonArray plainAllowedIP = wgConfig.value(amnezia::configKey::allowedIps).toArray();
|
||||
QJsonArray defaultAllowedIP = { "0.0.0.0/0", "::/0" };
|
||||
|
||||
// Use APP split tunnel
|
||||
if (plainAllowedIP != defaultAllowedIP && !plainAllowedIP.isEmpty()) {
|
||||
// Use AllowedIP list from WG config because of higher priority
|
||||
for (auto v : plainAllowedIP) {
|
||||
jsAllowedIPAddesses.append(ipRangeToJson(v.toString().trimmed()));
|
||||
}
|
||||
} else {
|
||||
// Use APP split tunnel
|
||||
if (splitTunnelType == 0 || splitTunnelType == 2) {
|
||||
QJsonObject range_ipv4;
|
||||
range_ipv4.insert("address", "0.0.0.0");
|
||||
range_ipv4.insert("range", 0);
|
||||
range_ipv4.insert("isIpv6", false);
|
||||
jsAllowedIPAddesses.append(range_ipv4);
|
||||
QJsonObject range_ipv4;
|
||||
range_ipv4.insert("address", "0.0.0.0");
|
||||
range_ipv4.insert("range", 0);
|
||||
range_ipv4.insert("isIpv6", false);
|
||||
jsAllowedIPAddesses.append(range_ipv4);
|
||||
|
||||
QJsonObject range_ipv6;
|
||||
range_ipv6.insert("address", "::");
|
||||
range_ipv6.insert("range", 0);
|
||||
range_ipv6.insert("isIpv6", true);
|
||||
jsAllowedIPAddesses.append(range_ipv6);
|
||||
QJsonObject range_ipv6;
|
||||
range_ipv6.insert("address", "::");
|
||||
range_ipv6.insert("range", 0);
|
||||
range_ipv6.insert("isIpv6", true);
|
||||
jsAllowedIPAddesses.append(range_ipv6);
|
||||
}
|
||||
|
||||
if (splitTunnelType == 1) {
|
||||
for (auto v : splitTunnelSites) {
|
||||
QString ipRange = v.toString();
|
||||
if (ipRange.split('/').size() > 1){
|
||||
QJsonObject range;
|
||||
range.insert("address", ipRange.split('/')[0]);
|
||||
range.insert("range", atoi(ipRange.split('/')[1].toLocal8Bit()));
|
||||
range.insert("isIpv6", false);
|
||||
jsAllowedIPAddesses.append(range);
|
||||
} else {
|
||||
QJsonObject range;
|
||||
range.insert("address",ipRange);
|
||||
range.insert("range", 32);
|
||||
range.insert("isIpv6", false);
|
||||
jsAllowedIPAddesses.append(range);
|
||||
}
|
||||
}
|
||||
for (auto v : splitTunnelSites) {
|
||||
jsAllowedIPAddesses.append(ipRangeToJson(v.toString().trimmed()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
json.insert("allowedIPAddressRanges", jsAllowedIPAddesses);
|
||||
|
||||
QJsonArray jsExcludedAddresses;
|
||||
jsExcludedAddresses.append(wgConfig.value(amnezia::configKey::hostName));
|
||||
if (isMultiPeer) {
|
||||
for (const QJsonValue& peerVal : std::as_const(peersArray)) {
|
||||
jsExcludedAddresses.append(peerVal.toObject().value(amnezia::configKey::hostName));
|
||||
}
|
||||
} else {
|
||||
jsExcludedAddresses.append(wgConfig.value(amnezia::configKey::hostName));
|
||||
}
|
||||
if (splitTunnelType == 2) {
|
||||
for (auto v : splitTunnelSites) {
|
||||
QString ipRange = v.toString();
|
||||
|
||||
@@ -20,7 +20,7 @@ extension PacketTunnelProvider {
|
||||
|
||||
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: wgConfigStr)
|
||||
|
||||
if tunnelConfiguration.peers.first!.allowedIPs
|
||||
if tunnelConfiguration.peers.first?.allowedIPs
|
||||
.map({ $0.stringRepresentation })
|
||||
.joined(separator: ", ") == "0.0.0.0/0, ::/0" {
|
||||
if wgConfig.splitTunnelType == 1 {
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
import Foundation
|
||||
|
||||
struct WGPeerConfig: Decodable {
|
||||
let serverPublicKey: String
|
||||
let presharedKey: String?
|
||||
let allowedIPs: [String]
|
||||
let hostName: String
|
||||
let port: Int
|
||||
let persistentKeepAlive: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case serverPublicKey = "server_pub_key"
|
||||
case presharedKey = "psk_key"
|
||||
case allowedIPs = "allowed_ips"
|
||||
case hostName
|
||||
case port
|
||||
case persistentKeepAlive = "persistent_keep_alive"
|
||||
}
|
||||
}
|
||||
|
||||
struct WGConfig: Decodable {
|
||||
let initPacketMagicHeader, responsePacketMagicHeader: String?
|
||||
let underloadPacketMagicHeader, transportPacketMagicHeader: String?
|
||||
@@ -19,6 +37,7 @@ struct WGConfig: Decodable {
|
||||
var persistentKeepAlive: String
|
||||
let splitTunnelType: Int
|
||||
let splitTunnelSites: [String]
|
||||
let peers: [WGPeerConfig]?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case initPacketMagicHeader = "H1", responsePacketMagicHeader = "H2"
|
||||
@@ -39,6 +58,7 @@ struct WGConfig: Decodable {
|
||||
case persistentKeepAlive = "persistent_keep_alive"
|
||||
case splitTunnelType
|
||||
case splitTunnelSites
|
||||
case peers
|
||||
}
|
||||
|
||||
var settings: String {
|
||||
@@ -103,7 +123,7 @@ struct WGConfig: Decodable {
|
||||
return settingsLines.joined(separator: "\n")
|
||||
}
|
||||
|
||||
var str: String {
|
||||
private var interfaceSection: String {
|
||||
"""
|
||||
[Interface]
|
||||
Address = \(clientIP)
|
||||
@@ -111,9 +131,30 @@ struct WGConfig: Decodable {
|
||||
MTU = \(mtu)
|
||||
PrivateKey = \(clientPrivateKey)
|
||||
\(settings)
|
||||
"""
|
||||
}
|
||||
|
||||
var str: String {
|
||||
if let peers = peers, !peers.isEmpty {
|
||||
let peerSections = peers.map { peer -> String in
|
||||
var lines = ["[Peer]", "PublicKey = \(peer.serverPublicKey)"]
|
||||
if let psk = peer.presharedKey, !psk.isEmpty {
|
||||
lines.append("PresharedKey = \(psk)")
|
||||
}
|
||||
lines.append("AllowedIPs = \(peer.allowedIPs.joined(separator: ", "))")
|
||||
lines.append("Endpoint = \(peer.hostName):\(peer.port)")
|
||||
if let ka = peer.persistentKeepAlive {
|
||||
lines.append("PersistentKeepalive = \(ka)")
|
||||
}
|
||||
return lines.joined(separator: "\n")
|
||||
}.joined(separator: "\n")
|
||||
return interfaceSection + "\n" + peerSections
|
||||
}
|
||||
return """
|
||||
\(interfaceSection)
|
||||
[Peer]
|
||||
PublicKey = \(serverPublicKey)
|
||||
\(presharedKey == nil ? "" : "PresharedKey = \(presharedKey!)")
|
||||
\((presharedKey?.isEmpty ?? true) ? "" : "PresharedKey = \(presharedKey!)")
|
||||
AllowedIPs = \(allowedIPs.joined(separator: ", "))
|
||||
Endpoint = \(hostName):\(port)
|
||||
PersistentKeepalive = \(persistentKeepAlive)
|
||||
@@ -121,19 +162,21 @@ struct WGConfig: Decodable {
|
||||
}
|
||||
|
||||
var redux: String {
|
||||
"""
|
||||
let peerCount = peers?.count ?? 1
|
||||
let peerInfo = peers.map { peers in
|
||||
peers.enumerated().map { i, peer in
|
||||
"[Peer \(i + 1)] Endpoint = \(peer.hostName):\(peer.port), AllowedIPs = \(peer.allowedIPs.joined(separator: ", "))"
|
||||
}.joined(separator: "\n")
|
||||
} ?? "Endpoint = \(hostName):\(port), AllowedIPs = \(allowedIPs.joined(separator: ", "))"
|
||||
return """
|
||||
[Interface]
|
||||
Address = \(clientIP)
|
||||
DNS = \(dns1), \(dns2)
|
||||
MTU = \(mtu)
|
||||
PrivateKey = ***
|
||||
\(settings)
|
||||
[Peer]
|
||||
PublicKey = ***
|
||||
PresharedKey = ***
|
||||
AllowedIPs = \(allowedIPs.joined(separator: ", "))
|
||||
Endpoint = \(hostName):\(port)
|
||||
PersistentKeepalive = \(persistentKeepAlive)
|
||||
PeerCount = \(peerCount)
|
||||
\(peerInfo)
|
||||
|
||||
SplitTunnelType = \(splitTunnelType)
|
||||
SplitTunnelSites = \(splitTunnelSites.joined(separator: ", "))
|
||||
|
||||
@@ -595,6 +595,10 @@ bool IosController::setupWireGuard()
|
||||
wgConfig.insert(configKey::persistentKeepAlive, "25");
|
||||
}
|
||||
|
||||
if (config.contains("peers") && config["peers"].isArray()) {
|
||||
wgConfig.insert("peers", config["peers"]);
|
||||
}
|
||||
|
||||
if (config.contains(configKey::isObfuscationEnabled) && config.value(configKey::isObfuscationEnabled).toBool()) {
|
||||
wgConfig.insert(configKey::initPacketMagicHeader, config[configKey::initPacketMagicHeader]);
|
||||
wgConfig.insert(configKey::responsePacketMagicHeader, config[configKey::responsePacketMagicHeader]);
|
||||
@@ -674,7 +678,29 @@ bool IosController::setupAwg()
|
||||
|
||||
wgConfig.insert(configKey::hostName, config[configKey::hostName]);
|
||||
wgConfig.insert(configKey::port, config[configKey::port]);
|
||||
wgConfig.insert(configKey::clientIp, config[configKey::clientIp]);
|
||||
|
||||
bool isMultiPeer = config.contains("peers") && config["peers"].isArray()
|
||||
&& !config["peers"].toArray().isEmpty();
|
||||
|
||||
if (isMultiPeer) {
|
||||
// Use only the first client IP (peer 1's IP)
|
||||
QString fullClientIp = config[configKey::clientIp].toString();
|
||||
QStringList ipList = fullClientIp.split(",");
|
||||
QString firstClientIp = ipList.isEmpty() ? fullClientIp : ipList.first().trimmed();
|
||||
wgConfig.insert(configKey::clientIp, firstClientIp);
|
||||
// Route all traffic through peer 1
|
||||
QJsonArray allowed_ips { "0.0.0.0/0", "::/0" };
|
||||
wgConfig.insert(configKey::allowedIps, allowed_ips);
|
||||
} else {
|
||||
wgConfig.insert(configKey::clientIp, config[configKey::clientIp]);
|
||||
if (config.contains(configKey::allowedIps) && config[configKey::allowedIps].isArray()) {
|
||||
wgConfig.insert(configKey::allowedIps, config[configKey::allowedIps]);
|
||||
} else {
|
||||
QJsonArray allowed_ips { "0.0.0.0/0", "::/0" };
|
||||
wgConfig.insert(configKey::allowedIps, allowed_ips);
|
||||
}
|
||||
}
|
||||
|
||||
wgConfig.insert(configKey::clientPrivKey, config[configKey::clientPrivKey]);
|
||||
wgConfig.insert(configKey::serverPubKey, config[configKey::serverPubKey]);
|
||||
wgConfig.insert(configKey::pskKey, config[configKey::pskKey]);
|
||||
@@ -688,13 +714,6 @@ bool IosController::setupAwg()
|
||||
|
||||
wgConfig.insert(configKey::splitTunnelSites, splitTunnelSites);
|
||||
|
||||
if (config.contains(configKey::allowedIps) && config[configKey::allowedIps].isArray()) {
|
||||
wgConfig.insert(configKey::allowedIps, config[configKey::allowedIps]);
|
||||
} else {
|
||||
QJsonArray allowed_ips { "0.0.0.0/0", "::/0" };
|
||||
wgConfig.insert(configKey::allowedIps, allowed_ips);
|
||||
}
|
||||
|
||||
if (config.contains(configKey::persistentKeepAlive)) {
|
||||
wgConfig.insert(configKey::persistentKeepAlive, config[configKey::persistentKeepAlive]);
|
||||
} else {
|
||||
|
||||
@@ -230,7 +230,10 @@ bool WireguardUtilsLinux::updatePeer(const InterfaceConfig& config) {
|
||||
|
||||
out << "replace_allowed_ips=true\n";
|
||||
out << "persistent_keepalive_interval=" << WG_KEEPALIVE_PERIOD << "\n";
|
||||
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||
const QList<IPAddress>& primaryIPs = config.m_primaryPeerAllowedIPRanges.isEmpty()
|
||||
? config.m_allowedIPAddressRanges
|
||||
: config.m_primaryPeerAllowedIPRanges;
|
||||
for (const IPAddress& ip : primaryIPs) {
|
||||
out << "allowed_ip=" << ip.toString() << "\n";
|
||||
}
|
||||
|
||||
@@ -244,8 +247,38 @@ bool WireguardUtilsLinux::updatePeer(const InterfaceConfig& config) {
|
||||
int err = uapiErrno(uapiCommand(message));
|
||||
if (err != 0) {
|
||||
logger.error() << "Peer configuration failed:" << strerror(err);
|
||||
return false;
|
||||
}
|
||||
return (err == 0);
|
||||
|
||||
for (const InterfaceConfig::AdditionalPeerConfig& peer : config.m_additionalPeers) {
|
||||
QByteArray pubKey = QByteArray::fromBase64(peer.m_serverPublicKey.toUtf8());
|
||||
QByteArray pskKey = QByteArray::fromBase64(peer.m_serverPskKey.toUtf8());
|
||||
|
||||
QString peerMsg;
|
||||
QTextStream peerOut(&peerMsg);
|
||||
peerOut << "set=1\n";
|
||||
peerOut << "public_key=" << QString(pubKey.toHex()) << "\n";
|
||||
if (!peer.m_serverPskKey.isEmpty()) {
|
||||
peerOut << "preshared_key=" << QString(pskKey.toHex()) << "\n";
|
||||
}
|
||||
peerOut << "endpoint=" << peer.m_serverIpv4AddrIn << ":" << peer.m_serverPort << "\n";
|
||||
peerOut << "replace_allowed_ips=true\n";
|
||||
peerOut << "persistent_keepalive_interval=" << WG_KEEPALIVE_PERIOD << "\n";
|
||||
for (const IPAddress& ip : peer.m_allowedIPAddressRanges) {
|
||||
peerOut << "allowed_ip=" << ip.toString() << "\n";
|
||||
}
|
||||
|
||||
if ((config.m_hopType != InterfaceConfig::MultiHopExit) && m_rtmonitor) {
|
||||
m_rtmonitor->addExclusionRoute(IPAddress(peer.m_serverIpv4AddrIn));
|
||||
}
|
||||
|
||||
int peerErr = uapiErrno(uapiCommand(peerMsg));
|
||||
if (peerErr != 0) {
|
||||
logger.error() << "Additional peer configuration failed:" << strerror(peerErr);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WireguardUtilsLinux::deletePeer(const InterfaceConfig& config) {
|
||||
|
||||
@@ -80,7 +80,9 @@ bool IPUtilsMacos::setMTUAndUp(const InterfaceConfig& config) {
|
||||
}
|
||||
|
||||
bool IPUtilsMacos::addIP4AddressToDevice(const InterfaceConfig& config) {
|
||||
Q_UNUSED(config);
|
||||
if (config.m_deviceIpv4Address.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
QString ifname = MacOSDaemon::instance()->m_wgutils->interfaceName();
|
||||
struct ifaliasreq ifr;
|
||||
struct sockaddr_in* ifrAddr = (struct sockaddr_in*)&ifr.ifra_addr;
|
||||
@@ -91,25 +93,28 @@ bool IPUtilsMacos::addIP4AddressToDevice(const InterfaceConfig& config) {
|
||||
memset(&ifr, 0, sizeof(ifr));
|
||||
strncpy(ifr.ifra_name, qPrintable(ifname), IFNAMSIZ);
|
||||
|
||||
// Get the device address to add to interface
|
||||
QPair<QHostAddress, int> parsedAddr =
|
||||
QHostAddress::parseSubnet(config.m_deviceIpv4Address);
|
||||
QByteArray _deviceAddr = parsedAddr.first.toString().toLocal8Bit();
|
||||
// Extract the host IP from CIDR notation (e.g. "10.8.0.2/24" → "10.8.0.2").
|
||||
// parseSubnet() zeroes host bits so we split manually to preserve the host address.
|
||||
QByteArray _deviceAddr = config.m_deviceIpv4Address.split('/').first().toLocal8Bit();
|
||||
char* deviceAddr = _deviceAddr.data();
|
||||
ifrAddr->sin_family = AF_INET;
|
||||
ifrAddr->sin_len = sizeof(struct sockaddr_in);
|
||||
inet_pton(AF_INET, deviceAddr, &ifrAddr->sin_addr);
|
||||
if (inet_pton(AF_INET, deviceAddr, &ifrAddr->sin_addr) != 1) {
|
||||
logger.error() << "Failed to parse IPv4 address:" << deviceAddr;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set the netmask to /32
|
||||
ifrMask->sin_family = AF_INET;
|
||||
ifrMask->sin_len = sizeof(struct sockaddr_in);
|
||||
memset(&ifrMask->sin_addr, 0xff, sizeof(ifrMask->sin_addr));
|
||||
|
||||
// Set the broadcast address.
|
||||
// For P2P (utun) interfaces, ifra_broadaddr is the destination address.
|
||||
// Set it equal to the local address to create only a host route (not a network
|
||||
// route that would cause a routing loop).
|
||||
ifrBcast->sin_family = AF_INET;
|
||||
ifrBcast->sin_len = sizeof(struct sockaddr_in);
|
||||
ifrBcast->sin_addr.s_addr =
|
||||
(ifrAddr->sin_addr.s_addr | ~ifrMask->sin_addr.s_addr);
|
||||
ifrBcast->sin_addr.s_addr = ifrAddr->sin_addr.s_addr;
|
||||
|
||||
// Create an IPv4 socket to perform the ioctl operations on
|
||||
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
|
||||
@@ -230,7 +230,11 @@ bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) {
|
||||
|
||||
out << "replace_allowed_ips=true\n";
|
||||
out << "persistent_keepalive_interval=" << WG_KEEPALIVE_PERIOD << "\n";
|
||||
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||
// For multi-peer use only the primary peer's own IPs to avoid routing trie conflicts.
|
||||
const QList<IPAddress>& primaryIPs = config.m_primaryPeerAllowedIPRanges.isEmpty()
|
||||
? config.m_allowedIPAddressRanges
|
||||
: config.m_primaryPeerAllowedIPRanges;
|
||||
for (const IPAddress& ip : primaryIPs) {
|
||||
out << "allowed_ip=" << ip.toString() << "\n";
|
||||
}
|
||||
|
||||
@@ -244,8 +248,38 @@ bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) {
|
||||
int err = uapiErrno(uapiCommand(message));
|
||||
if (err != 0) {
|
||||
logger.error() << "Peer configuration failed:" << strerror(err);
|
||||
return false;
|
||||
}
|
||||
return (err == 0);
|
||||
|
||||
for (const InterfaceConfig::AdditionalPeerConfig& peer : config.m_additionalPeers) {
|
||||
QByteArray pubKey = QByteArray::fromBase64(peer.m_serverPublicKey.toUtf8());
|
||||
QByteArray pskKey = QByteArray::fromBase64(peer.m_serverPskKey.toUtf8());
|
||||
|
||||
QString peerMsg;
|
||||
QTextStream peerOut(&peerMsg);
|
||||
peerOut << "set=1\n";
|
||||
peerOut << "public_key=" << QString(pubKey.toHex()) << "\n";
|
||||
if (!peer.m_serverPskKey.isEmpty()) {
|
||||
peerOut << "preshared_key=" << QString(pskKey.toHex()) << "\n";
|
||||
}
|
||||
peerOut << "endpoint=" << peer.m_serverIpv4AddrIn << ":" << peer.m_serverPort << "\n";
|
||||
peerOut << "replace_allowed_ips=true\n";
|
||||
peerOut << "persistent_keepalive_interval=" << WG_KEEPALIVE_PERIOD << "\n";
|
||||
for (const IPAddress& ip : peer.m_allowedIPAddressRanges) {
|
||||
peerOut << "allowed_ip=" << ip.toString() << "\n";
|
||||
}
|
||||
|
||||
if ((config.m_hopType != InterfaceConfig::MultiHopExit) && m_rtmonitor) {
|
||||
m_rtmonitor->addExclusionRoute(IPAddress(peer.m_serverIpv4AddrIn));
|
||||
}
|
||||
|
||||
int peerErr = uapiErrno(uapiCommand(peerMsg));
|
||||
if (peerErr != 0) {
|
||||
logger.error() << "Additional peer configuration failed:" << strerror(peerErr);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WireguardUtilsMacos::deletePeer(const InterfaceConfig& config) {
|
||||
|
||||
@@ -181,7 +181,10 @@ bool WireguardUtilsWindows::updatePeer(const InterfaceConfig& config) {
|
||||
|
||||
out << "replace_allowed_ips=true\n";
|
||||
out << "persistent_keepalive_interval=" << WG_KEEPALIVE_PERIOD << "\n";
|
||||
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||
const QList<IPAddress>& primaryIPs = config.m_primaryPeerAllowedIPRanges.isEmpty()
|
||||
? config.m_allowedIPAddressRanges
|
||||
: config.m_primaryPeerAllowedIPRanges;
|
||||
for (const IPAddress& ip : primaryIPs) {
|
||||
out << "allowed_ip=" << ip.toString() << "\n";
|
||||
}
|
||||
|
||||
@@ -193,6 +196,33 @@ bool WireguardUtilsWindows::updatePeer(const InterfaceConfig& config) {
|
||||
|
||||
QString reply = m_tunnel.uapiCommand(message);
|
||||
logger.debug() << "DATA:" << reply;
|
||||
|
||||
for (const InterfaceConfig::AdditionalPeerConfig& peer : config.m_additionalPeers) {
|
||||
QByteArray pubKey = QByteArray::fromBase64(peer.m_serverPublicKey.toUtf8());
|
||||
QByteArray pskKey = QByteArray::fromBase64(peer.m_serverPskKey.toUtf8());
|
||||
|
||||
QString peerMsg;
|
||||
QTextStream peerOut(&peerMsg);
|
||||
peerOut << "set=1\n";
|
||||
peerOut << "public_key=" << QString(pubKey.toHex()) << "\n";
|
||||
if (!peer.m_serverPskKey.isEmpty()) {
|
||||
peerOut << "preshared_key=" << QString(pskKey.toHex()) << "\n";
|
||||
}
|
||||
peerOut << "endpoint=" << peer.m_serverIpv4AddrIn << ":" << peer.m_serverPort << "\n";
|
||||
peerOut << "replace_allowed_ips=true\n";
|
||||
peerOut << "persistent_keepalive_interval=" << WG_KEEPALIVE_PERIOD << "\n";
|
||||
for (const IPAddress& ip : peer.m_allowedIPAddressRanges) {
|
||||
peerOut << "allowed_ip=" << ip.toString() << "\n";
|
||||
}
|
||||
|
||||
if (m_routeMonitor && config.m_hopType != InterfaceConfig::MultiHopExit) {
|
||||
m_routeMonitor->addExclusionRoute(IPAddress(peer.m_serverIpv4AddrIn));
|
||||
}
|
||||
|
||||
QString peerReply = m_tunnel.uapiCommand(peerMsg);
|
||||
logger.debug() << "Additional peer DATA:" << peerReply;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user