Skip to content

Commit

Permalink
Merge pull request #1258 from xinnige/fix/3411_pppoe_main_route
Browse files Browse the repository at this point in the history
Fix main route update
  • Loading branch information
jasonlyc authored May 13, 2024
2 parents 696845d + 44159b6 commit c06cf58
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 28 deletions.
105 changes: 83 additions & 22 deletions plugins/routing/routing_plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,11 @@ class RoutingPlugin extends Plugin {
}
if (!af || af == 4) {
// remove DNS specific routes
if (_.isArray(this._dnsRoutes)) {
for (const dnsRoute of this._dnsRoutes)
await routing.removeRouteFromTable(dnsRoute.dest, dnsRoute.gw, dnsRoute.viaIntf, "main", 4).catch((err) => { });
if (_.isObject(this._dnsRoutes)) {
for (const dnsRoute of Object.keys(this._dnsRoutes).map(key => this._dnsRoutes[key]))
await routing.removeRouteFromTable(dnsRoute.dest, dnsRoute.gw, dnsRoute.viaIntf, dnsRoute.tableName ? dnsRoute.tableName :"main", 4).catch((err) => { });
}
this._dnsRoutes = [];
this._dnsRoutes = {};
}
break;
}
Expand Down Expand Up @@ -166,13 +166,12 @@ class RoutingPlugin extends Plugin {
}

// should be protected by a lock when invoke
async _removeDeadRouting(tableName, af = null) {
async _removeDeviceRouting(intfPlugins, tableName, af = null) {
// remove dead wan device from route table
const deadWANIntfs = this.getUnreadyWANPlugins();
if (!deadWANIntfs) {
if (!intfPlugins) {
return;
}
for (const intf of deadWANIntfs) {
for (const intf of intfPlugins) {
// ignore gateway arg
if (!af || af == 4) {
await routing.removeDeviceRouteRule(intf.name, tableName).then(() => {
Expand All @@ -189,6 +188,58 @@ class RoutingPlugin extends Plugin {
}
}

async _removeDeviceDnsRouting(intfPlugins, af = null) {
if (!intfPlugins) {
return;
}
if (!af || af == 4) {
for (const intf of intfPlugins) {
if (this._dnsRoutes && _.isArray(this._dnsRoutes[intf.name])) {
for (const dnsRoute of this._dnsRoutes[intf.name]){
await routing.removeRouteFromTable(dnsRoute.dest, dnsRoute.gw, dnsRoute.viaIntf, dnsRoute.tableName ? dnsRoute.tableName: "main", 4).catch((err) => {
this.log.warn('fail to remove dns route from table main, err:', err.message)
})
}
delete (this._dnsRoutes, intf.name);
}
}
}
}

async _removeDeviceDefaultRouting(intfPlugins, tableName, af = null) {
if (!intfPlugins) {
return;
}
for (const intf of intfPlugins) {
if (!af || af == 4) {
await routing.removeRouteFromTable("default", null, intf.name, tableName).catch((err) => {
this.log.warn(`fail to remove route -4 default dev ${intf.name} table ${tableName}, err:`, err.message);
});
}
if (!af || af == 6) {
await routing.removeRouteFromTable("default", null, intf.name, tableName, 6).catch((err) => {
this.log.warn(`fail to remove route -6 default dev ${intf.name} table ${tableName}, err:`, err.message);
});
}
}
}

_updateDnsRouteCache(dnsIP, gw, viaIntf, metric, tableName="main") {
if (!this._dnsRoutes){
this._dnsRoutes = {}
}
if (!this._dnsRoutes[viaIntf]) {
this._dnsRoutes[viaIntf] = [];
}
for (const dns of this._dnsRoutes[viaIntf]) {
if (dns.dest == dnsIP && dns.gw == gw && dns.viaIntf == viaIntf && dns.metric == metric && dns.tableName == tableName) {
// ensure no duplicates
return;
}
}
this._dnsRoutes[viaIntf].push({dest: dnsIP, gw: gw, viaIntf: viaIntf, metric: metric, tableName: tableName});
}

async refreshGlobalIntfRoutes(intf, af = null) {
await lock.acquire(LOCK_SHARED, async () => {
// update global routes
Expand Down Expand Up @@ -243,7 +294,6 @@ class RoutingPlugin extends Plugin {
if (_.isArray(mainRules) && mainRules.length > 0) {
for (const rule of mainRules) {
if (rule.includes('default') || !rule.includes('via')) { // skip default
this.log.debug('[skip] del table main', rule);
continue
}
const cmd = `sudo ip -${af} route del ${rule} dev ${intf} table main`;
Expand All @@ -257,7 +307,9 @@ class RoutingPlugin extends Plugin {
for (const dnsIP of dns) {
await routing.addRouteToTable(dnsIP, gw, intf, routing.RT_GLOBAL_DEFAULT, metric, 4).catch((err) => {
this.log.warn(`fail to add route -4 ${dnsIP} via ${gw} dev ${intf} table ${routing.RT_GLOBAL_DEFAULT}, err:`, err.message)});
await routing.addRouteToTable(dnsIP, gw, intf, "main", metric, 4).catch((err) => {
await routing.addRouteToTable(dnsIP, gw, intf, "main", metric, 4).then(() => {
this._updateDnsRouteCache(dnsIP, gw, viaIntf, metric, "main");
}).catch((err) => {
this.log.warn(`fail to add route -4 ${dnsIP} via ${gw} dev ${intf} table main, err:`, err.message)});
}
}
Expand Down Expand Up @@ -349,13 +401,14 @@ class RoutingPlugin extends Plugin {
}
if (!af || af == 4) {
// remove DNS specific routes
if (_.isArray(this._dnsRoutes)) {
for (const dnsRoute of this._dnsRoutes)
await routing.removeRouteFromTable(dnsRoute.dest, dnsRoute.gw, dnsRoute.viaIntf, "main", 4).catch((err) => {
if (_.isObject(this._dnsRoutes)) {
for (const dnsRoute of Object.keys(this._dnsRoutes).map(key => this._dnsRoutes[key])) {
await routing.removeRouteFromTable(dnsRoute.dest, dnsRoute.gw, dnsRoute.viaIntf, dnsRoute.tableName ? dnsRoute.tableName : "main", 4).catch((err) => {
this.log.warn('fail to remove dns route from table main, err:', err.message)
});
}
}
this._dnsRoutes = [];
this._dnsRoutes = {};
}
}

Expand All @@ -365,18 +418,18 @@ class RoutingPlugin extends Plugin {
await lock.acquire(inAsyncContext ? LOCK_SHARED : LOCK_APPLY_ACTIVE_WAN, async () => {
// flush global default routing table, no need to touch global static routing table here
if (this.pluginConfig && this.pluginConfig.smooth_failover) {
await this._removeDeadRouting(routing.RT_GLOBAL_DEFAULT, af);
await this._removeDeadRouting(routing.RT_GLOBAL_LOCAL, af);
await this._removeDeadRouting('main', af);
const deadWANIntfs = this.getUnreadyWANPlugins();
await this._removeDeviceRouting(deadWANIntfs, routing.RT_GLOBAL_DEFAULT, af);
await this._removeDeviceRouting(deadWANIntfs, routing.RT_GLOBAL_LOCAL, af);
await this._removeDeviceDnsRouting(deadWANIntfs, af, "main");
await this._removeDeviceDefaultRouting(deadWANIntfs, "main", af);
} else {
await routing.flushRoutingTable(routing.RT_GLOBAL_DEFAULT, af);
await routing.flushRoutingTable(routing.RT_GLOBAL_LOCAL, af);
}

if (!this.pluginConfig || !this.pluginConfig.smooth_failover) {
await this._removeMainRoutes(af);
} else if (!af || af == 4) {
this._dnsRoutes = [];
}

const type = this.networkConfig.default.type || "single";
Expand Down Expand Up @@ -433,10 +486,11 @@ class RoutingPlugin extends Plugin {
this.log.error(`Failed to add route to ${routing.RT_GLOBAL_DEFAULT} for dns ${dnsIP} via ${gw} dev ${viaIntf}`, err.message);
});
// update all dns routes via the same interface but with new metrics in main table
await this.upsertRouteToTable(dnsIP, gw, viaIntf, "main", metric, 4).catch((err) => {
await this.upsertRouteToTable(dnsIP, gw, viaIntf, "main", metric, 4).then(() => {
this._updateDnsRouteCache(dnsIP, gw, viaIntf, metric, "main");
}).catch((err) => {
this.log.error(`Failed to add route to main for dns ${dnsIP} via ${gw} dev ${viaIntf}`, err.message);
});
this._dnsRoutes.push({dest: dnsIP, gw: gw, viaIntf: viaIntf, metric: metric});
}
}
} else {
Expand Down Expand Up @@ -522,7 +576,14 @@ class RoutingPlugin extends Plugin {
await routing.addRouteToTable(dnsIP, gw, viaIntf, "main", metric, 4, true).catch((err) => {
this.log.error(`Failed to add route to main for dns ${dnsIP} via ${gw} dev ${viaIntf}`, err.message);
});
this._dnsRoutes.push({dest: dnsIP, gw: gw, viaIntf: viaIntf, metric: metric});
if (!this._dnsRoutes){
log.warn("should init _dnsRoutes in load_balance mode");
this._dnsRoutes = {}
}
if (!this._dnsRoutes[viaIntf]) {
this._dnsRoutes[viaIntf] = [];
}
this._dnsRoutes[viaIntf].push({dest: dnsIP, gw: gw, viaIntf: viaIntf, metric: metric, tableName: "main"});
}
}
} else {
Expand Down
50 changes: 44 additions & 6 deletions tests/plugins/test_routing.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ let routing = require('../../util/routing.js');
let RoutingPlugin = require('../../plugins/routing/routing_plugin.js');
let InterfaceBasePlugin = require('../../plugins/interface/intf_base_plugin.js');


// path=routing {'routing': {...}} {"default":{"viaIntf":"eth0.204","type":"primary_standby","viaIntf2":"eth0","failback":true}}
const routingConfig = {
"default":
Expand Down Expand Up @@ -77,6 +76,10 @@ describe('Test Routing WAN', function(){
this.plugin._wanStatus["eth0"] = {seq:1, ready: true, active: false, plugin: new InterfaceBasePlugin('eth0')};
this.plugin._wanStatus["eth0.204"] = {seq:0, ready: true, active: true, plugin: new InterfaceBasePlugin('eth0.204')};
this.plugin._wanStatus["eth0.288"] = {ready: false, active: false, plugin: new InterfaceBasePlugin('eth0.288')};

await exec("echo 'nameserver 10.8.8.8' >> /home/pi/.router/run/eth0.288.resolv.conf").catch((err) => {log.error("add eth0.288 resolvconf err,", err.stderr)});
await exec("echo 'nameserver 8.8.8.8' >> /home/pi/.router/run/eth0.288.resolv.conf").catch((err) => {log.error("add eth0.288 resolvconf err,", err.stderr)});

done();
})()
);
Expand All @@ -88,6 +91,7 @@ describe('Test Routing WAN', function(){
await exec("sudo ip link set dev eth0.288 down").catch((err) => {});
await exec("sudo ip addr del 10.88.8.1/32 dev eth0.288").catch((err) => {});
await exec("sudo ip link del eth0.288").catch((err) => {});
await exec("rm /home/pi/.router/run/eth0.288.resolv.conf").catch((err) => {log.error("rm eth0.288 resolvconf err,", err.stderr)});
}
done();
})()
Expand All @@ -99,17 +103,48 @@ describe('Test Routing WAN', function(){
expect(deadWANs[0].name).to.be.equal('eth0.288');
});

it('should remove dead route rules', async() => {
it('should remove dead device route rules', async() => {
const deadWANs = this.plugin.getUnreadyWANPlugins();

let results = await routing.searchRouteRules(null, null, 'eth0.288', 'eth0.288_default');
expect(results.length).to.be.equal(4);

await this.plugin._removeDeadRouting("eth0.288_default");
await this.plugin._removeDeviceRouting(deadWANs, "eth0.288_default");

results = await routing.searchRouteRules(null, null, 'eth0.288', 'eth0.288_default');
expect(results.length).to.be.equal(0);
});

it('should remove dead target route rules', async() => {
await exec("sudo ip route flush table eth0.288_default dev eth0.288").catch((err) => {});
await exec("sudo ip route add table eth0.288_default default via 10.88.8.1").catch((err) => {log.error("add route", err.message)});
await exec("sudo ip route add table eth0.288_default 8.8.8.8 via 10.88.8.1 dev eth0.288 metric 101").catch((err) => {log.error("add route", err.message)});
await exec("sudo ip route add table eth0.288_default 10.8.8.8 via 10.88.8.1 dev eth0.288 metric 101").catch((err) => {log.error("add route", err.message)});

this.plugin._dnsRoutes = {"eth0.288":[
{dest: '8.8.8.8', viaIntf: 'eth0.288', gw: '10.88.8.1', metric: 101, tableName: 'eth0.288_default'},
{dest: '10.8.8.8', viaIntf: 'eth0.288', gw: '10.88.8.1', metric: 101, tableName: 'eth0.288_default'},
]};
const deadWANs = this.plugin.getUnreadyWANPlugins();
expect(deadWANs[0].name).to.be.equal('eth0.288');
expect(await deadWANs[0].getDNSNameservers()).to.be.eql(['10.8.8.8', '8.8.8.8']);

let results = await routing.searchRouteRules(null, null, 'eth0.288', 'eth0.288_default');
expect(results.length).to.be.equal(3);

// remote default route of dev eth0.288
await this.plugin._removeDeviceDefaultRouting(deadWANs, "eth0.288_default");
results = await routing.searchRouteRules(null, null, 'eth0.288', 'eth0.288_default');
expect(results.length).to.be.equal(2);

// remove dns routes
await this.plugin._removeDeviceDnsRouting(deadWANs);
results = await routing.searchRouteRules(null, null, 'eth0.288', 'eth0.288_default');
expect(results.length).to.be.equal(0);
});

it('should upsert route', async() => {
await exec("sudo ip route flush table eth0.288_default dev eth0.288").catch((err) => {});
await exec("sudo ip route add table eth0.288_default default via 10.88.8.1").catch((err) => {log.error("add route", err.message)});
await exec("sudo ip route add table eth0.288_default 10.88.8.0/30 dev eth0.288").catch((err) => {log.error("add route", err.message)});
await exec("sudo ip route add table eth0.288_default 10.88.8.3/32 via 10.88.8.1").catch((err) => {log.error("add route", err.message)});
Expand Down Expand Up @@ -187,6 +222,9 @@ describe('Test Routing WAN', function(){
expect(results.includes('default via 192.168.10.254 dev eth0.204 metric 101')).to.be.true;
expect(results.includes('default via 192.168.203.1 dev eth0 metric 2')).to.be.true;

results = await routing.searchRouteRules(null, null, 'eth0.204', 'main');
expect(results.includes('192.168.10.0/24 proto kernel scope link src 192.168.10.135')).to.be.true;

this.plugin.networkConfig = routingConfig;
this.plugin._wanStatus['eth0.204'].ready = true;
await this.plugin._applyActiveGlobalDefaultRouting(false, 4);
Expand All @@ -195,13 +233,13 @@ describe('Test Routing WAN', function(){
expect(results.includes('default via 192.168.203.1 dev eth0 metric 2')).to.be.true;
});

it ('should get rule metric', () => {
it('should get rule metric', () => {
expect(this.plugin._getRouteRuleMetric('table eth0.288_default 10.88.8.0/30 dev eth0.288')).to.be.null;
expect(this.plugin._getRouteRuleMetric('table eth0.288_default 10.88.8.0/30 dev eth0.288 metric 1')).to.be.equal('1');
expect(this.plugin._getRouteRuleMetric('fe80::/64 dev eth0.204 via fe80::226d:31ff:fe01:2b43 metric 1024 pref medium')).to.be.equal('1024');
});

it ('should get rule gateway', () => {
it('should get rule gateway', () => {
expect(this.plugin._getRouteRuleGateway('table eth0.288_default 10.88.8.0/30 dev eth0.288')).to.be.null;
expect(this.plugin._getRouteRuleGateway('table eth0.288_default via 10.88.8.1 10.88.8.0/30 dev eth0.288 metric 1')).to.be.equal('10.88.8.1');
expect(this.plugin._getRouteRuleGateway('fe80::/64 dev eth0.204 via fe80::226d:31ff:fe01:2b43 metric 1024 pref medium')).to.be.equal('fe80::226d:31ff:fe01:2b43');
Expand All @@ -216,4 +254,4 @@ describe('Test Routing WAN', function(){
expect(afterResults.length).to.be.equal(beforeResults.length);
});

});
});

0 comments on commit c06cf58

Please sign in to comment.