🌴 Sync 2026-05-27 21:11:55

This commit is contained in:
github-actions[bot] 2026-05-27 21:11:55 +08:00
parent 3a6f6005dc
commit dfd1e47fd7
24 changed files with 529 additions and 241 deletions

View File

@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=hickory-dns
PKG_VERSION:=0.26.1
PKG_RELEASE:=27
PKG_RELEASE:=28
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/hickory-dns/hickory-dns/tar.gz/v$(PKG_VERSION)?

View File

@ -0,0 +1,8 @@
$TTL 3D
@ IN SOA ns.lan. admin.lan. (
1 ; Serial
28800 ; Refresh
7200 ; Retry
604800 ; Expire
86400) ; Minimum TTL
NS ns.lan.

View File

@ -0,0 +1,8 @@
$TTL 3D
@ IN SOA ns.lan. admin.lan. (
1 ; Serial
28800 ; Refresh
7200 ; Retry
604800 ; Expire
86400) ; Minimum TTL
NS ns.lan.

View File

@ -0,0 +1,8 @@
$TTL 3D
@ IN SOA ns.lan. admin.lan. (
1 ; Serial
28800 ; Refresh
7200 ; Retry
604800 ; Expire
86400) ; Minimum TTL
NS ns.lan.

View File

@ -47,6 +47,57 @@ key_file = "/etc/hickory-dns/update.key"
algorithm = "hmac-sha256"
fudge = 300
[[zones]]
zone = "d.f.ip6.arpa"
zone_type = "Primary"
axfr_policy = "AllowAll"
[zones.stores]
type = "sqlite"
zone_path = "/etc/hickory-dns/d.f.ip6.arpa.zone"
journal_path = ":memory:"
allow_update = true
[[zones.stores.tsig_keys]]
name = "update-key."
key_file = "/etc/hickory-dns/update.key"
algorithm = "hmac-sha256"
fudge = 300
[[zones]]
zone = "0.0.10.in-addr.arpa"
zone_type = "Primary"
axfr_policy = "AllowAll"
[zones.stores]
type = "sqlite"
zone_path = "/etc/hickory-dns/0.0.10.zone"
journal_path = ":memory:"
allow_update = true
[[zones.stores.tsig_keys]]
name = "update-key."
key_file = "/etc/hickory-dns/update.key"
algorithm = "hmac-sha256"
fudge = 300
[[zones]]
zone = "3.168.192.in-addr.arpa"
zone_type = "Primary"
axfr_policy = "AllowAll"
[zones.stores]
type = "sqlite"
zone_path = "/etc/hickory-dns/3.168.192.zone"
journal_path = ":memory:"
allow_update = true
[[zones.stores.tsig_keys]]
name = "update-key."
key_file = "/etc/hickory-dns/update.key"
algorithm = "hmac-sha256"
fudge = 300
[[zones]]
zone = "."
zone_type = "External"

View File

@ -11,7 +11,10 @@ i=0
while [ "$i" -lt "$RETRY" ]; do
if ss -lnt | grep -q ':53'; then
logger -t ipv6-neigh-wrapper "DNS port 53 ready, starting ipv6-neigh"
exec "$NEIGH_PROG" --private-subnet-v4 -d "[::1]:53" -z lan --router-alias router --keepalive-gua
exec "$NEIGH_PROG" --private-subnet-v4 -d "[::1]:53" -z lan --router-alias router --keepalive-gua \
--ptr-ipv4-subnet 10.0.0.0/24 \
--ptr-ipv4-subnet 192.168.3.0/24 \
--ptr-ula
fi
i=$((i + 1))
sleep 1

111
ipv6-neigh/Cargo.lock generated
View File

@ -189,9 +189,9 @@ dependencies = [
[[package]]
name = "displaydoc"
version = "0.2.5"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
dependencies = [
"proc-macro2",
"quote",
@ -227,16 +227,6 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.61.2",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
@ -534,7 +524,13 @@ dependencies = [
]
[[package]]
name = "ip-neigh"
name = "ipnet"
version = "2.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
[[package]]
name = "ipv6-neigh"
version = "0.1.0"
dependencies = [
"clap",
@ -552,12 +548,6 @@ dependencies = [
"ubus",
]
[[package]]
name = "ipnet"
version = "2.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
@ -572,9 +562,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "jiff"
version = "0.2.24"
version = "0.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f00b5dbd620d61dfdcb6007c9c1f6054ebd75319f163d886a9055cec1155073d"
checksum = "392c70591e8749fe235ddaf513e6f58b26bce3dcc16524cecc8936f75afa161e"
dependencies = [
"jiff-static",
"log",
@ -585,9 +575,9 @@ dependencies = [
[[package]]
name = "jiff-static"
version = "0.2.24"
version = "0.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e000de030ff8022ea1da3f466fbb0f3a809f5e51ed31f6dd931c35181ad8e6d7"
checksum = "47b605b0c050d845fc355bb11eb3f9a8deddc218ea60c76e61aa1f2adfb2c96a"
dependencies = [
"proc-macro2",
"quote",
@ -612,26 +602,17 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.29"
version = "0.4.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5"
[[package]]
name = "memchr"
version = "2.8.0"
version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
[[package]]
name = "mio"
@ -706,9 +687,9 @@ dependencies = [
[[package]]
name = "num-conv"
version = "0.2.1"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967"
checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
[[package]]
name = "once_cell"
@ -726,29 +707,6 @@ version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "parking_lot"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-link",
]
[[package]]
name = "paste"
version = "1.0.15"
@ -848,15 +806,6 @@ version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
[[package]]
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags",
]
[[package]]
name = "regex"
version = "1.12.3"
@ -927,12 +876,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "semver"
version = "1.0.28"
@ -971,9 +914,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.149"
version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [
"itoa",
"memchr",
@ -988,16 +931,6 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook-registry"
version = "1.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
dependencies = [
"errno",
"libc",
]
[[package]]
name = "slab"
version = "0.4.12"
@ -1153,9 +1086,7 @@ dependencies = [
"bytes",
"libc",
"mio",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.61.2",

View File

@ -1,5 +1,5 @@
[package]
name = "ip-neigh"
name = "ipv6-neigh"
version = "0.1.0"
edition = "2024"
@ -8,14 +8,14 @@ edition = "2024"
[dependencies]
rtnetlink = { version = "0.21.0" }
netlink-sys = { version = "0.8" }
tokio = { version = "1.52.3", features = ["full"]}
tokio = { version = "1.52.3", default-features = false, features = ["rt", "macros", "net", "time", "io-util"] }
futures = "0.3.32"
netlink-packet-route = {version = "0.30" }
netlink-packet-core= {version ="0.8"}
clap = { version = "4.6", default-features = false, features = ["cargo", "derive", "help", "std", "suggestions"] }
hickory-proto = { git = "https://github.com/hickory-dns/hickory-dns.git", rev = "ee7ba64757eca61d86f323ef992eba482605d7c3", features = ["dnssec-ring"] }
ubus = "0.1.7"
serde_json = "1.0.149"
serde_json = "1.0.150"
log = "0.4"
env_logger = "0.11"
socket2 = { version = "0.6", features = ["all"] }
@ -25,3 +25,5 @@ opt-level = 3
lto = true
codegen-units = 1
strip = true
overflow-checks = false
panic = "abort"

View File

@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=ipv6-neigh
PKG_VERSION:=0.1.0
PKG_RELEASE:=15
PKG_RELEASE:=16
PKG_BUILD_DEPENDS:=rust/host
PKG_BUILD_PARALLEL:=1
@ -12,6 +12,16 @@ RUST_PKG_LOCKED:=0
include $(INCLUDE_DIR)/package.mk
include $(TOPDIR)/feeds/packages/lang/rust/rust-package.mk
# Derive Rust CPU flags from TARGET_CFLAGS (e.g. -mcpu=cortex-a53+crc+crypto)
_comma:=,
_empty:=
_space:=$(_empty) $(_empty)
_mcpu:=$(patsubst -mcpu=%,%,$(filter -mcpu=%,$(TARGET_CFLAGS)))
_mcpu_parts:=$(subst +,$(_space),$(_mcpu))
_cpu_base:=$(firstword $(_mcpu_parts))
_cpu_feats:=$(subst $(_space),$(_comma),$(addprefix +,$(wordlist 2,99,$(_mcpu_parts))))
PKG_RUST_CPU_FLAGS:=$(if $(_cpu_base),-Ctarget-cpu=$(_cpu_base)$(if $(_cpu_feats), -Ctarget-feature=$(_cpu_feats)))
define Package/ipv6-neigh
SECTION:=net
CATEGORY:=Network
@ -37,6 +47,10 @@ endef
define Build/Compile
+$(CARGO_PKG_VARS) \
CARGO_PROFILE_RELEASE_OPT_LEVEL=3 \
CARGO_PROFILE_RELEASE_OVERFLOW_CHECKS=false \
CARGO_PROFILE_RELEASE_PANIC=abort \
RUSTFLAGS="$(CARGO_RUSTFLAGS) $(PKG_RUST_CPU_FLAGS)" \
cargo build -v --profile $(CARGO_PKG_PROFILE) \
--manifest-path "$(PKG_BUILD_DIR)/Cargo.toml" \
--target $(RUSTC_TARGET_ARCH) \
@ -44,12 +58,12 @@ define Build/Compile
$(if $(filter --jobserver%,$(PKG_JOBS)),,-j1) \
$(CARGO_PKG_ARGS)
+$(INSTALL_DIR) $(PKG_INSTALL_DIR)/bin/
+$(INSTALL_BIN) $(PKG_BUILD_DIR)/target/$(RUSTC_TARGET_ARCH)/$(CARGO_PKG_PROFILE)/ip-neigh $(PKG_INSTALL_DIR)/bin/
+$(INSTALL_BIN) $(PKG_BUILD_DIR)/target/$(RUSTC_TARGET_ARCH)/$(CARGO_PKG_PROFILE)/ipv6-neigh $(PKG_INSTALL_DIR)/bin/
endef
define Package/ipv6-neigh/install
$(INSTALL_DIR) $(1)/usr/bin/
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/bin/ip-neigh $(1)/usr/bin/ipv6-neigh
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/bin/ipv6-neigh $(1)/usr/bin/ipv6-neigh
endef
$(eval $(call RustBinPackage,ipv6-neigh))

View File

@ -2,7 +2,8 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use hickory_proto::op::{Message, Query, ResponseCode, update_message};
use hickory_proto::rr::{Name, RData, RecordSet, RecordType, TSigner};
use hickory_proto::rr::{Name, RData, Record, RecordSet, RecordType, TSigner};
use hickory_proto::rr::rdata::PTR as PtrRData;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio::time::timeout;
@ -23,11 +24,100 @@ pub(crate) struct DnsUpdater {
server_addr: SocketAddr,
zone: Name,
signer: TSigner,
/// (network as u32, prefix_len, reverse zone name) for IPv4 PTR
ipv4_ptr_zones: Vec<(u32, u8, Name)>,
/// Reverse zone for ULA IPv6 (d.f.ip6.arpa)
ula_ptr_zone: Option<Name>,
}
impl DnsUpdater {
pub fn new(server_addr: SocketAddr, zone: Name, signer: TSigner) -> Self {
Self { server_addr, zone, signer }
Self { server_addr, zone, signer, ipv4_ptr_zones: vec![], ula_ptr_zone: None }
}
/// Configure reverse PTR zones.
/// `ipv4_subnets`: list of (network_addr, prefix_len); /8, /16, /24 boundaries supported.
/// `ula`: if true, enables ULA PTR via `d.f.ip6.arpa`.
pub fn with_ptr_zones(mut self, ipv4_subnets: &[(Ipv4Addr, u8)], ula: bool) -> Self {
self.ipv4_ptr_zones = ipv4_subnets
.iter()
.map(|(net, prefix_len)| (u32::from(*net), *prefix_len, ipv4_zone_name(*net, *prefix_len)))
.collect();
if ula {
self.ula_ptr_zone = Some(Name::from_ascii("d.f.ip6.arpa.").expect("always valid"));
}
self
}
/// Upsert a PTR record. Silently skips IPs with no configured reverse zone.
pub async fn upsert_ptr(
&self,
addr: IpAddr,
hostname: &str,
ttl: u32,
) -> Result<(), Box<dyn std::error::Error>> {
let (ptr_name, rev_zone) = match addr {
IpAddr::V4(ip) => {
let Some(zone) = self.find_ipv4_ptr_zone(ip).cloned() else { return Ok(()) };
(ipv4_ptr_name(ip), zone)
}
IpAddr::V6(ip) if (ip.segments()[0] & 0xfe00) == 0xfc00 => {
let Some(zone) = self.ula_ptr_zone.clone() else { return Ok(()) };
(ipv6_ptr_name(ip), zone)
}
_ => return Ok(()),
};
let target = Name::from_ascii(hostname)?.append_domain(&self.zone)?;
// Delete any existing PTR rrset first (replace semantics: one PTR per IP).
let del_record = Record::update0(ptr_name.clone(), 0, RecordType::PTR);
let del_msg = update_message::delete_rrset(del_record, rev_zone.clone(), false);
let del_resp = self.send_tcp(del_msg).await?;
check_response(&del_resp, &[])?;
// Add the new PTR.
let mut rrset = RecordSet::with_ttl(ptr_name, RecordType::PTR, ttl);
rrset.add_rdata(RData::PTR(PtrRData(target)));
let msg = update_message::append(rrset, rev_zone, false, false);
let response = self.send_tcp(msg).await?;
check_response(&response, &[])?;
Ok(())
}
/// Delete a PTR record. Silently skips IPs with no configured reverse zone.
pub async fn delete_ptr(
&self,
addr: IpAddr,
) -> Result<(), Box<dyn std::error::Error>> {
let (ptr_name, rev_zone) = match addr {
IpAddr::V4(ip) => {
let Some(zone) = self.find_ipv4_ptr_zone(ip).cloned() else { return Ok(()) };
(ipv4_ptr_name(ip), zone)
}
IpAddr::V6(ip) if (ip.segments()[0] & 0xfe00) == 0xfc00 => {
let Some(zone) = self.ula_ptr_zone.clone() else { return Ok(()) };
(ipv6_ptr_name(ip), zone)
}
_ => return Ok(()),
};
let record = Record::update0(ptr_name, 0, RecordType::PTR);
let msg = update_message::delete_rrset(record, rev_zone, false);
let response = self.send_tcp(msg).await?;
check_response(&response, &[])?;
Ok(())
}
fn find_ipv4_ptr_zone(&self, addr: Ipv4Addr) -> Option<&Name> {
let addr_u32 = u32::from(addr);
self.ipv4_ptr_zones
.iter()
.filter(|(net, prefix_len, _)| {
if *prefix_len == 0 { return true; }
let shift = 32u8.saturating_sub(*prefix_len);
(addr_u32 >> shift) == (net >> shift)
})
.max_by_key(|(_, prefix_len, _)| *prefix_len)
.map(|(_, _, name)| name)
}
/// Create or append a AAAA record.
@ -214,6 +304,41 @@ impl DnsUpdater {
}
}
/// Build the PTR owner name for an IPv4 address.
/// e.g. 192.168.3.5 → `5.3.168.192.in-addr.arpa.`
fn ipv4_ptr_name(addr: Ipv4Addr) -> Name {
let o = addr.octets();
Name::from_ascii(&format!("{}.{}.{}.{}.in-addr.arpa.", o[3], o[2], o[1], o[0]))
.expect("always valid")
}
/// Build the PTR owner name for an IPv6 address (nibble-reversed).
fn ipv6_ptr_name(addr: Ipv6Addr) -> Name {
let nibbles: String = addr
.octets()
.iter()
.rev()
.flat_map(|b| {
let lo = char::from_digit((b & 0x0f) as u32, 16).unwrap();
let hi = char::from_digit((b >> 4) as u32, 16).unwrap();
[lo, '.', hi, '.']
})
.collect();
Name::from_ascii(&format!("{}ip6.arpa.", nibbles)).expect("always valid")
}
/// Derive the reverse zone name for an IPv4 subnet (only /8, /16, /24 boundaries).
fn ipv4_zone_name(net: Ipv4Addr, prefix_len: u8) -> Name {
let o = net.octets();
let s = match prefix_len {
24..=32 => format!("{}.{}.{}.in-addr.arpa.", o[2], o[1], o[0]),
16..=23 => format!("{}.{}.in-addr.arpa.", o[1], o[0]),
8..=15 => format!("{}.in-addr.arpa.", o[0]),
_ => "in-addr.arpa.".to_string(),
};
Name::from_ascii(&s).expect("always valid")
}
/// Extract the single label that precedes the zone apex from a fully-qualified record name.
///
/// e.g. `"foo.lan."` with zone `"lan."` → `Some("foo")`.

View File

@ -112,6 +112,9 @@ pub(crate) async fn register_router_addresses(
Err(e) => warn!("failed to register router AAAA {} for {}: {}", addr, name, e),
}
}
if let Err(e) = updater.upsert_ptr(IpAddr::V6(*addr), hostname, DEFAULT_TTL).await {
warn!("failed to register router PTR for {}: {}", addr, e);
}
}
IpAddr::V4(addr) => {
for name in std::iter::once(hostname).chain(extra_alias) {
@ -120,6 +123,9 @@ pub(crate) async fn register_router_addresses(
Err(e) => warn!("failed to register router A {} for {}: {}", addr, name, e),
}
}
if let Err(e) = updater.upsert_ptr(IpAddr::V4(*addr), hostname, DEFAULT_TTL).await {
warn!("failed to register router PTR for {}: {}", addr, e);
}
}
}
}

View File

@ -1,5 +1,5 @@
use std::collections::HashMap;
use std::net::SocketAddr as StdSocketAddr;
use std::net::{IpAddr, Ipv4Addr, SocketAddr as StdSocketAddr};
use std::time::Instant;
use futures::stream::StreamExt;
@ -80,6 +80,12 @@ struct Cli {
/// Maximum number of ULA AAAA records to publish per host (oldest pruned first)
#[clap(long, default_value = "2")]
max_ula_per_host: usize,
/// IPv4 subnets for PTR reverse DNS (CIDR notation, e.g. "192.168.3.0/24", repeatable)
#[clap(long, value_name = "SUBNET")]
ptr_ipv4_subnet: Vec<String>,
/// Enable PTR reverse DNS for ULA IPv6 addresses (fc00::/7) via d.f.ip6.arpa
#[clap(long)]
ptr_ula: bool,
}
@ -96,6 +102,22 @@ fn is_failed_state(state: NeighbourState) -> bool {
matches!(state, NeighbourState::Failed)
}
/// Parse an IPv4 CIDR string like "192.168.3.0/24" into (network_addr, prefix_len).
/// Panics with a clear message on invalid input.
fn parse_ipv4_cidr(s: &str) -> (Ipv4Addr, u8) {
let (addr_str, prefix_str) = s.split_once('/').unwrap_or_else(|| {
panic!("invalid --ptr-ipv4-subnet \"{s}\": expected CIDR notation like \"192.168.3.0/24\"")
});
let addr: Ipv4Addr = addr_str.parse().unwrap_or_else(|_| {
panic!("invalid --ptr-ipv4-subnet \"{s}\": \"{addr_str}\" is not a valid IPv4 address")
});
let prefix_len: u8 = prefix_str.parse().unwrap_or_else(|_| {
panic!("invalid --ptr-ipv4-subnet \"{s}\": prefix length must be 032")
});
assert!(prefix_len <= 32, "invalid --ptr-ipv4-subnet \"{s}\": prefix length {prefix_len} > 32");
(addr, prefix_len)
}
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), ()> {
let args = Cli::parse();
@ -126,7 +148,12 @@ async fn main() -> Result<(), ()> {
let signer = TSigner::new(key_data, TsigAlgorithm::HmacSha256, key_name, 300)
.expect("invalid TSIG key");
let updater = db::DnsUpdater::new(args.dns_server, zone, signer);
let ipv4_ptr_subnets: Vec<(Ipv4Addr, u8)> = args.ptr_ipv4_subnet
.iter()
.map(|s| parse_ipv4_cidr(s))
.collect();
let updater = db::DnsUpdater::new(args.dns_server, zone, signer)
.with_ptr_zones(&ipv4_ptr_subnets, args.ptr_ula);
// Wait for the DNS server to become available before sending any updates.
wait_for_dns_server(args.dns_server).await;
@ -274,6 +301,16 @@ async fn main() -> Result<(), ()> {
match result {
Ok(()) => {
info!("DNS update: added {} -> {:?}", hostname, neigh.inet);
let ip = match &neigh.inet {
NeighbourAddress::Inet6(addr) => Some(IpAddr::V6(*addr)),
NeighbourAddress::Inet(addr) => Some(IpAddr::V4(*addr)),
_ => None,
};
if let Some(ip) = ip {
if let Err(e) = updater.upsert_ptr(ip, hostname, DEFAULT_TTL).await {
warn!("PTR update failed for {}: {}", hostname, e);
}
}
registered.insert(key, RegisteredEntry {
hostname: hostname.clone(),
last_confirmed: Instant::now(),
@ -416,18 +453,22 @@ async fn main() -> Result<(), ()> {
// Resolve key by hostname (handles MAC changes: new MAC in leases
// can now evict entries registered under an old MAC).
// Fall back to scanning registered by IP if MAC is no longer in leases.
debug!("Neighbour failed: {:?}", neigh);
let key_opt: Option<(String, String)> = leases
.get(&neigh.mac)
.map(|h| (h.clone(), ip_str.clone()))
.or_else(|| registered.keys().find(|(_, ip)| ip == &ip_str).cloned());
if let Some(key) = key_opt {
if let Some(entry) = registered.remove(&key) {
debug!("Neighbour failed: {:?}", neigh);
if !do_delete_dns(&entry.hostname, &neigh.inet, &updater).await {
// DNS delete failed, put back so we retry
registered.insert(key, entry);
}
} else {
debug!("Neighbour failed: not in registered map, key={:?}", key);
}
} else {
debug!("Neighbour failed: no registered key for ip={}", ip_str);
}
} else if neigh.state == NeighbourState::Reachable {
if let Some(hostname) = leases.get(&neigh.mac) {

View File

@ -33,12 +33,12 @@ fn compute_icmpv4_checksum(packet: &mut [u8]) {
}
pub(crate) fn send_icmpv6_echo(addr: Ipv6Addr, ifindex: u32) -> std::io::Result<()> {
let socket = Socket::new(Domain::IPV6, Type::DGRAM, Some(Protocol::ICMPV6))?;
let socket = Socket::new(Domain::IPV6, Type::RAW, Some(Protocol::ICMPV6))?;
socket.set_nonblocking(true)?;
// Bind outgoing packet to the specific interface via IPV6_UNICAST_IF so the
// kernel NUD state machine updates the correct neighbour entry.
set_ipv6_unicast_if(&socket, ifindex)?;
// ICMPv6 Echo Request: type=128, code=0, checksum=0 (kernel computes), id=0, seq=1
// ICMPv6 Echo Request: type=128, code=0, checksum=0 (kernel computes for RAW ICMPV6), id=0, seq=1
let packet: [u8; 8] = [128, 0, 0, 0, 0, 0, 0, 1];
let dest = SockAddr::from(std::net::SocketAddrV6::new(addr, 0, 0, 0));
socket.send_to(&packet, &dest)?;
@ -46,7 +46,7 @@ pub(crate) fn send_icmpv6_echo(addr: Ipv6Addr, ifindex: u32) -> std::io::Result<
}
pub(crate) fn send_icmpv4_echo(addr: Ipv4Addr, ifindex: u32) -> std::io::Result<()> {
let socket = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::ICMPV4))?;
let socket = Socket::new(Domain::IPV4, Type::RAW, Some(Protocol::ICMPV4))?;
socket.set_nonblocking(true)?;
// Bind outgoing packet to the specific interface via IP_UNICAST_IF.
set_ip_unicast_if(&socket, ifindex)?;

View File

@ -35,6 +35,14 @@ pub(crate) async fn process_new_neigh(
match result {
Ok(()) => {
info!("DNS update: added {} -> {:?}", hostname, neigh.inet);
let ip = match &neigh.inet {
NeighbourAddress::Inet6(addr) => IpAddr::V6(*addr),
NeighbourAddress::Inet(addr) => IpAddr::V4(*addr),
_ => return true,
};
if let Err(e) = updater.upsert_ptr(ip, hostname, DEFAULT_TTL).await {
warn!("PTR update failed for {} ({}): {}", hostname, ip, e);
}
true
}
Err(e) => {
@ -58,6 +66,14 @@ pub(crate) async fn do_delete_dns(
match result {
Ok(()) => {
info!("DNS update: removed {} -> {:?}", hostname, inet);
let ip = match inet {
NeighbourAddress::Inet6(addr) => IpAddr::V6(*addr),
NeighbourAddress::Inet(addr) => IpAddr::V4(*addr),
_ => return true,
};
if let Err(e) = updater.delete_ptr(ip).await {
warn!("PTR delete failed for {} ({}): {}", hostname, ip, e);
}
true
}
Err(e) => {
@ -172,7 +188,12 @@ pub(crate) async fn reconcile_dns(
IpAddr::V4(addr) => updater.delete_a(hostname, *addr).await,
};
match result {
Ok(()) => info!("reconcile: deleted stale DNS {} -> {}", hostname, ip),
Ok(()) => {
info!("reconcile: deleted stale DNS {} -> {}", hostname, ip);
if let Err(e) = updater.delete_ptr(*ip).await {
warn!("reconcile: PTR delete failed for stale {} {}: {}", hostname, ip, e);
}
}
Err(e) => warn!("reconcile: failed to delete stale DNS {} {}: {}", hostname, ip, e),
}
}
@ -198,7 +219,14 @@ pub(crate) async fn reconcile_dns(
_ => continue,
};
match del_result {
Ok(()) => info!("reconcile: deleted orphan DNS {} -> {}", hostname, ip_str),
Ok(()) => {
info!("reconcile: deleted orphan DNS {} -> {}", hostname, ip_str);
if let Ok(ip) = ip_str.parse::<IpAddr>() {
if let Err(e) = updater.delete_ptr(ip).await {
warn!("reconcile: PTR delete failed for orphan {} {}: {}", hostname, ip_str, e);
}
}
}
Err(e) => warn!("reconcile: failed to delete orphan {} {}: {}", hostname, ip_str, e),
}
// Probe so alive devices trigger a REACHABLE event and re-register.
@ -234,6 +262,11 @@ pub(crate) async fn reconcile_dns(
match result {
Ok(()) => {
info!("reconcile: re-pushed {} -> {}", hostname, ip_str);
if let Ok(ip) = ip_str.parse::<IpAddr>() {
if let Err(e) = updater.upsert_ptr(ip, hostname, DEFAULT_TTL).await {
warn!("reconcile: PTR re-push failed for {} {}: {}", hostname, ip_str, e);
}
}
entry.last_confirmed = Instant::now();
}
Err(e) => warn!("reconcile: failed to re-push {} {}: {}", hostname, ip_str, e),

View File

@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall
PKG_VERSION:=26.5.20
PKG_RELEASE:=138
PKG_RELEASE:=139
PKG_PO_VERSION:=$(PKG_VERSION)
PKG_CONFIG_DEPENDS:= \

View File

@ -194,7 +194,7 @@ config_lines = {
tonumber(LISTEN_PORT) ~= 0 and "bind [::]:" .. LISTEN_PORT .. "@lo" or "",
(tonumber(LOCAL_PORT) ~= 0 and LOCAL_GROUP) and "bind [::]:" .. LOCAL_PORT .. "@lo -group " .. LOCAL_GROUP or "",
tonumber(force_https_soa) == 1 and "force-qtype-SOA 65" or "force-qtype-SOA -,65",
"server 114.114.114.114 -bootstrap-dns",
"server 223.5.5.5 -bootstrap-dns",
is_file_nonzero("/etc/hosts") and "hosts-file /etc/hosts" or "",
DNS_MODE == "socks" and string.format("proxy-server socks5://%s -name %s", REMOTE_PROXY_SERVER, proxy_server_name) or ""
}

View File

@ -10,8 +10,8 @@ LUCI_TITLE:=Luci app for ssw
LUCI_DEPENDS:= +luci-app-modeminfo
PKG_LICENSE:=GPLv3
PKG_VERSION:=0.0.1
PKG_RELEASE:=1
PKG_VERSION:=0.0.2
PKG_RELEASE:=2
include $(TOPDIR)/feeds/luci/luci.mk

View File

@ -37,9 +37,9 @@ return view.extend({
_('Revert to default sim slot. Each failed attempt doubles revert time.'));
o.depends({enable: '1'});
o = s.option(form.ListValue, 'rsrp', _('RSRP value'),
_('Switch sim lower by value.'));
for (var rsrp = -120; rsrp <= -80; rsrp++) {
o = s.option(form.ListValue, 'rsrp', _('RSRP/RSCP value'),
_('Switch sim lower by value. For 4G/5G is RSRP, for 3G RSCP.'));
for (var rsrp = -120; rsrp <= -50; rsrp++) {
o.value(rsrp, rsrp +' '+ _('dBm'));
};
o.depends({enable: '1'});

View File

@ -22,11 +22,11 @@ msgstr "Вернуть"
msgid "Revert to default sim slot. Each failed attempt doubles revert time."
msgstr "Вернуть на SIM слот по-умолчанию. Каждая неудачная попытка увеличивает время возврата."
msgid "RSRP value"
msgstr "Значение RSRP"
msgid "RSRP/RSCP value"
msgstr "Значение RSRP/RSCP"
msgid "Switch sim lower by value."
msgstr "Переключить sim ниже значения."
msgid "Switch sim lower by value. For 4G/5G is RSRP, for 3G RSCP."
msgstr "Переключить sim ниже значения. Для 4G/5G значение RSRP, для 3G - RSCP."
msgid "Interval check. sec"
msgstr "Интервал проверки. сек"

View File

@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
LUCI_NAME:=luci-app-tinyfilemanager
PKG_VERSION:=2.6
PKG_RELEASE:=1
PKG_RELEASE:=2
LUCI_TITLE:=LuCI Tiny File Manager
LUCI_DEPENDS:=+php8 +php8-cgi +php8-fastcgi +php8-fpm +php8-mod-session +php8-mod-ctype +php8-mod-fileinfo +php8-mod-zip +php8-mod-iconv +php8-mod-mbstring +coreutils-stat +zoneinfo-asia +bash +curl +tar +inotifywait

View File

@ -1,33 +1,33 @@
location = /tinyfilemanager/ {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
if (!-f $document_root$fastcgi_script_name) {
return 404;
location /tinyfilemanager/ {
index index.php;
location ~ \.php$ {
try_files $uri =404;
# Mitigate https://httpoxy.org/ vulnerabilities
fastcgi_param HTTP_PROXY "";
#error_log /dev/null;
fastcgi_connect_timeout 300s;
fastcgi_read_timeout 300s;
fastcgi_send_timeout 300s;
fastcgi_buffer_size 32k;
fastcgi_buffers 4 32k;
fastcgi_busy_buffers_size 32k;
fastcgi_temp_file_write_size 32k;
client_body_timeout 10s;
send_timeout 60s; # default, increase if experiencing a lot of timeouts.
output_buffers 1 32k;
include fastcgi_params;
# Only throw it at PHP-FPM if file exists (prevents PHP exploits).
fastcgi_pass 127.0.0.1:1026; # or: unix:/var/run/php-fpm.sock;
# fastcgi_pass unix:/var/run/php8-fpm.sock;
# SCRIPT_FILENAME parameter is used for PHP FPM determining
# the script name. If it is not set in fastcgi_params file,
# i.e. /etc/nginx/fastcgi_params or in the parent contexts,
# please comment off following line:
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
# Mitigate https://httpoxy.org/ vulnerabilities
fastcgi_param HTTP_PROXY "";
#error_log /dev/null;
fastcgi_connect_timeout 300s;
fastcgi_read_timeout 300s;
fastcgi_send_timeout 300s;
fastcgi_buffer_size 32k;
fastcgi_buffers 4 32k;
fastcgi_busy_buffers_size 32k;
fastcgi_temp_file_write_size 32k;
client_body_timeout 10s;
send_timeout 60s; # default, increase if experiencing a lot of timeouts.
output_buffers 1 32k;
fastcgi_index index.php;
include fastcgi_params;
# Only throw it at PHP-FPM if file exists (prevents PHP exploits).
fastcgi_pass 127.0.0.1:1026; # or: unix:/var/run/php-fpm.sock;
# fastcgi_pass unix:/var/run/php8-fpm.sock;
# SCRIPT_FILENAME parameter is used for PHP FPM determining
# the script name. If it is not set in fastcgi_params file,
# i.e. /etc/nginx/fastcgi_params or in the parent contexts,
# please comment off following line:
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

View File

@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=ssw
PKG_VERSION:=0.0.2
PKG_RELEASE:=1
PKG_RELEASE:=2
PKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>
include $(INCLUDE_DIR)/package.mk

View File

@ -2,6 +2,7 @@
# SIM slot switcher for HuastLink HC-G60 or ZBT-WE2806-A
# by Konstantine Shevlakov at <shevlakov@132lan.ru> 2025
# Audited & fixed 2025
START=99
STOP=10
@ -11,7 +12,6 @@ load_config_gpio(){
local gpio value
for c in gpio value; do
config_get $1_$c "$1" $c
export $1_$c
done
}
@ -19,54 +19,58 @@ load_config_failover(){
local enable mode interval times_rsrp times_ping revert rsrp host
for c in enable mode interval times_rsrp times_ping revert rsrp host; do
config_get $1_$c "$1" $c
export $1_$c
done
}
reload_iface(){
[ "$iface" ] && {
[ "$iface" ] && {
for i in $iface; do
ifup $i
ifup "$i"
done
}
}
_apply_modem_state(){
echo "$sim_value" > /sys/class/gpio/"$sim_gpio"/value
echo "$modem_value" > /sys/class/gpio/"$modem_gpio"/value
if [ -x /etc/init.d/smstools3 ]; then
/etc/init.d/smstools3 restart
fi
sleep 30 && reload_iface &
}
start_service(){
config_load ssw
config_foreach load_config_gpio modem
config_foreach load_config_gpio sim
modem_cur=$(cat /sys/class/gpio/$modem_gpio/value)
sim_cur=$(cat /sys/class/gpio/$sim_gpio/value)
iface=$(uci show network | awk -F [.] '/devices/{gsub("'\''","");print $2}' | tail -1)
if [ "$modem_value" = "$modem_cur" -a "$sim_value" = "$sim_cur" ]; then
continue
elif [ "$modem_value" = "$modem_cur" -a "$modem_value" = "1" ]; then
logger -t "SIM SW" "modem already enabled on gpio $modem_gpio. Restart it"
echo "0" > /sys/class/gpio/$modem_gpio/value
local modem_cur sim_cur
modem_cur=$(cat /sys/class/gpio/"$modem_gpio"/value)
sim_cur=$(cat /sys/class/gpio/"$sim_gpio"/value)
iface=$(uci show network | awk -F'[.]' '/devices/{gsub("'\''",""); print $2}' | tail -1)
if [ "$modem_value" = "$modem_cur" ] && [ "$sim_value" = "$sim_cur" ]; then
logger -t "SIM SW" "Modem and SIM already in correct state. Nothing to do."
return 0
elif [ "$modem_value" = "$modem_cur" ] && [ "$modem_value" = "1" ]; then
logger -t "SIM SW" "Modem already enabled on gpio $modem_gpio. Restart it."
echo "0" > /sys/class/gpio/"$modem_gpio"/value
sleep 2
echo "$sim_value" > /sys/class/gpio/$sim_gpio/value
echo "$modem_value" > /sys/class/gpio/$modem_gpio/value
if [ -x /etc/init.d/smstools3 ]; then
/etc/init.d/smstools3 restart
fi
sleep 30 && reload_iface &
_apply_modem_state
else
logger -t "SIM SW" "modem powered changed"
echo "$sim_value" > /sys/class/gpio/$sim_gpio/value
echo "$modem_value" > /sys/class/gpio/$modem_gpio/value
if [ -x /etc/init.d/smstools3 ]; then
/etc/init.d/smstools3 restart
fi
sleep 30 && reload_iface &
logger -t "SIM SW" "Modem power state changed."
_apply_modem_state
fi
#config_foreach load_config_failover failover
procd_open_instance
procd_set_param command /usr/share/ssw_track.sh
procd_close_instance
}
reload_service(){
start
start_service
}
service_triggers(){

View File

@ -1,8 +1,9 @@
#!/bin/sh
# SIM slot switcher for HuastLink HC-G60 or ZBT-WE2806-A
# or similiar cpe OpenWrt-routers
# or similar CPE OpenWrt-routers
# by Konstantine Shevlakov at <shevlakov@132lan.ru> 2025
# Audited & fixed 2026
NODE="SW SIM"
@ -34,38 +35,58 @@ NODE="SW SIM"
# option value '1'
# Get Variables
. /lib/functions.sh
# Callback failover section
_load_failover(){
config_get enable "$1" enable ""
config_get interval "$1" interval 60
config_get revert "$1" revert ""
config_get rsrp "$1" rsrp ""
config_get times_rsrp "$1" times_rsrp 5
config_get apn1 "$1" apn1 ""
config_get apn2 "$1" apn2 ""
}
# Callback sections modem / sim (gpio type)
_load_gpio(){
local _gpio _value
config_get _gpio "$1" gpio ""
config_get _value "$1" value ""
eval "${1}_gpio=\"$_gpio\""
eval "${1}_value=\"$_value\""
}
# Get Variables — update for every cycle
get_vars(){
for v in enable interval revert rsrp times_rsrp apn1 apn2; do
eval $v=$(uci -q get ssw.failover.${v} 2>/dev/nul)
done
for d in modem sim; do
for s in gpio value; do
eval "${d}_${s}=$(uci -q get ssw.${d}.${s} 2>/dev/null)"
done
done
[ -n "$interval" ] || interval=60
[ -n "$times_rsrp" ] || times_rsrp=5
reset_cb
config_load ssw
config_foreach _load_failover failover
config_foreach _load_gpio modem
config_foreach _load_gpio sim
}
# SIM Switch
sw_sim(){
case $cur_sim in
case "$cur_sim" in
0) next_sim=1 ;;
1) next_sim=0 ;;
*)
logger -t "$NODE" "ERROR: cur_sim has unexpected value '$cur_sim'. Aborting sw_sim."
return 1
;;
esac
echo "0" > /sys/class/gpio/$modem_gpio/value
echo "$next_sim" > /sys/class/gpio/$sim_gpio/value
echo "0" > /sys/class/gpio/"$modem_gpio"/value
echo "$next_sim" > /sys/class/gpio/"$sim_gpio"/value
if [ "$modem_value" = "1" ]; then
sleep 2
echo "1" > /sys/class/gpio/$modem_gpio/value
echo "1" > /sys/class/gpio/"$modem_gpio"/value
if [ -x /etc/init.d/smstools3 ]; then
/etc/init.d/smstools3 restart
fi
fi
set > /tmp/apn.ssw
}
# Revert rule switch
@ -84,32 +105,59 @@ sw_rule(){
# Check interface state via mwan3
monitor_mwan3(){
iface=$(uci show network | awk -F [.] '/devices/{print $2}')
iface=$(uci show network | awk -F'[.]' '/devices/{print $2}' | tail -1)
# Check link status
if [ -r /tmp/run/mwan3/iface_state/$iface ]; then
link_status=$(cat /tmp/run/mwan3/iface_state/$iface | grep online | wc -l)
if [ -r "/tmp/run/mwan3/iface_state/$iface" ]; then
link_status=$(grep -c online "/tmp/run/mwan3/iface_state/$iface" || echo 0)
else
# Disable track link via mwan3
link_status=1
fi
}
# RSRP average value by modemmanager. Enable singnal monitor!
check_signal(){
local gen="$1" signal_json="$2"
local metric value
case "$gen" in
lte|5g) metric="rsrp" ;;
3g) metric="rscp" ;;
*)
logger -t "$NODE" "check_signal: unknown generation '$gen'"
return 1
;;
esac
value=$(echo "$signal_json" \
| jsonfilter -e "@[\"modem\"][*][\"${gen}\"][\"${metric}\"]" 2>/dev/null \
| awk '{printf "%.0f\n", $1}')
[ -n "$value" ] && echo "$value"
}
# RSRP/RSCP average value by modemmanager. Enable signal monitor!
monitor_rsrp(){
device="$(uci show network | awk -F [=] '/devices/{gsub("'\''","");print $2}')"
SIGNAL="$(mmcli -J -m $device --signal-get)"
CRSRP=$(echo "$SIGNAL" | jsonfilter -e '@["modem"][*]["lte"]["rsrp"]' | awk '{printf "%.0f\n", $1}')
if ! [ $CRSRP ]; then
CRSRP=$(echo "$SIGNAL" | jsonfilter -e '@["modem"][*]["5g"]["rsrp"]' | awk '{printf "%.0f\n", $1}')
local device SIGNAL CRSRP
device="$(uci show network | awk -F'[=]' '/devices/{gsub("'\''",""); print $2}' | tail -1)"
SIGNAL="$(mmcli -J -m "$device" --signal-get 2>/dev/null)"
for gen in lte 5g 3g; do
CRSRP=$(check_signal "$gen" "$SIGNAL")
[ -n "$CRSRP" ] && break
done
if [ -n "$CRSRP" ] && [ "$CRSRP" -ne 0 ]; then
echo "$CRSRP" >> /tmp/ssw_rsrp.var
fi
if [ $CRSRP -ne 0 ]; then
echo $CRSRP >> /tmp/ssw_rsrp.var
fi
if [ $cnt -eq $times_rsrp ]; then
RSRP=$(awk '{sum+=$1} END { printf "%.0f\n", sum/NR }' /tmp/ssw_rsrp.var)
cat /dev/null > /tmp/ssw_rsrp.var
if [ $RSRP -lt $rsrp ]; then
if [ "$cnt" -eq "$times_rsrp" ]; then
if [ -s /tmp/ssw_rsrp.var ]; then
RSRP=$(awk '{sum+=$1} END { printf "%.0f\n", sum/NR }' /tmp/ssw_rsrp.var)
fi
: > /tmp/ssw_rsrp.var
if [ -n "$RSRP" ] && [ "$RSRP" -lt "$rsrp" ]; then
mon_rsrp=0
else
mon_rsrp=1
@ -121,68 +169,74 @@ monitor_rsrp(){
reload_iface(){
[ "$iface" ] && {
for i in $iface; do
ifup $i
ifup "$i"
done
}
}
# Stuff
cnt=1
cnt=0
while true; do
get_vars
sleep $interval
sleep "$interval"
if [ "$enable" = "1" ]; then
cur_sim=$(cat /sys/class/gpio/$sim_gpio/value)
cur_sim=$(cat /sys/class/gpio/"$sim_gpio"/value)
cnt=$(( cnt + 1 ))
monitor_rsrp
monitor_mwan3
if [ "$cnt" -eq "$times_rsrp" ]; then
if [ "$link_status" = "0" -o "$mon_rsrp" = "0" ]; then
if [ "$link_status" = "0" ] || [ "$mon_rsrp" = "0" ]; then
if [ "$sim_value" -eq "$cur_sim" ]; then
apn=$apn2
else
apn=$apn1
fi
iface=$(uci show network | awk -F [.] '/devices/{gsub("'\''","");print $2}' | tail -1)
if [ $RSRP ]; then
iface=$(uci show network | awk -F'[.]' '/devices/{gsub("'\''",""); print $2}' | tail -1)
if [ -n "$RSRP" ]; then
if [ "$mon_rsrp" = "0" ]; then
logger -t "$NODE" "Modem interface: $iface is average RSRP= ${RSRP} dBm. Min. value ${rsrp} dBm."
logger -t "$NODE" "Modem interface: $iface average RSRP=${RSRP} dBm. Min. value ${rsrp} dBm."
fi
if [ "$link_status" = "0" ]; then
logger -t "$NODE" "Modem interface: $iface is loss connectivity"
logger -t "$NODE" "Modem interface: $iface lost connectivity."
fi
else
logger -t "$NODE" "WARNING: RSRP value not exist. Please check \"Resfesh signal\" option of modem interface!"
logger -t "$NODE" "WARNING: RSRP value not available. Check 'Refresh signal' option of modem interface!"
fi
if [ "${#apn}" -gt "0" ]; then
uci set network.$iface.apn="$apn"
if [ "${#apn}" -gt 0 ]; then
uci set network."$iface".apn="$apn"
logger -t "$NODE" "Switch SIM-card slot with APN: $apn"
else
logger -t "$NODE" "WARNING: APN not defined. Switch SIM-card slot with default APN."
uci set network.$iface.apn="internet"
logger -t "$NODE" "WARNING: APN not defined. Switching SIM-card slot with default APN."
uci set network."$iface".apn="internet"
fi
uci commit network
reload_config network
reload_config
if [ "$revert" = "1" ]; then
if [ "$cur_sim" -eq "$sim_value" ]; then
FBT=${FBT:=$((($interval+$times_rsrp)*2))}
FBT=$(($FBT*2))
SWDATE=$((`date +%s`+$FBT))
echo "FBT=$FBT" > /tmp/ssw.vars
echo SWDATE=$SWDATE >> /tmp/ssw.vars
logger -t "$NODE" "Back to default SIM-slot after $(date -d @${SWDATE})"
if [ "$cur_sim" -eq "$sim_value" ]; then
FBT=${FBT:=$(( (interval + times_rsrp) * 2 ))}
FBT=$(( FBT * 2 ))
SWDATE=$(( $(date +%s) + FBT ))
printf 'FBT=%s\nSWDATE=%s\n' "$FBT" "$SWDATE" > /tmp/ssw.vars
logger -t "$NODE" "Back to default SIM-slot after $(date -d @"${SWDATE}")"
fi
fi
sw_sim && sleep 20 && reload_iface &
fi
if [ "$revert" = "1" ]; then
elif [ "$revert" = "1" ]; then
sw_rule && sleep 20 && reload_iface &
fi
cnt=0
else
cnt=$(($cnt+1))
cnt=1
fi
fi
done
done