mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-07-27 10:31:38 +08:00
💋 Sync 2026-04-30 20:35:45
This commit is contained in:
parent
70052617ed
commit
a2af50b5c1
222
ipv6-neigh/Cargo.lock
generated
222
ipv6-neigh/Cargo.lock
generated
@ -2,12 +2,65 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "1.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstream"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"anstyle-parse",
|
||||||
|
"anstyle-query",
|
||||||
|
"anstyle-wincon",
|
||||||
|
"colorchoice",
|
||||||
|
"is_terminal_polyfill",
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstyle"
|
name = "anstyle"
|
||||||
version = "1.0.14"
|
version = "1.0.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-parse"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
|
||||||
|
dependencies = [
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-query"
|
||||||
|
version = "1.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-wincon"
|
||||||
|
version = "3.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"once_cell_polyfill",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.102"
|
version = "1.0.102"
|
||||||
@ -104,6 +157,12 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorchoice"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "combine"
|
name = "combine"
|
||||||
version = "4.6.7"
|
version = "4.6.7"
|
||||||
@ -135,6 +194,15 @@ version = "2.11.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
|
checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deranged"
|
||||||
|
version = "0.5.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
|
||||||
|
dependencies = [
|
||||||
|
"powerfmt",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "displaydoc"
|
name = "displaydoc"
|
||||||
version = "0.2.5"
|
version = "0.2.5"
|
||||||
@ -146,6 +214,29 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_filter"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_logger"
|
||||||
|
version = "0.11.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"env_filter",
|
||||||
|
"jiff",
|
||||||
|
"log",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
@ -322,6 +413,7 @@ name = "hickory-proto"
|
|||||||
version = "0.26.0-beta.3"
|
version = "0.26.0-beta.3"
|
||||||
source = "git+https://github.com/hickory-dns/hickory-dns.git?rev=34a4cca1a707b642052b427938b7d1e8aaa71e3a#34a4cca1a707b642052b427938b7d1e8aaa71e3a"
|
source = "git+https://github.com/hickory-dns/hickory-dns.git?rev=34a4cca1a707b642052b427938b7d1e8aaa71e3a#34a4cca1a707b642052b427938b7d1e8aaa71e3a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
"idna",
|
"idna",
|
||||||
"ipnet",
|
"ipnet",
|
||||||
@ -329,7 +421,9 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"rand",
|
"rand",
|
||||||
"ring",
|
"ring",
|
||||||
|
"rustls-pki-types",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
|
"time",
|
||||||
"tinyvec",
|
"tinyvec",
|
||||||
"tracing",
|
"tracing",
|
||||||
"url",
|
"url",
|
||||||
@ -461,8 +555,10 @@ name = "ip-neigh"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
|
"env_logger",
|
||||||
"futures",
|
"futures",
|
||||||
"hickory-proto",
|
"hickory-proto",
|
||||||
|
"log",
|
||||||
"netlink-packet-core",
|
"netlink-packet-core",
|
||||||
"netlink-packet-route",
|
"netlink-packet-route",
|
||||||
"netlink-sys",
|
"netlink-sys",
|
||||||
@ -478,12 +574,42 @@ version = "2.12.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
|
checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is_terminal_polyfill"
|
||||||
|
version = "1.70.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.18"
|
version = "1.0.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jiff"
|
||||||
|
version = "0.2.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f00b5dbd620d61dfdcb6007c9c1f6054ebd75319f163d886a9055cec1155073d"
|
||||||
|
dependencies = [
|
||||||
|
"jiff-static",
|
||||||
|
"log",
|
||||||
|
"portable-atomic",
|
||||||
|
"portable-atomic-util",
|
||||||
|
"serde_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jiff-static"
|
||||||
|
version = "0.2.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e000de030ff8022ea1da3f466fbb0f3a809f5e51ed31f6dd931c35181ad8e6d7"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jni"
|
name = "jni"
|
||||||
version = "0.22.4"
|
version = "0.22.4"
|
||||||
@ -660,6 +786,12 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-conv"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.4"
|
version = "1.21.4"
|
||||||
@ -670,6 +802,12 @@ dependencies = [
|
|||||||
"portable-atomic",
|
"portable-atomic",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell_polyfill"
|
||||||
|
version = "1.70.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.5"
|
version = "0.12.5"
|
||||||
@ -717,6 +855,15 @@ version = "1.13.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
|
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "portable-atomic-util"
|
||||||
|
version = "0.2.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618"
|
||||||
|
dependencies = [
|
||||||
|
"portable-atomic",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "potential_utf"
|
name = "potential_utf"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
@ -726,6 +873,12 @@ dependencies = [
|
|||||||
"zerovec",
|
"zerovec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "powerfmt"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prettyplease"
|
name = "prettyplease"
|
||||||
version = "0.2.37"
|
version = "0.2.37"
|
||||||
@ -786,6 +939,35 @@ dependencies = [
|
|||||||
"bitflags",
|
"bitflags",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.12.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.4.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.8.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ring"
|
name = "ring"
|
||||||
version = "0.17.14"
|
version = "0.17.14"
|
||||||
@ -826,6 +1008,15 @@ dependencies = [
|
|||||||
"semver",
|
"semver",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-pki-types"
|
||||||
|
version = "1.14.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
|
||||||
|
dependencies = [
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "same-file"
|
name = "same-file"
|
||||||
version = "1.0.6"
|
version = "1.0.6"
|
||||||
@ -1024,6 +1215,25 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.3.47"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
|
||||||
|
dependencies = [
|
||||||
|
"deranged",
|
||||||
|
"num-conv",
|
||||||
|
"powerfmt",
|
||||||
|
"serde_core",
|
||||||
|
"time-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-core"
|
||||||
|
version = "0.1.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinystr"
|
name = "tinystr"
|
||||||
version = "0.8.3"
|
version = "0.8.3"
|
||||||
@ -1144,6 +1354,12 @@ version = "1.0.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "walkdir"
|
name = "walkdir"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
@ -1453,6 +1669,12 @@ dependencies = [
|
|||||||
"synstructure",
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zeroize"
|
||||||
|
version = "1.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerotrie"
|
name = "zerotrie"
|
||||||
version = "0.2.4"
|
version = "0.2.4"
|
||||||
|
|||||||
@ -16,6 +16,8 @@ clap = { version = "4.0", default-features = false, features = ["cargo", "derive
|
|||||||
hickory-proto = { git = "https://github.com/hickory-dns/hickory-dns.git", rev = "34a4cca1a707b642052b427938b7d1e8aaa71e3a", features = ["dnssec-ring"] }
|
hickory-proto = { git = "https://github.com/hickory-dns/hickory-dns.git", rev = "34a4cca1a707b642052b427938b7d1e8aaa71e3a", features = ["dnssec-ring"] }
|
||||||
ubus = "0.1.6"
|
ubus = "0.1.6"
|
||||||
serde_json = "1.0.108"
|
serde_json = "1.0.108"
|
||||||
|
log = "0.4"
|
||||||
|
env_logger = "0.11"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
|||||||
@ -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:=2
|
PKG_RELEASE:=3
|
||||||
|
|
||||||
PKG_BUILD_DEPENDS:=rust/host
|
PKG_BUILD_DEPENDS:=rust/host
|
||||||
PKG_BUILD_PARALLEL:=1
|
PKG_BUILD_PARALLEL:=1
|
||||||
|
|||||||
@ -1,10 +1,22 @@
|
|||||||
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
|
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
use hickory_proto::op::{Message, ResponseCode, update_message};
|
use hickory_proto::op::{Message, ResponseCode, update_message};
|
||||||
use hickory_proto::rr::{Name, RData, RecordSet, RecordType, TSigner};
|
use hickory_proto::rr::{Name, RData, RecordSet, RecordType, TSigner};
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
|
use tokio::time::timeout;
|
||||||
|
|
||||||
|
const TCP_TIMEOUT: Duration = Duration::from_secs(5);
|
||||||
|
|
||||||
|
fn check_response(response: &Message, allowed: &[ResponseCode]) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let code = response.metadata.response_code;
|
||||||
|
if code == ResponseCode::NoError || allowed.contains(&code) {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(format!("DNS server returned {code:?}").into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// DNS dynamic update client that sends RFC 2136 updates over TCP to the hickory-dns server.
|
/// DNS dynamic update client that sends RFC 2136 updates over TCP to the hickory-dns server.
|
||||||
pub(crate) struct DnsUpdater {
|
pub(crate) struct DnsUpdater {
|
||||||
@ -35,7 +47,10 @@ impl DnsUpdater {
|
|||||||
let mut rrset = RecordSet::with_ttl(name, RecordType::AAAA, ttl);
|
let mut rrset = RecordSet::with_ttl(name, RecordType::AAAA, ttl);
|
||||||
rrset.add_rdata(RData::AAAA(addr.into()));
|
rrset.add_rdata(RData::AAAA(addr.into()));
|
||||||
let msg = update_message::append(rrset, self.zone.clone(), false, false);
|
let msg = update_message::append(rrset, self.zone.clone(), false, false);
|
||||||
self.send_tcp(msg).await?;
|
let response = self.send_tcp(msg).await?;
|
||||||
|
check_response(&response, &[])?;
|
||||||
|
} else {
|
||||||
|
check_response(&response, &[])?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -57,7 +72,10 @@ impl DnsUpdater {
|
|||||||
let mut rrset = RecordSet::with_ttl(name, RecordType::A, ttl);
|
let mut rrset = RecordSet::with_ttl(name, RecordType::A, ttl);
|
||||||
rrset.add_rdata(RData::A(addr.into()));
|
rrset.add_rdata(RData::A(addr.into()));
|
||||||
let msg = update_message::append(rrset, self.zone.clone(), false, false);
|
let msg = update_message::append(rrset, self.zone.clone(), false, false);
|
||||||
self.send_tcp(msg).await?;
|
let response = self.send_tcp(msg).await?;
|
||||||
|
check_response(&response, &[])?;
|
||||||
|
} else {
|
||||||
|
check_response(&response, &[])?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -72,7 +90,8 @@ impl DnsUpdater {
|
|||||||
let mut rrset = RecordSet::new(name, RecordType::AAAA, 0);
|
let mut rrset = RecordSet::new(name, RecordType::AAAA, 0);
|
||||||
rrset.add_rdata(RData::AAAA(addr.into()));
|
rrset.add_rdata(RData::AAAA(addr.into()));
|
||||||
let msg = update_message::delete_by_rdata(rrset, self.zone.clone(), false);
|
let msg = update_message::delete_by_rdata(rrset, self.zone.clone(), false);
|
||||||
self.send_tcp(msg).await?;
|
let response = self.send_tcp(msg).await?;
|
||||||
|
check_response(&response, &[])?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,7 +105,8 @@ impl DnsUpdater {
|
|||||||
let mut rrset = RecordSet::new(name, RecordType::A, 0);
|
let mut rrset = RecordSet::new(name, RecordType::A, 0);
|
||||||
rrset.add_rdata(RData::A(addr.into()));
|
rrset.add_rdata(RData::A(addr.into()));
|
||||||
let msg = update_message::delete_by_rdata(rrset, self.zone.clone(), false);
|
let msg = update_message::delete_by_rdata(rrset, self.zone.clone(), false);
|
||||||
self.send_tcp(msg).await?;
|
let response = self.send_tcp(msg).await?;
|
||||||
|
check_response(&response, &[])?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,15 +118,23 @@ impl DnsUpdater {
|
|||||||
let bytes = msg.to_vec()?;
|
let bytes = msg.to_vec()?;
|
||||||
let len = u16::try_from(bytes.len())?;
|
let len = u16::try_from(bytes.len())?;
|
||||||
|
|
||||||
let mut stream = TcpStream::connect(self.server_addr).await?;
|
let result = timeout(TCP_TIMEOUT, async {
|
||||||
stream.write_all(&len.to_be_bytes()).await?;
|
let mut stream = TcpStream::connect(self.server_addr).await?;
|
||||||
stream.write_all(&bytes).await?;
|
stream.write_all(&len.to_be_bytes()).await?;
|
||||||
stream.flush().await?;
|
stream.write_all(&bytes).await?;
|
||||||
|
stream.flush().await?;
|
||||||
|
|
||||||
let resp_len = stream.read_u16().await? as usize;
|
let resp_len = stream.read_u16().await? as usize;
|
||||||
let mut resp_buf = vec![0u8; resp_len];
|
let mut resp_buf = vec![0u8; resp_len];
|
||||||
stream.read_exact(&mut resp_buf).await?;
|
stream.read_exact(&mut resp_buf).await?;
|
||||||
|
|
||||||
Ok(Message::from_vec(&resp_buf)?)
|
Ok::<_, Box<dyn std::error::Error>>(Message::from_vec(&resp_buf)?)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|_| -> Box<dyn std::error::Error> {
|
||||||
|
format!("DNS TCP timeout after {}s connecting to {}", TCP_TIMEOUT.as_secs(), self.server_addr).into()
|
||||||
|
})??;
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr as StdSocketAddr};
|
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr as StdSocketAddr};
|
||||||
|
|
||||||
use futures::stream::StreamExt;
|
use futures::stream::StreamExt;
|
||||||
use futures::stream::TryStreamExt;
|
use futures::stream::TryStreamExt;
|
||||||
|
use log::{debug, error, info, warn};
|
||||||
use netlink_packet_core::NetlinkPayload;
|
use netlink_packet_core::NetlinkPayload;
|
||||||
use netlink_packet_route::RouteNetlinkMessage;
|
use netlink_packet_route::RouteNetlinkMessage;
|
||||||
use netlink_packet_route::neighbour::NeighbourMessage;
|
use netlink_packet_route::neighbour::NeighbourMessage;
|
||||||
@ -47,6 +48,9 @@ struct Cli {
|
|||||||
/// TSIG key name
|
/// TSIG key name
|
||||||
#[clap(short = 'n', long, default_value = "update-key.")]
|
#[clap(short = 'n', long, default_value = "update-key.")]
|
||||||
key_name: String,
|
key_name: String,
|
||||||
|
/// Log level (error, warn, info, debug, trace)
|
||||||
|
#[clap(short = 'l', long, default_value = "info")]
|
||||||
|
log_level: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -95,52 +99,70 @@ fn if_ipv4_in_private_subnet(ip: &Ipv4Addr) -> bool {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn process_new_neigh(neigh: &Neigh, updater: &db::DnsUpdater, leases: &HashMap<String, String>) {
|
async fn process_new_neigh(neigh: &Neigh, updater: &db::DnsUpdater, leases: &HashMap<String, String>) -> bool {
|
||||||
let Some(hostname) = leases.get(&neigh.mac) else {
|
let Some(hostname) = leases.get(&neigh.mac) else {
|
||||||
eprintln!("no lease for mac {}, skipping DNS update", neigh.mac);
|
debug!("no lease for mac {}, skipping DNS update", neigh.mac);
|
||||||
return;
|
return true;
|
||||||
};
|
};
|
||||||
let result = match &neigh.inet {
|
let result = match &neigh.inet {
|
||||||
NeighbourAddress::Inet6(addr) => updater.upsert_aaaa(hostname, *addr, DEFAULT_TTL).await,
|
NeighbourAddress::Inet6(addr) => updater.upsert_aaaa(hostname, *addr, DEFAULT_TTL).await,
|
||||||
NeighbourAddress::Inet(addr) => updater.upsert_a(hostname, *addr, DEFAULT_TTL).await,
|
NeighbourAddress::Inet(addr) => updater.upsert_a(hostname, *addr, DEFAULT_TTL).await,
|
||||||
_ => return,
|
_ => return true,
|
||||||
};
|
};
|
||||||
match result {
|
match result {
|
||||||
Ok(()) => println!("DNS update: added {} -> {:?}", hostname, neigh.inet),
|
Ok(()) => {
|
||||||
Err(e) => eprintln!("DNS update failed for {}: {}", hostname, e),
|
info!("DNS update: added {} -> {:?}", hostname, neigh.inet);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("DNS update failed for {}: {}", hostname, e);
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn process_del_neigh(neigh: &Neigh, updater: &db::DnsUpdater, leases: &HashMap<String, String>) {
|
async fn process_del_neigh(neigh: &Neigh, updater: &db::DnsUpdater, leases: &HashMap<String, String>) -> bool {
|
||||||
let Some(hostname) = leases.get(&neigh.mac) else {
|
let Some(hostname) = leases.get(&neigh.mac) else {
|
||||||
return;
|
return true;
|
||||||
};
|
};
|
||||||
let result = match &neigh.inet {
|
let result = match &neigh.inet {
|
||||||
NeighbourAddress::Inet6(addr) => updater.delete_aaaa(hostname, *addr).await,
|
NeighbourAddress::Inet6(addr) => updater.delete_aaaa(hostname, *addr).await,
|
||||||
NeighbourAddress::Inet(addr) => updater.delete_a(hostname, *addr).await,
|
NeighbourAddress::Inet(addr) => updater.delete_a(hostname, *addr).await,
|
||||||
_ => return,
|
_ => return true,
|
||||||
};
|
};
|
||||||
match result {
|
match result {
|
||||||
Ok(()) => println!("DNS update: removed {} -> {:?}", hostname, neigh.inet),
|
Ok(()) => {
|
||||||
Err(e) => eprintln!("DNS delete failed for {}: {}", hostname, e),
|
info!("DNS update: removed {} -> {:?}", hostname, neigh.inet);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("DNS delete failed for {}: {}", hostname, e);
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_multicast_or_broadcast_route_type(route_type: RouteType) -> bool {
|
fn should_skip_route_type(route_type: RouteType) -> bool {
|
||||||
match route_type {
|
matches!(route_type, RouteType::Multicast | RouteType::Broadcast | RouteType::Local)
|
||||||
RouteType::Multicast => true,
|
|
||||||
RouteType::Broadcast => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_multicast_or_broadcast(neigh: &Neigh) -> bool {
|
fn should_skip_neigh(neigh: &Neigh) -> bool {
|
||||||
return is_multicast_or_broadcast_route_type(neigh.kind);
|
should_skip_route_type(neigh.kind)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), ()> {
|
async fn main() -> Result<(), ()> {
|
||||||
let args = Cli::parse();
|
let args = Cli::parse();
|
||||||
|
|
||||||
|
env_logger::Builder::new()
|
||||||
|
.filter_level(
|
||||||
|
args.log_level
|
||||||
|
.parse()
|
||||||
|
.expect("invalid log level (use: error, warn, info, debug, trace)"),
|
||||||
|
)
|
||||||
|
.format_timestamp_secs()
|
||||||
|
.init();
|
||||||
|
|
||||||
let private_subnet = args.private_subnet;
|
let private_subnet = args.private_subnet;
|
||||||
let zone = Name::from_ascii(&args.zone).expect("invalid zone name");
|
let zone = Name::from_ascii(&args.zone).expect("invalid zone name");
|
||||||
|
|
||||||
@ -154,23 +176,34 @@ async fn main() -> Result<(), ()> {
|
|||||||
|
|
||||||
let (connection, handle, _) = new_connection().unwrap();
|
let (connection, handle, _) = new_connection().unwrap();
|
||||||
tokio::spawn(connection);
|
tokio::spawn(connection);
|
||||||
dump_addresses(handle.clone(), args.iface).await.unwrap();
|
if let Some(ref iface) = args.iface {
|
||||||
|
if let Err(e) = dump_addresses(handle.clone(), iface.clone()).await {
|
||||||
|
warn!("failed to dump addresses for iface {}: {}", iface, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Load DHCP leases (mac -> hostname) from ubus
|
// Load DHCP leases (mac -> hostname) from ubus
|
||||||
let mut leases = op::get_lease().unwrap_or_default();
|
let mut leases = op::get_lease().unwrap_or_default();
|
||||||
println!("loaded {} DHCP leases", leases.len());
|
info!("loaded {} DHCP leases", leases.len());
|
||||||
|
|
||||||
|
// State cache: tracks (mac, ip_string) pairs already registered
|
||||||
|
let mut registered: HashSet<(String, String)> = HashSet::new();
|
||||||
|
|
||||||
// Dump existing neighbours and register them
|
// Dump existing neighbours and register them
|
||||||
println!("dumping neighbours");
|
debug!("dumping neighbours");
|
||||||
if let Ok(neighbours) = dump_neighbours(handle.clone(), private_subnet).await {
|
if let Ok(neighbours) = dump_neighbours(handle.clone(), private_subnet).await {
|
||||||
for neigh in &neighbours {
|
for neigh in &neighbours {
|
||||||
println!("{:?}", neigh);
|
debug!("{:?}", neigh);
|
||||||
if !is_multicast_or_broadcast(neigh) {
|
if !should_skip_neigh(neigh) {
|
||||||
process_new_neigh(neigh, &updater, &leases).await;
|
let key = (neigh.mac.clone(), inet_to_string(&neigh.inet));
|
||||||
|
if registered.insert(key.clone()) {
|
||||||
|
if !process_new_neigh(neigh, &updater, &leases).await {
|
||||||
|
registered.remove(&key);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
println!();
|
|
||||||
|
|
||||||
let (mut conn, mut _handle, mut messages) =
|
let (mut conn, mut _handle, mut messages) =
|
||||||
new_connection().map_err(|e| format!("{e}")).unwrap();
|
new_connection().map_err(|e| format!("{e}")).unwrap();
|
||||||
@ -194,7 +227,7 @@ async fn main() -> Result<(), ()> {
|
|||||||
if event_count % 50 == 0 {
|
if event_count % 50 == 0 {
|
||||||
if let Ok(new_leases) = op::get_lease() {
|
if let Ok(new_leases) = op::get_lease() {
|
||||||
leases = new_leases;
|
leases = new_leases;
|
||||||
println!("refreshed {} DHCP leases", leases.len());
|
info!("refreshed {} DHCP leases", leases.len());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,21 +238,41 @@ async fn main() -> Result<(), ()> {
|
|||||||
let Some(neigh) = parse_neighbour_message(new_neigh, private_subnet) else {
|
let Some(neigh) = parse_neighbour_message(new_neigh, private_subnet) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if is_multicast_or_broadcast(&neigh) {
|
if should_skip_neigh(&neigh) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
println!("New neighbour: {:?}", neigh);
|
// Only act on Reachable state to avoid redundant updates
|
||||||
process_new_neigh(&neigh, &updater, &leases).await;
|
if neigh.state != NeighbourState::Reachable {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let key = (neigh.mac.clone(), inet_to_string(&neigh.inet));
|
||||||
|
if !registered.insert(key.clone()) {
|
||||||
|
// Already registered, skip
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
debug!("New neighbour: {:?}", neigh);
|
||||||
|
if !process_new_neigh(&neigh, &updater, &leases).await {
|
||||||
|
// DNS update failed, remove from cache so we retry next time
|
||||||
|
registered.remove(&key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
RouteNetlinkMessage::DelNeighbour(del_neigh) => {
|
RouteNetlinkMessage::DelNeighbour(del_neigh) => {
|
||||||
let Some(neigh) = parse_neighbour_message(del_neigh, private_subnet) else {
|
let Some(neigh) = parse_neighbour_message(del_neigh, private_subnet) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if is_multicast_or_broadcast(&neigh) {
|
if should_skip_neigh(&neigh) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
println!("Del neighbour: {:?}", neigh);
|
let key = (neigh.mac.clone(), inet_to_string(&neigh.inet));
|
||||||
process_del_neigh(&neigh, &updater, &leases).await;
|
if !registered.remove(&key) {
|
||||||
|
// Was not registered, skip
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
debug!("Del neighbour: {:?}", neigh);
|
||||||
|
if !process_del_neigh(&neigh, &updater, &leases).await {
|
||||||
|
// DNS delete failed, put back so we retry delete next time
|
||||||
|
registered.insert(key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@ -237,11 +290,17 @@ fn format_mac(mac: Vec<u8>) -> String {
|
|||||||
mac_str
|
mac_str
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn dump_addresses(handle: Handle, link: Option<String>) -> Result<(), Error> {
|
fn inet_to_string(addr: &NeighbourAddress) -> String {
|
||||||
let mut request = handle.link().get();
|
match addr {
|
||||||
if let Some(link) = link {
|
NeighbourAddress::Inet(ip) => ip.to_string(),
|
||||||
request = request.match_name(link);
|
NeighbourAddress::Inet6(ip) => ip.to_string(),
|
||||||
|
other => format!("{other:?}"),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn dump_addresses(handle: Handle, link: String) -> Result<(), Error> {
|
||||||
|
let mut request = handle.link().get();
|
||||||
|
request = request.match_name(link);
|
||||||
|
|
||||||
let mut links = request.execute();
|
let mut links = request.execute();
|
||||||
if let Some(link) = links.try_next().await? {
|
if let Some(link) = links.try_next().await? {
|
||||||
@ -251,24 +310,33 @@ async fn dump_addresses(handle: Handle, link: Option<String>) -> Result<(), Erro
|
|||||||
.set_link_index_filter(link.header.index)
|
.set_link_index_filter(link.header.index)
|
||||||
.execute();
|
.execute();
|
||||||
while let Some(msg) = addresses.try_next().await? {
|
while let Some(msg) = addresses.try_next().await? {
|
||||||
println!("{msg:?}");
|
debug!("{msg:?}");
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
eprintln!("link not found");
|
warn!("link not found");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_link_local_ipv6(addr: &NeighbourAddress) -> bool {
|
||||||
|
matches!(addr, NeighbourAddress::Inet6(ip) if (ip.segments()[0] & 0xffc0) == 0xfe80)
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_neighbour_message(neigh: NeighbourMessage, private_subnet: bool) -> Option<Neigh> {
|
fn parse_neighbour_message(neigh: NeighbourMessage, private_subnet: bool) -> Option<Neigh> {
|
||||||
let state = neigh.header.state;
|
let state = neigh.header.state;
|
||||||
if state == NeighbourState::Permanent {
|
// Filter out static and incomplete entries
|
||||||
|
if matches!(state, NeighbourState::Permanent | NeighbourState::Noarp) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let addr: NeighbourAddress = neigh.attributes.iter().find_map(|attr| match attr {
|
let addr: NeighbourAddress = neigh.attributes.iter().find_map(|attr| match attr {
|
||||||
NeighbourAttribute::Destination(inet) => Some(inet.to_owned()),
|
NeighbourAttribute::Destination(inet) => Some(inet.to_owned()),
|
||||||
_ => None,
|
_ => None,
|
||||||
})?;
|
})?;
|
||||||
|
// Link-local IPv6 addresses are interface-scoped and useless in DNS
|
||||||
|
if is_link_local_ipv6(&addr) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
if private_subnet {
|
if private_subnet {
|
||||||
match addr {
|
match addr {
|
||||||
NeighbourAddress::Inet(addr) => {
|
NeighbourAddress::Inet(addr) => {
|
||||||
@ -290,12 +358,17 @@ fn parse_neighbour_message(neigh: NeighbourMessage, private_subnet: bool) -> Opt
|
|||||||
NeighbourAttribute::LinkLocalAddress(mac) => Some(mac.to_owned()),
|
NeighbourAttribute::LinkLocalAddress(mac) => Some(mac.to_owned()),
|
||||||
_ => None,
|
_ => None,
|
||||||
})?;
|
})?;
|
||||||
|
let mac_str = format_mac(mac);
|
||||||
|
// Filter out empty or all-zero MACs (router own addresses, incomplete entries)
|
||||||
|
if mac_str.is_empty() || mac_str == "00:00:00:00:00:00" {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
Some(Neigh {
|
Some(Neigh {
|
||||||
ifindex,
|
ifindex,
|
||||||
state,
|
state,
|
||||||
kind,
|
kind,
|
||||||
inet: addr,
|
inet: addr,
|
||||||
mac: format_mac(mac),
|
mac: mac_str,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,7 +377,7 @@ async fn dump_neighbours(handle: Handle, private_subnet: bool) -> Result<Vec<Nei
|
|||||||
let mut vec: Vec<Neigh> = Vec::new();
|
let mut vec: Vec<Neigh> = Vec::new();
|
||||||
while let Some(route) = neighbours.try_next().await? {
|
while let Some(route) = neighbours.try_next().await? {
|
||||||
if let Some(neigh) = parse_neighbour_message(route, private_subnet) {
|
if let Some(neigh) = parse_neighbour_message(route, private_subnet) {
|
||||||
if !is_multicast_or_broadcast(&neigh) {
|
if !should_skip_neigh(&neigh) {
|
||||||
vec.push(neigh);
|
vec.push(neigh);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,32 +5,65 @@ use std::path::Path;
|
|||||||
pub fn call_ubus(obj_path: &str, method: &str) -> Result<Value, Box<dyn std::error::Error>> {
|
pub fn call_ubus(obj_path: &str, method: &str) -> Result<Value, Box<dyn std::error::Error>> {
|
||||||
let socket = Path::new("/var/run/ubus/ubus.sock");
|
let socket = Path::new("/var/run/ubus/ubus.sock");
|
||||||
|
|
||||||
let mut connection = match ubus::Connection::connect(&socket) {
|
let mut connection = ubus::Connection::connect(&socket)?;
|
||||||
Ok(connection) => connection,
|
|
||||||
Err(err) => {
|
|
||||||
return Err(Box::new(err));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let json = connection.call(obj_path, method, "").unwrap();
|
let json = connection.call(obj_path, method, "")?;
|
||||||
let parsed: Value = serde_json::from_str(&json).unwrap();
|
let parsed: Value = serde_json::from_str(&json)?;
|
||||||
Ok(parsed)
|
Ok(parsed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert a hex MAC string like "44237cdcb75b" to colon-separated "44:23:7c:dc:b7:5b"
|
||||||
|
fn format_mac_from_hex(hex: &str) -> String {
|
||||||
|
hex.as_bytes()
|
||||||
|
.chunks(2)
|
||||||
|
.map(|chunk| std::str::from_utf8(chunk).unwrap())
|
||||||
|
.collect::<Vec<&str>>()
|
||||||
|
.join(":")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sanitize a DHCP hostname into a valid DNS label.
|
||||||
|
/// Replaces invalid characters with '-', collapses consecutive hyphens,
|
||||||
|
/// strips leading/trailing hyphens, and truncates to 63 chars.
|
||||||
|
fn sanitize_hostname(raw: &str) -> String {
|
||||||
|
let sanitized: String = raw
|
||||||
|
.chars()
|
||||||
|
.map(|c| {
|
||||||
|
if c.is_ascii_alphanumeric() || c == '-' {
|
||||||
|
c.to_ascii_lowercase()
|
||||||
|
} else {
|
||||||
|
'-'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
// Collapse consecutive hyphens and strip leading/trailing
|
||||||
|
let collapsed: String = sanitized
|
||||||
|
.split('-')
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.collect::<Vec<&str>>()
|
||||||
|
.join("-");
|
||||||
|
// Truncate to 63 chars (max DNS label length)
|
||||||
|
collapsed.chars().take(63).collect()
|
||||||
|
}
|
||||||
|
|
||||||
// get mac to hostname mapping
|
// get mac to hostname mapping
|
||||||
pub fn get_lease() -> Result<HashMap<String, String>, Box<dyn std::error::Error>> {
|
pub fn get_lease() -> Result<HashMap<String, String>, Box<dyn std::error::Error>> {
|
||||||
// dhcpv6 does not use mac address, so we only need to get ipv4 leases
|
// dhcpv6 does not use mac address, so we only need to get ipv4 leases
|
||||||
let ip4lease = call_ubus("dhcp", "ipv4leases");
|
let leases = call_ubus("dhcp", "ipv4leases")?;
|
||||||
let leases = ip4lease.unwrap();
|
let devices = leases["device"].as_object()
|
||||||
let devices = leases["device"].as_object().unwrap();
|
.ok_or("missing 'device' in ipv4leases response")?;
|
||||||
let mut result = HashMap::new();
|
let mut result = HashMap::new();
|
||||||
// hard code
|
for (_device, leases) in devices {
|
||||||
for (device, leases) in devices {
|
let leases = leases["leases"].as_array()
|
||||||
let suffix = if device == "phy1-ap0" { ".iot" } else { ".lan" };
|
.ok_or("missing 'leases' array in device")?;
|
||||||
let leases = leases["leases"].as_array().unwrap();
|
|
||||||
for lease in leases {
|
for lease in leases {
|
||||||
let mac = lease["mac"].as_str().unwrap().to_string();
|
let Some(raw_mac) = lease["mac"].as_str() else { continue };
|
||||||
let hostname = lease["hostname"].as_str().unwrap().to_string() + suffix;
|
let Some(hostname) = lease["hostname"].as_str() else { continue };
|
||||||
|
let mac = format_mac_from_hex(raw_mac);
|
||||||
|
let hostname = sanitize_hostname(hostname);
|
||||||
|
if hostname.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Store bare hostname only; the zone is appended by DnsUpdater
|
||||||
result.insert(mac, hostname);
|
result.insert(mac, hostname);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=luci-app-passwall
|
PKG_NAME:=luci-app-passwall
|
||||||
PKG_VERSION:=26.4.15
|
PKG_VERSION:=26.4.15
|
||||||
PKG_RELEASE:=103
|
PKG_RELEASE:=104
|
||||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||||
|
|
||||||
PKG_CONFIG_DEPENDS:= \
|
PKG_CONFIG_DEPENDS:= \
|
||||||
|
|||||||
@ -509,7 +509,7 @@ o = s:option(ListValue, "xray_dns_mode", translate("Request protocol"))
|
|||||||
o.default = "tcp"
|
o.default = "tcp"
|
||||||
o:value("tcp", "TCP")
|
o:value("tcp", "TCP")
|
||||||
o:value("udp", "UDP")
|
o:value("udp", "UDP")
|
||||||
o:value("tcp+doh", "TCP + DoH (" .. translate("A/AAAA type") .. ")")
|
o:value("doh", "DoH")
|
||||||
o:depends("dns_mode", "xray")
|
o:depends("dns_mode", "xray")
|
||||||
o.cfgvalue = function(self, section)
|
o.cfgvalue = function(self, section)
|
||||||
return m:get(section, "v2ray_dns_mode")
|
return m:get(section, "v2ray_dns_mode")
|
||||||
@ -550,7 +550,6 @@ o:value("208.67.222.222", "208.67.222.222 (OpenDNS)")
|
|||||||
o:depends({dns_mode = "dns2socks"})
|
o:depends({dns_mode = "dns2socks"})
|
||||||
o:depends({xray_dns_mode = "udp"})
|
o:depends({xray_dns_mode = "udp"})
|
||||||
o:depends({xray_dns_mode = "tcp"})
|
o:depends({xray_dns_mode = "tcp"})
|
||||||
o:depends({xray_dns_mode = "tcp+doh"})
|
|
||||||
o:depends({singbox_dns_mode = "udp"})
|
o:depends({singbox_dns_mode = "udp"})
|
||||||
o:depends({singbox_dns_mode = "tcp"})
|
o:depends({singbox_dns_mode = "tcp"})
|
||||||
|
|
||||||
@ -589,7 +588,7 @@ o.validate = function(self, value, t)
|
|||||||
end
|
end
|
||||||
return nil, translate("DoH request address") .. " " .. translate("Format must be:") .. " URL,IP"
|
return nil, translate("DoH request address") .. " " .. translate("Format must be:") .. " URL,IP"
|
||||||
end
|
end
|
||||||
o:depends({xray_dns_mode = "tcp+doh"})
|
o:depends({xray_dns_mode = "doh"})
|
||||||
o:depends({singbox_dns_mode = "doh"})
|
o:depends({singbox_dns_mode = "doh"})
|
||||||
o:depends({singbox_dns_mode = "http3"})
|
o:depends({singbox_dns_mode = "http3"})
|
||||||
|
|
||||||
|
|||||||
@ -379,7 +379,7 @@ o = s:taboption("DNS", ListValue, "xray_dns_mode", translate("Remote DNS") .. "
|
|||||||
o.default = "tcp"
|
o.default = "tcp"
|
||||||
o:value("tcp", "TCP")
|
o:value("tcp", "TCP")
|
||||||
o:value("udp", "UDP")
|
o:value("udp", "UDP")
|
||||||
o:value("tcp+doh", "TCP + DoH (" .. translate("A/AAAA type") .. ")")
|
o:value("doh", "DoH")
|
||||||
o:depends("dns_mode", "xray")
|
o:depends("dns_mode", "xray")
|
||||||
o:depends("smartdns_dns_mode", "xray")
|
o:depends("smartdns_dns_mode", "xray")
|
||||||
o.cfgvalue = function(self, section)
|
o.cfgvalue = function(self, section)
|
||||||
@ -438,7 +438,6 @@ o:depends({dns_mode = "tcp"})
|
|||||||
o:depends({dns_mode = "udp"})
|
o:depends({dns_mode = "udp"})
|
||||||
o:depends({xray_dns_mode = "udp"})
|
o:depends({xray_dns_mode = "udp"})
|
||||||
o:depends({xray_dns_mode = "tcp"})
|
o:depends({xray_dns_mode = "tcp"})
|
||||||
o:depends({xray_dns_mode = "tcp+doh"})
|
|
||||||
o:depends({singbox_dns_mode = "udp"})
|
o:depends({singbox_dns_mode = "udp"})
|
||||||
o:depends({singbox_dns_mode = "tcp"})
|
o:depends({singbox_dns_mode = "tcp"})
|
||||||
|
|
||||||
@ -457,7 +456,7 @@ o:value("https://dns.adguard.com/dns-query,94.140.14.14", "94.140.14.14 (AdGuard
|
|||||||
o:value("https://doh.libredns.gr/dns-query,116.202.176.26", "116.202.176.26 (LibreDNS)")
|
o:value("https://doh.libredns.gr/dns-query,116.202.176.26", "116.202.176.26 (LibreDNS)")
|
||||||
o:value("https://doh.libredns.gr/ads,116.202.176.26", "116.202.176.26 (LibreDNS-NoAds)")
|
o:value("https://doh.libredns.gr/ads,116.202.176.26", "116.202.176.26 (LibreDNS-NoAds)")
|
||||||
o.validate = doh_validate
|
o.validate = doh_validate
|
||||||
o:depends({xray_dns_mode = "tcp+doh"})
|
o:depends({xray_dns_mode = "doh"})
|
||||||
o:depends({singbox_dns_mode = "doh"})
|
o:depends({singbox_dns_mode = "doh"})
|
||||||
o:depends({singbox_dns_mode = "http3"})
|
o:depends({singbox_dns_mode = "http3"})
|
||||||
|
|
||||||
|
|||||||
@ -1498,7 +1498,7 @@ function gen_config(var)
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
if (remote_dns_udp_server and remote_dns_udp_port) or (remote_dns_tcp_server and remote_dns_tcp_port) or #node_dns > 0 then
|
if (remote_dns_udp_server and remote_dns_udp_port) or (remote_dns_tcp_server and remote_dns_tcp_port) or remote_dns_doh_url or #node_dns > 0 then
|
||||||
if not routing then
|
if not routing then
|
||||||
routing = {
|
routing = {
|
||||||
domainStrategy = "IPOnDemand",
|
domainStrategy = "IPOnDemand",
|
||||||
@ -1659,19 +1659,9 @@ function gen_config(var)
|
|||||||
table.insert(outbounds, {
|
table.insert(outbounds, {
|
||||||
tag = "dns-out",
|
tag = "dns-out",
|
||||||
protocol = "dns",
|
protocol = "dns",
|
||||||
proxySettings = dns_outbound_tag and {
|
|
||||||
tag = dns_outbound_tag
|
|
||||||
} or nil,
|
|
||||||
settings = {
|
settings = {
|
||||||
address = remote_dns_udp_server or remote_dns_tcp_server,
|
|
||||||
port = tonumber(remote_dns_udp_port) or tonumber(remote_dns_tcp_port),
|
|
||||||
network = remote_dns_udp_server and "udp" or "tcp",
|
|
||||||
nonIPQuery = (api.compare_versions(xray_version, "<", "26.4.25")) and "reject" or nil, -- Todo is to remove it
|
nonIPQuery = (api.compare_versions(xray_version, "<", "26.4.25")) and "reject" or nil, -- Todo is to remove it
|
||||||
rules = (api.compare_versions(xray_version, ">", "26.4.17")) and {
|
rules = (api.compare_versions(xray_version, ">", "26.4.17")) and {{ action = "hijack" }} or nil
|
||||||
{
|
|
||||||
action = ((node and node.protocol == "_shunt") or remote_dns_fake or dns.clientIP) and "hijack" or "direct"
|
|
||||||
}
|
|
||||||
} or nil
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -181,9 +181,6 @@ msgstr "%s 不能为空。"
|
|||||||
msgid "Filter Mode"
|
msgid "Filter Mode"
|
||||||
msgstr "过滤模式"
|
msgstr "过滤模式"
|
||||||
|
|
||||||
msgid "A/AAAA type"
|
|
||||||
msgstr "A/AAAA 类型"
|
|
||||||
|
|
||||||
msgid "TCP node must be '%s' type to use FakeDNS."
|
msgid "TCP node must be '%s' type to use FakeDNS."
|
||||||
msgstr "TCP 节点必须是 '%s' 类型才能使用 FakeDNS。"
|
msgstr "TCP 节点必须是 '%s' 类型才能使用 FakeDNS。"
|
||||||
|
|
||||||
|
|||||||
@ -181,6 +181,7 @@ run_singbox() {
|
|||||||
[ "$remote_dns_protocol" = "http3" ] && json_add_string "remote_dns_http3" "1"
|
[ "$remote_dns_protocol" = "http3" ] && json_add_string "remote_dns_http3" "1"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
[ -n "$remote_dns_client_ip" ] && json_add_string "remote_dns_client_ip" "${remote_dns_client_ip}"
|
[ -n "$remote_dns_client_ip" ] && json_add_string "remote_dns_client_ip" "${remote_dns_client_ip}"
|
||||||
[ "$remote_fakedns" = "1" ] && json_add_string "remote_dns_fake" "1"
|
[ "$remote_fakedns" = "1" ] && json_add_string "remote_dns_fake" "1"
|
||||||
[ -n "$no_run" ] && json_add_string "no_run" "1"
|
[ -n "$no_run" ] && json_add_string "no_run" "1"
|
||||||
@ -259,29 +260,24 @@ run_xray() {
|
|||||||
[ -n "$dns_cache" ] && json_add_string "dns_cache" "${dns_cache}"
|
[ -n "$dns_cache" ] && json_add_string "dns_cache" "${dns_cache}"
|
||||||
|
|
||||||
case "$remote_dns_protocol" in
|
case "$remote_dns_protocol" in
|
||||||
udp)
|
udp|tcp)
|
||||||
local _dns=$(get_first_dns remote_dns_udp_server 53 | sed 's/#/:/g')
|
local _proto="$remote_dns_protocol"
|
||||||
|
local _dns=$(get_first_dns remote_dns_${_proto}_server 53 | sed 's/#/:/g')
|
||||||
local _dns_address=$(echo ${_dns} | awk -F ':' '{print $1}')
|
local _dns_address=$(echo ${_dns} | awk -F ':' '{print $1}')
|
||||||
local _dns_port=$(echo ${_dns} | awk -F ':' '{print $2}')
|
local _dns_port=$(echo ${_dns} | awk -F ':' '{print $2}')
|
||||||
json_add_string "remote_dns_udp_server" "${_dns_address}"
|
json_add_string "remote_dns_${_proto}_server" "${_dns_address}"
|
||||||
json_add_string "remote_dns_udp_port" "${_dns_port}"
|
json_add_string "remote_dns_${_proto}_port" "${_dns_port}"
|
||||||
;;
|
;;
|
||||||
tcp|tcp+doh)
|
doh)
|
||||||
local _dns=$(get_first_dns remote_dns_tcp_server 53 | sed 's/#/:/g')
|
local _doh_url _doh_host _doh_port _doh_bootstrap
|
||||||
local _dns_address=$(echo ${_dns} | awk -F ':' '{print $1}')
|
parse_doh "$remote_dns_doh" _doh_url _doh_host _doh_port _doh_bootstrap
|
||||||
local _dns_port=$(echo ${_dns} | awk -F ':' '{print $2}')
|
[ -n "$_doh_bootstrap" ] && json_add_string "remote_dns_doh_ip" "${_doh_bootstrap}"
|
||||||
json_add_string "remote_dns_tcp_server" "${_dns_address}"
|
json_add_string "remote_dns_doh_port" "${_doh_port}"
|
||||||
json_add_string "remote_dns_tcp_port" "${_dns_port}"
|
json_add_string "remote_dns_doh_url" "${_doh_url}"
|
||||||
[ "$remote_dns_protocol" = "tcp+doh" ] && {
|
json_add_string "remote_dns_doh_host" "${_doh_host}"
|
||||||
local _doh_url _doh_host _doh_port _doh_bootstrap
|
|
||||||
parse_doh "$remote_dns_doh" _doh_url _doh_host _doh_port _doh_bootstrap
|
|
||||||
[ -n "$_doh_bootstrap" ] && json_add_string "remote_dns_doh_ip" "${_doh_bootstrap}"
|
|
||||||
json_add_string "remote_dns_doh_port" "${_doh_port}"
|
|
||||||
json_add_string "remote_dns_doh_url" "${_doh_url}"
|
|
||||||
json_add_string "remote_dns_doh_host" "${_doh_host}"
|
|
||||||
}
|
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
json_add_string "loglevel" "$loglevel"
|
json_add_string "loglevel" "$loglevel"
|
||||||
[ -n "$no_run" ] && json_add_string "no_run" "1"
|
[ -n "$no_run" ] && json_add_string "no_run" "1"
|
||||||
local _json_arg="$(json_dump)"
|
local _json_arg="$(json_dump)"
|
||||||
@ -754,7 +750,7 @@ run_redir() {
|
|||||||
local v2ray_dns_mode=$(config_t_get global v2ray_dns_mode tcp)
|
local v2ray_dns_mode=$(config_t_get global v2ray_dns_mode tcp)
|
||||||
[ "${DNS_MODE}" != "sing-box" ] && [ "$protocol" = "_shunt" ] && {
|
[ "${DNS_MODE}" != "sing-box" ] && [ "$protocol" = "_shunt" ] && {
|
||||||
DNS_MODE="sing-box"
|
DNS_MODE="sing-box"
|
||||||
[ "$v2ray_dns_mode" != "udp" ] && [ "$v2ray_dns_mode" != "tcp" ] && v2ray_dns_mode="tcp"
|
[ "$v2ray_dns_mode" = "tcp+doh" ] && v2ray_dns_mode="tcp"
|
||||||
}
|
}
|
||||||
|
|
||||||
[ "$protocol" = "_shunt" ] && {
|
[ "$protocol" = "_shunt" ] && {
|
||||||
@ -793,7 +789,7 @@ run_redir() {
|
|||||||
resolve_dns_log="Sing-Box DNS(127.0.0.1#${resolve_dns_port}) -> ${_proto}://${REMOTE_DNS}"
|
resolve_dns_log="Sing-Box DNS(127.0.0.1#${resolve_dns_port}) -> ${_proto}://${REMOTE_DNS}"
|
||||||
;;
|
;;
|
||||||
doh|http3)
|
doh|http3)
|
||||||
remote_dns_doh=$(config_t_get global remote_dns_doh "https://1.1.1.1/dns-query")
|
local remote_dns_doh=$(config_t_get global remote_dns_doh "https://1.1.1.1/dns-query")
|
||||||
_args="${_args} remote_dns_doh=${remote_dns_doh}"
|
_args="${_args} remote_dns_doh=${remote_dns_doh}"
|
||||||
resolve_dns_log="Sing-Box DNS(127.0.0.1#${resolve_dns_port}) -> ${remote_dns_doh}"
|
resolve_dns_log="Sing-Box DNS(127.0.0.1#${resolve_dns_port}) -> ${remote_dns_doh}"
|
||||||
;;
|
;;
|
||||||
@ -833,8 +829,10 @@ run_redir() {
|
|||||||
local v2ray_dns_mode=$(config_t_get global v2ray_dns_mode tcp)
|
local v2ray_dns_mode=$(config_t_get global v2ray_dns_mode tcp)
|
||||||
[ "${DNS_MODE}" != "xray" ] && [ "$protocol" = "_shunt" ] && {
|
[ "${DNS_MODE}" != "xray" ] && [ "$protocol" = "_shunt" ] && {
|
||||||
DNS_MODE="xray"
|
DNS_MODE="xray"
|
||||||
[ "$v2ray_dns_mode" != "udp" ] && [ "$v2ray_dns_mode" != "tcp" ] && v2ray_dns_mode="tcp"
|
[ "$v2ray_dns_mode" = "http3" ] && v2ray_dns_mode="tcp"
|
||||||
}
|
}
|
||||||
|
#兼容旧模式,择机移除
|
||||||
|
[ "$v2ray_dns_mode" = "tcp+doh" ] && v2ray_dns_mode="tcp"
|
||||||
|
|
||||||
[ "$protocol" = "_shunt" ] && {
|
[ "$protocol" = "_shunt" ] && {
|
||||||
local geoip_path="${V2RAY_LOCATION_ASSET%*/}/geoip.dat"
|
local geoip_path="${V2RAY_LOCATION_ASSET%*/}/geoip.dat"
|
||||||
@ -866,19 +864,15 @@ run_redir() {
|
|||||||
|
|
||||||
_args="${_args} remote_dns_protocol=${v2ray_dns_mode}"
|
_args="${_args} remote_dns_protocol=${v2ray_dns_mode}"
|
||||||
case "$v2ray_dns_mode" in
|
case "$v2ray_dns_mode" in
|
||||||
udp)
|
udp|tcp)
|
||||||
_args="${_args} remote_dns_udp_server=${REMOTE_DNS}"
|
local _proto="$v2ray_dns_mode"
|
||||||
resolve_dns_log="Xray DNS(127.0.0.1#${resolve_dns_port}) -> udp://${REMOTE_DNS}"
|
_args="${_args} remote_dns_${_proto}_server=${REMOTE_DNS}"
|
||||||
|
resolve_dns_log="Xray DNS(127.0.0.1#${resolve_dns_port}) -> ${_proto}://${REMOTE_DNS}"
|
||||||
;;
|
;;
|
||||||
tcp|tcp+doh)
|
doh)
|
||||||
_args="${_args} remote_dns_tcp_server=${REMOTE_DNS}"
|
local remote_dns_doh=$(config_t_get global remote_dns_doh "https://1.1.1.1/dns-query")
|
||||||
if [ "$v2ray_dns_mode" = "tcp+doh" ]; then
|
_args="${_args} remote_dns_doh=${remote_dns_doh}"
|
||||||
remote_dns_doh=$(config_t_get global remote_dns_doh "https://1.1.1.1/dns-query")
|
resolve_dns_log="Xray DNS(127.0.0.1#${resolve_dns_port}) -> ${remote_dns_doh}"
|
||||||
_args="${_args} remote_dns_doh=${remote_dns_doh}"
|
|
||||||
resolve_dns_log="Xray DNS(127.0.0.1#${resolve_dns_port}) -> (${remote_dns_doh})(A/AAAA) + tcp://${REMOTE_DNS}"
|
|
||||||
else
|
|
||||||
resolve_dns_log="Xray DNS(127.0.0.1#${resolve_dns_port}) -> tcp://${REMOTE_DNS}"
|
|
||||||
fi
|
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
local remote_fakedns=$(config_t_get global remote_fakedns 0)
|
local remote_fakedns=$(config_t_get global remote_fakedns 0)
|
||||||
@ -1348,7 +1342,7 @@ start_dns() {
|
|||||||
echolog " - Sing-Box DNS(${TUN_DNS}) -> ${_proto}://${REMOTE_DNS}"
|
echolog " - Sing-Box DNS(${TUN_DNS}) -> ${_proto}://${REMOTE_DNS}"
|
||||||
;;
|
;;
|
||||||
doh|http3)
|
doh|http3)
|
||||||
remote_dns_doh=$(config_t_get global remote_dns_doh "https://1.1.1.1/dns-query")
|
local remote_dns_doh=$(config_t_get global remote_dns_doh "https://1.1.1.1/dns-query")
|
||||||
_args="${_args} remote_dns_doh=${remote_dns_doh}"
|
_args="${_args} remote_dns_doh=${remote_dns_doh}"
|
||||||
echolog " - Sing-Box DNS(${TUN_DNS}) -> ${remote_dns_doh}"
|
echolog " - Sing-Box DNS(${TUN_DNS}) -> ${remote_dns_doh}"
|
||||||
|
|
||||||
@ -1376,26 +1370,24 @@ start_dns() {
|
|||||||
[ -n "${_remote_dns_client_ip}" ] && _args="${_args} remote_dns_client_ip=${_remote_dns_client_ip}"
|
[ -n "${_remote_dns_client_ip}" ] && _args="${_args} remote_dns_client_ip=${_remote_dns_client_ip}"
|
||||||
TCP_PROXY_DNS=1
|
TCP_PROXY_DNS=1
|
||||||
local v2ray_dns_mode=$(config_t_get global v2ray_dns_mode tcp)
|
local v2ray_dns_mode=$(config_t_get global v2ray_dns_mode tcp)
|
||||||
|
#兼容旧模式,择机移除
|
||||||
|
[ "$v2ray_dns_mode" = "tcp+doh" ] && v2ray_dns_mode="tcp"
|
||||||
_args="${_args} dns_listen_port=${NEXT_DNS_LISTEN_PORT}"
|
_args="${_args} dns_listen_port=${NEXT_DNS_LISTEN_PORT}"
|
||||||
_args="${_args} remote_dns_protocol=${v2ray_dns_mode}"
|
_args="${_args} remote_dns_protocol=${v2ray_dns_mode}"
|
||||||
case "$v2ray_dns_mode" in
|
case "$v2ray_dns_mode" in
|
||||||
udp)
|
udp|tcp)
|
||||||
_args="${_args} remote_dns_udp_server=${REMOTE_DNS}"
|
local _proto="$v2ray_dns_mode"
|
||||||
echolog " - Xray DNS(${TUN_DNS}) -> udp://${REMOTE_DNS}"
|
_args="${_args} remote_dns_${_proto}_server=${REMOTE_DNS}"
|
||||||
|
echolog " - Xray DNS(${TUN_DNS}) -> ${_proto}://${REMOTE_DNS}"
|
||||||
;;
|
;;
|
||||||
tcp|tcp+doh)
|
doh)
|
||||||
_args="${_args} remote_dns_tcp_server=${REMOTE_DNS}"
|
local remote_dns_doh=$(config_t_get global remote_dns_doh "https://1.1.1.1/dns-query")
|
||||||
if [ "$v2ray_dns_mode" = "tcp+doh" ]; then
|
_args="${_args} remote_dns_doh=${remote_dns_doh}"
|
||||||
remote_dns_doh=$(config_t_get global remote_dns_doh "https://1.1.1.1/dns-query")
|
echolog " - Xray DNS(${TUN_DNS}) -> ${remote_dns_doh}"
|
||||||
_args="${_args} remote_dns_doh=${remote_dns_doh}"
|
|
||||||
echolog " - Xray DNS(${TUN_DNS}) -> (${remote_dns_doh})(A/AAAA) + tcp://${REMOTE_DNS}"
|
|
||||||
|
|
||||||
local _doh_url _doh_host _doh_port _doh_bootstrap
|
local _doh_url _doh_host _doh_port _doh_bootstrap
|
||||||
parse_doh "$remote_dns_doh" _doh_url _doh_host _doh_port _doh_bootstrap
|
parse_doh "$remote_dns_doh" _doh_url _doh_host _doh_port _doh_bootstrap
|
||||||
[ -n "${_doh_bootstrap}" ] && REMOTE_DNS="${REMOTE_DNS},${_doh_bootstrap}#${_doh_port}"
|
[ -n "${_doh_bootstrap}" ] && REMOTE_DNS="${REMOTE_DNS},${_doh_bootstrap}#${_doh_port}"
|
||||||
else
|
|
||||||
echolog " - Xray DNS(${TUN_DNS}) -> tcp://${REMOTE_DNS}"
|
|
||||||
fi
|
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
_args="${_args} dns_socks_address=127.0.0.1 dns_socks_port=${tcp_node_socks_port}"
|
_args="${_args} dns_socks_address=127.0.0.1 dns_socks_port=${tcp_node_socks_port}"
|
||||||
@ -1664,12 +1656,14 @@ acl_app() {
|
|||||||
else
|
else
|
||||||
local type=$(echo $(config_n_get $tcp_node type) | tr 'A-Z' 'a-z')
|
local type=$(echo $(config_n_get $tcp_node type) | tr 'A-Z' 'a-z')
|
||||||
local protocol=$(config_n_get $tcp_node protocol)
|
local protocol=$(config_n_get $tcp_node protocol)
|
||||||
|
#兼容旧模式,择机移除
|
||||||
|
[ "$v2ray_dns_mode" = "tcp+doh" ] && v2ray_dns_mode="tcp"
|
||||||
([ "$type" = "sing-box" ] || [ "$type" = "xray" ]) && [ "$protocol" = "_shunt" ] && [ "$type" != "$dns_mode" ] && {
|
([ "$type" = "sing-box" ] || [ "$type" = "xray" ]) && [ "$protocol" = "_shunt" ] && [ "$type" != "$dns_mode" ] && {
|
||||||
dns_mode=$type
|
dns_mode=$type
|
||||||
([ "$type" = "xray" ] || [ "$type" = "sing-box" ]) && [ "$v2ray_dns_mode" != "udp" ] && [ "$v2ray_dns_mode" != "tcp" ] && v2ray_dns_mode="tcp"
|
[ "$type" = "xray" ] && [ "$v2ray_dns_mode" = "http3" ] && v2ray_dns_mode="tcp"
|
||||||
}
|
}
|
||||||
dns_cache_key="${dns_mode}_${remote_dns}_${v2ray_dns_mode:-none}_${remote_dns_client_ip:-0}_${remote_fakedns:-0}"
|
dns_cache_key="${dns_mode}_${remote_dns}_${v2ray_dns_mode:-none}_${remote_dns_client_ip:-0}_${remote_fakedns:-0}"
|
||||||
[ "$dns_mode" = "sing-box" ] && [ "$v2ray_dns_mode" != "udp" ] && [ "$v2ray_dns_mode" != "tcp" ] && {
|
([ "$v2ray_dns_mode" = "doh" ] || [ "$v2ray_dns_mode" = "http3" ]) && {
|
||||||
dns_cache_key="${dns_mode}_${remote_dns_doh:-https://1.1.1.1/dns-query}_${v2ray_dns_mode:-doh}_${remote_dns_client_ip:-0}_${remote_fakedns:-0}"
|
dns_cache_key="${dns_mode}_${remote_dns_doh:-https://1.1.1.1/dns-query}_${v2ray_dns_mode:-doh}_${remote_dns_client_ip:-0}_${remote_fakedns:-0}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1940,11 +1940,12 @@ local function update_node(manual)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
if domain_strategy then
|
if domain_strategy then
|
||||||
|
local ds = domain_strategy
|
||||||
if vvv == "sing-box" then
|
if vvv == "sing-box" then
|
||||||
local map = { UseIPv4v6 = "prefer_ipv4", UseIPv6v4 = "prefer_ipv6", UseIPv4 = "ipv4_only", UseIPv6 = "ipv6_only" }
|
local map = { UseIPv4v6 = "prefer_ipv4", UseIPv6v4 = "prefer_ipv6", UseIPv4 = "ipv4_only", UseIPv6 = "ipv6_only" }
|
||||||
domain_strategy = map[domain_strategy] or ""
|
ds = map[ds] or ""
|
||||||
end
|
end
|
||||||
uci:set(appname, cfgid, "domain_strategy", domain_strategy)
|
uci:set(appname, cfgid, "domain_strategy", ds)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
-- 订阅组链式代理
|
-- 订阅组链式代理
|
||||||
|
|||||||
12
luci-app-rtp2httpd/Makefile
Normal file
12
luci-app-rtp2httpd/Makefile
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
# Copyright (C) 2026 ImmortalWrt.org
|
||||||
|
|
||||||
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
|
LUCI_TITLE:=LuCI support for rtp2httpd
|
||||||
|
LUCI_DEPENDS:=+rtp2httpd
|
||||||
|
|
||||||
|
include $(TOPDIR)/feeds/luci/luci.mk
|
||||||
|
|
||||||
|
# call BuildPackage - OpenWrt buildroot signature
|
||||||
@ -0,0 +1,160 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
'require view';
|
||||||
|
'require form';
|
||||||
|
'require tools.widgets as widgets';
|
||||||
|
|
||||||
|
return view.extend({
|
||||||
|
render() {
|
||||||
|
let m, s, o;
|
||||||
|
|
||||||
|
m = new form.Map('rtp2httpd', _('rtp2httpd'),
|
||||||
|
_('Multicast RTP/RTSP to Unicast HTTP stream converter.'));
|
||||||
|
|
||||||
|
s = m.section(form.TypedSection, 'instance');
|
||||||
|
s.anonymous = true;
|
||||||
|
s.addremove = true;
|
||||||
|
s.addbtntitle = _('Add instance');
|
||||||
|
|
||||||
|
s.tab('general', _('General settings'));
|
||||||
|
|
||||||
|
o = s.taboption('general', form.Flag, 'enabled', _('Enable'));
|
||||||
|
|
||||||
|
o = s.taboption('general', form.DynamicList, 'listen', _('Bind address'));
|
||||||
|
o.datatype = 'ipaddrport(1)';
|
||||||
|
o.rmempty = false;
|
||||||
|
|
||||||
|
o = s.taboption('general', form.ListValue, 'verbose', _('Log level'),
|
||||||
|
_('syslog severity level.'));
|
||||||
|
o.value('0', _('Fatal'));
|
||||||
|
o.value('1', _('Error'));
|
||||||
|
o.value('2', _('Warn'));
|
||||||
|
o.value('3', _('Info'));
|
||||||
|
o.value('4', _('Debug'));
|
||||||
|
o.default = '1';
|
||||||
|
|
||||||
|
o = s.taboption('general', widgets.DeviceSelect, 'upstream_interface', _('Upstream interface'),
|
||||||
|
_('Default interface for all upstream traffic.'));
|
||||||
|
o.noaliases = true;
|
||||||
|
o.nocreate = true;
|
||||||
|
o.optional = true;
|
||||||
|
|
||||||
|
o = s.taboption('general', form.Value, 'mcast_rejoin_interval', _('Multicast rejoin interval'),
|
||||||
|
_('Periodic multicast rejoin interval in seconds.'));
|
||||||
|
o.datatype = 'and(uinteger, range(0,86400))';
|
||||||
|
o.placeholder = '0';
|
||||||
|
|
||||||
|
o = s.taboption('general', form.Value, 'maxclients', _('Max clients'),
|
||||||
|
_('Serve max N requests simultaneously.'));
|
||||||
|
o.datatype = 'and(uinteger, range(1,5000))';
|
||||||
|
o.placeholder = '5';
|
||||||
|
|
||||||
|
o = s.taboption('general', form.Value, 'workers', _('Worker processes'),
|
||||||
|
_('Number of worker processes with <code>SO_REUSEPORT</code>.'));
|
||||||
|
o.datatype = 'and(uinteger, range(1,64))';
|
||||||
|
o.placeholder = '1';
|
||||||
|
|
||||||
|
o = s.taboption('general', form.Flag, 'zerocopy_on_send', _('Zero-copy on send'),
|
||||||
|
_('Enable zero-copy send with <code>MSG_ZEROCOPY</code> for better performance.'));
|
||||||
|
|
||||||
|
s.tab('web', _('Web settings'));
|
||||||
|
|
||||||
|
o = s.taboption('web', form.Value, 'hostname', _('Hostname'),
|
||||||
|
_('HTTP host header will be checked and must match this value to allow access when configured.'));
|
||||||
|
o.datatype = 'host';
|
||||||
|
|
||||||
|
o = s.taboption('web', form.Value, 'r2h_token', _('R2H token'),
|
||||||
|
_('Authentication token for HTTP requests.'));
|
||||||
|
|
||||||
|
o = s.taboption('web', form.Value, 'cors_allow_origin', _('CORS allowed origin'),
|
||||||
|
_('Set <code>Access-Control-Allow-Origin</code> header to enable CORS.'));
|
||||||
|
|
||||||
|
o = s.taboption('web', form.Flag, 'xff', _('Accept X-Forwarded-For header'),
|
||||||
|
_('Use <code>X-Forwarded-For</code> header as client address, also accepts <code>X-Forwarded-Host</code> / <code>X-Forwarded-Proto</code> headers as the base URL for M3U playlist conversion.'));
|
||||||
|
|
||||||
|
o = s.taboption('web', form.Value, 'external_m3u', _('External M3U playlist URL'),
|
||||||
|
_('External M3U playlist URL (file://, http://, https:// supported).'));
|
||||||
|
|
||||||
|
o = s.taboption('web', form.Value, 'external_m3u_update_interval', _('External M3U playlist update interval'),
|
||||||
|
_('External M3U playlist update interval in seconds.'));
|
||||||
|
o.datatype = 'uinteger';
|
||||||
|
o.placeholder = '7200';
|
||||||
|
o.depends({ 'external_m3u': '', '!reverse': true });
|
||||||
|
|
||||||
|
o = s.taboption('web', form.Value, 'player_page_path', _('Player page path'),
|
||||||
|
_('HTTP path for player UI.'));
|
||||||
|
o.placeholder = '/player';
|
||||||
|
|
||||||
|
o = s.taboption('web', form.Value, 'status_page_path', _('Status page path'),
|
||||||
|
_('HTTP path for status UI.'));
|
||||||
|
o.placeholder = '/status';
|
||||||
|
|
||||||
|
s.tab('advanced', _('Advanced settings'));
|
||||||
|
|
||||||
|
o = s.taboption('advanced', form.Flag, 'noudpxy', _('Disable udpxy compatibility'));
|
||||||
|
|
||||||
|
o = s.taboption('advanced', widgets.DeviceSelect, 'upstream_interface_fcc', _('Upstream FCC interface'),
|
||||||
|
_('Interface for FCC unicast traffic (overrides upstream interface).'));
|
||||||
|
o.noaliases = true;
|
||||||
|
o.nocreate = true;
|
||||||
|
o.optional = true;
|
||||||
|
|
||||||
|
o = s.taboption('advanced', widgets.DeviceSelect, 'upstream_interface_http', _('Upstream HTTP interface'),
|
||||||
|
_('Interface for HTTP proxy upstream traffic (overrides upstream interface).'));
|
||||||
|
o.noaliases = true;
|
||||||
|
o.nocreate = true;
|
||||||
|
o.optional = true;
|
||||||
|
|
||||||
|
o = s.taboption('advanced', widgets.DeviceSelect, 'upstream_interface_multicast', _('Upstream multicast interface'),
|
||||||
|
_('Interface for multicast traffic (overrides upstream interface).'));
|
||||||
|
o.noaliases = true;
|
||||||
|
o.nocreate = true;
|
||||||
|
o.optional = true;
|
||||||
|
|
||||||
|
o = s.taboption('advanced', widgets.DeviceSelect, 'upstream_interface_rtsp', _('Upstream RTSP interface'),
|
||||||
|
_('Interface for RTSP unicast traffic (overrides upstream interface).'));
|
||||||
|
o.noaliases = true;
|
||||||
|
o.nocreate = true;
|
||||||
|
o.optional = true;
|
||||||
|
|
||||||
|
o = s.taboption('advanced', form.Value, 'fcc_listen_port_range', _('FCC listen port range'),
|
||||||
|
_('Restrict FCC UDP listen sockets to specific ports.'));
|
||||||
|
o.datatype = 'or(port,portrange)';
|
||||||
|
|
||||||
|
o = s.taboption('advanced', form.Value, 'http_proxy_user_agent', _('HTTP proxy User-Agent'),
|
||||||
|
_('Override the User-Agent header sent to upstream HTTP proxy requests.'));
|
||||||
|
|
||||||
|
o = s.taboption('advanced', form.Value, 'rtsp_stun_server', _('RTSP STUN server'),
|
||||||
|
_('STUN server for RTSP NAT traversal.'));
|
||||||
|
o.datatype = 'or(host,hostport)';
|
||||||
|
|
||||||
|
o = s.taboption('advanced', form.Value, 'rtsp_user_agent', _('RTSP User-Agent'),
|
||||||
|
_('User-Agent header for upstream RTSP requests.'));
|
||||||
|
|
||||||
|
o = s.taboption('advanced', form.Value, 'buffer_pool_max_size', _('Buffer pool max size'),
|
||||||
|
_('Maximum number of buffers in zero-copy pool.'));
|
||||||
|
o.datatype = 'and(uinteger, range(1024,1048576))';
|
||||||
|
o.placeholder = '16384';
|
||||||
|
|
||||||
|
o = s.taboption('advanced', form.Value, 'udp_rcvbuf_size', _('UDP receive buffer size'),
|
||||||
|
_('UDP socket receive buffer size (in bytes) for multicast/FCC/RTSP.'));
|
||||||
|
o.datatype = 'and(uinteger, range(65536,16777216))';
|
||||||
|
o.placeholder = '524288';
|
||||||
|
|
||||||
|
o = s.taboption('advanced', form.Flag, 'video_snapshot', _('Video snapshot'),
|
||||||
|
_('Enable video snapshot feature. Clients can request snapshots with <code>snapshot=1</code> query parameter.'));
|
||||||
|
|
||||||
|
o = s.taboption('advanced', form.Value, 'ffmpeg_path', _('FFmpeg path'),
|
||||||
|
_('Path to FFmpeg executable.'));
|
||||||
|
o.placeholder = 'ffmpeg';
|
||||||
|
o.depends('video_snapshot', '1');
|
||||||
|
|
||||||
|
o = s.taboption('advanced', form.Value, 'ffmpeg_args', _('FFmpeg arguments'),
|
||||||
|
_('Additional FFmpeg arguments for snapshot generation.'));
|
||||||
|
o.placeholder = '-hwaccel none';
|
||||||
|
o.depends('video_snapshot', '1');
|
||||||
|
|
||||||
|
return m.render();
|
||||||
|
}
|
||||||
|
});
|
||||||
288
luci-app-rtp2httpd/po/templates/rtp2httpd.pot
Normal file
288
luci-app-rtp2httpd/po/templates/rtp2httpd.pot
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
msgid ""
|
||||||
|
msgstr "Content-Type: text/plain; charset=UTF-8"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:73
|
||||||
|
msgid "Accept X-Forwarded-For header"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:18
|
||||||
|
msgid "Add instance"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:154
|
||||||
|
msgid "Additional FFmpeg arguments for snapshot generation."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:93
|
||||||
|
msgid "Advanced settings"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:68
|
||||||
|
msgid "Authentication token for HTTP requests."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:24
|
||||||
|
msgid "Bind address"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:135
|
||||||
|
msgid "Buffer pool max size"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:70
|
||||||
|
msgid "CORS allowed origin"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:34
|
||||||
|
msgid "Debug"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:38
|
||||||
|
msgid "Default interface for all upstream traffic."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:95
|
||||||
|
msgid "Disable udpxy compatibility"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:22
|
||||||
|
msgid "Enable"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:146
|
||||||
|
msgid ""
|
||||||
|
"Enable video snapshot feature. Clients can request snapshots with "
|
||||||
|
"<code>snapshot=1</code> query parameter."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:59
|
||||||
|
msgid ""
|
||||||
|
"Enable zero-copy send with <code>MSG_ZEROCOPY</code> for better performance."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:31
|
||||||
|
msgid "Error"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:76
|
||||||
|
msgid "External M3U playlist URL"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:77
|
||||||
|
msgid "External M3U playlist URL (file://, http://, https:// supported)."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:79
|
||||||
|
msgid "External M3U playlist update interval"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:80
|
||||||
|
msgid "External M3U playlist update interval in seconds."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:121
|
||||||
|
msgid "FCC listen port range"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:153
|
||||||
|
msgid "FFmpeg arguments"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:148
|
||||||
|
msgid "FFmpeg path"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:30
|
||||||
|
msgid "Fatal"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:20
|
||||||
|
msgid "General settings"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/root/usr/share/rpcd/acl.d/luci-app-rtp2httpd.json:3
|
||||||
|
msgid "Grant UCI access for luci-app-rtp2httpd"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:64
|
||||||
|
msgid ""
|
||||||
|
"HTTP host header will be checked and must match this value to allow access "
|
||||||
|
"when configured."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:86
|
||||||
|
msgid "HTTP path for player UI."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:90
|
||||||
|
msgid "HTTP path for status UI."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:125
|
||||||
|
msgid "HTTP proxy User-Agent"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:63
|
||||||
|
msgid "Hostname"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:33
|
||||||
|
msgid "Info"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:98
|
||||||
|
msgid "Interface for FCC unicast traffic (overrides upstream interface)."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:104
|
||||||
|
msgid ""
|
||||||
|
"Interface for HTTP proxy upstream traffic (overrides upstream interface)."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:116
|
||||||
|
msgid "Interface for RTSP unicast traffic (overrides upstream interface)."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:110
|
||||||
|
msgid "Interface for multicast traffic (overrides upstream interface)."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:28
|
||||||
|
msgid "Log level"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:48
|
||||||
|
msgid "Max clients"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:136
|
||||||
|
msgid "Maximum number of buffers in zero-copy pool."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:13
|
||||||
|
msgid "Multicast RTP/RTSP to Unicast HTTP stream converter."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:43
|
||||||
|
msgid "Multicast rejoin interval"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:54
|
||||||
|
msgid "Number of worker processes with <code>SO_REUSEPORT</code>."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:126
|
||||||
|
msgid "Override the User-Agent header sent to upstream HTTP proxy requests."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:149
|
||||||
|
msgid "Path to FFmpeg executable."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:44
|
||||||
|
msgid "Periodic multicast rejoin interval in seconds."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:85
|
||||||
|
msgid "Player page path"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:67
|
||||||
|
msgid "R2H token"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:128
|
||||||
|
msgid "RTSP STUN server"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:132
|
||||||
|
msgid "RTSP User-Agent"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:122
|
||||||
|
msgid "Restrict FCC UDP listen sockets to specific ports."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:129
|
||||||
|
msgid "STUN server for RTSP NAT traversal."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:49
|
||||||
|
msgid "Serve max N requests simultaneously."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:71
|
||||||
|
msgid "Set <code>Access-Control-Allow-Origin</code> header to enable CORS."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:89
|
||||||
|
msgid "Status page path"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:140
|
||||||
|
msgid "UDP receive buffer size"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:141
|
||||||
|
msgid "UDP socket receive buffer size (in bytes) for multicast/FCC/RTSP."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:97
|
||||||
|
msgid "Upstream FCC interface"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:103
|
||||||
|
msgid "Upstream HTTP interface"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:115
|
||||||
|
msgid "Upstream RTSP interface"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:37
|
||||||
|
msgid "Upstream interface"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:109
|
||||||
|
msgid "Upstream multicast interface"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:74
|
||||||
|
msgid ""
|
||||||
|
"Use <code>X-Forwarded-For</code> header as client address, also accepts "
|
||||||
|
"<code>X-Forwarded-Host</code> / <code>X-Forwarded-Proto</code> headers as "
|
||||||
|
"the base URL for M3U playlist conversion."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:133
|
||||||
|
msgid "User-Agent header for upstream RTSP requests."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:145
|
||||||
|
msgid "Video snapshot"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:32
|
||||||
|
msgid "Warn"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:61
|
||||||
|
msgid "Web settings"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:53
|
||||||
|
msgid "Worker processes"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:58
|
||||||
|
msgid "Zero-copy on send"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:12
|
||||||
|
#: applications/luci-app-rtp2httpd/root/usr/share/luci/menu.d/luci-app-rtp2httpd.json:3
|
||||||
|
msgid "rtp2httpd"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:29
|
||||||
|
msgid "syslog severity level."
|
||||||
|
msgstr ""
|
||||||
1
luci-app-rtp2httpd/po/zh-cn
Symbolic link
1
luci-app-rtp2httpd/po/zh-cn
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
zh_Hans
|
||||||
299
luci-app-rtp2httpd/po/zh_Hans/rtp2httpd.po
Normal file
299
luci-app-rtp2httpd/po/zh_Hans/rtp2httpd.po
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"Last-Translator: Automatically generated\n"
|
||||||
|
"Language-Team: none\n"
|
||||||
|
"Language: zh-Hans\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:73
|
||||||
|
msgid "Accept X-Forwarded-For header"
|
||||||
|
msgstr "接受 X-Forwarded-For 头"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:18
|
||||||
|
msgid "Add instance"
|
||||||
|
msgstr "添加实例"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:154
|
||||||
|
msgid "Additional FFmpeg arguments for snapshot generation."
|
||||||
|
msgstr "用于快照生成的额外 FFmpeg 参数。"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:93
|
||||||
|
msgid "Advanced settings"
|
||||||
|
msgstr "高级设置"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:68
|
||||||
|
msgid "Authentication token for HTTP requests."
|
||||||
|
msgstr "HTTP 请求的认证令牌。"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:24
|
||||||
|
msgid "Bind address"
|
||||||
|
msgstr "绑定地址"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:135
|
||||||
|
msgid "Buffer pool max size"
|
||||||
|
msgstr "缓冲池最大容量"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:70
|
||||||
|
msgid "CORS allowed origin"
|
||||||
|
msgstr "CORS 允许来源"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:34
|
||||||
|
msgid "Debug"
|
||||||
|
msgstr "调试"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:38
|
||||||
|
msgid "Default interface for all upstream traffic."
|
||||||
|
msgstr "所有上游流量的默认接口。"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:95
|
||||||
|
msgid "Disable udpxy compatibility"
|
||||||
|
msgstr "禁用 udpxy 兼容性"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:22
|
||||||
|
msgid "Enable"
|
||||||
|
msgstr "启用"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:146
|
||||||
|
msgid ""
|
||||||
|
"Enable video snapshot feature. Clients can request snapshots with "
|
||||||
|
"<code>snapshot=1</code> query parameter."
|
||||||
|
msgstr ""
|
||||||
|
"启用视频快照功能。客户端可以通过 <code>snapshot=1</code> 查询参数请求快照。"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:59
|
||||||
|
msgid ""
|
||||||
|
"Enable zero-copy send with <code>MSG_ZEROCOPY</code> for better performance."
|
||||||
|
msgstr "启用 <code>MSG_ZEROCOPY</code> 零拷贝发送以提高性能。"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:31
|
||||||
|
msgid "Error"
|
||||||
|
msgstr "错误"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:76
|
||||||
|
msgid "External M3U playlist URL"
|
||||||
|
msgstr "外部 M3U 播放列表 URL"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:77
|
||||||
|
msgid "External M3U playlist URL (file://, http://, https:// supported)."
|
||||||
|
msgstr "外部 M3U 播放列表 URL(支持 file://、http://、https://)。"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:79
|
||||||
|
msgid "External M3U playlist update interval"
|
||||||
|
msgstr "外部 M3U 播放列表更新间隔"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:80
|
||||||
|
msgid "External M3U playlist update interval in seconds."
|
||||||
|
msgstr "外部 M3U 播放列表更新间隔(秒)。"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:121
|
||||||
|
msgid "FCC listen port range"
|
||||||
|
msgstr "FCC 监听端口范围"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:153
|
||||||
|
msgid "FFmpeg arguments"
|
||||||
|
msgstr "FFmpeg 参数"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:148
|
||||||
|
msgid "FFmpeg path"
|
||||||
|
msgstr "FFmpeg 路径"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:30
|
||||||
|
msgid "Fatal"
|
||||||
|
msgstr "致命"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:20
|
||||||
|
msgid "General settings"
|
||||||
|
msgstr "常规设置"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/root/usr/share/rpcd/acl.d/luci-app-rtp2httpd.json:3
|
||||||
|
msgid "Grant UCI access for luci-app-rtp2httpd"
|
||||||
|
msgstr "授予 luci-app-rtp2httpd 访问 UCI 配置的权限"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:64
|
||||||
|
msgid ""
|
||||||
|
"HTTP host header will be checked and must match this value to allow access "
|
||||||
|
"when configured."
|
||||||
|
msgstr "配置后将检查 HTTP Host 头并且必须匹配此值才允许访问。"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:86
|
||||||
|
msgid "HTTP path for player UI."
|
||||||
|
msgstr "播放器页面 HTTP 路径。"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:90
|
||||||
|
msgid "HTTP path for status UI."
|
||||||
|
msgstr "状态页面 HTTP 路径。"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:125
|
||||||
|
msgid "HTTP proxy User-Agent"
|
||||||
|
msgstr "HTTP 代理 User-Agent"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:63
|
||||||
|
msgid "Hostname"
|
||||||
|
msgstr "主机名"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:33
|
||||||
|
msgid "Info"
|
||||||
|
msgstr "信息"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:98
|
||||||
|
msgid "Interface for FCC unicast traffic (overrides upstream interface)."
|
||||||
|
msgstr "用于 FCC 单播流量的接口(覆盖默认上游接口)。"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:104
|
||||||
|
msgid ""
|
||||||
|
"Interface for HTTP proxy upstream traffic (overrides upstream interface)."
|
||||||
|
msgstr "用于 HTTP 代理上游流量的接口(覆盖默认上游接口)。"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:116
|
||||||
|
msgid "Interface for RTSP unicast traffic (overrides upstream interface)."
|
||||||
|
msgstr "用于 RTSP 单播流量的接口(覆盖默认上游接口)。"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:110
|
||||||
|
msgid "Interface for multicast traffic (overrides upstream interface)."
|
||||||
|
msgstr "用于组播流量的接口(覆盖默认上游接口)。"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:28
|
||||||
|
msgid "Log level"
|
||||||
|
msgstr "日志级别"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:48
|
||||||
|
msgid "Max clients"
|
||||||
|
msgstr "最大客户端数"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:136
|
||||||
|
msgid "Maximum number of buffers in zero-copy pool."
|
||||||
|
msgstr "零拷贝池中缓冲区的最大数量。"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:13
|
||||||
|
msgid "Multicast RTP/RTSP to Unicast HTTP stream converter."
|
||||||
|
msgstr "组播 RTP/RTSP 到单播 HTTP 流转换器。"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:43
|
||||||
|
msgid "Multicast rejoin interval"
|
||||||
|
msgstr "组播重新加入间隔"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:54
|
||||||
|
msgid "Number of worker processes with <code>SO_REUSEPORT</code>."
|
||||||
|
msgstr "使用 <code>SO_REUSEPORT</code> 端口复用的工作进程数量。"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:126
|
||||||
|
msgid "Override the User-Agent header sent to upstream HTTP proxy requests."
|
||||||
|
msgstr "覆盖发送到上游 HTTP 代理请求的 User-Agent 头。"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:149
|
||||||
|
msgid "Path to FFmpeg executable."
|
||||||
|
msgstr "FFmpeg 可执行文件路径。"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:44
|
||||||
|
msgid "Periodic multicast rejoin interval in seconds."
|
||||||
|
msgstr "周期性组播重新加入间隔(秒)。"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:85
|
||||||
|
msgid "Player page path"
|
||||||
|
msgstr "播放器页面路径"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:67
|
||||||
|
msgid "R2H token"
|
||||||
|
msgstr "HTTP 请求认证令牌"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:128
|
||||||
|
msgid "RTSP STUN server"
|
||||||
|
msgstr "RTSP STUN 服务器"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:132
|
||||||
|
msgid "RTSP User-Agent"
|
||||||
|
msgstr "RTSP User-Agent"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:122
|
||||||
|
msgid "Restrict FCC UDP listen sockets to specific ports."
|
||||||
|
msgstr "限制 FCC UDP 监听套接字到特定端口。"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:129
|
||||||
|
msgid "STUN server for RTSP NAT traversal."
|
||||||
|
msgstr "用于 RTSP NAT 穿透的 STUN 服务器。"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:49
|
||||||
|
msgid "Serve max N requests simultaneously."
|
||||||
|
msgstr "最多同时处理 N 个请求。"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:71
|
||||||
|
msgid "Set <code>Access-Control-Allow-Origin</code> header to enable CORS."
|
||||||
|
msgstr "设置 <code>Access-Control-Allow-Origin</code> 头以启用 CORS。"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:89
|
||||||
|
msgid "Status page path"
|
||||||
|
msgstr "状态页面路径"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:140
|
||||||
|
msgid "UDP receive buffer size"
|
||||||
|
msgstr "UDP 接收缓冲区大小"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:141
|
||||||
|
msgid "UDP socket receive buffer size (in bytes) for multicast/FCC/RTSP."
|
||||||
|
msgstr "用于组播/FCC/RTSP 的 UDP 套接字接收缓冲区大小(字节)。"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:97
|
||||||
|
msgid "Upstream FCC interface"
|
||||||
|
msgstr "上游 FCC 接口"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:103
|
||||||
|
msgid "Upstream HTTP interface"
|
||||||
|
msgstr "上游 HTTP 接口"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:115
|
||||||
|
msgid "Upstream RTSP interface"
|
||||||
|
msgstr "上游 RTSP 接口"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:37
|
||||||
|
msgid "Upstream interface"
|
||||||
|
msgstr "上游接口"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:109
|
||||||
|
msgid "Upstream multicast interface"
|
||||||
|
msgstr "上游组播接口"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:74
|
||||||
|
msgid ""
|
||||||
|
"Use <code>X-Forwarded-For</code> header as client address, also accepts "
|
||||||
|
"<code>X-Forwarded-Host</code> / <code>X-Forwarded-Proto</code> headers as "
|
||||||
|
"the base URL for M3U playlist conversion."
|
||||||
|
msgstr ""
|
||||||
|
"使用 <code>X-Forwarded-For</code> 头作为客户端地址,并接受 <code>X-Forwarded-"
|
||||||
|
"Host</code> / <code>X-Forwarded-Proto</code> 头作为 M3U 播放列表中的地址前"
|
||||||
|
"缀。"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:133
|
||||||
|
msgid "User-Agent header for upstream RTSP requests."
|
||||||
|
msgstr "用于上游 RTSP 请求的 User-Agent 头。"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:145
|
||||||
|
msgid "Video snapshot"
|
||||||
|
msgstr "视频快照"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:32
|
||||||
|
msgid "Warn"
|
||||||
|
msgstr "警告"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:61
|
||||||
|
msgid "Web settings"
|
||||||
|
msgstr "Web 设置"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:53
|
||||||
|
msgid "Worker processes"
|
||||||
|
msgstr "工作进程"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:58
|
||||||
|
msgid "Zero-copy on send"
|
||||||
|
msgstr "零拷贝发送"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:12
|
||||||
|
#: applications/luci-app-rtp2httpd/root/usr/share/luci/menu.d/luci-app-rtp2httpd.json:3
|
||||||
|
msgid "rtp2httpd"
|
||||||
|
msgstr "rtp2httpd"
|
||||||
|
|
||||||
|
#: applications/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js:29
|
||||||
|
msgid "syslog severity level."
|
||||||
|
msgstr "系统日志级别。"
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"admin/services/rtp2httpd": {
|
||||||
|
"title": "rtp2httpd",
|
||||||
|
"action": {
|
||||||
|
"type": "view",
|
||||||
|
"path": "rtp2httpd"
|
||||||
|
},
|
||||||
|
"depends": {
|
||||||
|
"acl": [ "luci-app-rtp2httpd" ],
|
||||||
|
"fs": { "/etc/config/rtp2httpd": "file" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"luci-app-rtp2httpd": {
|
||||||
|
"description": "Grant UCI access for luci-app-rtp2httpd",
|
||||||
|
"read": {
|
||||||
|
"uci": [ "rtp2httpd" ]
|
||||||
|
},
|
||||||
|
"write": {
|
||||||
|
"uci": [ "rtp2httpd" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,12 +10,12 @@ include $(INCLUDE_DIR)/kernel.mk
|
|||||||
|
|
||||||
PKG_NAME:=natflow
|
PKG_NAME:=natflow
|
||||||
PKG_VERSION:=20260313
|
PKG_VERSION:=20260313
|
||||||
PKG_RELEASE:=5
|
PKG_RELEASE:=6
|
||||||
|
|
||||||
PKG_SOURCE:=$(PKG_VERSION).tar.xz
|
PKG_SOURCE:=$(PKG_VERSION).tar.xz
|
||||||
PKG_SOURCE_URL:=https://github.com/ptpt52/natflow.git
|
PKG_SOURCE_URL:=https://github.com/ptpt52/natflow.git
|
||||||
PKG_SOURCE_PROTO:=git
|
PKG_SOURCE_PROTO:=git
|
||||||
PKG_SOURCE_VERSION:=0659b45f42a8bc80bc9bf17537aa6ef379a92faf
|
PKG_SOURCE_VERSION:=499d98ccb21b83c8b626d51d96009f2d570b1270
|
||||||
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
||||||
PKG_MAINTAINER:=Chen Minqiang <ptpt52@gmail.com>
|
PKG_MAINTAINER:=Chen Minqiang <ptpt52@gmail.com>
|
||||||
PKG_LICENSE:=GPL-2.0
|
PKG_LICENSE:=GPL-2.0
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user