🏅 Sync 2026-10-03 01:21:24

This commit is contained in:
github-actions[bot] committed 2026-10-03 01:21:24 +08:00
1 parent cb1d978497
commit 54d0dfd24f
9 files changed
+209 -78

No files matched your search

+5 -1
View File
@@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=linkback PKG_NAME:=linkback
PKG_VERSION:=1.0.0 PKG_VERSION:=1.0.0
PKG_RELEASE:=2 PKG_RELEASE:=3
PKG_MAINTAINER:=Dante PKG_MAINTAINER:=Dante
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME) PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
@@ -56,6 +56,10 @@ define Package/$(PKG_NAME)/install
$(INSTALL_CONF) ./files/linkback.config $(1)/etc/config/linkback $(INSTALL_CONF) ./files/linkback.config $(1)/etc/config/linkback
$(INSTALL_DIR) $(1)/etc/init.d $(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_BIN) ./files/linkback.init $(1)/etc/init.d/linkback $(INSTALL_BIN) ./files/linkback.init $(1)/etc/init.d/linkback
# Install hotplug script
$(INSTALL_DIR) $(1)/etc/hotplug.d/iface
$(INSTALL_BIN) ./files/90-linkback $(1)/etc/hotplug.d/iface/90-linkback
endef endef
define Package/$(PKG_NAME)/postinst define Package/$(PKG_NAME)/postinst
+16
View File
@@ -0,0 +1,16 @@
#!/bin/sh
# SPDX-License-Identifier: Apache-2.0
# Copyright (C) 2026 Dante <dante@seckv.com>
#
# Hotplug event trigger script for LinkBack daemon.
# Instantly wakes up linkbackd on interface ifup/ifdown state changes.
[ "$ACTION" = "ifup" ] || [ "$ACTION" = "ifdown" ] || exit 0
[ -n "$INTERFACE" ] || exit 0
# Check if linkbackd is running
PID=$(pidof linkbackd)
[ -n "$PID" ] || exit 0
# Send SIGUSR1 to trigger immediate health check / short-circuit
kill -USR1 $PID 2>/dev/null
+125 -36
View File
@@ -40,6 +40,12 @@ static struct ubus_context *ubus_ctx = NULL;
// Prototypes // Prototypes
static void restore_all_metrics(void); static void restore_all_metrics(void);
static bool validate_loaded_config(void); static bool validate_loaded_config(void);
static volatile sig_atomic_t hotplug_triggered = 0;
static void handle_sigusr1(int sig) {
(void)sig;
hotplug_triggered = 1;
}
static void handle_signal(int sig) { static void handle_signal(int sig) {
syslog(LOG_INFO, "Received signal %d, exiting...", sig); syslog(LOG_INFO, "Received signal %d, exiting...", sig);
keep_running = false; keep_running = false;
@@ -439,6 +445,9 @@ static bool load_config(void) {
const char *failover = uci_lookup_option_string(ctx, global_sec, "failover_delay"); const char *failover = uci_lookup_option_string(ctx, global_sec, "failover_delay");
if (failover) global_cfg.failover_delay = atoi(failover); if (failover) global_cfg.failover_delay = atoi(failover);
global_cfg.is_up = false;
global_cfg.last_is_up = false;
} }
// Parse links // Parse links
@@ -527,6 +536,7 @@ static bool load_config(void) {
// Default runtime states // Default runtime states
link->healthy = true; link->healthy = true;
link->is_up = false; link->is_up = false;
link->last_is_up = false;
link->last_checked = 0; // Force immediate check on startup link->last_checked = 0; // Force immediate check on startup
link_count++; link_count++;
@@ -843,9 +853,10 @@ int main(int argc, char **argv) {
openlog("linkbackd", LOG_PID | LOG_NDELAY, LOG_DAEMON); openlog("linkbackd", LOG_PID | LOG_NDELAY, LOG_DAEMON);
syslog(LOG_INFO, "Starting LinkBack daemon..."); syslog(LOG_INFO, "Starting LinkBack daemon...");
// Register signal handlers for clean exits and metric restoration // Register signal handlers for clean exits, metric restoration, and hotplug wakeup
signal(SIGTERM, handle_signal); signal(SIGTERM, handle_signal);
signal(SIGINT, handle_signal); signal(SIGINT, handle_signal);
signal(SIGUSR1, handle_sigusr1);
// Load configuration // Load configuration
if (!load_config()) { if (!load_config()) {
@@ -871,59 +882,137 @@ int main(int argc, char **argv) {
time_t now = time(NULL); time_t now = time(NULL);
bool any_checked = false; bool any_checked = false;
// If in multi_gw mode, resolve bind interface physical device once per cycle bool force_check = false;
if (hotplug_triggered) {
hotplug_triggered = 0;
force_check = true;
syslog(LOG_INFO, "[Diagnostic] Hotplug signal received (SIGUSR1), triggering immediate interface & health evaluation.");
}
// In multi_gw mode, resolve and verify the bind interface state first
if (global_cfg.mode == MODE_MULTI_GW) { if (global_cfg.mode == MODE_MULTI_GW) {
char dev[MAX_NAME_LEN] = {0}; char dev[MAX_NAME_LEN] = {0};
char gw[MAX_IP_LEN] = {0}; char gw[MAX_IP_LEN] = {0};
bool if_up = false; bool if_up = false;
get_interface_ubus_status(global_cfg.interface, dev, sizeof(dev), gw, sizeof(gw), &if_up); bool ubus_ok = get_interface_ubus_status(global_cfg.interface, dev, sizeof(dev), gw, sizeof(gw), &if_up);
strncpy(global_cfg.device, dev, sizeof(global_cfg.device) - 1); bool physical_up = ubus_ok && if_up && (dev[0] != '\0');
if (dev[0] != '\0') {
strncpy(global_cfg.device, dev, sizeof(global_cfg.device) - 1);
global_cfg.device[sizeof(global_cfg.device) - 1] = '\0';
}
global_cfg.is_up = physical_up;
// Diagnostic logging on bind interface UP/DOWN transition
if (global_cfg.is_up != global_cfg.last_is_up) {
syslog(LOG_NOTICE, "[Diagnostic] Multi-GW bind interface '%s' (%s) link state changed: %s -> %s",
global_cfg.interface, global_cfg.device[0] ? global_cfg.device : "unknown",
global_cfg.last_is_up ? "UP" : "DOWN",
global_cfg.is_up ? "UP" : "DOWN");
global_cfg.last_is_up = global_cfg.is_up;
}
// P2 & Diagnostic: Global short-circuit when bind interface is DOWN.
// Stop all gateway probe attempts immediately without wasting timeout intervals.
if (!global_cfg.is_up) {
bool state_changed = false;
for (int i = 0; i < link_count; i++) {
link_t *link = &links[i];
link->is_up = false;
if (global_cfg.device[0] != '\0') {
strncpy(link->device, global_cfg.device, sizeof(link->device) - 1);
link->device[sizeof(link->device) - 1] = '\0';
}
if (link->gateway_cfg[0] != '\0') {
strncpy(link->gateway, link->gateway_cfg, sizeof(link->gateway) - 1);
link->gateway[sizeof(link->gateway) - 1] = '\0';
}
if (link->healthy || link->current_metric != 1000 + link->metric) {
syslog(LOG_WARNING, "[Diagnostic] Multi-GW bind interface '%s' is DOWN. Short-circuiting probe for target '%s' (gw=%s) and demoting metric.",
global_cfg.interface, link->name, link->gateway[0] ? link->gateway : "none");
link->healthy = false;
update_route_metric(link, 1000 + link->metric, -1);
state_changed = true;
}
link->current_score = 0;
link->ping_ok = false;
link->dns_ok = false;
link->tcp_ok = false;
link->consecutive_success = 0;
link->consecutive_failure = link->failover_delay;
link->last_checked = now;
}
if (state_changed) {
write_status_json();
}
sleep(1);
continue;
}
} }
for (int i = 0; i < link_count; i++) { for (int i = 0; i < link_count; i++) {
link_t *link = &links[i]; link_t *link = &links[i];
// Check if this link is due for checking // Check if this link is due for checking (bypass interval if hotplug triggered)
if (now - link->last_checked < link->check_interval) { if (!force_check && (now - link->last_checked < link->check_interval)) {
continue; continue;
} }
link->last_checked = now; link->last_checked = now;
any_checked = true; any_checked = true;
// 1. Fetch real-time netifd status // 1. Fetch real-time netifd status
char dev[MAX_NAME_LEN] = {0};
char gw[MAX_IP_LEN] = {0};
bool is_up = false;
if (global_cfg.mode == MODE_MULTI_GW) { if (global_cfg.mode == MODE_MULTI_GW) {
// Inherit physical device from global bind interface // Inherit verified UP device from global bind interface
is_up = (global_cfg.device[0] != '\0'); link->is_up = true;
strncpy(dev, global_cfg.device, sizeof(dev) - 1); strncpy(link->device, global_cfg.device, sizeof(link->device) - 1);
strncpy(gw, link->gateway_cfg, sizeof(gw) - 1); link->device[sizeof(link->device) - 1] = '\0';
strncpy(link->gateway, link->gateway_cfg, sizeof(link->gateway) - 1);
link->gateway[sizeof(link->gateway) - 1] = '\0';
} else { } else {
// Query netifd for WAN interface // Multi-WAN mode: query netifd for independent interface status
get_interface_ubus_status(link->name, dev, sizeof(dev), gw, sizeof(gw), &is_up); char dev[MAX_NAME_LEN] = {0};
} char gw[MAX_IP_LEN] = {0};
bool is_up = false;
bool ubus_ok = get_interface_ubus_status(link->name, dev, sizeof(dev), gw, sizeof(gw), &is_up);
bool physical_up = ubus_ok && is_up && (dev[0] != '\0');
link->is_up = is_up; if (physical_up != link->last_is_up) {
strncpy(link->device, dev, MAX_NAME_LEN - 1); syslog(LOG_NOTICE, "[Diagnostic] Multi-WAN interface '%s' (%s) link state changed: %s -> %s",
strncpy(link->gateway, gw, MAX_IP_LEN - 1); link->name, dev[0] ? dev : "unknown",
link->last_is_up ? "UP" : "DOWN",
physical_up ? "UP" : "DOWN");
link->last_is_up = physical_up;
}
// P2 optimization: Interface down short-circuit link->is_up = physical_up;
if (!is_up || dev[0] == '\0') { if (dev[0] != '\0') {
link->healthy = false; strncpy(link->device, dev, sizeof(link->device) - 1);
link->current_score = 0; link->device[sizeof(link->device) - 1] = '\0';
link->ping_ok = false; }
link->dns_ok = false; if (gw[0] != '\0') {
link->tcp_ok = false; strncpy(link->gateway, gw, sizeof(link->gateway) - 1);
link->consecutive_success = 0; link->gateway[sizeof(link->gateway) - 1] = '\0';
link->consecutive_failure = 0; }
// If it still has low metric, float it immediately // Local short-circuit: interface DOWN, skip probe for this link
if (link->current_metric == link->metric) { if (!physical_up) {
update_route_metric(link, 1000 + link->metric, -1); if (link->healthy || link->current_metric != 1000 + link->metric) {
syslog(LOG_WARNING, "[Diagnostic] Multi-WAN interface '%s' is DOWN. Short-circuiting probe for link '%s' and demoting metric.",
link->name, link->name);
link->healthy = false;
update_route_metric(link, 1000 + link->metric, -1);
}
link->current_score = 0;
link->ping_ok = false;
link->dns_ok = false;
link->tcp_ok = false;
link->consecutive_success = 0;
link->consecutive_failure = link->failover_delay;
continue;
} }
continue;
} }
// 2. Perform health checks (P2: 1s non-blocking timeout) // 2. Perform health checks (P2: 1s non-blocking timeout)
@@ -973,7 +1062,7 @@ int main(int argc, char **argv) {
if (!link->healthy && link->consecutive_success >= link->recovery_delay) { if (!link->healthy && link->consecutive_success >= link->recovery_delay) {
// Recovered! Failback! // Recovered! Failback!
link->healthy = true; link->healthy = true;
syslog(LOG_NOTICE, "Link %s (%s, gw=%s, priority %d) recovered to healthy after %d successes.", syslog(LOG_NOTICE, "[Diagnostic] Link %s (%s, gw=%s, priority %d) recovered to healthy after %d successes.",
link->name, link->device, link->gateway[0] ? link->gateway : "none", link->priority, link->consecutive_success); link->name, link->device, link->gateway[0] ? link->gateway : "none", link->priority, link->consecutive_success);
// Restore original metric // Restore original metric
@@ -986,7 +1075,7 @@ int main(int argc, char **argv) {
if (link->healthy && link->consecutive_failure >= link->failover_delay) { if (link->healthy && link->consecutive_failure >= link->failover_delay) {
// Failed! Failover! // Failed! Failover!
link->healthy = false; link->healthy = false;
syslog(LOG_WARNING, "Link %s (%s, gw=%s, priority %d) went down after %d failures.", syslog(LOG_WARNING, "[Diagnostic] Link %s (%s, gw=%s, priority %d) went down after %d failures.",
link->name, link->device, link->gateway[0] ? link->gateway : "none", link->priority, link->consecutive_failure); link->name, link->device, link->gateway[0] ? link->gateway : "none", link->priority, link->consecutive_failure);
// Push metric out of choice range // Push metric out of choice range
@@ -999,7 +1088,7 @@ int main(int argc, char **argv) {
int expected_metric = (link->is_up && link->healthy) ? link->metric : (1000 + link->metric); int expected_metric = (link->is_up && link->healthy) ? link->metric : (1000 + link->metric);
int real_metric = get_system_route_metric(link->device, link->gateway, expected_metric); int real_metric = get_system_route_metric(link->device, link->gateway, expected_metric);
if (real_metric != -1 && real_metric != expected_metric) { if (real_metric != -1 && real_metric != expected_metric) {
syslog(LOG_WARNING, "Route metric mismatch detected on %s (%s, gw=%s, priority %d): expected %d, got %d. Correcting...", syslog(LOG_WARNING, "[Diagnostic] Route metric mismatch detected on %s (%s, gw=%s, priority %d): expected %d, got %d. Correcting...",
link->name, link->device, link->gateway[0] ? link->gateway : "none", link->priority, expected_metric, real_metric); link->name, link->device, link->gateway[0] ? link->gateway : "none", link->priority, expected_metric, real_metric);
update_route_metric(link, expected_metric, real_metric); update_route_metric(link, expected_metric, real_metric);
} }
+3
View File
@@ -51,6 +51,7 @@ typedef struct {
char device[MAX_NAME_LEN]; // physical interface name e.g., pppoe-wan, eth1, br-lan char device[MAX_NAME_LEN]; // physical interface name e.g., pppoe-wan, eth1, br-lan
char gateway[MAX_IP_LEN]; // gateway IP address char gateway[MAX_IP_LEN]; // gateway IP address
bool is_up; // whether interface is reported up by netifd bool is_up; // whether interface is reported up by netifd
bool last_is_up; // track interface UP/DOWN transition for logging
bool healthy; // daemon link health state bool healthy; // daemon link health state
int consecutive_success; int consecutive_success;
int consecutive_failure; int consecutive_failure;
@@ -73,6 +74,8 @@ typedef struct {
linkback_mode_t mode; linkback_mode_t mode;
char interface[MAX_NAME_LEN]; // Bind interface in multi_gw mode (e.g. lan) char interface[MAX_NAME_LEN]; // Bind interface in multi_gw mode (e.g. lan)
char device[MAX_NAME_LEN]; // Resolved device in multi_gw mode (e.g. br-lan) char device[MAX_NAME_LEN]; // Resolved device in multi_gw mode (e.g. br-lan)
bool is_up; // Bind interface netifd up status
bool last_is_up; // track bind interface UP/DOWN transition for logging
int check_interval; int check_interval;
int check_timeout; int check_timeout;
int recovery_delay; int recovery_delay;
+1 -1
View File
@@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-linkback PKG_NAME:=luci-app-linkback
PKG_VERSION:=1.0.0 PKG_VERSION:=1.0.0
PKG_RELEASE:=2 PKG_RELEASE:=3
PKG_MAINTAINER:=Dante PKG_MAINTAINER:=Dante
LUCI_TITLE:=LuCI support for Multi-WAN LinkBack Failover/Failback LUCI_TITLE:=LuCI support for Multi-WAN LinkBack Failover/Failback
@@ -168,6 +168,8 @@ return view.extend({
uci.set('linkback', section_id, 'enabled', value); uci.set('linkback', section_id, 'enabled', value);
}; };
var refreshTargetsSection = null;
// 2. Working Mode // 2. Working Mode
o = s.option(form.ListValue, 'mode', _('Working Mode'), o = s.option(form.ListValue, 'mode', _('Working Mode'),
_('Choose failover mode: Multi-WAN interface failover or Single-WAN multi-gateway redundancy.')); _('Choose failover mode: Multi-WAN interface failover or Single-WAN multi-gateway redundancy.'));
@@ -177,17 +179,12 @@ return view.extend({
o.rmempty = false; o.rmempty = false;
o.onchange = function(ev, section_id, value) { o.onchange = function(ev, section_id, value) {
uci.set('linkback', section_id, 'mode', value); uci.set('linkback', section_id, 'mode', value);
var links = uci.sections('linkback', 'link') || []; // 切换工作模式时直接清空旧链路配置,保持单模式纯净性
if (links.length > 0) { uci.sections('linkback', 'link').forEach(function(sec) {
if (value === 'multi_gw') { uci.remove('linkback', sec['.name']);
ui.addNotification(null, E('p', });
_('已切换为单wan多网关模式。请确保监控目标列表中配置了有效的网关 IP。') if (typeof refreshTargetsSection === 'function') {
), 'info'); refreshTargetsSection();
} else {
ui.addNotification(null, E('p',
_('已切换为多wan口模式。请确保监控目标列表中选择了正确的 WAN 接口。')
), 'info');
}
} }
}; };
@@ -228,11 +225,37 @@ return view.extend({
o.rmempty = false; o.rmempty = false;
// --- Monitored Targets Section --- // --- Monitored Targets Section ---
s = m.section(form.GridSection, 'link', _('Monitored Targets'), var targets_section = s = m.section(form.GridSection, 'link', _('Monitored Targets'),
_('Configure monitored WAN interfaces or next-hop gateways with custom probe targets and priorities (1 = primary, 2 = backup).')); _('Configure monitored WAN interfaces or next-hop gateways with custom probe targets and priorities (1 = primary, 2 = backup).'));
s.anonymous = true; s.anonymous = true;
s.addremove = true; s.addremove = true;
var origSectionRender = s.render;
s.render = function() {
var mode = getActiveMode();
if (mode === 'multi_gw') {
this.title = _('Monitored Gateways');
this.description = _('Add next-hop gateways with custom probe targets and priorities (1 = primary, 2 = backup).');
} else {
this.title = _('Monitored WAN Interfaces');
this.description = _('Add WAN interfaces from firewall zone with custom probe targets and priorities (1 = primary, 2 = backup).');
}
return origSectionRender.apply(this, arguments);
};
refreshTargetsSection = function() {
var old_node = document.getElementById('cbi-linkback-link') ||
document.querySelector('.cbi-section[data-tab="link"]') ||
document.querySelector('.cbi-section[id*="link"]');
if (old_node && old_node.parentNode) {
targets_section.render().then(function(new_node) {
if (old_node.parentNode) {
old_node.parentNode.replaceChild(new_node, old_node);
}
});
}
};
// Custom dynamic Modal title // Custom dynamic Modal title
s.modaltitle = function(section_id) { s.modaltitle = function(section_id) {
var parent_title = _('LinkBack 链路守护') + ' - ' + _('Settings'); var parent_title = _('LinkBack 链路守护') + ' - ' + _('Settings');
@@ -284,27 +307,19 @@ return view.extend({
o.cfgvalue = function(section_id) { o.cfgvalue = function(section_id) {
var gw = uci.get('linkback', section_id, 'gateway'); var gw = uci.get('linkback', section_id, 'gateway');
var name = uci.get('linkback', section_id, 'name'); var name = uci.get('linkback', section_id, 'name');
var mode = getActiveMode(); if (gw) {
if (mode === 'multi_gw' || gw) { return (name && name !== gw) ? (gw + ' (' + name + ')') : gw;
if (gw) {
if (name && name !== gw) {
return gw + ' (' + name + ')';
}
return gw;
}
return name ? (_('Missing Gateway IP: %s').format(name)) : _('Unset');
} else {
return name || _('Unselected');
} }
return name || '-';
}; };
makeTableColumnExpand(o, '24%'); makeTableColumnExpand(o, '24%');
// 3a. Gateway IP (Multi-GW mode only, Modal only) // 3a. Gateway IP (Multi-GW mode only, Modal only)
var o_gw = s.option(form.Value, 'gateway', _('Gateway IP'), var o_gw = s.option(form.Value, 'gateway', _('Gateway IP'),
_('Next-hop IPv4 address of this gateway (e.g. 192.168.1.254).')); _('Next-hop IPv4 address of this gateway (e.g. 192.168.1.1).'));
o_gw.datatype = 'ip4addr'; o_gw.datatype = 'ip4addr';
o_gw.modalonly = true; o_gw.modalonly = true;
o_gw.placeholder = '192.168.1.254'; o_gw.placeholder = '192.168.1.1';
var origRenderGw = o_gw.render; var origRenderGw = o_gw.render;
o_gw.render = function(option_index, section_id, in_table) { o_gw.render = function(option_index, section_id, in_table) {
if (getActiveMode() !== 'multi_gw') { if (getActiveMode() !== 'multi_gw') {
@@ -350,9 +365,9 @@ return view.extend({
// 3b. Gateway Alias (Multi-GW mode only, Modal only) // 3b. Gateway Alias (Multi-GW mode only, Modal only)
var o_alias = s.option(form.Value, 'name_alias', _('Gateway Alias (Optional)'), var o_alias = s.option(form.Value, 'name_alias', _('Gateway Alias (Optional)'),
_('Descriptive alias for this gateway (e.g. Bypass_GW, Main_Router). If empty, Gateway IP will be used.')); _('Descriptive alias for this gateway (e.g. Primary_GW, Backup_GW). If empty, Gateway IP will be used.'));
o_alias.modalonly = true; o_alias.modalonly = true;
o_alias.placeholder = 'Bypass_GW'; o_alias.placeholder = 'Primary_GW';
var origRenderAlias = o_alias.render; var origRenderAlias = o_alias.render;
o_alias.render = function(option_index, section_id, in_table) { o_alias.render = function(option_index, section_id, in_table) {
if (getActiveMode() !== 'multi_gw') { if (getActiveMode() !== 'multi_gw') {
@@ -432,11 +447,19 @@ return view.extend({
}; };
// 3. Priority // 3. Priority
o = s.option(form.Value, 'priority', _('Priority'), o = s.option(form.Value, 'priority', _('Priority'));
_('Lower number indicates higher priority (e.g. 1 = Primary, 2 = Backup).'));
o.datatype = 'uinteger'; o.datatype = 'uinteger';
o.default = '1'; o.default = '1';
o.rmempty = false; o.rmempty = false;
var origRenderPrio = o.render;
o.render = function(option_index, section_id, in_table) {
if (in_table) {
this.description = null;
} else {
this.description = _('Lower number indicates higher priority (e.g. 1 = Primary, 2 = Backup).');
}
return origRenderPrio.call(this, option_index, section_id, in_table);
};
o.validate = function(section_id, value) { o.validate = function(section_id, value) {
if (value == null || value === '') { if (value == null || value === '') {
return t_priority_empty; return t_priority_empty;
+5 -9
View File
@@ -98,16 +98,16 @@ msgid "Logical interface from firewall WAN zone."
msgstr "来自防火墙 WAN 区域的逻辑接口。" msgstr "来自防火墙 WAN 区域的逻辑接口。"
msgid "Gateway Alias (Optional)" msgid "Gateway Alias (Optional)"
msgstr "网关别名 (可选)" msgstr "网关别名 (选填)"
msgid "Descriptive alias for this gateway (e.g. Bypass_GW, Main_Router). If empty, Gateway IP will be used." msgid "Descriptive alias for this gateway (e.g. Primary_GW, Backup_GW). If empty, Gateway IP will be used."
msgstr "该网关的自定义别名 (例如:旁路由主网关、光猫备用网关)。若留空则自动使用网关 IP。" msgstr "该网关的自定义名称 (例如:主网关、备用网关)。若留空则自动使用网关 IP。"
msgid "Gateway IP" msgid "Gateway IP"
msgstr "网关 IP" msgstr "网关 IP"
msgid "Next-hop IPv4 address of this gateway (e.g. 192.168.1.254)." msgid "Next-hop IPv4 address of this gateway (e.g. 192.168.1.1)."
msgstr "该网关的下一跳 IPv4 地址 (例如:192.168.1.254)。" msgstr "该网关的下一跳 IPv4 地址 (例如:192.168.1.1)。"
msgid "Gateway IP is required." msgid "Gateway IP is required."
msgstr "网关 IP 不能为空。" msgstr "网关 IP 不能为空。"
@@ -346,9 +346,5 @@ msgstr "未选择"
msgid "WAN Interface is required." msgid "WAN Interface is required."
msgstr "WAN 网络接口不能为空。" msgstr "WAN 网络接口不能为空。"
msgid "已切换为单wan多网关模式。请确保监控目标列表中配置了有效的网关 IP。"
msgstr "已切换为单wan多网关模式。请确保监控目标列表中配置了有效的网关 IP。"
msgid "已切换为多wan口模式。请确保监控目标列表中选择了正确的 WAN 接口。"
msgstr "已切换为多wan口模式。请确保监控目标列表中选择了正确的 WAN 接口。"
+1 -1
View File
@@ -7,7 +7,7 @@ PKG_BASE_VERSION?=0.21.2
# Rewritten automatically by .github/workflows/sync-upstream.yml; the LuCI # Rewritten automatically by .github/workflows/sync-upstream.yml; the LuCI
# package always carries the same version as the core it drives. # package always carries the same version as the core it drives.
PKG_VERSION?=$(PKG_BASE_VERSION).202610010807 PKG_VERSION?=$(PKG_BASE_VERSION).202610021113
PKG_RELEASE?=1 PKG_RELEASE?=1
include $(INCLUDE_DIR)/package.mk include $(INCLUDE_DIR)/package.mk
+1 -1
View File
@@ -11,7 +11,7 @@ PKG_BASE_VERSION?=0.21.2
# All-numeric dotted segments keep the version valid for both package # All-numeric dotted segments keep the version valid for both package
# managers: apk -> meow-0.21.2.202610010430-r1.apk # managers: apk -> meow-0.21.2.202610010430-r1.apk
# ipk -> meow_0.21.2.202610010430-1_<arch>.ipk # ipk -> meow_0.21.2.202610010430-1_<arch>.ipk
PKG_VERSION?=$(PKG_BASE_VERSION).202610010807 PKG_VERSION?=$(PKG_BASE_VERSION).202610021113
PKG_RELEASE?=1 PKG_RELEASE?=1
include $(INCLUDE_DIR)/package.mk include $(INCLUDE_DIR)/package.mk