2024-01-28 05:39:12 -05:00
// Copyright (c) 2023 Private Internet Access, Inc.
//
// This file is part of the Private Internet Access Desktop Client.
//
// The Private Internet Access Desktop Client is free software: you can
// redistribute it and/or modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
//
// The Private Internet Access Desktop Client is distributed in the hope that
// it will be useful, but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with the Private Internet Access Desktop Client. If not, see
// <https://www.gnu.org/licenses/>.
2024-01-27 07:50:50 -05:00
// Copyright (c) 2024 AmneziaVPN
// This file has been modified for AmneziaVPN
//
// This file is based on the work of the Private Internet Access Desktop Client.
// The original code of the Private Internet Access Desktop Client is copyrighted (c) 2023 Private Internet Access, Inc. and licensed under GPL3.
//
// The modified version of this file is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this file. If not, see <https://www.gnu.org/licenses/>.
2023-12-16 09:19:04 -05:00
# include "linuxfirewall.h"
# include "logger.h"
2026-04-10 23:06:16 +03:00
# include <QHostAddress>
2023-12-16 09:19:04 -05:00
# 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..
2026-04-10 23:06:16 +03:00
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 -r %1 -t %4 -D %2 ; fi " ) . arg ( cmd , parent , chain , tableName ) ) ;
2023-12-16 09:19:04 -05:00
}
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 )
{
2026-04-10 23:06:16 +03:00
result < < QStringLiteral ( " -d %1 -p udp --dport 53 -j ACCEPT " ) . arg ( server ) ;
result < < QStringLiteral ( " -d %1 -p tcp --dport 53 -j ACCEPT " ) . arg ( server ) ;
2023-12-16 09:19:04 -05:00
}
return result ;
}
2024-01-24 17:20:50 -05:00
QStringList LinuxFirewall : : getAllowRule ( const QStringList & servers )
2023-12-16 09:19:04 -05:00
{
QStringList result ;
for ( const QString & server : servers )
{
result < < QStringLiteral ( " -d %1 -j ACCEPT " ) . arg ( server ) ;
}
return result ;
}
2024-01-24 17:20:50 -05:00
QStringList LinuxFirewall : : getBlockRule ( const QStringList & servers )
{
QStringList result ;
for ( const QString & server : servers )
{
result < < QStringLiteral ( " -d %1 -j REJECT " ) . arg ( server ) ;
}
return result ;
}
2023-12-16 09:19:04 -05:00
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 " ) ,
} ) ;
2026-04-10 23:06:16 +03:00
installAnchor ( Both , QStringLiteral ( " 320.allowDNS " ) , { } ) ;
2023-12-16 09:19:04 -05:00
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 " ) ,
2024-12-30 19:23:53 -08:00
QStringLiteral ( " -o tun2+ -j ACCEPT " ) ,
2023-12-16 09:19:04 -05:00
} ) ;
2024-01-24 17:20:50 -05:00
installAnchor ( IPv4 , QStringLiteral ( " 120.blockNets " ) , { } ) ;
installAnchor ( IPv4 , QStringLiteral ( " 110.allowNets " ) , { } ) ;
2023-12-16 09:19:04 -05:00
installAnchor ( Both , QStringLiteral ( " 100.blockAll " ) , {
QStringLiteral ( " -j REJECT " ) ,
} ) ;
2026-04-10 23:06:16 +03:00
installAnchor ( Both , QStringLiteral ( " 400.allowPIA " ) , { } ) ;
2023-12-16 09:19:04 -05:00
// 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 " ) ) ;
2026-04-10 23:06:16 +03:00
uninstallAnchor ( Both , QStringLiteral ( " 320.allowDNS " ) ) ;
2023-12-16 09:19:04 -05:00
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 " ) ) ;
2024-01-24 17:20:50 -05:00
uninstallAnchor ( IPv4 , QStringLiteral ( " 120.blockNets " ) ) ;
uninstallAnchor ( IPv4 , QStringLiteral ( " 110.allowNets " ) ) ;
2023-12-16 09:19:04 -05:00
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 ( ) ;
2026-04-10 23:06:16 +03:00
anchorCallbacks . clear ( ) ;
2023-12-16 09:19:04 -05:00
logger . debug ( ) < < " LinuxFirewall::uninstall() complete " ;
}
bool LinuxFirewall : : isInstalled ( )
{
2026-04-01 00:51:21 +03:00
return execute ( QStringLiteral ( " iptables -C %1 -j %2 2> /dev/null " ) . arg ( kOutputChain , kRootChain ) ) = = 0 ;
2023-12-16 09:19:04 -05:00
}
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 )
{
execute ( QStringLiteral ( " iptables -F %1.320.allowDNS " ) . arg ( kAnchorName ) ) ;
2026-04-10 23:06:16 +03:00
execute ( QStringLiteral ( " ip6tables -F %1.320.allowDNS " ) . arg ( kAnchorName ) ) ;
for ( const QString & server : servers ) {
if ( server . isEmpty ( ) ) continue ;
const QString cmd = ( QHostAddress ( server ) . protocol ( ) = = QAbstractSocket : : IPv6Protocol )
? QStringLiteral ( " ip6tables " )
: QStringLiteral ( " iptables " ) ;
execute ( QStringLiteral ( " %1 -A %2.320.allowDNS -d %3 -p udp --dport 53 -j ACCEPT " ) . arg ( cmd , kAnchorName , server ) ) ;
execute ( QStringLiteral ( " %1 -A %2.320.allowDNS -d %3 -p tcp --dport 53 -j ACCEPT " ) . arg ( cmd , kAnchorName , server ) ) ;
}
2023-12-16 09:19:04 -05:00
}
2024-01-24 17:20:50 -05:00
void LinuxFirewall : : updateAllowNets ( const QStringList & servers )
2023-12-16 09:19:04 -05:00
{
2024-01-24 17:20:50 -05:00
execute ( QStringLiteral ( " iptables -F %1.110.allowNets " ) . arg ( kAnchorName ) ) ;
for ( const QString & rule : getAllowRule ( servers ) )
execute ( QStringLiteral ( " iptables -A %1.110.allowNets %2 " ) . arg ( kAnchorName , rule ) ) ;
2023-12-16 09:19:04 -05:00
}
2024-01-24 17:20:50 -05:00
void LinuxFirewall : : updateBlockNets ( const QStringList & servers )
{
static QStringList existingServers { } ;
existingServers = servers ;
execute ( QStringLiteral ( " iptables -F %1.120.blockNets " ) . arg ( kAnchorName ) ) ;
for ( const QString & rule : getBlockRule ( servers ) )
execute ( QStringLiteral ( " iptables -A %1.120.blockNets %2 " ) . arg ( kAnchorName , rule ) ) ;
}
2023-12-16 09:19:04 -05:00
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 ;
2026-04-10 23:06:16 +03:00
if ( ! err . isEmpty ( ) & & ! ignoreErrors )
2023-12-16 09:19:04 -05:00
logger . warning ( ) < < err ;
return exitCode ;
}
void LinuxFirewall : : setupTrafficSplitting ( )
{
2026-04-10 23:06:16 +03:00
execute ( QStringLiteral ( " grep -q '%1' /etc/iproute2/rt_tables || printf '200 \\ t%1 \\ n' >> /etc/iproute2/rt_tables " ) . arg ( kRtableName ) ) ;
execute ( QStringLiteral (
" if [ ! -d /sys/fs/cgroup/net_cls ] ; then "
" mkdir -p /sys/fs/cgroup/net_cls && "
" mount -t cgroup -o net_cls cgroup /sys/fs/cgroup/net_cls ; fi " ) ) ;
2023-12-16 09:19:04 -05:00
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 ) ) ;
2026-04-10 23:06:16 +03:00
execute ( QStringLiteral ( " ip route flush table %1 " ) . arg ( kRtableName ) , true ) ;
2023-12-16 09:19:04 -05:00
execute ( QStringLiteral ( " ip route flush cache " ) ) ;
2026-04-10 23:06:16 +03:00
execute ( QStringLiteral ( " sed -i '/%1/d' /etc/iproute2/rt_tables " ) . arg ( kRtableName ) ) ;
2023-12-16 09:19:04 -05:00
}