Linux killswitch

This commit is contained in:
Mykola Baibuz 2023-12-16 09:19:04 -05:00
parent c3fdd977b1
commit d94e27bfa9
9 changed files with 627 additions and 17 deletions

View file

@ -0,0 +1,462 @@
#include "linuxfirewall.h"
#include "logger.h"
#include <QProcess>
#define BRAND_CODE "amn"
namespace {
Logger logger("LinuxFirewall");
} // namespace
namespace
{
const QString kAnchorName{BRAND_CODE "vpn"};
const QString kPacketTag{"0x3211"};
const QString kCGroupId{"0x567"};
const QString enabledKeyTemplate = "enabled:%1:%2";
const QString disabledKeyTemplate = "disabled:%1:%2";
const QString kVpnGroupName = BRAND_CODE "vpn";
QHash<QString, LinuxFirewall::FilterCallbackFunc> anchorCallbacks;
}
QString LinuxFirewall::kRtableName = QStringLiteral("%1rt").arg(kAnchorName);
QString LinuxFirewall::kOutputChain = QStringLiteral("OUTPUT");
QString LinuxFirewall::kPostRoutingChain = QStringLiteral("POSTROUTING");
QString LinuxFirewall::kPreRoutingChain = QStringLiteral("PREROUTING");
QString LinuxFirewall::kRootChain = QStringLiteral("%1.anchors").arg(kAnchorName);
QString LinuxFirewall::kFilterTable = QStringLiteral("filter");
QString LinuxFirewall::kNatTable = QStringLiteral("nat");
QString LinuxFirewall::kRawTable = QStringLiteral("raw");
QString LinuxFirewall::kMangleTable = QStringLiteral("mangle");
static QString getCommand(LinuxFirewall::IPVersion ip)
{
return ip == LinuxFirewall::IPv6 ? QStringLiteral("ip6tables") : QStringLiteral("iptables");
}
int LinuxFirewall::createChain(LinuxFirewall::IPVersion ip, const QString& chain, const QString& tableName)
{
if (ip == Both)
{
int result4 = createChain(IPv4, chain, tableName);
int result6 = createChain(IPv6, chain, tableName);
return result4 ? result4 : result6;
}
const QString cmd = getCommand(ip);
return execute(QStringLiteral("%1 -N %2 -t %3 || %1 -F %2 -t %3").arg(cmd, chain, tableName));
}
int LinuxFirewall::deleteChain(LinuxFirewall::IPVersion ip, const QString& chain, const QString& tableName)
{
if (ip == Both)
{
int result4 = deleteChain(IPv4, chain, tableName);
int result6 = deleteChain(IPv6, chain, tableName);
return result4 ? result4 : result6;
}
const QString cmd = getCommand(ip);
return execute(QStringLiteral("if %1 -L %2 -n -t %3 > /dev/null 2> /dev/null ; then %1 -F %2 -t %3 && %1 -X %2 -t %3; fi").arg(cmd, chain, tableName));
}
int LinuxFirewall::linkChain(LinuxFirewall::IPVersion ip, const QString& chain, const QString& parent, bool mustBeFirst, const QString& tableName)
{
if (ip == Both)
{
int result4 = linkChain(IPv4, chain, parent, mustBeFirst, tableName);
int result6 = linkChain(IPv6, chain, parent, mustBeFirst, tableName);
return result4 ? result4 : result6;
}
const QString cmd = getCommand(ip);
if (mustBeFirst)
{
// This monster shell script does the following:
// 1. Check if a rule with the appropriate target exists at the top of the parent chain
// 2. If not, insert a jump rule at the top of the parent chain
// 3. Look for and delete a single rule with the designated target at an index > 1
// (we can't safely delete all rules at once since rule numbers change)
// TODO: occasionally this script results in warnings in logs "Bad rule (does a matching rule exist in the chain?)" - this happens when
// the e.g OUTPUT chain is empty but this script attempts to delete things from it anyway. It doesn't cause any problems, but we should still fix at some point..
return execute(QStringLiteral("if ! %1 -L %2 -n --line-numbers -t %4 2> /dev/null | awk 'int($1) == 1 && $2 == \"%3\" { found=1 } END { if(found==1) { exit 0 } else { exit 1 } }' ; then %1 -I %2 -j %3 -t %4 && %1 -L %2 -n --line-numbers -t %4 2> /dev/null | awk 'int($1) > 1 && $2 == \"%3\" { print $1; exit }' | xargs %1 -t %4 -D %2 ; fi").arg(cmd, parent, chain, tableName));
}
else
return execute(QStringLiteral("if ! %1 -C %2 -j %3 -t %4 2> /dev/null ; then %1 -A %2 -j %3 -t %4; fi").arg(cmd, parent, chain, tableName));
}
int LinuxFirewall::unlinkChain(LinuxFirewall::IPVersion ip, const QString& chain, const QString& parent, const QString& tableName)
{
if (ip == Both)
{
int result4 = unlinkChain(IPv4, chain, parent, tableName);
int result6 = unlinkChain(IPv6, chain, parent, tableName);
return result4 ? result4 : result6;
}
const QString cmd = getCommand(ip);
return execute(QStringLiteral("if %1 -C %2 -j %3 -t %4 2> /dev/null ; then %1 -D %2 -j %3 -t %4; fi").arg(cmd, parent, chain, tableName));
}
void LinuxFirewall::ensureRootAnchorPriority(LinuxFirewall::IPVersion ip)
{
linkChain(ip, kRootChain, kOutputChain, true);
}
void LinuxFirewall::installAnchor(LinuxFirewall::IPVersion ip, const QString& anchor, const QStringList& rules, const QString& tableName,
const FilterCallbackFunc& enableFunc, const FilterCallbackFunc& disableFunc)
{
if (ip == Both)
{
installAnchor(IPv4, anchor, rules, tableName, enableFunc, disableFunc);
installAnchor(IPv6, anchor, rules, tableName, enableFunc, disableFunc);
return;
}
const QString cmd = getCommand(ip);
const QString anchorChain = QStringLiteral("%1.a.%2").arg(kAnchorName, anchor);
const QString actualChain = QStringLiteral("%1.%2").arg(kAnchorName, anchor);
// Start by defining a placeholder chain, which stays locked into place
// in the root chain without being removed or recreated, ensuring the
// intended precedence order.
createChain(ip, anchorChain, tableName);
linkChain(ip, anchorChain, kRootChain, false, tableName);
if(enableFunc)
{
const QString key = enabledKeyTemplate.arg(tableName, anchor);
if(!anchorCallbacks.contains(key)) anchorCallbacks[key] = enableFunc;
}
if(disableFunc)
{
const QString key = disabledKeyTemplate.arg(tableName, anchor);
if(!anchorCallbacks.contains(key)) anchorCallbacks[key] = disableFunc;
}
// Create the actual rule chain, which we'll insert or remove from the
// placeholder anchor when needed.
createChain(ip, actualChain, tableName);
for (const QString& rule : rules)
execute(QStringLiteral("%1 -A %2 %3 -t %4").arg(cmd, actualChain, rule, tableName));
}
void LinuxFirewall::uninstallAnchor(LinuxFirewall::IPVersion ip, const QString& anchor, const QString& tableName)
{
if (ip == Both)
{
uninstallAnchor(IPv4, anchor, tableName);
uninstallAnchor(IPv6, anchor, tableName);
return;
}
const QString cmd = getCommand(ip);
const QString anchorChain = QStringLiteral("%1.a.%2").arg(kAnchorName, anchor);
const QString actualChain = QStringLiteral("%1.%2").arg(kAnchorName, anchor);
unlinkChain(ip, anchorChain, kRootChain, tableName);
deleteChain(ip, anchorChain, tableName);
deleteChain(ip, actualChain, tableName);
}
QStringList LinuxFirewall::getDNSRules(const QStringList& servers)
{
QStringList result;
for (const QString& server : servers)
{
result << QStringLiteral("-o amn0+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
result << QStringLiteral("-o amn0+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
result << QStringLiteral("-o tun0+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
result << QStringLiteral("-o tun0+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
}
return result;
}
QStringList LinuxFirewall::getExcludeRule(const QStringList& servers)
{
QStringList result;
for (const QString& server : servers)
{
result << QStringLiteral("-d %1 -j ACCEPT").arg(server);
}
return result;
}
void LinuxFirewall::install()
{
// Clean up any existing rules if they exist.
uninstall();
// Create a root filter chain to hold all our other anchors in order.
createChain(Both, kRootChain, kFilterTable);
// Create a root raw chain
createChain(Both, kRootChain, kRawTable);
// Create a root NAT chain
createChain(Both, kRootChain, kNatTable);
// Create a root Mangle chain
createChain(Both, kRootChain, kMangleTable);
// Install our filter rulesets in each corresponding anchor chain.
installAnchor(Both, QStringLiteral("000.allowLoopback"), {
QStringLiteral("-o lo+ -j ACCEPT"),
});
installAnchor(IPv4, QStringLiteral("320.allowDNS"), {});
installAnchor(Both, QStringLiteral("310.blockDNS"), {
QStringLiteral("-p udp --dport 53 -j REJECT"),
QStringLiteral("-p tcp --dport 53 -j REJECT"),
});
installAnchor(IPv4, QStringLiteral("300.allowLAN"), {
QStringLiteral("-d 10.0.0.0/8 -j ACCEPT"),
QStringLiteral("-d 169.254.0.0/16 -j ACCEPT"),
QStringLiteral("-d 172.16.0.0/12 -j ACCEPT"),
QStringLiteral("-d 192.168.0.0/16 -j ACCEPT"),
QStringLiteral("-d 224.0.0.0/4 -j ACCEPT"),
QStringLiteral("-d 255.255.255.255/32 -j ACCEPT"),
});
installAnchor(IPv6, QStringLiteral("300.allowLAN"), {
QStringLiteral("-d fc00::/7 -j ACCEPT"),
QStringLiteral("-d fe80::/10 -j ACCEPT"),
QStringLiteral("-d ff00::/8 -j ACCEPT"),
});
installAnchor(IPv4, QStringLiteral("290.allowDHCP"), {
QStringLiteral("-p udp -d 255.255.255.255 --sport 68 --dport 67 -j ACCEPT"),
});
installAnchor(IPv6, QStringLiteral("290.allowDHCP"), {
QStringLiteral("-p udp -d ff00::/8 --sport 546 --dport 547 -j ACCEPT"),
});
installAnchor(IPv6, QStringLiteral("250.blockIPv6"), {
QStringLiteral("! -o lo+ -j REJECT"),
});
installAnchor(Both, QStringLiteral("200.allowVPN"), {
QStringLiteral("-o amn0+ -j ACCEPT"),
QStringLiteral("-o tun0+ -j ACCEPT"),
});
installAnchor(Both, QStringLiteral("100.blockAll"), {
QStringLiteral("-j REJECT"),
});
// NAT rules
installAnchor(Both, QStringLiteral("100.transIp"), {
// Only need the original interface, not the IP.
// The interface should remain much more stable/unchangeable than the IP
// (IP can change when changing networks, but interface only changes if adding/removing NICs)
// this is just a stub rule - the real rule is set at run-time
// and updates dynamically (via replaceAnchor) when our interface changes
// it'll take this form: "-o <interface name> -j MASQUERADE"
QStringLiteral("-j MASQUERADE")
}, kNatTable);
// Mangle rules
installAnchor(Both, QStringLiteral("100.tagPkts"), {
QStringLiteral("-m cgroup --cgroup %1 -j MARK --set-mark %2").arg(kCGroupId, kPacketTag)
}, kMangleTable, setupTrafficSplitting, teardownTrafficSplitting);
// A rule to mitigate CVE-2019-14899 - drop packets addressed to the local
// VPN IP but that are not actually received on the VPN interface.
// See here: https://seclists.org/oss-sec/2019/q4/122
installAnchor(Both, QStringLiteral("100.vpnTunOnly"), {
// To be replaced at runtime
QStringLiteral("-j ACCEPT")
}, kRawTable);
// Insert our fitler root chain at the top of the OUTPUT chain.
linkChain(Both, kRootChain, kOutputChain, true, kFilterTable);
// Insert our NAT root chain at the top of the POSTROUTING chain.
linkChain(Both, kRootChain, kPostRoutingChain, true, kNatTable);
// Insert our Mangle root chain at the top of the OUTPUT chain.
linkChain(Both, kRootChain, kOutputChain, true, kMangleTable);
// Insert our Raw root chain at the top of the PREROUTING chain.
linkChain(Both, kRootChain, kPreRoutingChain, true, kRawTable);
setupTrafficSplitting();
}
void LinuxFirewall::uninstall()
{
// Filter chain
unlinkChain(Both, kRootChain, kOutputChain, kFilterTable);
deleteChain(Both, kRootChain, kFilterTable);
// Raw chain
unlinkChain(Both, kRootChain, kPreRoutingChain, kRawTable);
deleteChain(Both, kRootChain, kRawTable);
// NAT chain
unlinkChain(Both, kRootChain, kPostRoutingChain, kNatTable);
deleteChain(Both, kRootChain, kNatTable);
// Mangle chain
unlinkChain(Both, kRootChain, kOutputChain, kMangleTable);
deleteChain(Both, kRootChain, kMangleTable);
// Remove filter anchors
uninstallAnchor(Both, QStringLiteral("000.allowLoopback"));
uninstallAnchor(Both, QStringLiteral("400.allowPIA"));
uninstallAnchor(IPv4, QStringLiteral("320.allowDNS"));
uninstallAnchor(Both, QStringLiteral("310.blockDNS"));
uninstallAnchor(Both, QStringLiteral("300.allowLAN"));
uninstallAnchor(Both, QStringLiteral("290.allowDHCP"));
uninstallAnchor(IPv6, QStringLiteral("250.blockIPv6"));
uninstallAnchor(Both, QStringLiteral("200.allowVPN"));
uninstallAnchor(Both, QStringLiteral("100.blockAll"));
// Remove Nat anchors
uninstallAnchor(Both, QStringLiteral("100.transIp"), kNatTable);
// Remove Mangle anchors
uninstallAnchor(Both, QStringLiteral("100.tagPkts"), kMangleTable);
// Remove Raw anchors
uninstallAnchor(Both, QStringLiteral("100.vpnTunOnly"), kRawTable);
teardownTrafficSplitting();
logger.debug() << "LinuxFirewall::uninstall() complete";
}
bool LinuxFirewall::isInstalled()
{
return execute(QStringLiteral("iptables -C %1 -j %2 2> /dev/null").arg(kOutputChain, kRootChain)) == 0;
}
void LinuxFirewall::enableAnchor(LinuxFirewall::IPVersion ip, const QString &anchor, const QString& tableName)
{
if (ip == Both)
{
enableAnchor(IPv4, anchor, tableName);
enableAnchor(IPv6, anchor, tableName);
return;
}
const QString cmd = getCommand(ip);
const QString ipStr = ip == IPv6 ? QStringLiteral("(IPv6)") : QStringLiteral("(IPv4)");
execute(QStringLiteral("if %1 -C %5.a.%2 -j %5.%2 -t %4 2> /dev/null ; then echo '%2%3: ON' ; else echo '%2%3: OFF -> ON' ; %1 -A %5.a.%2 -j %5.%2 -t %4; fi").arg(cmd, anchor, ipStr, tableName, kAnchorName));
}
void LinuxFirewall::replaceAnchor(LinuxFirewall::IPVersion ip, const QString &anchor, const QString &newRule, const QString& tableName)
{
if (ip == Both)
{
replaceAnchor(IPv4, anchor, newRule, tableName);
replaceAnchor(IPv6, anchor, newRule, tableName);
return;
}
const QString cmd = getCommand(ip);
const QString ipStr = ip == IPv6 ? QStringLiteral("(IPv6)") : QStringLiteral("(IPv4)");
execute(QStringLiteral("%1 -R %7.%2 1 %3 -t %4 ; echo 'Replaced rule %7.%2 %5 with %6'").arg(cmd, anchor, newRule, tableName, ipStr, newRule, kAnchorName));
}
void LinuxFirewall::disableAnchor(LinuxFirewall::IPVersion ip, const QString &anchor, const QString& tableName)
{
if (ip == Both)
{
disableAnchor(IPv4, anchor, tableName);
disableAnchor(IPv6, anchor, tableName);
return;
}
const QString cmd = getCommand(ip);
const QString ipStr = ip == IPv6 ? QStringLiteral("(IPv6)") : QStringLiteral("(IPv4)");
execute(QStringLiteral("if ! %1 -C %5.a.%2 -j %5.%2 -t %4 2> /dev/null ; then echo '%2%3: OFF' ; else echo '%2%3: ON -> OFF' ; %1 -F %5.a.%2 -t %4; fi").arg(cmd, anchor, ipStr, tableName, kAnchorName));
}
bool LinuxFirewall::isAnchorEnabled(LinuxFirewall::IPVersion ip, const QString &anchor, const QString& tableName)
{
const QString cmd = getCommand(ip);
return execute(QStringLiteral("%1 -C %4.a.%2 -j %4.%2 -t %3 2> /dev/null").arg(cmd, anchor, tableName, kAnchorName)) == 0;
}
void LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPVersion ip, const QString &anchor, bool enabled, const QString &tableName)
{
if (enabled)
{
enableAnchor(ip, anchor, tableName);
const QString key = enabledKeyTemplate.arg(tableName, anchor);
if(anchorCallbacks.contains(key)) anchorCallbacks[key]();
}
else
{
disableAnchor(ip, anchor, tableName);
const QString key = disabledKeyTemplate.arg(tableName, anchor);
if(anchorCallbacks.contains(key)) anchorCallbacks[key]();
}
}
void LinuxFirewall::updateDNSServers(const QStringList& servers)
{
static QStringList existingServers {};
existingServers = servers;
execute(QStringLiteral("iptables -F %1.320.allowDNS").arg(kAnchorName));
for (const QString& rule : getDNSRules(servers))
execute(QStringLiteral("iptables -A %1.320.allowDNS %2").arg(kAnchorName, rule));
}
void LinuxFirewall::updateExcludeAddrs(const QStringList& servers)
{
static QStringList existingServers {};
existingServers = servers;
execute(QStringLiteral("iptables -F %1.100.blockAll").arg(kAnchorName));
for (const QString& rule : getExcludeRule(servers))
execute(QStringLiteral("iptables -A %1.100.blockAll %2").arg(kAnchorName, rule));
}
int waitForExitCode(QProcess& process)
{
if (!process.waitForFinished() || process.error() == QProcess::FailedToStart)
return -2;
else if (process.exitStatus() != QProcess::NormalExit)
return -1;
else
return process.exitCode();
}
int LinuxFirewall::execute(const QString &command, bool ignoreErrors)
{
QProcess p;
p.start(QStringLiteral("/bin/bash"), {QStringLiteral("-c"), command}, QProcess::ReadOnly);
p.closeWriteChannel();
int exitCode = waitForExitCode(p);
auto out = p.readAllStandardOutput().trimmed();
auto err = p.readAllStandardError().trimmed();
if ((exitCode != 0 || !err.isEmpty()) && !ignoreErrors)
logger.warning() << "(" << exitCode << ") $ " << command;
else if (false)
logger.debug() << "(" << exitCode << ") $ " << command;
if (!out.isEmpty())
logger.info() << out;
if (!err.isEmpty())
logger.warning() << err;
return exitCode;
}
void LinuxFirewall::setupTrafficSplitting()
{
auto cGroupDir = "/sys/fs/cgroup/net_cls/" BRAND_CODE "vpnexclusions/";
logger.info() << "Should be setting up cgroup in" << cGroupDir << "for traffic splitting";
execute(QStringLiteral("if [ ! -d %1 ] ; then mkdir %1 ; sleep 0.1 ; echo %2 > %1/net_cls.classid ; fi").arg(cGroupDir).arg(kCGroupId));
// Set a rule with priority 100 (lower priority than local but higher than main/default, 0 is highest priority)
execute(QStringLiteral("if ! ip rule list | grep -q %1 ; then ip rule add from all fwmark %1 lookup %2 pri 100 ; fi").arg(kPacketTag, kRtableName));
}
void LinuxFirewall::teardownTrafficSplitting()
{
logger.info() << "Tearing down cgroup and routing rules";
execute(QStringLiteral("if ip rule list | grep -q %1; then ip rule del from all fwmark %1 lookup %2 2> /dev/null ; fi").arg(kPacketTag, kRtableName));
execute(QStringLiteral("ip route flush table %1").arg(kRtableName));
execute(QStringLiteral("ip route flush cache"));
}

View file

@ -0,0 +1,73 @@
#ifndef LINUXFIREWALL_H
#define LINUXFIREWALL_H
#include <QString>
#include <QStringList>
// Descriptor for a set of firewall rules to be appled.
//
struct FirewallParams
{
QStringList dnsServers;
// QSharedPointer<NetworkAdapter> adapter;
QVector<QString> excludeApps; // Apps to exclude if VPN exemptions are enabled
QStringList excludeAddrs;
// The follow flags indicate which general rulesets are needed. Note that
// this is after some sanity filtering, i.e. an allow rule may be listed
// as not needed if there were no block rules preceding it. The rulesets
// should be thought of as in last-match order.
bool blockAll; // Block all traffic by default
bool allowVPN; // Exempt traffic through VPN tunnel
bool allowDHCP; // Exempt DHCP traffic
bool blockIPv6; // Block all IPv6 traffic
bool allowLAN; // Exempt LAN traffic, including IPv6 LAN traffic
bool blockDNS; // Block all DNS traffic except specified DNS servers
bool allowPIA; // Exempt PIA executables
bool allowLoopback; // Exempt loopback traffic
bool allowHnsd; // Exempt Handshake DNS traffic
bool allowVpnExemptions; // Exempt specified traffic from the tunnel (route it over the physical uplink instead)
};
class LinuxFirewall
{
public:
enum IPVersion { IPv4, IPv6, Both };
// Table names
static QString kFilterTable, kNatTable, kMangleTable, kRtableName, kRawTable;
public:
using FilterCallbackFunc = std::function<void()>;
private:
static int createChain(IPVersion ip, const QString& chain, const QString& tableName = kFilterTable);
static int deleteChain(IPVersion ip, const QString& chain, const QString& tableName = kFilterTable);
static int linkChain(IPVersion ip, const QString& chain, const QString& parent, bool mustBeFirst = false, const QString& tableName = kFilterTable);
static int unlinkChain(IPVersion ip, const QString& chain, const QString& parent, const QString& tableName = kFilterTable);
static void installAnchor(IPVersion ip, const QString& anchor, const QStringList& rules, const QString& tableName = kFilterTable, const FilterCallbackFunc& enableFunc = {}, const FilterCallbackFunc& disableFunc = {});
static void uninstallAnchor(IPVersion ip, const QString& anchor, const QString& tableName = kFilterTable);
static QStringList getDNSRules(const QStringList& servers);
static QStringList getExcludeRule(const QStringList& servers);
static void setupTrafficSplitting();
static void teardownTrafficSplitting();
static int execute(const QString& command, bool ignoreErrors = false);
private:
// Chain names
static QString kOutputChain, kRootChain, kPostRoutingChain, kPreRoutingChain;
public:
static void install();
static void uninstall();
static bool isInstalled();
static void ensureRootAnchorPriority(IPVersion ip = Both);
static void enableAnchor(IPVersion ip, const QString& anchor, const QString& tableName = kFilterTable);
static void disableAnchor(IPVersion ip, const QString& anchor, const QString& tableName = kFilterTable);
static bool isAnchorEnabled(IPVersion ip, const QString& anchor, const QString& tableName = kFilterTable);
static void setAnchorEnabled(IPVersion ip, const QString& anchor, bool enabled, const QString& tableName = kFilterTable);
static void replaceAnchor(LinuxFirewall::IPVersion ip, const QString &anchor, const QString &newRule, const QString& tableName);
static void updateDNSServers(const QStringList& servers);
static void updateExcludeAddrs(const QStringList& servers);
};
#endif // LINUXFIREWALL_H

View file

@ -11,7 +11,9 @@
#include <QFile>
#include <QLocalSocket>
#include <QTimer>
#include <QThread>
#include "linuxfirewall.h"
#include "leakdetector.h"
#include "logger.h"
@ -117,6 +119,12 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) {
if (err != 0) {
logger.error() << "Interface configuration failed:" << strerror(err);
}
FirewallParams params {};
params.dnsServers.append(config.m_dnsServer);
params.excludeAddrs.append(config.m_serverIpv4AddrIn);
applyFirewallRules(params);
return (err == 0);
}
@ -140,6 +148,9 @@ bool WireguardUtilsLinux::deleteInterface() {
// Garbage collect.
QDir wgRuntimeDir(WG_RUNTIME_DIR);
QFile::remove(wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name"));
// double-check + ensure our firewall is installed and enabled
LinuxFirewall::uninstall();
return true;
}
@ -252,6 +263,34 @@ QList<WireguardUtils::PeerStatus> WireguardUtilsLinux::getPeerStatus() {
return peerList;
}
void WireguardUtilsLinux::applyFirewallRules(FirewallParams& params)
{
// double-check + ensure our firewall is installed and enabled
if (!LinuxFirewall::isInstalled()) LinuxFirewall::install();
// Note: rule precedence is handled inside IpTablesFirewall
LinuxFirewall::ensureRootAnchorPriority();
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("000.allowLoopback"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("100.blockAll"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("200.allowVPN"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv6, QStringLiteral("250.blockIPv6"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("290.allowDHCP"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("300.allowLAN"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("310.blockDNS"), true);
LinuxFirewall::updateDNSServers(params.dnsServers);
LinuxFirewall::updateExcludeAddrs(params.excludeAddrs);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("320.allowDNS"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4,
QStringLiteral("100.vpnTunOnly"),
true,
LinuxFirewall::kRawTable);
}
bool WireguardUtilsLinux::updateRoutePrefix(const IPAddress& prefix) {
if (!m_rtmonitor) {
return false;

View file

@ -8,8 +8,11 @@
#include <QObject>
#include <QProcess>
#include "daemon/wireguardutils.h"
#include "linuxroutemonitor.h"
#include "linuxfirewall.h"
class WireguardUtilsLinux final : public WireguardUtils {
Q_OBJECT
@ -34,7 +37,7 @@ public:
bool addExclusionRoute(const IPAddress& prefix) override;
bool deleteExclusionRoute(const IPAddress& prefix) override;
void applyFirewallRules(FirewallParams& params);
signals:
void backendFailure();

View file

@ -55,7 +55,7 @@ void OpenVpnProtocol::stop()
m_managementServer.stop();
}
#ifdef Q_OS_WIN
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX)
IpcClient::Interface()->disableKillSwitch();
#endif
@ -338,12 +338,15 @@ void OpenVpnProtocol::updateVpnGateway(const QString &line)
for (int j=0; j < netInterfaces.at(i).addressEntries().size(); j++)
{
if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) {
IpcClient::Interface()->enableKillSwitch(netInterfaces.at(i).index());
IpcClient::Interface()->enableKillSwitch(QJsonObject(), netInterfaces.at(i).index());
m_configData.insert("vpnGateway", m_vpnGateway);
IpcClient::Interface()->enablePeerTraffic(m_configData);
}
}
}
#endif
#ifdef Q_OS_LINUX
IpcClient::Interface()->enableKillSwitch(m_configData, 0);
#endif
qDebug() << QString("Set vpn local address %1, gw %2").arg(m_vpnLocalAddress).arg(vpnGateway());
}

View file

@ -24,8 +24,8 @@ class IpcInterface
SLOT( bool copyWireguardConfig(const QString &sourcePath) );
SLOT( bool isWireguardRunning() );
SLOT( bool isWireguardConfigExists(const QString &configPath) );
SLOT( bool enableKillSwitch(int vpnAdapterIndex) );
SLOT( bool disableKillSwitch() );
SLOT( bool enablePeerTraffic(const QJsonObject &configStr));
SLOT( bool enablePeerTraffic( const QJsonObject &configStr) );
SLOT( bool enableKillSwitch( const QJsonObject &excludeAddr, int vpnAdapterIndex) );
};

View file

@ -12,7 +12,10 @@
#ifdef Q_OS_WIN
#include "tapcontroller_win.h"
#include "../client/platforms/windows/daemon/windowsfirewall.h"
#endif
#ifdef Q_OS_LINUX
#include "../client/platforms/linux/daemon/linuxfirewall.h"
#endif
IpcServer::IpcServer(QObject *parent):
@ -217,21 +220,44 @@ bool IpcServer::isWireguardRunning()
#endif
}
bool IpcServer::isWireguardConfigExists(const QString &configPath)
{
#ifdef MZ_DEBUG
qDebug() << "IpcServer::isWireguardConfigExists";
#endif
return QFileInfo::exists(configPath);
}
bool IpcServer::enableKillSwitch(int vpnAdapterIndex)
bool IpcServer::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIndex)
{
#ifdef Q_OS_WIN
return WindowsFirewall::instance()->enableKillSwitch(vpnAdapterIndex);
#endif
// double-check + ensure our firewall is installed and enabled
if (!LinuxFirewall::isInstalled()) LinuxFirewall::install();
// Note: rule precedence is handled inside IpTablesFirewall
LinuxFirewall::ensureRootAnchorPriority();
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("000.allowLoopback"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("100.blockAll"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("200.allowVPN"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv6, QStringLiteral("250.blockIPv6"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("290.allowDHCP"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("300.allowLAN"), true);
// LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("310.blockDNS"), true);
QStringList serverAddr;
serverAddr.append(configStr.value(amnezia::config_key::hostName).toString());
LinuxFirewall::updateExcludeAddrs(serverAddr);
QStringList dnsServers;
dnsServers.append(configStr.value(amnezia::config_key::dns1).toString());
dnsServers.append(configStr.value(amnezia::config_key::dns2).toString());
dnsServers.append("127.0.0.1");
dnsServers.append("127.0.0.53");
LinuxFirewall::updateDNSServers(dnsServers);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("320.allowDNS"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), true);
// LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4,
// QStringLiteral("100.vpnTunOnly"),
// true,
// LinuxFirewall::kRawTable);
return true;
}
bool IpcServer::disableKillSwitch()
@ -239,6 +265,8 @@ bool IpcServer::disableKillSwitch()
#ifdef Q_OS_WIN
return WindowsFirewall::instance()->disableKillSwitch();
#endif
LinuxFirewall::uninstall();
return true;
}

View file

@ -30,9 +30,9 @@ public:
virtual bool copyWireguardConfig(const QString &sourcePath) override;
virtual bool isWireguardRunning() override;
virtual bool isWireguardConfigExists(const QString &configPath) override;
virtual bool enableKillSwitch(int vpnAdapterIndex) override;
virtual bool disableKillSwitch() override;
virtual bool enablePeerTraffic(const QJsonObject &configStr) override;
virtual bool enableKillSwitch(const QJsonObject &excludeAddr, int vpnAdapterIndex) override;
virtual bool disableKillSwitch() override;
private:
int m_localpid = 0;

View file

@ -223,6 +223,8 @@ if(LINUX)
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxdaemon.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/wireguardutilslinux.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxroutemonitor.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxfirewall.h
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxfirewall.cpp
)
endif()