diff --git a/client/daemon/daemon.cpp b/client/daemon/daemon.cpp index e74a613f5..c77c90b74 100644 --- a/client/daemon/daemon.cpp +++ b/client/daemon/daemon.cpp @@ -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; } diff --git a/client/daemon/interfaceconfig.h b/client/daemon/interfaceconfig.h index 71f326355..fe9ac17c6 100644 --- a/client/daemon/interfaceconfig.h +++ b/client/daemon/interfaceconfig.h @@ -37,6 +37,9 @@ class InterfaceConfig { int m_serverPort = 0; int m_deviceMTU = 1420; QList 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 m_primaryPeerAllowedIPRanges; QStringList m_excludedAddresses; QStringList m_vpnDisabledApps; QStringList m_allowedDnsServers; @@ -58,6 +61,15 @@ class InterfaceConfig { QString m_transportPacketMagicHeader; QMap m_specialJunk; + struct AdditionalPeerConfig { + QString m_serverPublicKey; + QString m_serverPskKey; + QString m_serverIpv4AddrIn; + int m_serverPort = 0; + QList m_allowedIPAddressRanges; + }; + QList m_additionalPeers; + QJsonObject toJson() const; QString toWgConf( const QMap& extra = QMap()) const; diff --git a/client/mozilla/localsocketcontroller.cpp b/client/mozilla/localsocketcontroller.cpp index 17bf5a5e9..30791dbbc 100644 --- a/client/mozilla/localsocketcontroller.cpp +++ b/client/mozilla/localsocketcontroller.cpp @@ -166,68 +166,96 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) { QJsonArray jsAllowedIPAddesses; - QJsonArray plainAllowedIP = wgConfig.value(amnezia::config_key::allowed_ips).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 seenIps; + for (const QJsonValue& peerVal : std::as_const(peersArray)) { + for (const QJsonValue& ipVal : peerVal.toObject().value(amnezia::config_key::allowed_ips).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::config_key::allowed_ips).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::config_key::server_pub_key)); + additionalPeer.insert("serverPskKey", peerObj.value(amnezia::config_key::psk_key)); + additionalPeer.insert("serverIpv4AddrIn", peerObj.value(amnezia::config_key::hostName)); + additionalPeer.insert("serverPort", peerObj.value(amnezia::config_key::port).toInt()); + QJsonArray additionalPeerIps; + for (const QJsonValue& ipVal : peerObj.value(amnezia::config_key::allowed_ips).toArray()) { + additionalPeerIps.append(ipRangeToJson(ipVal.toString().trimmed())); + } + additionalPeer.insert("allowedIPAddressRanges", additionalPeerIps); + additionalPeersJson.append(additionalPeer); + } + json.insert("additionalPeers", additionalPeersJson); } else { + QJsonArray plainAllowedIP = wgConfig.value(amnezia::config_key::allowed_ips).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::config_key::hostName)); + if (isMultiPeer) { + for (const QJsonValue& peerVal : std::as_const(peersArray)) { + jsExcludedAddresses.append(peerVal.toObject().value(amnezia::config_key::hostName)); + } + } else { + jsExcludedAddresses.append(wgConfig.value(amnezia::config_key::hostName)); + } if (splitTunnelType == 2) { for (auto v : splitTunnelSites) { QString ipRange = v.toString(); diff --git a/client/platforms/ios/WGConfig.swift b/client/platforms/ios/WGConfig.swift index 85f37b76c..cf0c742f7 100644 --- a/client/platforms/ios/WGConfig.swift +++ b/client/platforms/ios/WGConfig.swift @@ -154,7 +154,7 @@ struct WGConfig: Decodable { \(interfaceSection) [Peer] PublicKey = \(serverPublicKey) - \(presharedKey == nil ? "" : "PresharedKey = \(presharedKey!)") + \((presharedKey?.isEmpty ?? true) ? "" : "PresharedKey = \(presharedKey!)") AllowedIPs = \(allowedIPs.joined(separator: ", ")) Endpoint = \(hostName):\(port) PersistentKeepalive = \(persistentKeepAlive) diff --git a/client/platforms/linux/daemon/iputilslinux.cpp b/client/platforms/linux/daemon/iputilslinux.cpp index 25d4f631e..0e70f4e30 100644 --- a/client/platforms/linux/daemon/iputilslinux.cpp +++ b/client/platforms/linux/daemon/iputilslinux.cpp @@ -5,8 +5,12 @@ #include "iputilslinux.h" #include +#include +#include +#include #include #include +#include #include #include @@ -71,39 +75,104 @@ bool IPUtilsLinux::setMTUAndUp(const InterfaceConfig& config) { return true; } -bool IPUtilsLinux::addIP4AddressToDevice(const InterfaceConfig& config) { - struct ifreq ifr; - struct sockaddr_in* ifrAddr = (struct sockaddr_in*)&ifr.ifr_addr; +static bool addIPv4AddressNetlink(int ifindex, const QHostAddress& addr, + int prefixlen) { + int nlsock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (nlsock < 0) return false; + auto guard = qScopeGuard([&] { close(nlsock); }); - // Name the interface and set family - strncpy(ifr.ifr_name, WG_INTERFACE, IFNAMSIZ); - ifr.ifr_addr.sa_family = AF_INET; + char buf[512]; + memset(buf, 0, sizeof(buf)); - // Get the device address to add to interface - QPair parsedAddr = - QHostAddress::parseSubnet(config.m_deviceIpv4Address); - QByteArray _deviceAddr = parsedAddr.first.toString().toLocal8Bit(); - char* deviceAddr = _deviceAddr.data(); - inet_pton(AF_INET, deviceAddr, &ifrAddr->sin_addr); + struct nlmsghdr* nlmsg = reinterpret_cast(buf); + nlmsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); + nlmsg->nlmsg_type = RTM_NEWADDR; + nlmsg->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK; + nlmsg->nlmsg_seq = 1; + nlmsg->nlmsg_pid = 0; - // Create IPv4 socket to perform the ioctl operations on - int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); - if (sockfd < 0) { - logger.error() << "Failed to create ioctl socket."; + struct ifaddrmsg* ifa = static_cast(NLMSG_DATA(nlmsg)); + ifa->ifa_family = AF_INET; + ifa->ifa_prefixlen = prefixlen; + ifa->ifa_flags = IFA_F_PERMANENT; + ifa->ifa_scope = RT_SCOPE_UNIVERSE; + ifa->ifa_index = ifindex; + + struct in_addr ip4; + QByteArray addrBytes = addr.toString().toLocal8Bit(); + inet_pton(AF_INET, addrBytes.constData(), &ip4); + + auto appendAttr = [](struct nlmsghdr* nlmsg, size_t maxlen, int type, + const void* data, size_t len) { + size_t newlen = NLMSG_ALIGN(nlmsg->nlmsg_len) + RTA_SPACE(len); + if (newlen > maxlen) return; + char* p = reinterpret_cast(nlmsg) + NLMSG_ALIGN(nlmsg->nlmsg_len); + struct rtattr* rta = reinterpret_cast(p); + rta->rta_type = type; + rta->rta_len = RTA_LENGTH(len); + memcpy(RTA_DATA(rta), data, len); + nlmsg->nlmsg_len = newlen; + }; + + appendAttr(nlmsg, sizeof(buf), IFA_LOCAL, &ip4, sizeof(ip4)); + appendAttr(nlmsg, sizeof(buf), IFA_ADDRESS, &ip4, sizeof(ip4)); + + struct sockaddr_nl nladdr; + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + + if (sendto(nlsock, buf, nlmsg->nlmsg_len, 0, + reinterpret_cast(&nladdr), + sizeof(nladdr)) < 0) { return false; } - auto guard = qScopeGuard([&] { close(sockfd); }); - // Set ifr to interface - int ret = ioctl(sockfd, SIOCSIFADDR, &ifr); - if (ret) { - logger.error() << "Failed to set IPv4: " << deviceAddr - << "error:" << strerror(errno); - return false; + char ackbuf[1024]; + ssize_t acklen = recv(nlsock, ackbuf, sizeof(ackbuf), 0); + if (acklen >= static_cast(sizeof(struct nlmsghdr))) { + struct nlmsghdr* ackmsg = reinterpret_cast(ackbuf); + if (ackmsg->nlmsg_type == NLMSG_ERROR) { + struct nlmsgerr* err = static_cast(NLMSG_DATA(ackmsg)); + if (err->error != 0) { + errno = -err->error; + return false; + } + } } return true; } +bool IPUtilsLinux::addIP4AddressToDevice(const InterfaceConfig& config) { + if (config.m_deviceIpv4Address.isEmpty()) return true; + + int ifindex = if_nametoindex(WG_INTERFACE); + if (ifindex == 0) { + logger.error() << "Failed to get ifindex for" << WG_INTERFACE; + return false; + } + + bool ok = false; + const QStringList addresses = + config.m_deviceIpv4Address.split(',', Qt::SkipEmptyParts); + for (const QString& entry : addresses) { + QPair parsed = + QHostAddress::parseSubnet(entry.trimmed()); + if (parsed.first.isNull()) { + logger.warning() << "Failed to parse IPv4 address:" << entry.trimmed(); + continue; + } + if (!addIPv4AddressNetlink(ifindex, parsed.first, parsed.second)) { + logger.error() << "Failed to add IPv4" << parsed.first.toString() << "/" + << parsed.second << ":" << strerror(errno); + } else { + logger.debug() << "Added IPv4" << parsed.first.toString() << "/" + << parsed.second << "to" << WG_INTERFACE; + ok = true; + } + } + return ok; +} + bool IPUtilsLinux::addIP6AddressToDevice(const InterfaceConfig& config) { // Set up the ifr and the companion ifr6 struct in6_ifreq ifr6; diff --git a/client/platforms/linux/daemon/wireguardutilslinux.cpp b/client/platforms/linux/daemon/wireguardutilslinux.cpp index 1b7cddc8e..9d7dc8008 100644 --- a/client/platforms/linux/daemon/wireguardutilslinux.cpp +++ b/client/platforms/linux/daemon/wireguardutilslinux.cpp @@ -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& 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) { diff --git a/client/platforms/macos/daemon/iputilsmacos.cpp b/client/platforms/macos/daemon/iputilsmacos.cpp index 901436ae7..509823e70 100644 --- a/client/platforms/macos/daemon/iputilsmacos.cpp +++ b/client/platforms/macos/daemon/iputilsmacos.cpp @@ -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 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); diff --git a/client/platforms/macos/daemon/wireguardutilsmacos.cpp b/client/platforms/macos/daemon/wireguardutilsmacos.cpp index 55a5526be..a9b063252 100644 --- a/client/platforms/macos/daemon/wireguardutilsmacos.cpp +++ b/client/platforms/macos/daemon/wireguardutilsmacos.cpp @@ -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& 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) { diff --git a/client/platforms/windows/daemon/wireguardutilswindows.cpp b/client/platforms/windows/daemon/wireguardutilswindows.cpp index a5c9c84d1..79a0aeb08 100644 --- a/client/platforms/windows/daemon/wireguardutilswindows.cpp +++ b/client/platforms/windows/daemon/wireguardutilswindows.cpp @@ -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& 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; }