diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index 3997ed02c..6d59e6d01 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -88,8 +88,14 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state) QString dns1 = m_vpnConfiguration.value(config_key::dns1).toString(); QString dns2 = m_vpnConfiguration.value(config_key::dns2).toString(); +#ifdef Q_OS_MACOS + if (!m_settings->isSitesSplitTunnelingEnabled() || m_settings->routeMode() != Settings::VpnAllExceptSites) { +#endif // TODO: add error code handling for all routeAddList (or rework the code below) iface->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << dns1 << dns2); +#ifdef Q_OS_MACOS + } +#endif if (m_settings->isSitesSplitTunnelingEnabled()) { iface->routeDeleteList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0"); @@ -102,6 +108,9 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state) iface->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "128.0.0.0/1"); iface->routeAddList(m_vpnProtocol->routeGateway(), QStringList() << remoteAddress()); +#ifdef Q_OS_MACOS + iface->routeAddList(m_vpnProtocol->routeGateway(), QStringList() << dns1 << dns2); +#endif addSitesRoutes(m_vpnProtocol->routeGateway(), m_settings->routeMode()); } } diff --git a/deploy/data/macos/AmneziaVPN.plist b/deploy/data/macos/AmneziaVPN.plist index 0627024c9..394d263cb 100644 --- a/deploy/data/macos/AmneziaVPN.plist +++ b/deploy/data/macos/AmneziaVPN.plist @@ -12,6 +12,8 @@ RunAtLoad + GroupName + amnvpn Sockets Listeners diff --git a/deploy/data/macos/post_install.sh b/deploy/data/macos/post_install.sh index 6a01bdbd3..2d5fe0857 100755 --- a/deploy/data/macos/post_install.sh +++ b/deploy/data/macos/post_install.sh @@ -1,6 +1,7 @@ #!/bin/bash APP_NAME=AmneziaVPN +SERVICE_GROUP=amnvpn PLIST_NAME=$APP_NAME.plist LAUNCH_DAEMONS_PLIST_NAME=/Library/LaunchDaemons/$PLIST_NAME LOG_FOLDER=/var/log/$APP_NAME @@ -34,6 +35,18 @@ fi run_cmd launchctl bootout system "$LAUNCH_DAEMONS_PLIST_NAME" || run_cmd launchctl unload "$LAUNCH_DAEMONS_PLIST_NAME" run_cmd rm -f "$LAUNCH_DAEMONS_PLIST_NAME" +# Add separate group for xray filtering +if dscl . -read "/Groups/$SERVICE_GROUP" >/dev/null 2>&1; then + log "Group $SERVICE_GROUP already exists" + return 0 +else + local next_gid + next_gid=$(dscl . -list /Groups PrimaryGroupID 2>/dev/null | awk '{print $2}' | sort -n | awk '$1>=500{g=$1} END{print (g?g+1:501)}') + run_cmd dscl . -create "/Groups/$SERVICE_GROUP" + run_cmd dscl . -create "/Groups/$SERVICE_GROUP" PrimaryGroupID "$next_gid" + run_cmd dscl . -create "/Groups/$SERVICE_GROUP" RealName "Amnezia VPN Service Group" +fi + run_cmd sudo chmod -R a-w "$APP_PATH/" run_cmd sudo chown -R root "$APP_PATH/" run_cmd sudo chgrp -R wheel "$APP_PATH/" diff --git a/deploy/data/macos/post_uninstall.sh b/deploy/data/macos/post_uninstall.sh index c9336b8af..b5c8c0fe7 100755 --- a/deploy/data/macos/post_uninstall.sh +++ b/deploy/data/macos/post_uninstall.sh @@ -8,6 +8,7 @@ USER_APP_SUPPORT="$HOME/Library/Application Support/$APP_NAME" SYSTEM_APP_SUPPORT="/Library/Application Support/$APP_NAME" LOG_FOLDER="/var/log/$APP_NAME" CACHES_FOLDER="$HOME/Library/Caches/$APP_NAME" +SERVICE_GROUP="amnvpn" # Attempt to quit the GUI application if it's currently running if pgrep -x "$APP_NAME" > /dev/null; then @@ -81,4 +82,20 @@ if sudo pfctl -s info 2>/dev/null | grep -q '^Status: Enabled' && \ sudo pfctl -d 2>/dev/null || true fi +# Remove amnvpn group if it's not referenced by users +if dscl . -read "/Groups/$SERVICE_GROUP" >/dev/null 2>&1; then + group_gid=$(dscl . -read "/Groups/$SERVICE_GROUP" PrimaryGroupID 2>/dev/null | awk '{print $2}') + users_with_primary_gid="" + if [ -n "$group_gid" ]; then + users_with_primary_gid=$(dscl . -list /Users PrimaryGroupID 2>/dev/null | awk -v gid="$group_gid" '$2 == gid {print $1}') + fi + + if [ -z "$users_with_primary_gid" ]; then + echo "Removing group $SERVICE_GROUP" + sudo dscl . -delete "/Groups/$SERVICE_GROUP" || true + else + echo "Keeping group $SERVICE_GROUP (still used by users): $users_with_primary_gid" + fi +fi + # ----------------------------------------------------------- diff --git a/service/server/killswitch.cpp b/service/server/killswitch.cpp index a2799d59f..be8fe54e9 100644 --- a/service/server/killswitch.cpp +++ b/service/server/killswitch.cpp @@ -117,6 +117,7 @@ bool KillSwitch::disableKillSwitch() { MacOSFirewall::setAnchorEnabled(QStringLiteral("290.allowDHCP"), false); MacOSFirewall::setAnchorEnabled(QStringLiteral("300.allowLAN"), false); MacOSFirewall::setAnchorEnabled(QStringLiteral("310.blockDNS"), false); + MacOSFirewall::setAnchorEnabled(QStringLiteral("400.allowPIA"), false); } else { MacOSFirewall::uninstall(); } @@ -382,6 +383,7 @@ bool KillSwitch::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIn MacOSFirewall::setAnchorEnabled(QStringLiteral("310.blockDNS"), true); MacOSFirewall::setAnchorTable(QStringLiteral("310.blockDNS"), true, QStringLiteral("dnsaddr"), dnsServers); + MacOSFirewall::setAnchorEnabled(QStringLiteral("400.allowPIA"), true); #endif return true; } diff --git a/service/server/router_mac.cpp b/service/server/router_mac.cpp index f56e3f9f6..ea15c9a65 100644 --- a/service/server/router_mac.cpp +++ b/service/server/router_mac.cpp @@ -162,6 +162,96 @@ bool RouterMac::restoreResolvers() { return m_dnsUtil->restoreResolvers(); } +bool RouterMac::routeAddXray(const QString& ifname, const QString& gateway) +{ + if (ifname.isEmpty() || gateway.isEmpty()) { + qWarning().noquote() << "routeAddXray: invalid iface/gateway:" << ifname << gateway; + return false; + } + + QString cmd = QString("route add -net 0.0.0.0/1 %1 -ifscope %2").arg(gateway).arg(ifname); + QStringList parts = cmd.split(" "); + + int argc = parts.size(); + char **argv = new char*[argc]; + for (int i = 0; i < argc; i++) { + argv[i] = new char[parts.at(i).toStdString().length() + 1]; + strcpy(argv[i], parts.at(i).toStdString().c_str()); + } + mainRouteIface(argc, argv); + for (int i = 0; i < argc; i++) { + delete [] argv[i]; + } + delete[] argv; + + cmd = QString("route add -net 128.0.0.0/1 %1 -ifscope %2").arg(gateway).arg(ifname); + parts = cmd.split(" "); + + argc = parts.size(); + argv = new char*[argc]; + for (int i = 0; i < argc; i++) { + argv[i] = new char[parts.at(i).toStdString().length() + 1]; + strcpy(argv[i], parts.at(i).toStdString().c_str()); + } + mainRouteIface(argc, argv); + for (int i = 0; i < argc; i++) { + delete [] argv[i]; + } + delete[] argv; + + qDebug().noquote() << "Installed xray routes via" << gateway << "on" << ifname; + return true; +} + +bool RouterMac::routeDeleteXray(const QString& ifname, const QString& gateway) +{ + if (ifname.isEmpty()) { + return false; + } + + QString cmd; + if (!gateway.isEmpty()) { + cmd = QString("route delete -net 0.0.0.0/1 %1 -ifscope %2").arg(gateway).arg(ifname); + } else { + cmd = QString("route delete -net 0.0.0.0/1 -ifscope %1").arg(ifname); + } + QStringList parts = cmd.split(" "); + + int argc = parts.size(); + char **argv = new char*[argc]; + for (int i = 0; i < argc; i++) { + argv[i] = new char[parts.at(i).toStdString().length() + 1]; + strcpy(argv[i], parts.at(i).toStdString().c_str()); + } + mainRouteIface(argc, argv); + for (int i = 0; i < argc; i++) { + delete [] argv[i]; + } + delete[] argv; + + if (!gateway.isEmpty()) { + cmd = QString("route delete -net 128.0.0.0/1 %1 -ifscope %2").arg(gateway).arg(ifname); + } else { + cmd = QString("route delete -net 128.0.0.0/1 -ifscope %1").arg(ifname); + } + parts = cmd.split(" "); + + argc = parts.size(); + argv = new char*[argc]; + for (int i = 0; i < argc; i++) { + argv[i] = new char[parts.at(i).toStdString().length() + 1]; + strcpy(argv[i], parts.at(i).toStdString().c_str()); + } + mainRouteIface(argc, argv); + for (int i = 0; i < argc; i++) { + delete [] argv[i]; + } + delete[] argv; + + qDebug().noquote() << "Removed xray routes on" << ifname; + return true; +} + bool RouterMac::deleteTun(const QString &dev) { qDebug().noquote() << "deleteTun start"; diff --git a/service/server/router_mac.h b/service/server/router_mac.h index 72beb7ea8..bcfca8fe3 100644 --- a/service/server/router_mac.h +++ b/service/server/router_mac.h @@ -34,6 +34,8 @@ public: bool deleteTun(const QString &dev); bool updateResolvers(const QString& ifname, const QList& resolvers); bool restoreResolvers(); + bool routeAddXray(const QString& ifname, const QString& gateway); + bool routeDeleteXray(const QString& ifname, const QString& gateway); public slots: @@ -47,4 +49,3 @@ private: }; #endif // ROUTERMAC_H - diff --git a/service/server/xray.cpp b/service/server/xray.cpp index 487a46435..831699899 100644 --- a/service/server/xray.cpp +++ b/service/server/xray.cpp @@ -1,5 +1,8 @@ #include "xray.h" #include "core/networkUtilities.h" +#ifdef Q_OS_MAC +#include "router_mac.h" +#endif #include #include @@ -31,13 +34,28 @@ bool Xray::startXray(const QString &cfg) { qDebug() << "Xray::startXray()"; - - auto defaultIface = NetworkUtilities::getGatewayAndIface().second; + const auto gatewayAndIface = NetworkUtilities::getGatewayAndIface(); + const QString defaultGateway = gatewayAndIface.first; + const QNetworkInterface defaultIface = gatewayAndIface.second; #ifdef Q_OS_LINUX m_defaultIfaceName = defaultIface.name().toUtf8(); #else m_defaultIfaceIdx = defaultIface.index(); #endif + if (defaultIface.index() > 0) { + qDebug() << "[xray] using uplink interface:" << defaultIface.name() << "(" << defaultIface.index() << ")"; + } + +#ifdef Q_OS_MAC + m_uplinkIfaceName = defaultIface.name(); + m_uplinkGateway = defaultGateway; + if (!m_uplinkIfaceName.isEmpty()) { + const bool installed = RouterMac::Instance().routeAddXray(m_uplinkIfaceName, m_uplinkGateway); + if (!installed) { + qWarning() << "[xray] failed to install xray routes on" << m_uplinkIfaceName; + } + } +#endif if (auto err = amnezia_xray_setsockcallback(ctxSockCallback, this); err != nullptr) { qDebug() << "[xray] sockopt failed: " << err; @@ -66,13 +84,22 @@ bool Xray::startXray(const QString &cfg) bool Xray::stopXray() { qDebug() << "Xray::stopXray()"; + bool success = true; if (auto err = amnezia_xray_stop(); err != nullptr) { qDebug() << "[xray] failed to stop: " << err; amnezia_xray_free(err); - return false; + success = false; } - return true; +#ifdef Q_OS_MAC + if (!m_uplinkIfaceName.isEmpty()) { + RouterMac::Instance().routeDeleteXray(m_uplinkIfaceName, m_uplinkGateway); + } + m_uplinkIfaceName.clear(); + m_uplinkGateway.clear(); +#endif + + return success; } void Xray::logHandler(char* str) diff --git a/service/server/xray.h b/service/server/xray.h index f54d9902f..45704137a 100644 --- a/service/server/xray.h +++ b/service/server/xray.h @@ -31,6 +31,11 @@ private: #else int m_defaultIfaceIdx; #endif + +#ifdef Q_OS_MAC + QString m_uplinkIfaceName; + QString m_uplinkGateway; +#endif }; #endif // XRAY_H