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

This commit is contained in:
github-actions[bot] committed 2026-05-27 21:11:55 +08:00
1 parent 9a80633c00
commit a5d0ad7cb9
24 files changed
+529 -241

No files matched your search

+1 -1
View File
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=hickory-dns PKG_NAME:=hickory-dns
PKG_VERSION:=0.26.1 PKG_VERSION:=0.26.1
PKG_RELEASE:=27 PKG_RELEASE:=28
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/hickory-dns/hickory-dns/tar.gz/v$(PKG_VERSION)? PKG_SOURCE_URL:=https://codeload.github.com/hickory-dns/hickory-dns/tar.gz/v$(PKG_VERSION)?
@@ -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.
@@ -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.
@@ -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.
@@ -47,6 +47,57 @@ key_file = "/etc/hickory-dns/update.key"
algorithm = "hmac-sha256" algorithm = "hmac-sha256"
fudge = 300 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]] [[zones]]
zone = "." zone = "."
zone_type = "External" zone_type = "External"
+4 -1
View File
@@ -11,7 +11,10 @@ i=0
while [ "$i" -lt "$RETRY" ]; do while [ "$i" -lt "$RETRY" ]; do
if ss -lnt | grep -q ':53'; then if ss -lnt | grep -q ':53'; then
logger -t ipv6-neigh-wrapper "DNS port 53 ready, starting ipv6-neigh" 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 fi
i=$((i + 1)) i=$((i + 1))
sleep 1 sleep 1
+21 -90
View File
@@ -189,9 +189,9 @@ dependencies = [
[[package]] [[package]]
name = "displaydoc" name = "displaydoc"
version = "0.2.5" version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -227,16 +227,6 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 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]] [[package]]
name = "find-msvc-tools" name = "find-msvc-tools"
version = "0.1.9" version = "0.1.9"
@@ -534,7 +524,13 @@ dependencies = [
] ]
[[package]] [[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" version = "0.1.0"
dependencies = [ dependencies = [
"clap", "clap",
@@ -552,12 +548,6 @@ dependencies = [
"ubus", "ubus",
] ]
[[package]]
name = "ipnet"
version = "2.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
[[package]] [[package]]
name = "is_terminal_polyfill" name = "is_terminal_polyfill"
version = "1.70.2" version = "1.70.2"
@@ -572,9 +562,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]] [[package]]
name = "jiff" name = "jiff"
version = "0.2.24" version = "0.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f00b5dbd620d61dfdcb6007c9c1f6054ebd75319f163d886a9055cec1155073d" checksum = "392c70591e8749fe235ddaf513e6f58b26bce3dcc16524cecc8936f75afa161e"
dependencies = [ dependencies = [
"jiff-static", "jiff-static",
"log", "log",
@@ -585,9 +575,9 @@ dependencies = [
[[package]] [[package]]
name = "jiff-static" name = "jiff-static"
version = "0.2.24" version = "0.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e000de030ff8022ea1da3f466fbb0f3a809f5e51ed31f6dd931c35181ad8e6d7" checksum = "47b605b0c050d845fc355bb11eb3f9a8deddc218ea60c76e61aa1f2adfb2c96a"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -612,26 +602,17 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" 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]] [[package]]
name = "log" name = "log"
version = "0.4.29" version = "0.4.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.8.0" version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
[[package]] [[package]]
name = "mio" name = "mio"
@@ -706,9 +687,9 @@ dependencies = [
[[package]] [[package]]
name = "num-conv" name = "num-conv"
version = "0.2.1" version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
[[package]] [[package]]
name = "once_cell" name = "once_cell"
@@ -726,29 +707,6 @@ version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" 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]] [[package]]
name = "paste" name = "paste"
version = "1.0.15" version = "1.0.15"
@@ -848,15 +806,6 @@ version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" 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]] [[package]]
name = "regex" name = "regex"
version = "1.12.3" version = "1.12.3"
@@ -927,12 +876,6 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]] [[package]]
name = "semver" name = "semver"
version = "1.0.28" version = "1.0.28"
@@ -971,9 +914,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.149" version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
@@ -988,16 +931,6 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 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]] [[package]]
name = "slab" name = "slab"
version = "0.4.12" version = "0.4.12"
@@ -1153,9 +1086,7 @@ dependencies = [
"bytes", "bytes",
"libc", "libc",
"mio", "mio",
"parking_lot",
"pin-project-lite", "pin-project-lite",
"signal-hook-registry",
"socket2", "socket2",
"tokio-macros", "tokio-macros",
"windows-sys 0.61.2", "windows-sys 0.61.2",
+5 -3
View File
@@ -1,5 +1,5 @@
[package] [package]
name = "ip-neigh" name = "ipv6-neigh"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
@@ -8,14 +8,14 @@ edition = "2024"
[dependencies] [dependencies]
rtnetlink = { version = "0.21.0" } rtnetlink = { version = "0.21.0" }
netlink-sys = { version = "0.8" } 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" futures = "0.3.32"
netlink-packet-route = {version = "0.30" } netlink-packet-route = {version = "0.30" }
netlink-packet-core= {version ="0.8"} netlink-packet-core= {version ="0.8"}
clap = { version = "4.6", default-features = false, features = ["cargo", "derive", "help", "std", "suggestions"] } 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"] } hickory-proto = { git = "https://github.com/hickory-dns/hickory-dns.git", rev = "ee7ba64757eca61d86f323ef992eba482605d7c3", features = ["dnssec-ring"] }
ubus = "0.1.7" ubus = "0.1.7"
serde_json = "1.0.149" serde_json = "1.0.150"
log = "0.4" log = "0.4"
env_logger = "0.11" env_logger = "0.11"
socket2 = { version = "0.6", features = ["all"] } socket2 = { version = "0.6", features = ["all"] }
@@ -25,3 +25,5 @@ opt-level = 3
lto = true lto = true
codegen-units = 1 codegen-units = 1
strip = true strip = true
overflow-checks = false
panic = "abort"
+17 -3
View File
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=ipv6-neigh PKG_NAME:=ipv6-neigh
PKG_VERSION:=0.1.0 PKG_VERSION:=0.1.0
PKG_RELEASE:=15 PKG_RELEASE:=16
PKG_BUILD_DEPENDS:=rust/host PKG_BUILD_DEPENDS:=rust/host
PKG_BUILD_PARALLEL:=1 PKG_BUILD_PARALLEL:=1
@@ -12,6 +12,16 @@ RUST_PKG_LOCKED:=0
include $(INCLUDE_DIR)/package.mk include $(INCLUDE_DIR)/package.mk
include $(TOPDIR)/feeds/packages/lang/rust/rust-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 define Package/ipv6-neigh
SECTION:=net SECTION:=net
CATEGORY:=Network CATEGORY:=Network
@@ -37,6 +47,10 @@ endef
define Build/Compile define Build/Compile
+$(CARGO_PKG_VARS) \ +$(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) \ cargo build -v --profile $(CARGO_PKG_PROFILE) \
--manifest-path "$(PKG_BUILD_DIR)/Cargo.toml" \ --manifest-path "$(PKG_BUILD_DIR)/Cargo.toml" \
--target $(RUSTC_TARGET_ARCH) \ --target $(RUSTC_TARGET_ARCH) \
@@ -44,12 +58,12 @@ define Build/Compile
$(if $(filter --jobserver%,$(PKG_JOBS)),,-j1) \ $(if $(filter --jobserver%,$(PKG_JOBS)),,-j1) \
$(CARGO_PKG_ARGS) $(CARGO_PKG_ARGS)
+$(INSTALL_DIR) $(PKG_INSTALL_DIR)/bin/ +$(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 endef
define Package/ipv6-neigh/install define Package/ipv6-neigh/install
$(INSTALL_DIR) $(1)/usr/bin/ $(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 endef
$(eval $(call RustBinPackage,ipv6-neigh)) $(eval $(call RustBinPackage,ipv6-neigh))
+127 -2
View File
@@ -2,7 +2,8 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use std::time::{Duration, SystemTime, UNIX_EPOCH}; use std::time::{Duration, SystemTime, UNIX_EPOCH};
use hickory_proto::op::{Message, Query, ResponseCode, update_message}; 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::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream; use tokio::net::TcpStream;
use tokio::time::timeout; use tokio::time::timeout;
@@ -23,11 +24,100 @@ pub(crate) struct DnsUpdater {
server_addr: SocketAddr, server_addr: SocketAddr,
zone: Name, zone: Name,
signer: TSigner, 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 { impl DnsUpdater {
pub fn new(server_addr: SocketAddr, zone: Name, signer: TSigner) -> Self { 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. /// 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. /// Extract the single label that precedes the zone apex from a fully-qualified record name.
/// ///
/// e.g. `"foo.lan."` with zone `"lan."` → `Some("foo")`. /// e.g. `"foo.lan."` with zone `"lan."` → `Some("foo")`.
+6
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), 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) => { IpAddr::V4(addr) => {
for name in std::iter::once(hostname).chain(extra_alias) { 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), 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);
}
} }
} }
} }
+44 -3
View File
@@ -1,5 +1,5 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::net::SocketAddr as StdSocketAddr; use std::net::{IpAddr, Ipv4Addr, SocketAddr as StdSocketAddr};
use std::time::Instant; use std::time::Instant;
use futures::stream::StreamExt; use futures::stream::StreamExt;
@@ -80,6 +80,12 @@ struct Cli {
/// Maximum number of ULA AAAA records to publish per host (oldest pruned first) /// Maximum number of ULA AAAA records to publish per host (oldest pruned first)
#[clap(long, default_value = "2")] #[clap(long, default_value = "2")]
max_ula_per_host: usize, 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) 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 0–32")
});
assert!(prefix_len <= 32, "invalid --ptr-ipv4-subnet \"{s}\": prefix length {prefix_len} > 32");
(addr, prefix_len)
}
#[tokio::main(flavor = "current_thread")] #[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), ()> { async fn main() -> Result<(), ()> {
let args = Cli::parse(); let args = Cli::parse();
@@ -126,7 +148,12 @@ async fn main() -> Result<(), ()> {
let signer = TSigner::new(key_data, TsigAlgorithm::HmacSha256, key_name, 300) let signer = TSigner::new(key_data, TsigAlgorithm::HmacSha256, key_name, 300)
.expect("invalid TSIG key"); .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 the DNS server to become available before sending any updates.
wait_for_dns_server(args.dns_server).await; wait_for_dns_server(args.dns_server).await;
@@ -274,6 +301,16 @@ async fn main() -> Result<(), ()> {
match result { match result {
Ok(()) => { Ok(()) => {
info!("DNS update: added {} -> {:?}", hostname, neigh.inet); 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 { registered.insert(key, RegisteredEntry {
hostname: hostname.clone(), hostname: hostname.clone(),
last_confirmed: Instant::now(), last_confirmed: Instant::now(),
@@ -416,18 +453,22 @@ async fn main() -> Result<(), ()> {
// Resolve key by hostname (handles MAC changes: new MAC in leases // Resolve key by hostname (handles MAC changes: new MAC in leases
// can now evict entries registered under an old MAC). // can now evict entries registered under an old MAC).
// Fall back to scanning registered by IP if MAC is no longer in leases. // 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 let key_opt: Option<(String, String)> = leases
.get(&neigh.mac) .get(&neigh.mac)
.map(|h| (h.clone(), ip_str.clone())) .map(|h| (h.clone(), ip_str.clone()))
.or_else(|| registered.keys().find(|(_, ip)| ip == &ip_str).cloned()); .or_else(|| registered.keys().find(|(_, ip)| ip == &ip_str).cloned());
if let Some(key) = key_opt { if let Some(key) = key_opt {
if let Some(entry) = registered.remove(&key) { if let Some(entry) = registered.remove(&key) {
debug!("Neighbour failed: {:?}", neigh);
if !do_delete_dns(&entry.hostname, &neigh.inet, &updater).await { if !do_delete_dns(&entry.hostname, &neigh.inet, &updater).await {
// DNS delete failed, put back so we retry // DNS delete failed, put back so we retry
registered.insert(key, entry); 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 { } else if neigh.state == NeighbourState::Reachable {
if let Some(hostname) = leases.get(&neigh.mac) { if let Some(hostname) = leases.get(&neigh.mac) {
+3 -3
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<()> { 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)?; socket.set_nonblocking(true)?;
// Bind outgoing packet to the specific interface via IPV6_UNICAST_IF so the // Bind outgoing packet to the specific interface via IPV6_UNICAST_IF so the
// kernel NUD state machine updates the correct neighbour entry. // kernel NUD state machine updates the correct neighbour entry.
set_ipv6_unicast_if(&socket, ifindex)?; 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 packet: [u8; 8] = [128, 0, 0, 0, 0, 0, 0, 1];
let dest = SockAddr::from(std::net::SocketAddrV6::new(addr, 0, 0, 0)); let dest = SockAddr::from(std::net::SocketAddrV6::new(addr, 0, 0, 0));
socket.send_to(&packet, &dest)?; 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<()> { 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)?; socket.set_nonblocking(true)?;
// Bind outgoing packet to the specific interface via IP_UNICAST_IF. // Bind outgoing packet to the specific interface via IP_UNICAST_IF.
set_ip_unicast_if(&socket, ifindex)?; set_ip_unicast_if(&socket, ifindex)?;
+35 -2
View File
@@ -35,6 +35,14 @@ pub(crate) async fn process_new_neigh(
match result { match result {
Ok(()) => { Ok(()) => {
info!("DNS update: added {} -> {:?}", hostname, neigh.inet); 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 true
} }
Err(e) => { Err(e) => {
@@ -58,6 +66,14 @@ pub(crate) async fn do_delete_dns(
match result { match result {
Ok(()) => { Ok(()) => {
info!("DNS update: removed {} -> {:?}", hostname, inet); 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 true
} }
Err(e) => { Err(e) => {
@@ -172,7 +188,12 @@ pub(crate) async fn reconcile_dns(
IpAddr::V4(addr) => updater.delete_a(hostname, *addr).await, IpAddr::V4(addr) => updater.delete_a(hostname, *addr).await,
}; };
match result { 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), Err(e) => warn!("reconcile: failed to delete stale DNS {} {}: {}", hostname, ip, e),
} }
} }
@@ -198,7 +219,14 @@ pub(crate) async fn reconcile_dns(
_ => continue, _ => continue,
}; };
match del_result { 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), Err(e) => warn!("reconcile: failed to delete orphan {} {}: {}", hostname, ip_str, e),
} }
// Probe so alive devices trigger a REACHABLE event and re-register. // Probe so alive devices trigger a REACHABLE event and re-register.
@@ -234,6 +262,11 @@ pub(crate) async fn reconcile_dns(
match result { match result {
Ok(()) => { Ok(()) => {
info!("reconcile: re-pushed {} -> {}", hostname, ip_str); 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(); entry.last_confirmed = Instant::now();
} }
Err(e) => warn!("reconcile: failed to re-push {} {}: {}", hostname, ip_str, e), Err(e) => warn!("reconcile: failed to re-push {} {}: {}", hostname, ip_str, e),
+1 -1
View File
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall PKG_NAME:=luci-app-passwall
PKG_VERSION:=26.5.20 PKG_VERSION:=26.5.20
PKG_RELEASE:=138 PKG_RELEASE:=139
PKG_PO_VERSION:=$(PKG_VERSION) PKG_PO_VERSION:=$(PKG_VERSION)
PKG_CONFIG_DEPENDS:= \ PKG_CONFIG_DEPENDS:= \
@@ -194,7 +194,7 @@ config_lines = {
tonumber(LISTEN_PORT) ~= 0 and "bind [::]:" .. LISTEN_PORT .. "@lo" or "", 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(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", 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 "", 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 "" DNS_MODE == "socks" and string.format("proxy-server socks5://%s -name %s", REMOTE_PROXY_SERVER, proxy_server_name) or ""
} }
+2 -2
View File
@@ -10,8 +10,8 @@ LUCI_TITLE:=Luci app for ssw
LUCI_DEPENDS:= +luci-app-modeminfo LUCI_DEPENDS:= +luci-app-modeminfo
PKG_LICENSE:=GPLv3 PKG_LICENSE:=GPLv3
PKG_VERSION:=0.0.1 PKG_VERSION:=0.0.2
PKG_RELEASE:=1 PKG_RELEASE:=2
include $(TOPDIR)/feeds/luci/luci.mk include $(TOPDIR)/feeds/luci/luci.mk
@@ -37,9 +37,9 @@ return view.extend({
_('Revert to default sim slot. Each failed attempt doubles revert time.')); _('Revert to default sim slot. Each failed attempt doubles revert time.'));
o.depends({enable: '1'}); o.depends({enable: '1'});
o = s.option(form.ListValue, 'rsrp', _('RSRP value'), o = s.option(form.ListValue, 'rsrp', _('RSRP/RSCP value'),
_('Switch sim lower by value.')); _('Switch sim lower by value. For 4G/5G is RSRP, for 3G RSCP.'));
for (var rsrp = -120; rsrp <= -80; rsrp++) { for (var rsrp = -120; rsrp <= -50; rsrp++) {
o.value(rsrp, rsrp +' '+ _('dBm')); o.value(rsrp, rsrp +' '+ _('dBm'));
}; };
o.depends({enable: '1'}); o.depends({enable: '1'});
+4 -4
View File
@@ -22,11 +22,11 @@ msgstr "Вернуть"
msgid "Revert to default sim slot. Each failed attempt doubles revert time." msgid "Revert to default sim slot. Each failed attempt doubles revert time."
msgstr "Вернуть на SIM слот по-умолчанию. Каждая неудачная попытка увеличивает время возврата." msgstr "Вернуть на SIM слот по-умолчанию. Каждая неудачная попытка увеличивает время возврата."
msgid "RSRP value" msgid "RSRP/RSCP value"
msgstr "Значение RSRP" msgstr "Значение RSRP/RSCP"
msgid "Switch sim lower by value." msgid "Switch sim lower by value. For 4G/5G is RSRP, for 3G RSCP."
msgstr "Переключить sim ниже значения." msgstr "Переключить sim ниже значения. Для 4G/5G значение RSRP, для 3G - RSCP."
msgid "Interval check. sec" msgid "Interval check. sec"
msgstr "Интервал проверки. сек" msgstr "Интервал проверки. сек"
+1 -1
View File
@@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
LUCI_NAME:=luci-app-tinyfilemanager LUCI_NAME:=luci-app-tinyfilemanager
PKG_VERSION:=2.6 PKG_VERSION:=2.6
PKG_RELEASE:=1 PKG_RELEASE:=2
LUCI_TITLE:=LuCI Tiny File Manager 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 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
@@ -1,33 +1,33 @@
location = /tinyfilemanager/ { location /tinyfilemanager/ {
fastcgi_split_path_info ^(.+?\.php)(/.*)$; index index.php;
if (!-f $document_root$fastcgi_script_name) {
return 404; 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;
} }
+1 -1
View File
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=ssw PKG_NAME:=ssw
PKG_VERSION:=0.0.2 PKG_VERSION:=0.0.2
PKG_RELEASE:=1 PKG_RELEASE:=2
PKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru> PKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>
include $(INCLUDE_DIR)/package.mk include $(INCLUDE_DIR)/package.mk
+31 -27
View File
@@ -2,6 +2,7 @@
# SIM slot switcher for HuastLink HC-G60 or ZBT-WE2806-A # SIM slot switcher for HuastLink HC-G60 or ZBT-WE2806-A
# by Konstantine Shevlakov at <shevlakov@132lan.ru> 2025 # by Konstantine Shevlakov at <shevlakov@132lan.ru> 2025
# Audited & fixed 2025
START=99 START=99
STOP=10 STOP=10
@@ -11,7 +12,6 @@ load_config_gpio(){
local gpio value local gpio value
for c in gpio value; do for c in gpio value; do
config_get $1_$c "$1" $c config_get $1_$c "$1" $c
export $1_$c
done done
} }
@@ -19,54 +19,58 @@ load_config_failover(){
local enable mode interval times_rsrp times_ping revert rsrp host 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 for c in enable mode interval times_rsrp times_ping revert rsrp host; do
config_get $1_$c "$1" $c config_get $1_$c "$1" $c
export $1_$c
done done
} }
reload_iface(){ reload_iface(){
[ "$iface" ] && { [ "$iface" ] && {
for i in $iface; do for i in $iface; do
ifup $i ifup "$i"
done 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(){ start_service(){
config_load ssw config_load ssw
config_foreach load_config_gpio modem config_foreach load_config_gpio modem
config_foreach load_config_gpio sim config_foreach load_config_gpio sim
modem_cur=$(cat /sys/class/gpio/$modem_gpio/value)
sim_cur=$(cat /sys/class/gpio/$sim_gpio/value) local modem_cur sim_cur
iface=$(uci show network | awk -F [.] '/devices/{gsub("'\''","");print $2}' | tail -1) modem_cur=$(cat /sys/class/gpio/"$modem_gpio"/value)
if [ "$modem_value" = "$modem_cur" -a "$sim_value" = "$sim_cur" ]; then sim_cur=$(cat /sys/class/gpio/"$sim_gpio"/value)
continue
elif [ "$modem_value" = "$modem_cur" -a "$modem_value" = "1" ]; then iface=$(uci show network | awk -F'[.]' '/devices/{gsub("'\''",""); print $2}' | tail -1)
logger -t "SIM SW" "modem already enabled on gpio $modem_gpio. Restart it"
echo "0" > /sys/class/gpio/$modem_gpio/value 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 sleep 2
echo "$sim_value" > /sys/class/gpio/$sim_gpio/value _apply_modem_state
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 &
else else
logger -t "SIM SW" "modem powered changed" logger -t "SIM SW" "Modem power state changed."
echo "$sim_value" > /sys/class/gpio/$sim_gpio/value _apply_modem_state
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 &
fi fi
#config_foreach load_config_failover failover
procd_open_instance procd_open_instance
procd_set_param command /usr/share/ssw_track.sh procd_set_param command /usr/share/ssw_track.sh
procd_close_instance procd_close_instance
} }
reload_service(){ reload_service(){
start start_service
} }
service_triggers(){ service_triggers(){
+116 -62
View File
@@ -1,8 +1,9 @@
#!/bin/sh #!/bin/sh
# SIM slot switcher for HuastLink HC-G60 or ZBT-WE2806-A # 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 # by Konstantine Shevlakov at <shevlakov@132lan.ru> 2025
# Audited & fixed 2026
NODE="SW SIM" NODE="SW SIM"
@@ -34,38 +35,58 @@ NODE="SW SIM"
# option value '1' # 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(){ get_vars(){
for v in enable interval revert rsrp times_rsrp apn1 apn2; do reset_cb
eval $v=$(uci -q get ssw.failover.${v} 2>/dev/nul) config_load ssw
done config_foreach _load_failover failover
for d in modem sim; do config_foreach _load_gpio modem
for s in gpio value; do config_foreach _load_gpio sim
eval "${d}_${s}=$(uci -q get ssw.${d}.${s} 2>/dev/null)"
done
done
[ -n "$interval" ] || interval=60
[ -n "$times_rsrp" ] || times_rsrp=5
} }
# SIM Switch # SIM Switch
sw_sim(){ sw_sim(){
case $cur_sim in case "$cur_sim" in
0) next_sim=1 ;; 0) next_sim=1 ;;
1) next_sim=0 ;; 1) next_sim=0 ;;
*)
logger -t "$NODE" "ERROR: cur_sim has unexpected value '$cur_sim'. Aborting sw_sim."
return 1
;;
esac esac
echo "0" > /sys/class/gpio/$modem_gpio/value echo "0" > /sys/class/gpio/"$modem_gpio"/value
echo "$next_sim" > /sys/class/gpio/$sim_gpio/value echo "$next_sim" > /sys/class/gpio/"$sim_gpio"/value
if [ "$modem_value" = "1" ]; then if [ "$modem_value" = "1" ]; then
sleep 2 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 if [ -x /etc/init.d/smstools3 ]; then
/etc/init.d/smstools3 restart /etc/init.d/smstools3 restart
fi fi
fi fi
set > /tmp/apn.ssw
} }
# Revert rule switch # Revert rule switch
@@ -84,32 +105,59 @@ sw_rule(){
# Check interface state via mwan3 # Check interface state via mwan3
monitor_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 # Check link status
if [ -r /tmp/run/mwan3/iface_state/$iface ]; then if [ -r "/tmp/run/mwan3/iface_state/$iface" ]; then
link_status=$(cat /tmp/run/mwan3/iface_state/$iface | grep online | wc -l) link_status=$(grep -c online "/tmp/run/mwan3/iface_state/$iface" || echo 0)
else else
# Disable track link via mwan3 # Disable track link via mwan3
link_status=1 link_status=1
fi 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(){ monitor_rsrp(){
device="$(uci show network | awk -F [=] '/devices/{gsub("'\''","");print $2}')" local device SIGNAL CRSRP
SIGNAL="$(mmcli -J -m $device --signal-get)"
CRSRP=$(echo "$SIGNAL" | jsonfilter -e '@["modem"][*]["lte"]["rsrp"]' | awk '{printf "%.0f\n", $1}') device="$(uci show network | awk -F'[=]' '/devices/{gsub("'\''",""); print $2}' | tail -1)"
if ! [ $CRSRP ]; then SIGNAL="$(mmcli -J -m "$device" --signal-get 2>/dev/null)"
CRSRP=$(echo "$SIGNAL" | jsonfilter -e '@["modem"][*]["5g"]["rsrp"]' | awk '{printf "%.0f\n", $1}')
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 fi
if [ $CRSRP -ne 0 ]; then if [ "$cnt" -eq "$times_rsrp" ]; then
echo $CRSRP >> /tmp/ssw_rsrp.var if [ -s /tmp/ssw_rsrp.var ]; then
fi RSRP=$(awk '{sum+=$1} END { printf "%.0f\n", sum/NR }' /tmp/ssw_rsrp.var)
if [ $cnt -eq $times_rsrp ]; then fi
RSRP=$(awk '{sum+=$1} END { printf "%.0f\n", sum/NR }' /tmp/ssw_rsrp.var) : > /tmp/ssw_rsrp.var
cat /dev/null > /tmp/ssw_rsrp.var
if [ $RSRP -lt $rsrp ]; then if [ -n "$RSRP" ] && [ "$RSRP" -lt "$rsrp" ]; then
mon_rsrp=0 mon_rsrp=0
else else
mon_rsrp=1 mon_rsrp=1
@@ -121,68 +169,74 @@ monitor_rsrp(){
reload_iface(){ reload_iface(){
[ "$iface" ] && { [ "$iface" ] && {
for i in $iface; do for i in $iface; do
ifup $i ifup "$i"
done done
} }
} }
# Stuff cnt=0
cnt=1
while true; do while true; do
get_vars get_vars
sleep $interval sleep "$interval"
if [ "$enable" = "1" ]; then 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_rsrp
monitor_mwan3 monitor_mwan3
if [ "$cnt" -eq "$times_rsrp" ]; then 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 if [ "$sim_value" -eq "$cur_sim" ]; then
apn=$apn2 apn=$apn2
else else
apn=$apn1 apn=$apn1
fi 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 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 fi
if [ "$link_status" = "0" ]; then if [ "$link_status" = "0" ]; then
logger -t "$NODE" "Modem interface: $iface is loss connectivity" logger -t "$NODE" "Modem interface: $iface lost connectivity."
fi fi
else 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 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" logger -t "$NODE" "Switch SIM-card slot with APN: $apn"
else else
logger -t "$NODE" "WARNING: APN not defined. Switch SIM-card slot with default APN." logger -t "$NODE" "WARNING: APN not defined. Switching SIM-card slot with default APN."
uci set network.$iface.apn="internet" uci set network."$iface".apn="internet"
fi fi
uci commit network uci commit network
reload_config network reload_config
if [ "$revert" = "1" ]; then if [ "$revert" = "1" ]; then
if [ "$cur_sim" -eq "$sim_value" ]; then if [ "$cur_sim" -eq "$sim_value" ]; then
FBT=${FBT:=$((($interval+$times_rsrp)*2))} FBT=${FBT:=$(( (interval + times_rsrp) * 2 ))}
FBT=$(($FBT*2)) FBT=$(( FBT * 2 ))
SWDATE=$((`date +%s`+$FBT)) SWDATE=$(( $(date +%s) + FBT ))
echo "FBT=$FBT" > /tmp/ssw.vars printf 'FBT=%s\nSWDATE=%s\n' "$FBT" "$SWDATE" > /tmp/ssw.vars
echo SWDATE=$SWDATE >> /tmp/ssw.vars logger -t "$NODE" "Back to default SIM-slot after $(date -d @"${SWDATE}")"
logger -t "$NODE" "Back to default SIM-slot after $(date -d @${SWDATE})"
fi fi
fi fi
sw_sim && sleep 20 && reload_iface & sw_sim && sleep 20 && reload_iface &
fi
if [ "$revert" = "1" ]; then elif [ "$revert" = "1" ]; then
sw_rule && sleep 20 && reload_iface & sw_rule && sleep 20 && reload_iface &
fi fi
cnt=0
else cnt=1
cnt=$(($cnt+1))
fi fi
fi fi
done done