From 7e036e4e7ce22e9c62d07c1d51d90bae677dc467 Mon Sep 17 00:00:00 2001 From: xinnige Date: Tue, 5 Nov 2024 10:29:59 +0800 Subject: [PATCH 1/2] Fix dns6 option delegate --- core/event.js | 1 + plugins/dhcp/dhcp6_plugin.js | 3 +- plugins/interface/intf_base_plugin.js | 13 ++++ plugins/routing/routing_plugin.js | 103 ++++++++++++++++++++++---- scripts/firerouter_dhcpcd_resolv_dns6 | 18 +++++ scripts/firerouter_upgrade_check.sh | 2 +- sensors/ipchange_sensor.js | 4 + 7 files changed, 129 insertions(+), 15 deletions(-) diff --git a/core/event.js b/core/event.js index 70b6813b..74e27c0f 100644 --- a/core/event.js +++ b/core/event.js @@ -46,6 +46,7 @@ module.exports = { EVENT_WLAN_DOWN: "wlan_down", EVENT_IP_CHANGE: "ipchange", EVENT_IP6_CHANGE: "ip6change", + EVENT_DNS6_CHANGE: "dns6change", EVENT_PD_CHANGE: "pdchange", EVENT_PPPOE_IPV6_UP: "pppoe_ipv6_up", EVENT_WAN_CONN_CHECK: "wan_conn_check", diff --git a/plugins/dhcp/dhcp6_plugin.js b/plugins/dhcp/dhcp6_plugin.js index 4ca21a0c..6f55e6e9 100644 --- a/plugins/dhcp/dhcp6_plugin.js +++ b/plugins/dhcp/dhcp6_plugin.js @@ -16,6 +16,7 @@ 'use strict'; const pl = require('../plugin_loader.js'); +const event = require('../../core/event.js'); const r = require('../../util/firerouter.js'); const fs = require('fs'); const Promise = require('bluebird'); @@ -51,7 +52,7 @@ class DHCP6Plugin extends DHCPPlugin { if (nameservers.length > 0){ content.push(`dhcp-option=tag:${iface},option6:dns-server,${nameservers.map(a => `[${a}]`).join(",")}`); } else { // return router's link-local address as RDNSS option in ra - content.push(`dhcp-option=tag:${iface},option6:dns-server,[fe80::]`); + content.push(`dhcp-option=tag:${iface},option6:dns-server,[::]`); } switch (type) { diff --git a/plugins/interface/intf_base_plugin.js b/plugins/interface/intf_base_plugin.js index 1f74ff71..f7189219 100644 --- a/plugins/interface/intf_base_plugin.js +++ b/plugins/interface/intf_base_plugin.js @@ -1553,6 +1553,19 @@ class InterfaceBasePlugin extends Plugin { } break; } + case event.EVENT_DNS6_CHANGE: { + const payload = event.getEventPayload(e); + if (payload.intf === this.name && this.isWAN()) { + // update DNS from DHCP + pl.acquireApplyLock(async () => { + await this.applyDnsSettings().then(() => this.updateRouteForDNS()).catch((err) => { + this.log.error(`Failed to apply DNS settings and update DNS route on ${this.name}`, err.message); + }); + // this.propagateConfigChanged(true); + }); + } + break; + } case event.EVENT_PD_CHANGE: { const payload = event.getEventPayload(e); const iface = payload.intf; diff --git a/plugins/routing/routing_plugin.js b/plugins/routing/routing_plugin.js index 9edc8b54..1b52ab08 100644 --- a/plugins/routing/routing_plugin.js +++ b/plugins/routing/routing_plugin.js @@ -80,10 +80,18 @@ class RoutingPlugin extends Plugin { if (!af || af == 4) { // remove DNS specific routes 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 = {}; + for (const dnsRoute of Object.keys(this._dnsRoutes).map(key => this._dnsRoutes[key]).filter(i => i.af == 4)) + await routing.removeRouteFromTable(dnsRoute.dest, dnsRoute.gw, dnsRoute.viaIntf, dnsRoute.tableName ? dnsRoute.tableName :"main", dnsRoute.af).catch((err) => { }); + this._dnsRoutes = Object.entries(this._dnsRoutes).reduce((routes, item) => { if (item[1].af == 6) routes[item[0]] = item[1]; return routes }, {}); + } else { this._dnsRoutes = {} } + } + if (!af || af == 6) { + // remove DNS specific routes + if (_.isObject(this._dnsRoutes)) { + for (const dnsRoute of Object.keys(this._dnsRoutes).map(key => this._dnsRoutes[key]).filter(i => i.af == 6)) + await routing.removeRouteFromTable(dnsRoute.dest, dnsRoute.gw, dnsRoute.viaIntf, dnsRoute.tableName ? dnsRoute.tableName :"main", dnsRoute.af).catch((err) => { }); + this._dnsRoutes = Object.entries(this._dnsRoutes).reduce((routes, item) => { if (item[1].af != 6) routes[item[0]] = item[1]; return routes }, {}); + } else {this._dnsRoutes = {}} } break; } @@ -224,7 +232,7 @@ class RoutingPlugin extends Plugin { } } - _updateDnsRouteCache(dnsIP, gw, viaIntf, metric, tableName="main") { + _updateDnsRouteCache(dnsIP, gw, viaIntf, metric, tableName="main", af=4) { if (!this._dnsRoutes){ this._dnsRoutes = {} } @@ -237,7 +245,7 @@ class RoutingPlugin extends Plugin { return; } } - this._dnsRoutes[viaIntf].push({dest: dnsIP, gw: gw, viaIntf: viaIntf, metric: metric, tableName: tableName}); + this._dnsRoutes[viaIntf].push({dest: dnsIP, gw: gw, viaIntf: viaIntf, metric: metric, tableName: tableName, af:af}); } async refreshGlobalIntfRoutes(intf, af = null) { @@ -302,7 +310,7 @@ class RoutingPlugin extends Plugin { } } - const dns = await intfPlugin.getDNSNameservers(); + const dns = await intfPlugin.getDns4Nameservers(); if (_.isArray(dns) && dns.length > 0) { for (const dnsIP of dns) { await routing.addRouteToTable(dnsIP, gw, intf, routing.RT_GLOBAL_DEFAULT, metric, 4).catch((err) => { @@ -321,6 +329,18 @@ class RoutingPlugin extends Plugin { if (gw6) { await this.upsertRouteToTable("default", gw6, intf, routing.RT_GLOBAL_DEFAULT, metric, 6).catch((err) => { this.log.warn('fail to upsert route', err)}); await this.upsertRouteToTable("default", gw6, intf, "main", metric, 6).catch((err) => { this.log.warn('fail to upsert route', err)}); + + const dns6 = await intfPlugin.getDns6Nameservers(); + if (_.isArray(dns6) && dns6.length > 0) { + for (const dns6IP of dns6) { + await routing.addRouteToTable(dns6IP, gw6, intf, routing.RT_GLOBAL_DEFAULT, metric, 6).catch((err) => { + this.log.warn(`fail to add route -6 ${dns6IP} via ${gw6} dev ${intf} table ${routing.RT_GLOBAL_DEFAULT}, err:`, err.message)}); + await routing.addRouteToTable(dns6IP, gw6, intf, "main", metric, 6).then(() => { + this._updateDnsRouteCache(dns6IP, gw6, intf, metric, "main", 6); + }).catch((err) => { + this.log.warn(`fail to add route -6 ${dns6IP} via ${gw6} dev ${intf} table main, err:`, err.message)}); + } + } } } }); @@ -402,13 +422,24 @@ class RoutingPlugin extends Plugin { if (!af || af == 4) { // remove DNS specific routes if (_.isObject(this._dnsRoutes)) { - for (const dnsRoute of Object.keys(this._dnsRoutes).map(key => this._dnsRoutes[key])) { + for (const dnsRoute of Object.keys(this._dnsRoutes).map(key => this._dnsRoutes[key]).filter(i => i.af == 4)) { 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 = Object.entries(this._dnsRoutes).reduce((routes, item) => { if (item[1].af == 6) routes[item[0]] = item[1]; return routes }, {}); + } else { this._dnsRoutes = {}} + } + if (!af || af == 6) { + // remove DNS specific routes + if (_.isObject(this._dnsRoutes)) { + for (const dnsRoute of Object.keys(this._dnsRoutes).map(key => this._dnsRoutes[key]).filter(i => i.af == 6)) { + await routing.removeRouteFromTable(dnsRoute.dest, dnsRoute.gw, dnsRoute.viaIntf, dnsRoute.tableName ? dnsRoute.tableName : "main", 6).catch((err) => { + this.log.warn('fail to remove dns route from table main, err:', err.message) + }); + } + this._dnsRoutes = Object.entries(this._dnsRoutes).reduce((routes, item) => { if (item[1].af != 6) routes[item[0]] = item[1]; return routes }, {}); + } else { this._dnsRoutes = {}} } } @@ -479,7 +510,7 @@ class RoutingPlugin extends Plugin { await this.upsertRouteToTable("default", gw, viaIntf, routing.RT_GLOBAL_DEFAULT, metric, 4).catch((err) => { }); await this.upsertRouteToTable("default", gw, viaIntf, "main", metric, 4).catch((err) => { }); // add route for DNS nameserver IP in global_default table - const dns = await viaIntfPlugin.getDNSNameservers(); + const dns = await viaIntfPlugin.getDns4Nameservers(); if (_.isArray(dns) && dns.length !== 0) { for (const dnsIP of dns) { await this.upsertRouteToTable(dnsIP, gw, viaIntf, routing.RT_GLOBAL_DEFAULT, metric, 4).catch((err) => { @@ -503,6 +534,22 @@ class RoutingPlugin extends Plugin { if (gw6 && (ready || type === "single")) { // do not add IPv6 default route for inactive WAN under dual WAN setup, WAN connectivity check only uses IPv4 await this.upsertRouteToTable("default", gw6, viaIntf, routing.RT_GLOBAL_DEFAULT, metric, 6).catch((err) => { }); await this.upsertRouteToTable("default", gw6, viaIntf, "main", metric, 6).catch((err) => { }); + // add route for ipv6 DNS nameserver IP in global_default table + const dns6 = await viaIntfPlugin.getDns6Nameservers(); + if (_.isArray(dns6) && dns6.length !== 0 ){ + for (const dns6IP of dns6) { + await this.upsertRouteToTable(dns6IP, gw6, viaIntf, routing.RT_GLOBAL_DEFAULT, metric, 6).catch((err) => { + this.log.error(`Failed to add route ipv6 to ${routing.RT_GLOBAL_DEFAULT} for dns ${dns6IP} via ${gw6} dev ${viaIntf}`, err.message); + }); + // update all dns routes via the same interface but with new metrics in main table + await this.upsertRouteToTable(dns6IP, gw6, viaIntf, "main", metric, 6).then(() => { + this._updateDnsRouteCache(dns6IP, gw6, viaIntf, metric, "main", 6); + }).catch((err) => { + this.log.error(`Failed to add route to main for dns ${dns6IP} via ${gw6} dev ${viaIntf}`, err.message); + }); + } + } + } else { this.log.info("IPv6 gateway is not defined on global default interface " + viaIntf); } @@ -558,7 +605,7 @@ class RoutingPlugin extends Plugin { await routing.addRouteToTable("default", gw, viaIntf, "main", metric, 4).catch((err) => { }); } // add route for DNS nameserver IP in global_default table - const dns = await viaIntfPlugin.getDNSNameservers(); + const dns = await viaIntfPlugin.getDns4Nameservers(); if (_.isArray(dns) && dns.length !== 0) { for (const dnsIP of dns) { await routing.addRouteToTable(dnsIP, gw, viaIntf, routing.RT_GLOBAL_DEFAULT, metric, 4, true).catch((err) => { @@ -583,7 +630,7 @@ class RoutingPlugin extends Plugin { if (!this._dnsRoutes[viaIntf]) { this._dnsRoutes[viaIntf] = []; } - this._dnsRoutes[viaIntf].push({dest: dnsIP, gw: gw, viaIntf: viaIntf, metric: metric, tableName: "main"}); + this._dnsRoutes[viaIntf].push({dest: dnsIP, gw: gw, viaIntf: viaIntf, metric: metric, tableName: "main", af: af}); } } } else { @@ -603,6 +650,35 @@ class RoutingPlugin extends Plugin { await routing.addRouteToTable("default", gw6, viaIntf, "main", metric, 6).catch((err) => { }); */ } + // add route for ipv6 DNS nameserver IP in global_default table + const dns6 = await viaIntfPlugin.getDns6Nameservers(); + if (_.isArray(dns6) && dns6.length !== 0) { + for (const dns6IP of dns6) { + await routing.addRouteToTable(dns6IP, gw6, viaIntf, routing.RT_GLOBAL_DEFAULT, metric, 6, true).catch((err) => { + this.log.error(`Failed to add route to ${routing.RT_GLOBAL_DEFAULT} for dns ${dns6IP} via ${gw6} dev ${viaIntf}`, err.message); + }); + let dnsRouteRemoved = false; + // remove all ipv6 dns routes via the same interface but with different metrics in main table + do { + await routing.removeRouteFromTable(dns6IP, gw6, viaIntf, "main", 6).then(() => { + dnsRouteRemoved = true; + }).catch((err) => { + dnsRouteRemoved = false; + }) + } while (dnsRouteRemoved) + await routing.addRouteToTable(dns6IP, gw6, viaIntf, "main", metric, 6, true).catch((err) => { + this.log.error(`Failed to add route to main for dns ${dns6IP} via ${gw6} dev ${viaIntf}`, err.message); + }); + 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: dns6IP, gw: gw6, viaIntf: viaIntf, metric: metric, tableName: "main", af: af}); + } + } } else { this.log.info("Failed to get IPv6 gateway of global default interface " + viaIntf); } @@ -616,6 +692,7 @@ class RoutingPlugin extends Plugin { await routing.addMultiPathRouteToTable("default", routing.RT_GLOBAL_DEFAULT, 6, ...multiPathDesc6).catch((err) => { }); await routing.addMultiPathRouteToTable("default", "main", 6, ...multiPathDesc6).catch((err) => { }); } + break; } default: diff --git a/scripts/firerouter_dhcpcd_resolv_dns6 b/scripts/firerouter_dhcpcd_resolv_dns6 index 3867f5a8..f2ee40de 100644 --- a/scripts/firerouter_dhcpcd_resolv_dns6 +++ b/scripts/firerouter_dhcpcd_resolv_dns6 @@ -10,6 +10,8 @@ resolv_conf_file="/run/resolvconf/interface/$interface.dhcpcd" NL=" " : ${resolvconf:=resolvconf} +dns_changed="" +dns6_md5="" # Extract any ND DNS options from the RA # For now, we ignore the lifetime of the DNS options unless they @@ -93,6 +95,7 @@ add_resolv_conf() [ "$new_domain_name" = "$1" ] && warn=true fi fi + if [ -n "$new_domain_search" ]; then if valid_domainname_list $new_domain_search; then conf="${conf}search $new_domain_search$NL" @@ -106,6 +109,10 @@ add_resolv_conf() done echo "${conf}" > $resolv_conf_file + new_dns6_md5=`md5sum $resolv_conf_file | cut -d" " -f1` + if [ "$dns6_md5" != "$new_dns6_md5" ];then + redis-cli -n 1 publish "dhcpcd6.dns_change" "$interface" + fi if type "$resolvconf" >/dev/null 2>&1; then [ -n "$ifmetric" ] && export IF_METRIC="$ifmetric" @@ -120,8 +127,19 @@ remove_resolv_conf() "$resolvconf" -d "$ifname" -f fi rm $resolv_conf_file + if [ -n "$dns6_md5" ]; then + redis-cli -n 1 publish "dhcpcd6.dns_change" "$interface" + fi +} + +md5_resolv_conf() +{ + if [ -s $resolv_conf_file ]; then + dns6_md5=`md5sum $resolv_conf_file | cut -d" " -f1` + fi } +md5_resolv_conf # For ease of use, map DHCP6 names onto our DHCP4 names case "$reason" in BOUND6|RENEW6|REBIND6|REBOOT6|INFORM6) diff --git a/scripts/firerouter_upgrade_check.sh b/scripts/firerouter_upgrade_check.sh index 50424f00..4cb670a1 100755 --- a/scripts/firerouter_upgrade_check.sh +++ b/scripts/firerouter_upgrade_check.sh @@ -32,7 +32,7 @@ FIREROUTER_CANARY_SCRIPT="${FIREROUTER_HOME}/scripts/firerouter_upgrade_canary.s FRCANARY_FLAG="/home/pi/.router/config/.no_upgrade_canary" if [[ -e "$FIREROUTER_CANARY_SCRIPT" ]];then - $FIREROUTER_CANARY_SCRIPT &> /tmp/firerouter_upgrade_canary.log + bash $FIREROUTER_CANARY_SCRIPT &> /tmp/firerouter_upgrade_canary.log fi if [[ -e $FRCANARY_FLAG ]]; then diff --git a/sensors/ipchange_sensor.js b/sensors/ipchange_sensor.js index d5314681..5782b088 100644 --- a/sensors/ipchange_sensor.js +++ b/sensors/ipchange_sensor.js @@ -39,6 +39,9 @@ class IPChangeSensor extends Sensor { case "dhcpcd6.pd_change": eventType = event.EVENT_PD_CHANGE; break; + case "dhcpcd6.dns_change": + eventType = event.EVENT_DNS6_CHANGE; + break; case "pppoe.ipv6_up": eventType = event.EVENT_PPPOE_IPV6_UP; break; @@ -68,6 +71,7 @@ class IPChangeSensor extends Sensor { sclient.subscribe("pppoe.ip_change"); sclient.subscribe("dhcpcd6.ip_change"); sclient.subscribe("dhcpcd6.pd_change"); + sclient.subscribe("dhcpcd6.dns_change"); sclient.subscribe("pppoe.ipv6_up"); } } From d406b32e04808b4c29163d012b356fd656037312 Mon Sep 17 00:00:00 2001 From: xinnige Date: Tue, 5 Nov 2024 18:37:57 +0800 Subject: [PATCH 2/2] Fix resolvconf default dns --- plugins/dhcp/dhcp6_plugin.js | 3 +-- plugins/interface/intf_base_plugin.js | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/plugins/dhcp/dhcp6_plugin.js b/plugins/dhcp/dhcp6_plugin.js index 6f55e6e9..a39ce392 100644 --- a/plugins/dhcp/dhcp6_plugin.js +++ b/plugins/dhcp/dhcp6_plugin.js @@ -16,7 +16,6 @@ 'use strict'; const pl = require('../plugin_loader.js'); -const event = require('../../core/event.js'); const r = require('../../util/firerouter.js'); const fs = require('fs'); const Promise = require('bluebird'); @@ -51,7 +50,7 @@ class DHCP6Plugin extends DHCPPlugin { if (nameservers.length > 0){ content.push(`dhcp-option=tag:${iface},option6:dns-server,${nameservers.map(a => `[${a}]`).join(",")}`); - } else { // return router's link-local address as RDNSS option in ra + } else { // return global address as RDNSS option in ra content.push(`dhcp-option=tag:${iface},option6:dns-server,[::]`); } diff --git a/plugins/interface/intf_base_plugin.js b/plugins/interface/intf_base_plugin.js index f7189219..76dc8257 100644 --- a/plugins/interface/intf_base_plugin.js +++ b/plugins/interface/intf_base_plugin.js @@ -643,10 +643,14 @@ class InterfaceBasePlugin extends Plugin { let dnsservers = []; if (this.networkConfig.nameservers && this.networkConfig.nameservers.length > 0 && this.networkConfig.nameservers.some(s => new Address4(s).isValid())) { dnsservers = this.networkConfig.nameservers.filter(s => new Address4(s).isValid()); + } else { + dnsservers = await this.getOrigDNSNameservers(); } if (this.networkConfig.dns6Servers && this.networkConfig.dns6Servers.some(s => new Address6(s).isValid())) { dnsservers = dnsservers.concat(this.networkConfig.dns6Servers.filter(s => new Address6(s).isValid())); + } else { + dnsservers = dnsservers.concat(await this.getOrigDNS6Nameservers()); } if (dnsservers.length > 0) { let nameservers = dnsservers.map((nameserver) => `nameserver ${nameserver}`).join("\n") + "\n";