Skip to content

Commit

Permalink
merge from upstream
Browse files Browse the repository at this point in the history
  • Loading branch information
MelvinTo committed Jan 22, 2024
2 parents 61783a2 + 74dd068 commit 00186fa
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 71 deletions.
24 changes: 16 additions & 8 deletions extension/country/country.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,8 @@ class Country {
sem.on('GEO_REFRESH', (event) => {
this.reloadDataSync(event.dataType)
});
if (fc.isFeatureOn('country')) {
this.checkDBFiles().then(exist => {
if (exist) {
this.updateGeodatadir(this.countryDataFolder)
this.reloadDataSync()
}
})
}

this.init()
}
return instance;
}
Expand All @@ -52,6 +46,20 @@ class Country {
&& await fileExist(`${this.countryDataFolder}/geoip-country6.dat`)
}

async init() {
try {
// make sure features are loaded
await fc.getConfig(true)

if (fc.isFeatureOn('country') && await this.checkDBFiles()) {
this.updateGeodatadir(this.countryDataFolder)
this.reloadDataSync()
}
} catch(err) {
log.error('Error init geoip data', err)
}
}

getCountry(ip) {
try {
const result = this.geoip.lookup(ip);
Expand Down
25 changes: 16 additions & 9 deletions extension/vpnclient/VPNClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -291,20 +291,21 @@ class VPNClient {
}
const remoteIP = await this._getRemoteIP();
const remoteIP6 = await this._getRemoteIP6();
const localIP6 = await this._getLocalIP6();
const intf = this.getInterfaceName();
const snatNeeded = await this.isSNATNeeded();
if (snatNeeded) {
await exec(iptables.wrapIptables(`sudo iptables -w -t nat -A FW_POSTROUTING -o ${intf} -j MASQUERADE`)).catch((err) => {});
if(remoteIP6) {
await exec(iptables.wrapIptables(`sudo ip6tables -w -t nat -A FW_POSTROUTING -o ${intf} -j MASQUERADE`)).catch((err) => {});
}
await exec(iptables.wrapIptables(`sudo ip6tables -w -t nat -A FW_POSTROUTING -o ${intf} -j MASQUERADE`)).catch((err) => {});
}
log.info(`Refresh VPN client routes for ${this.profileId}, remote: ${remoteIP}, remote6: ${remoteIP6} intf: ${intf}`);
log.info(`Refresh VPN client routes for ${this.profileId}, remote: ${remoteIP}, intf: ${intf}`);
// remove routes from main table which is inserted by VPN client automatically,
// otherwise tunnel will be enabled globally
await routing.removeRouteFromTable("0.0.0.0/1", remoteIP, intf, "main").catch((err) => { log.info("No need to remove 0.0.0.0/1 for " + this.profileId) });
await routing.removeRouteFromTable("128.0.0.0/1", remoteIP, intf, "main").catch((err) => { log.info("No need to remove 128.0.0.0/1 for " + this.profileId) });
await routing.removeRouteFromTable("default", remoteIP, intf, "main").catch((err) => { log.info("No need to remove default route for " + this.profileId) });
if (localIP6)
await routing.removeRouteFromTable("default", remoteIP, intf, "main", null, 6).catch((err) => { log.info("No need to remove IPv6 default route for " + this.profileId) });
let routedSubnets = settings.serverSubnets || [];
// add vpn client specific routes
try {
Expand All @@ -319,7 +320,7 @@ class VPNClient {

log.info(`Adding routes for vpn ${this.profileId}`, routedSubnets);
// always add default route into VPN client's routing table, the switch is implemented in ipset, so no need to implement it in routing tables
await vpnClientEnforcer.enforceVPNClientRoutes(remoteIP, remoteIP6, intf, routedSubnets, dnsServers, true);
await vpnClientEnforcer.enforceVPNClientRoutes(remoteIP, remoteIP6, intf, routedSubnets, dnsServers, true, Boolean(localIP6));
// loosen reverse path filter
await exec(`sudo sysctl -w net.ipv4.conf.${intf}.rp_filter=2`).catch((err) => { });
const rtId = await vpnClientEnforcer.getRtId(this.getInterfaceName());
Expand Down Expand Up @@ -353,15 +354,15 @@ class VPNClient {
}
}
if (dnsServers.length > 0) {
await vpnClientEnforcer.enforceDNSRedirect(this.getInterfaceName(), dnsServers, await this._getRemoteIP(), dnsRedirectChain);
await vpnClientEnforcer.enforceDNSRedirect(this.getInterfaceName(), dnsServers, dnsRedirectChain);
}
dnsmasq.scheduleRestartDNSService();
} else {
await exec(`sudo ipset del -! ${VPNClient.getRouteIpsetName(this.profileId, false)} ${ipset.CONSTANTS.IPSET_MATCH_DNS_PORT_SET}`).catch((err) => { });
if (!settings.strictVPN)
await exec(`sudo ipset del -! ${VPNClient.getRouteIpsetName(this.profileId)} ${ipset.CONSTANTS.IPSET_MATCH_DNS_PORT_SET}`).catch((err) => { });
if (dnsServers.length > 0)
await vpnClientEnforcer.unenforceDNSRedirect(this.getInterfaceName(), dnsServers, await this._getRemoteIP(), dnsRedirectChain);
await vpnClientEnforcer.unenforceDNSRedirect(this.getInterfaceName(), dnsServers, dnsRedirectChain);
await fs.unlinkAsync(this._getDnsmasqConfigPath()).catch((err) => {});
await this._disableDNSRoute("hard");
await this._disableDNSRoute("soft");
Expand Down Expand Up @@ -664,6 +665,11 @@ class VPNClient {
return exec(`ip addr show dev ${intf} | awk '/inet /' | awk '{print $2}' | head -n 1`).then(result => result.stdout.trim().split('/')[0]).catch((err) => null);
}

async _getLocalIP6() {
const intf = this.getInterfaceName();
return exec(`ip addr show dev ${intf} | awk '/inet6 /' | awk '{print $2}' | head -n 1`).then(result => result.stdout.trim().split('/')[0]).catch((err) => null);
}

async checkAndSaveProfile(value) {
const protocol = this.constructor.getProtocol();
const config = value && value.config || {};
Expand Down Expand Up @@ -910,7 +916,7 @@ class VPNClient {
const dnsServers = await this._getDNSServers() || [];
if (dnsServers.length > 0) {
// always attempt to remove dns redirect rule, no matter whether 'routeDNS' in set in settings
await vpnClientEnforcer.unenforceDNSRedirect(this.getInterfaceName(), dnsServers, await this._getRemoteIP(), VPNClient.getDNSRedirectChainName(this.profileId));
await vpnClientEnforcer.unenforceDNSRedirect(this.getInterfaceName(), dnsServers, VPNClient.getDNSRedirectChainName(this.profileId));
}
await this.flushRemoteEndpointRoutes().catch((err) => {});
await this._stop().catch((err) => {
Expand Down Expand Up @@ -1101,14 +1107,15 @@ class VPNClient {

const config = await this.loadJSONConfig() || {};
const remoteIP = await this._getRemoteIP();
const remoteIP6 = await this._getRemoteIP6();
const localIP = await this._getLocalIP();
const rtId = await vpnClientEnforcer.getRtId(this.getInterfaceName());
const type = await this.constructor.getProtocol();
let sessionLog = null;
if (includeContent) {
sessionLog = await this.getLatestSessionLog();
}
return {profileId, settings, status, stats, message, routedSubnets, type, config, remoteIP, localIP, rtId, sessionLog};
return {profileId, settings, status, stats, message, routedSubnets, type, config, remoteIP, remoteIP6, localIP, rtId, sessionLog};
}

async resolveFirewallaDDNS(domain) {
Expand Down
42 changes: 32 additions & 10 deletions extension/vpnclient/VPNClientEnforcer.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ class VPNClientEnforcer {
});
}

async enforceVPNClientRoutes(remoteIP, remoteIP6, vpnIntf, routedSubnets = [], dnsServers = [], overrideDefaultRoute = true) {
async enforceVPNClientRoutes(remoteIP, remoteIP6, vpnIntf, routedSubnets = [], dnsServers = [], overrideDefaultRoute = true, v6Enabled = false) {
if (!vpnIntf)
throw "Interface is not specified";
const tableName = this._getRoutingTableName(vpnIntf);
Expand Down Expand Up @@ -148,7 +148,12 @@ class VPNClientEnforcer {
log.error(`Malformed route subnet ${routedSubnet}`);
continue;
}
await routing.addRouteToTable(formattedSubnet, remoteIP, vpnIntf, tableName, null, af).catch((err) => {});
if (af == 4)
await routing.addRouteToTable(formattedSubnet, remoteIP, vpnIntf, tableName, null, af).catch((err) => {});
else {
if (v6Enabled)
await routing.addRouteToTable(formattedSubnet, remoteIP6, vpnIntf, tableName, null, af).catch((err) => {});
}
// make routed subnets reachable from all lan networks
let maskNum = Number(routing.MASK_VC);
let offset = 0;
Expand All @@ -159,19 +164,27 @@ class VPNClientEnforcer {
const pref = rtId >>> offset;
// add routes with different metrics for different vpn client interface
// in case multiple VPN clients have overlapped subnets, turning off one vpn client will not affect routes of others
await routing.addRouteToTable(formattedSubnet, remoteIP, vpnIntf, "main", pref, af).catch((err) => {});
if (af == 4)
await routing.addRouteToTable(formattedSubnet, remoteIP, vpnIntf, "main", pref, af).catch((err) => {});
else {
if (v6Enabled)
await routing.addRouteToTable(formattedSubnet, remoteIP6, vpnIntf, "main", pref, af).catch((err) => {});
}
}
for (const dnsServer of dnsServers) {
// add dns server to vpn client table
await routing.addRouteToTable(dnsServer, remoteIP, vpnIntf, tableName, null, new Address4(dnsServer).isValid() ? 4 : 6).catch((err) => {});
if (new Address4(dnsServer).isValid())
await routing.addRouteToTable(dnsServer, remoteIP, vpnIntf, tableName, null, 4).catch((err) => {});
else {
if (v6Enabled)
await routing.addRouteToTable(dnsServer, remoteIP6, vpnIntf, tableName, null, 6).catch((err) => {});
}
}
if (overrideDefaultRoute) {
// then add remote IP as gateway of default route to vpn client table
await routing.addRouteToTable("default", remoteIP, vpnIntf, tableName).catch((err) => {}); // this usually happens when multiple function calls are executed simultaneously. It should have no side effect and will be consistent eventually
// FIXME: need to handle server subnets also in the future
if(remoteIP6) {
if (v6Enabled)
await routing.addRouteToTable("default", remoteIP6, vpnIntf, tableName, null, 6).catch((err) => {}); // this usually happens when multiple function calls are executed simultaneously. It should have no side effect and will be consistent eventually
}
}
// add inbound connmark rule for vpn client interface
await execAsync(wrapIptables(`sudo iptables -w -t nat -A FW_PREROUTING_VC_INBOUND -i ${vpnIntf} -j CONNMARK --set-xmark ${rtId}/${routing.MASK_ALL}`)).catch((err) => {
Expand All @@ -196,7 +209,16 @@ class VPNClientEnforcer {
log.error(`Failed to remove policy routing rule`, err.message);
});
await routing.removePolicyRoutingRule("all", vpnIntf, "global_default", 10000, null, 4).catch((err) => {
log.error(`Failed tp remove policy routing rule`, err.message);
log.error(`Failed to remove policy routing rule`, err.message);
});
await routing.removePolicyRoutingRule("all", vpnIntf, "wan_routable", 5000, null, 6).catch((err) => {
log.error(`Failed to remove policy routing rule`, err.message);
});
await routing.removePolicyRoutingRule("all", vpnIntf, "global_local", 5000, null, 6).catch((err) => {
log.error(`Failed to remove policy routing rule`, err.message);
});
await routing.removePolicyRoutingRule("all", vpnIntf, "global_default", 10000, null, 6).catch((err) => {
log.error(`Failed to remove policy routing rule`, err.message);
});
// remove inbound connmark rule for vpn client interface
await execAsync(wrapIptables(`sudo iptables -w -t nat -D FW_PREROUTING_VC_INBOUND -i ${vpnIntf} -j CONNMARK --set-xmark ${rtId}/${routing.MASK_ALL}`)).catch((err) => {
Expand All @@ -211,15 +233,15 @@ class VPNClientEnforcer {
return `vpn_client_${vpnIntf}_set`;
}

async enforceDNSRedirect(vpnIntf, dnsServers, remoteIP, dnsRedirectChain) {
async enforceDNSRedirect(vpnIntf, dnsServers, dnsRedirectChain) {
if (!vpnIntf || !dnsServers || dnsServers.length == 0)
return;
const tableName = this._getRoutingTableName(vpnIntf);
await execAsync(wrapIptables(`sudo iptables -w -t nat -A FW_PREROUTING_DNS_VPN_CLIENT -j ${dnsRedirectChain}`)).catch((err) => {});
await execAsync(wrapIptables(`sudo ip6tables -w -t nat -A FW_PREROUTING_DNS_VPN_CLIENT -j ${dnsRedirectChain}`)).catch((err) => {});
}

async unenforceDNSRedirect(vpnIntf, dnsServers, remoteIP, dnsRedirectChain) {
async unenforceDNSRedirect(vpnIntf, dnsServers, dnsRedirectChain) {
if (!vpnIntf || !dnsServers || dnsServers.length == 0)
return;
const tableName = this._getRoutingTableName(vpnIntf);
Expand Down
10 changes: 10 additions & 0 deletions extension/vpnclient/docker/DockerBaseVPNClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -336,10 +336,14 @@ if $programname == 'docker_vpn_${this.profileId}' then {
const carrier = await fs.readFileAsync(`/sys/class/net/${this.getInterfaceName()}/carrier`, {encoding: "utf8"}).then(content => content.trim()).catch((err) => null);
if (carrier === "1") {
const remoteIP = await this._getRemoteIP();
const remoteIP6 = await this._getRemoteIP6();
if (remoteIP) {
// add the container IP to wan_routable so that packets from wan interfaces can be routed to the container
await routing.addRouteToTable(remoteIP, null, this.getInterfaceName(), "wan_routable", 1024, 4);
}
if (remoteIP6) {
await routing.addRouteToTable(remoteIP6, null, this.getInterfaceName(), "wan_routable", 1024, 6);
}
break;
}
t++;
Expand All @@ -350,8 +354,11 @@ if $programname == 'docker_vpn_${this.profileId}' then {
async _stop() {
await this._testAndStartDocker();
const remoteIP = await this._getRemoteIP();
const remoteIP6 = await this._getRemoteIP6();
if (remoteIP)
await exec(wrapIptables(`sudo iptables -w -t nat -D FW_POSTROUTING -s ${remoteIP} -j MASQUERADE`)).catch((err) => {});
if (remoteIP6)
await exec(wrapIptables(`sudo ip6tables -w -t nat -D FW_POSTROUTING -s ${remoteIP6} -j MASQUERADE`)).catch((err) => {});
await exec(`sudo systemctl stop docker-compose@${this.profileId}`);
await this._removeNetwork();
await this._removeRsyslogConf();
Expand All @@ -365,6 +372,9 @@ if $programname == 'docker_vpn_${this.profileId}' then {
const remoteIP = await this._getRemoteIP();
if (remoteIP)
subnets.push(remoteIP);
const remoteIP6 = await this._getRemoteIP6();
if (remoteIP6)
subnets.push(remoteIP6);
const results = _.uniq(subnets);
return results;
} else {
Expand Down
42 changes: 38 additions & 4 deletions net2/VirtWanGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,22 +176,37 @@ class VirtWanGroup {
wan.active = false;
const metric = wan.seq + 1 + (wan.ready ? 0 : 100);
const gw = await c._getRemoteIP();
const gw6 = await c._getRemoteIP6();
const localIP6 = await c._getLocalIP6();
await routing.addRouteToTable("default", gw, c.getInterfaceName(), this._getRTName(), metric, 4).catch((err) => {});
if (localIP6)
await routing.addRouteToTable("default", gw6, c.getInterfaceName(), this._getRTName(), metric, 6).catch((err) => {});
const dnsServers = await c._getDNSServers() || [];
for (const dnsServer of dnsServers) {
let af = 4;
if (!ipTool.isV4Format(dnsServer) && ipTool.isV6Format(dnsServer)) {
af = 6;
}
await routing.addRouteToTable(dnsServer, gw, c.getInterfaceName(), this._getRTName(), metric, af).catch((err) => {});
if (af == 4)
await routing.addRouteToTable(dnsServer, gw, c.getInterfaceName(), this._getRTName(), metric, af).catch((err) => {});
else {
if (localIP6)
await routing.addRouteToTable(dnsServer, gw6, c.getInterfaceName(), this._getRTName(), metric, af).catch((err) => {});
}
}
const vpnSubnets = await c.getRoutedSubnets();
if (_.isArray(vpnSubnets)) {
for (const vpnSubnet of vpnSubnets) {
let af = 4;
if (!ipTool.isV4Format(vpnSubnet) && ipTool.isV6Format(vpnSubnet))
af = 6;
await routing.addRouteToTable(vpnSubnet, gw, c.getInterfaceName(), this._getRTName(), metric, af).catch((err) => {});
if (af == 4)
await routing.addRouteToTable(vpnSubnet, gw, c.getInterfaceName(), this._getRTName(), metric, af).catch((err) => {});
else {
if (localIP6) {
await routing.addRouteToTable(vpnSubnet, gw6, c.getInterfaceName(), this._getRTName(), metric, af).catch((err) => {});
}
}
}
}
const settings = await c.loadSettings();
Expand All @@ -204,6 +219,7 @@ class VirtWanGroup {
let seq = 0;
const wans = Object.values(this.connState);
const multiPathDesc = [];
const multiPathDesc6 = [];
for (const wan of wans) {
const profileId = wan.profileId
const c = VPNClient.getInstance(profileId);
Expand All @@ -221,27 +237,43 @@ class VirtWanGroup {
wan.active = wan.ready;
let metric = seq + 1;
const gw = await c._getRemoteIP();
const gw6 = await c._getRemoteIP6();
const localIP6 = await c._getLocalIP6();
if (wan.ready) {
multiPathDesc.push({nextHop: gw, dev: c.getInterfaceName(), weight: wan.weight});
if (localIP6)
multiPathDesc6.push({nextHop: gw6, dev: c.getInterfaceName(), weight: wan.weight});
} else {
metric = seq + 1 + 100;
await routing.addRouteToTable("default", gw, c.getInterfaceName(), this._getRTName(), metric, 4).catch((err) => {});
if (localIP6)
await routing.addRouteToTable("default", gw6, c.getInterfaceName(), this._getRTName(), metric, 6).catch((err) => {});
}
const dnsServers = await c._getDNSServers() || [];
for (const dnsServer of dnsServers) {
let af = 4;
if (!ipTool.isV4Format(dnsServer) && ipTool.isV6Format(dnsServer)) {
af = 6;
}
await routing.addRouteToTable(dnsServer, gw, c.getInterfaceName(), this._getRTName(), metric, af).catch((err) => {});
if (af == 4)
await routing.addRouteToTable(dnsServer, gw, c.getInterfaceName(), this._getRTName(), metric, af).catch((err) => {});
else {
if (localIP6)
await routing.addRouteToTable(dnsServer, gw6, c.getInterfaceName(), this._getRTName(), metric, af).catch((err) => {});
}
}
const vpnSubnets = await c.getRoutedSubnets();
if (_.isArray(vpnSubnets)) {
for (const vpnSubnet of vpnSubnets) {
let af = 4;
if (!ipTool.isV4Format(vpnSubnet) && ipTool.isV6Format(vpnSubnet))
af = 6;
await routing.addRouteToTable(vpnSubnet, gw, c.getInterfaceName(), this._getRTName(), metric, af).catch((err) => { });
if (af == 4)
await routing.addRouteToTable(vpnSubnet, gw, c.getInterfaceName(), this._getRTName(), metric, af).catch((err) => { });
else {
if (localIP6)
await routing.addRouteToTable(vpnSubnet, gw6, c.getInterfaceName(), this._getRTName(), metric, af).catch((err) => { });
}
}
}
const settings = await c.loadSettings();
Expand All @@ -251,6 +283,8 @@ class VirtWanGroup {
}
if (multiPathDesc.length > 0)
await routing.addMultiPathRouteToTable("default", this._getRTName(), 4, ...multiPathDesc).catch((err) => {});
if (multiPathDesc6.length > 0)
await routing.addMultiPathRouteToTable("default", this._getRTName(), 6, ...multiPathDesc6).catch((err) => {});
break;
}
default:
Expand Down
Loading

0 comments on commit 00186fa

Please sign in to comment.