🎨 Sync 2026-04-28 20:38:51

This commit is contained in:
github-actions[bot] 2026-04-28 20:38:51 +08:00
parent 37afaea9db
commit cabfade482
21 changed files with 2134 additions and 46 deletions

View File

@ -6,8 +6,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=gowebdav
PKG_VERSION:=5.11.6
PKG_RELEASE:=6
PKG_VERSION:=5.11.7
PKG_RELEASE:=7
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/hacdias/webdav/tar.gz/v$(PKG_VERSION)?

View File

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

View File

@ -2,6 +2,16 @@ listen_addrs_ipv6 = ["::1"]
listen_port = 5335
prometheus_listen_addr = "[::]:9000"
[[zones]]
zone = "lan"
zone_type = "Primary"
[zones.stores]
type = "sqlite"
zone_path = "/etc/hickory-dns/lan.zone"
journal_path = "/var/lib/hickory-dns/lan.jrnl"
allow_update = true
[[zones]]
zone = "."
zone_type = "External"

View File

@ -0,0 +1,11 @@
$TTL 60
@ IN SOA ns.lan. admin.lan. (
1 ; Serial
3600 ; Refresh
600 ; Retry
86400 ; Expire
60 ) ; Negative caching TTL
IN NS ns.lan.
ns IN AAAA ::1

1493
ipv6-neigh/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

24
ipv6-neigh/Cargo.toml Normal file
View File

@ -0,0 +1,24 @@
[package]
name = "ip-neigh"
version = "0.1.0"
edition = "2024"
[workspace]
[dependencies]
rtnetlink = { git = "https://github.com/rust-netlink/rtnetlink.git", rev = "515471f" }
netlink-sys = { version = "0.8" }
tokio = { version = "1.0.1", features = ["full"]}
futures = "0.3.11"
netlink-packet-route = {version = "0.21" }
netlink-packet-core= {version ="0.7"}
clap = { version = "4.0", default-features = false, features = ["cargo", "derive", "help", "std", "suggestions"] }
hickory-proto = { git = "https://github.com/hickory-dns/hickory-dns.git", rev = "34a4cca1a707b642052b427938b7d1e8aaa71e3a" }
ubus = "0.1.6"
serde_json = "1.0.108"
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
strip = true

51
ipv6-neigh/Makefile Normal file
View File

@ -0,0 +1,51 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=ipv6-neigh
PKG_VERSION:=0.1.0
PKG_RELEASE:=1
PKG_BUILD_DEPENDS:=rust/host
PKG_BUILD_PARALLEL:=1
RUST_PKG_LOCKED:=0
include $(INCLUDE_DIR)/package.mk
include $(TOPDIR)/feeds/packages/lang/rust/rust-package.mk
define Package/ipv6-neigh
SECTION:=net
CATEGORY:=Network
SUBMENU:=IP Addresses and Names
TITLE:=LAN neighbor DNS updater for hickory-dns
URL:=https://github.com/nicebonn/hickory-dns-openwrt
DEPENDS:=$(RUST_ARCH_DEPENDS) +hickory-dns
endef
define Package/ipv6-neigh/description
Monitors Linux neighbor table (ARP/NDP) via netlink and dynamically
updates DNS records in a local hickory-dns server using RFC 2136
DNS UPDATE over TCP. Maps MAC addresses to hostnames via OpenWrt
DHCP leases (ubus).
endef
define Build/Compile
+$(CARGO_PKG_VARS) \
cargo build -v --profile $(CARGO_PKG_PROFILE) \
--manifest-path "$(PKG_BUILD_DIR)/Cargo.toml" \
--target $(RUSTC_TARGET_ARCH) \
--no-default-features \
$(if $(filter --jobserver%,$(PKG_JOBS)),,-j1) \
$(CARGO_PKG_ARGS)
+$(INSTALL_DIR) $(PKG_INSTALL_DIR)/bin/
+$(INSTALL_BIN) $(PKG_BUILD_DIR)/target/$(RUSTC_TARGET_ARCH)/$(CARGO_PKG_PROFILE)/ip-neigh $(PKG_INSTALL_DIR)/bin/
endef
define Package/ipv6-neigh/install
$(INSTALL_DIR) $(1)/usr/bin/
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/bin/ip-neigh $(1)/usr/bin/ipv6-neigh
$(INSTALL_DIR) $(1)/etc/init.d/
$(INSTALL_BIN) ./files/etc/init.d/ipv6-neigh $(1)/etc/init.d/ipv6-neigh
endef
$(eval $(call RustBinPackage,ipv6-neigh))
$(eval $(call BuildPackage,ipv6-neigh))

View File

@ -0,0 +1,20 @@
#!/bin/sh /etc/rc.common
USE_PROCD=1
START=19
STOP=50
PROG=/usr/bin/ipv6-neigh
start_service() {
procd_open_instance ipv6-neigh
procd_set_param command $PROG -p -d "[::1]:5335" -z lan
procd_set_param stdout 1
procd_set_param stderr 1
procd_set_param respawn "${respawn_threshold:-3600}" "${respawn_timeout:-5}" "${respawn_retry:-5}"
procd_close_instance ipv6-neigh
}
service_triggers() {
procd_add_reload_trigger "dhcp" "network"
}

107
ipv6-neigh/src/db.rs Normal file
View File

@ -0,0 +1,107 @@
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
use hickory_proto::op::{Message, ResponseCode, update_message};
use hickory_proto::rr::{Name, RData, RecordSet, RecordType};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
/// DNS dynamic update client that sends RFC 2136 updates over TCP to the hickory-dns server.
pub(crate) struct DnsUpdater {
server_addr: SocketAddr,
zone: Name,
}
impl DnsUpdater {
pub fn new(server_addr: SocketAddr, zone: Name) -> Self {
Self { server_addr, zone }
}
/// Create or append a AAAA record.
pub async fn upsert_aaaa(
&self,
hostname: &str,
addr: Ipv6Addr,
ttl: u32,
) -> Result<(), Box<dyn std::error::Error>> {
let name = Name::from_ascii(hostname)?.append_domain(&self.zone)?;
let mut rrset = RecordSet::with_ttl(name.clone(), RecordType::AAAA, ttl);
rrset.add_rdata(RData::AAAA(addr.into()));
let msg = update_message::create(rrset, self.zone.clone(), false);
let response = self.send_tcp(msg).await?;
if response.metadata.response_code == ResponseCode::YXRRSet {
let mut rrset = RecordSet::with_ttl(name, RecordType::AAAA, ttl);
rrset.add_rdata(RData::AAAA(addr.into()));
let msg = update_message::append(rrset, self.zone.clone(), false, false);
self.send_tcp(msg).await?;
}
Ok(())
}
/// Create or append an A record.
pub async fn upsert_a(
&self,
hostname: &str,
addr: Ipv4Addr,
ttl: u32,
) -> Result<(), Box<dyn std::error::Error>> {
let name = Name::from_ascii(hostname)?.append_domain(&self.zone)?;
let mut rrset = RecordSet::with_ttl(name.clone(), RecordType::A, ttl);
rrset.add_rdata(RData::A(addr.into()));
let msg = update_message::create(rrset, self.zone.clone(), false);
let response = self.send_tcp(msg).await?;
if response.metadata.response_code == ResponseCode::YXRRSet {
let mut rrset = RecordSet::with_ttl(name, RecordType::A, ttl);
rrset.add_rdata(RData::A(addr.into()));
let msg = update_message::append(rrset, self.zone.clone(), false, false);
self.send_tcp(msg).await?;
}
Ok(())
}
/// Delete a specific AAAA record.
pub async fn delete_aaaa(
&self,
hostname: &str,
addr: Ipv6Addr,
) -> Result<(), Box<dyn std::error::Error>> {
let name = Name::from_ascii(hostname)?.append_domain(&self.zone)?;
let mut rrset = RecordSet::new(name, RecordType::AAAA, 0);
rrset.add_rdata(RData::AAAA(addr.into()));
let msg = update_message::delete_by_rdata(rrset, self.zone.clone(), false);
self.send_tcp(msg).await?;
Ok(())
}
/// Delete a specific A record.
pub async fn delete_a(
&self,
hostname: &str,
addr: Ipv4Addr,
) -> Result<(), Box<dyn std::error::Error>> {
let name = Name::from_ascii(hostname)?.append_domain(&self.zone)?;
let mut rrset = RecordSet::new(name, RecordType::A, 0);
rrset.add_rdata(RData::A(addr.into()));
let msg = update_message::delete_by_rdata(rrset, self.zone.clone(), false);
self.send_tcp(msg).await?;
Ok(())
}
/// Send a DNS message over TCP (2-byte length prefix + message bytes) and read the response.
async fn send_tcp(&self, msg: Message) -> Result<Message, Box<dyn std::error::Error>> {
let bytes = msg.to_vec()?;
let len = u16::try_from(bytes.len())?;
let mut stream = TcpStream::connect(self.server_addr).await?;
stream.write_all(&len.to_be_bytes()).await?;
stream.write_all(&bytes).await?;
stream.flush().await?;
let resp_len = stream.read_u16().await? as usize;
let mut resp_buf = vec![0u8; resp_len];
stream.read_exact(&mut resp_buf).await?;
Ok(Message::from_vec(&resp_buf)?)
}
}

298
ipv6-neigh/src/main.rs Normal file
View File

@ -0,0 +1,298 @@
use std::collections::HashMap;
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr as StdSocketAddr};
use futures::stream::StreamExt;
use futures::stream::TryStreamExt;
use netlink_packet_core::NetlinkPayload;
use netlink_packet_route::RouteNetlinkMessage;
use netlink_packet_route::neighbour::NeighbourMessage;
use netlink_packet_route::neighbour::{NeighbourAddress, NeighbourAttribute, NeighbourState};
use netlink_packet_route::route::RouteType;
use rtnetlink::{Error, Handle, new_connection};
use clap::Parser;
use hickory_proto::rr::Name;
use netlink_sys::{AsyncSocket, SocketAddr};
mod db;
mod op;
const RTNLGRP_NEIGH: u32 = 3;
const DEFAULT_TTL: u32 = 60;
const fn nl_mgrp(group: u32) -> u32 {
if group > 31 {
panic!("use netlink_sys::Socket::add_membership() for this group");
}
if group == 0 { 0 } else { 1 << (group - 1) }
}
#[derive(Debug, Parser)]
#[clap()]
struct Cli {
#[clap(short, long)]
iface: Option<String>,
#[clap(short, long)]
private_subnet: bool,
/// hickory-dns server address for DNS updates (e.g. "[::1]:5335")
#[clap(short, long, default_value = "[::1]:5335")]
dns_server: StdSocketAddr,
/// DNS zone to update (e.g. "lan")
#[clap(short, long, default_value = "lan")]
zone: String,
}
#[derive(Debug)]
struct Neigh {
ifindex: u32,
state: NeighbourState,
kind: RouteType,
inet: NeighbourAddress,
mac: String,
}
fn if_ipv6_in_private_subnet(ip: &Ipv6Addr) -> bool {
// Check if address is ULA (fc00::/7)
let is_ula = (ip.segments()[0] & 0xfe00) == 0xfc00;
// Check if address is link-local (fe80::/10)
let is_link_local = (ip.segments()[0] & 0xffc0) == 0xfe80;
is_ula || is_link_local
}
fn if_ipv4_in_private_subnet(ip: &Ipv4Addr) -> bool {
// Check for private network ranges
let octets = ip.octets();
// 10.0.0.0/8
if octets[0] == 10 {
return true;
}
// 172.16.0.0/12
if octets[0] == 172 && (octets[1] >= 16 && octets[1] <= 31) {
return true;
}
// 192.168.0.0/16
if octets[0] == 192 && octets[1] == 168 {
return true;
}
// 127.0.0.0/8 (loopback)
if octets[0] == 127 {
return true;
}
false
}
async fn process_new_neigh(neigh: &Neigh, updater: &db::DnsUpdater, leases: &HashMap<String, String>) {
let Some(hostname) = leases.get(&neigh.mac) else {
eprintln!("no lease for mac {}, skipping DNS update", neigh.mac);
return;
};
let result = match &neigh.inet {
NeighbourAddress::Inet6(addr) => updater.upsert_aaaa(hostname, *addr, DEFAULT_TTL).await,
NeighbourAddress::Inet(addr) => updater.upsert_a(hostname, *addr, DEFAULT_TTL).await,
_ => return,
};
match result {
Ok(()) => println!("DNS update: added {} -> {:?}", hostname, neigh.inet),
Err(e) => eprintln!("DNS update failed for {}: {}", hostname, e),
}
}
async fn process_del_neigh(neigh: &Neigh, updater: &db::DnsUpdater, leases: &HashMap<String, String>) {
let Some(hostname) = leases.get(&neigh.mac) else {
return;
};
let result = match &neigh.inet {
NeighbourAddress::Inet6(addr) => updater.delete_aaaa(hostname, *addr).await,
NeighbourAddress::Inet(addr) => updater.delete_a(hostname, *addr).await,
_ => return,
};
match result {
Ok(()) => println!("DNS update: removed {} -> {:?}", hostname, neigh.inet),
Err(e) => eprintln!("DNS delete failed for {}: {}", hostname, e),
}
}
fn is_multicast_or_broadcast_route_type(route_type: RouteType) -> bool {
match route_type {
RouteType::Multicast => true,
RouteType::Broadcast => true,
_ => false,
}
}
fn is_multicast_or_broadcast(neigh: &Neigh) -> bool {
return is_multicast_or_broadcast_route_type(neigh.kind);
}
#[tokio::main]
async fn main() -> Result<(), ()> {
let args = Cli::parse();
let private_subnet = args.private_subnet;
let zone = Name::from_ascii(&args.zone).expect("invalid zone name");
let updater = db::DnsUpdater::new(args.dns_server, zone);
let (connection, handle, _) = new_connection().unwrap();
tokio::spawn(connection);
dump_addresses(handle.clone(), args.iface).await.unwrap();
// Load DHCP leases (mac -> hostname) from ubus
let mut leases = op::get_lease().unwrap_or_default();
println!("loaded {} DHCP leases", leases.len());
// Dump existing neighbours and register them
println!("dumping neighbours");
if let Ok(neighbours) = dump_neighbours(handle.clone(), private_subnet).await {
for neigh in &neighbours {
println!("{:?}", neigh);
if !is_multicast_or_broadcast(neigh) {
process_new_neigh(neigh, &updater, &leases).await;
}
}
}
println!();
let (mut conn, mut _handle, mut messages) =
new_connection().map_err(|e| format!("{e}")).unwrap();
let groups = nl_mgrp(RTNLGRP_NEIGH);
let addr = SocketAddr::new(0, groups);
conn.socket_mut()
.socket_mut()
.bind(&addr)
.expect("Failed to bind");
tokio::spawn(conn);
let mut event_count: u64 = 0;
// Start receiving events through `messages` channel.
while let Some((message, _)) = messages.next().await {
// Refresh leases periodically (every 50 events)
event_count += 1;
if event_count % 50 == 0 {
if let Ok(new_leases) = op::get_lease() {
leases = new_leases;
println!("refreshed {} DHCP leases", leases.len());
}
}
let payload = message.payload;
if let NetlinkPayload::InnerMessage(msg) = payload {
match msg {
RouteNetlinkMessage::NewNeighbour(new_neigh) => {
let Some(neigh) = parse_neighbour_message(new_neigh, private_subnet) else {
continue;
};
if is_multicast_or_broadcast(&neigh) {
continue;
}
println!("New neighbour: {:?}", neigh);
process_new_neigh(&neigh, &updater, &leases).await;
}
RouteNetlinkMessage::DelNeighbour(del_neigh) => {
let Some(neigh) = parse_neighbour_message(del_neigh, private_subnet) else {
continue;
};
if is_multicast_or_broadcast(&neigh) {
continue;
}
println!("Del neighbour: {:?}", neigh);
process_del_neigh(&neigh, &updater, &leases).await;
}
_ => {}
}
}
}
Ok(())
}
fn format_mac(mac: Vec<u8>) -> String {
let mut mac_str = String::new();
for byte in mac {
mac_str.push_str(&format!("{:02x}:", byte));
}
mac_str.pop();
mac_str
}
async fn dump_addresses(handle: Handle, link: Option<String>) -> Result<(), Error> {
let mut request = handle.link().get();
if let Some(link) = link {
request = request.match_name(link);
}
let mut links = request.execute();
if let Some(link) = links.try_next().await? {
let mut addresses = handle
.address()
.get()
.set_link_index_filter(link.header.index)
.execute();
while let Some(msg) = addresses.try_next().await? {
println!("{msg:?}");
}
Ok(())
} else {
eprintln!("link not found");
Ok(())
}
}
fn parse_neighbour_message(neigh: NeighbourMessage, private_subnet: bool) -> Option<Neigh> {
let state = neigh.header.state;
if state == NeighbourState::Permanent {
return None;
}
let addr: NeighbourAddress = neigh.attributes.iter().find_map(|attr| match attr {
NeighbourAttribute::Destination(inet) => Some(inet.to_owned()),
_ => None,
})?;
if private_subnet {
match addr {
NeighbourAddress::Inet(addr) => {
if !if_ipv4_in_private_subnet(&addr) {
return None;
}
}
NeighbourAddress::Inet6(addr) => {
if !if_ipv6_in_private_subnet(&addr) {
return None;
}
}
_ => {}
}
};
let kind = neigh.header.kind;
let ifindex = neigh.header.ifindex;
let mac = neigh.attributes.iter().find_map(|attr| match attr {
NeighbourAttribute::LinkLocalAddress(mac) => Some(mac.to_owned()),
_ => None,
})?;
Some(Neigh {
ifindex,
state,
kind,
inet: addr,
mac: format_mac(mac),
})
}
async fn dump_neighbours(handle: Handle, private_subnet: bool) -> Result<Vec<Neigh>, Error> {
let mut neighbours = handle.neighbours().get().execute();
let mut vec: Vec<Neigh> = Vec::new();
while let Some(route) = neighbours.try_next().await? {
if let Some(neigh) = parse_neighbour_message(route, private_subnet) {
if !is_multicast_or_broadcast(&neigh) {
vec.push(neigh);
}
}
}
Ok(vec)
}

39
ipv6-neigh/src/op.rs Normal file
View File

@ -0,0 +1,39 @@
use serde_json::Value;
use std::collections::HashMap;
use std::path::Path;
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 mut connection = match ubus::Connection::connect(&socket) {
Ok(connection) => connection,
Err(err) => {
return Err(Box::new(err));
}
};
let json = connection.call(obj_path, method, "").unwrap();
let parsed: Value = serde_json::from_str(&json).unwrap();
Ok(parsed)
}
// get mac to hostname mapping
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
let ip4lease = call_ubus("dhcp", "ipv4leases");
let leases = ip4lease.unwrap();
let devices = leases["device"].as_object().unwrap();
let mut result = HashMap::new();
// hard code
for (device, leases) in devices {
let suffix = if device == "phy1-ap0" { ".iot" } else { ".lan" };
let leases = leases["leases"].as_array().unwrap();
for lease in leases {
let mac = lease["mac"].as_str().unwrap().to_string();
let hostname = lease["hostname"].as_str().unwrap().to_string() + suffix;
result.insert(mac, hostname);
}
}
Ok(result)
}

View File

@ -16,8 +16,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-amlogic
PKG_VERSION:=3.1.289
PKG_RELEASE:=4
PKG_VERSION:=3.1.290
PKG_RELEASE:=5
PKG_LICENSE:=GPL-2.0 License
PKG_MAINTAINER:=ophub <https://github.com/ophub/luci-app-amlogic>

View File

@ -283,8 +283,8 @@ update_kernel() {
rm -f /boot/uInitrd*
else
valid_files="vmlinuz-${kernel_name} uInitrd-${kernel_name} config-${kernel_name} System.map-${kernel_name}"
# wxy-oect: MODEL_ID numbers r304,r306 and w[0-9]{3}, require special handling of uInitrd
[[ "${MODEL_ID}" =~ ^(r304|r306|w[0-9]{3})$ ]] || rm -f /boot/initrd.img*
# wxy-oect: MODEL_ID numbers r306,r307 and w[0-9]{3}, require special handling of uInitrd
[[ "${MODEL_ID}" =~ ^(r306|r307|w[0-9]{3})$ ]] || rm -f /boot/initrd.img*
fi
for f in ${valid_files}; do [[ -f "/boot/${f}" ]] || restore_kernel; done
@ -304,8 +304,8 @@ update_kernel() {
ln -sf vmlinuz-${kernel_name} ${MYBOOT_VMLINUZ}
fi
# wxy-oect: MODEL_ID numbers r304 and r306, require special handling of uInitrd
[[ "${MODEL_ID}" =~ ^(r304|r306)$ ]] && ln -sf initrd.img-${kernel_name} uInitrd
# wxy-oect: MODEL_ID numbers r306 and r307, require special handling of uInitrd
[[ "${MODEL_ID}" =~ ^(r306|r307)$ ]] && ln -sf initrd.img-${kernel_name} uInitrd
)
echo -e "(1/3) Unpacking [ boot-${kernel_name}.tar.gz ] done."

View File

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

View File

@ -401,13 +401,6 @@ o.validate = function(self, value)
return api.trim(value:gsub("[\r\n]", ""))
end
o = s:option(ListValue, _n("ech_ForceQuery"), translate("ECH Query Policy"), translate("Controls the policy used when performing DNS queries for ECH configuration."))
o.default = "full"
o:value("none")
o:value("half")
o:value("full")
o:depends({ [_n("ech")] = true })
-- [[ REALITY部分 ]] --
o = s:option(Value, _n("reality_publicKey"), translate("Public Key"))
o:depends({ [_n("tls")] = true, [_n("reality")] = true })

View File

@ -163,8 +163,7 @@ function gen_outbound(flag, node, tag, proxy_table)
if not node.tls_CertByName then return "" end
return node.tls_CertByName
end)(),
echConfigList = (node.ech == "1") and node.ech_config or nil,
echForceQuery = (node.ech == "1") and (node.ech_ForceQuery or "full") or nil
echConfigList = (node.ech == "1") and node.ech_config or nil
} or nil,
realitySettings = (node.stream_security == "reality") and {
serverName = node.tls_serverName,

View File

@ -1909,12 +1909,6 @@ msgstr "ECH 配置"
msgid "ECH Key"
msgstr "ECH 密钥"
msgid "ECH Query Policy"
msgstr "ECH 查询策略"
msgid "Controls the policy used when performing DNS queries for ECH configuration."
msgstr "控制使用 DNS 查询 ECH 配置时的策略。"
msgid "ECH Query Domain"
msgstr "ECH 查询域名"

View File

@ -939,13 +939,8 @@ add_firewall_rule() {
fi
}
ipset -! -R <<-EOF
$(ip address show | grep -w "inet" | awk '{print $2}' | awk -F '/' '{print $1}' | sed -e "s/^/add $IPSET_LOCAL /")
EOF
ipset -! -R <<-EOF
$(ip address show | grep -w "inet6" | awk '{print $2}' | awk -F '/' '{print $1}' | sed -e "s/^/add $IPSET_LOCAL6 /")
EOF
get_local_ips ip4 | sed "s/^/add $IPSET_LOCAL /" | ipset -! -R
get_local_ips ip6 | sed "s/^/add $IPSET_LOCAL6 /" | ipset -! -R
#局域网IP列表
ipset -! -R <<-EOF
@ -1023,9 +1018,9 @@ add_firewall_rule() {
$ipt_n -N PSW_DNS
if [ $(config_t_get global dns_redirect "1") = "0" ]; then
#Only hijack when dest address is local IP
$ipt_n -I PREROUTING $(dst $IPSET_LOCAL) -j PSW_DNS
$ipt_n -I PREROUTING -m set --match-set $IPSET_LAN src $(dst $IPSET_LOCAL) -j PSW_DNS
else
$ipt_n -I PREROUTING 1 -j PSW_DNS
$ipt_n -I PREROUTING -m set --match-set $IPSET_LAN src -j PSW_DNS
fi
$ipt_m -N PSW_DIVERT
@ -1094,9 +1089,9 @@ add_firewall_rule() {
$ip6t_n -N PSW_DNS
if [ $(config_t_get global dns_redirect "1") = "0" ]; then
#Only hijack when dest address is local IP
$ip6t_n -I PREROUTING $(dst $IPSET_LOCAL6) -j PSW_DNS
$ip6t_n -I PREROUTING -m set --match-set $IPSET_LAN6 src $(dst $IPSET_LOCAL6) -j PSW_DNS
else
$ip6t_n -I PREROUTING 1 -j PSW_DNS
$ip6t_n -I PREROUTING -m set --match-set $IPSET_LAN6 src -j PSW_DNS
fi
$ip6t_m -N PSW_DIVERT

View File

@ -1011,8 +1011,8 @@ add_firewall_rule() {
fi
}
ip address show | grep -w "inet" | awk '{print $2}' | awk -F '/' '{print $1}' | insert_nftset $NFTSET_LOCAL "-1"
ip address show | grep -w "inet6" | awk '{print $2}' | awk -F '/' '{print $1}' | insert_nftset $NFTSET_LOCAL6 "-1"
get_local_ips ip4 | insert_nftset $NFTSET_LOCAL "-1"
get_local_ips ip6 | insert_nftset $NFTSET_LOCAL6 "-1"
# 忽略特殊IP段
local lan_ifname lan_ip
@ -1074,10 +1074,11 @@ add_firewall_rule() {
nft "flush chain $NFTABLE_NAME PSW_DNS"
if [ $(config_t_get global dns_redirect "1") = "0" ]; then
#Only hijack when dest address is local IP
nft "insert rule $NFTABLE_NAME dstnat ip daddr @${NFTSET_LOCAL} jump PSW_DNS"
nft "insert rule $NFTABLE_NAME dstnat ip6 daddr @${NFTSET_LOCAL6} jump PSW_DNS"
nft "insert rule $NFTABLE_NAME dstnat ip saddr @${NFTSET_LAN} ip daddr @${NFTSET_LOCAL} jump PSW_DNS"
nft "insert rule $NFTABLE_NAME dstnat ip6 saddr @${NFTSET_LAN6} ip6 daddr @${NFTSET_LOCAL6} jump PSW_DNS"
else
nft "insert rule $NFTABLE_NAME dstnat jump PSW_DNS"
nft "insert rule $NFTABLE_NAME dstnat ip saddr @${NFTSET_LAN} jump PSW_DNS"
nft "insert rule $NFTABLE_NAME dstnat ip6 saddr @${NFTSET_LAN6} jump PSW_DNS"
fi
# for ipv4 ipv6 tproxy mark

View File

@ -518,3 +518,33 @@ get_wan_ips() {
done
echo "$NET_ADDR"
}
get_local_ips() {
local family="$1"
local ALL_IPS WAN_IPS ip NET_ADDR
if [ "$family" = "ip6" ]; then
ALL_IPS=$(ip -o -6 addr show scope global | awk '{print $4}' | cut -d/ -f1)
WAN_IPS=$(get_wan_ips ip6)
else
ALL_IPS=$(ip -o -4 addr show scope global | awk '{print $4}' | cut -d/ -f1)
WAN_IPS=$(get_wan_ips ip4)
fi
# 补充回环scope global 不包含)
[ "$family" = "ip6" ] && ALL_IPS="$ALL_IPS ::1"
[ "$family" != "ip6" ] && ALL_IPS="$ALL_IPS 127.0.0.1"
for ip in $ALL_IPS; do
case "$ip" in
""|0.0.0.0|::) continue ;;
esac
case " $WAN_IPS " in
*" $ip "*) continue ;;
esac
case " $NET_ADDR " in
*" $ip "*) ;;
*) NET_ADDR="${NET_ADDR:+$NET_ADDR }$ip" ;;
esac
done
for ip in $NET_ADDR; do
echo "$ip"
done
}

View File

@ -46,6 +46,16 @@ function pushUnique(list, value) {
list.push(value);
}
function mergeUniqueValues(...lists) {
const merged = [];
for (const list of lists)
for (const value of L.toArray(list))
pushUnique(merged, value);
return merged;
}
function getQcaFallbackIfname(device, section) {
if (/^ath\d+$/.test(String(section || '')))
return section;
@ -1541,7 +1551,9 @@ var CBIWifiFrequencyValue = form.Value.extend({
const statuscfg = L.isObject(wstatus[device_section]?.config) ? wstatus[device_section].config : {};
const devcfg = {
channel: statuscfg.channel ?? (wifidevs ? wifidevs.ubus('dev', 'config', 'channel') : null),
band: statuscfg.band ?? (wifidevs ? wifidevs.ubus('dev', 'config', 'band') : null)
band: statuscfg.band ?? (wifidevs ? wifidevs.ubus('dev', 'config', 'band') : null),
hwmode: statuscfg.hwmode ?? (wifidevs ? wifidevs.ubus('dev', 'config', 'hwmode') : null),
htmode: statuscfg.htmode ?? (wifidevs ? wifidevs.ubus('dev', 'config', 'htmode') : null)
};
this.devinfo = devinfo;
@ -1592,8 +1604,19 @@ var CBIWifiFrequencyValue = form.Value.extend({
return Array.isArray(channels) && (channels.length > offset || (configured_band == band && channels.length > 0));
};
let hwmode_values = L.toArray(wifidevs ? wifidevs.getHWModes() : null);
let htmode_values = L.toArray(wifidevs ? wifidevs.getHTModes() : null);
const merge_mode_sources = (hwtype == 'mac80211');
let hwmode_values = merge_mode_sources
? mergeUniqueValues(
wifidevs ? wifidevs.getHWModes() : null,
devinfo.hwmodes
)
: L.toArray(wifidevs ? wifidevs.getHWModes() : null);
let htmode_values = merge_mode_sources
? mergeUniqueValues(
wifidevs ? wifidevs.getHTModes() : null,
devinfo.htmodes
)
: L.toArray(wifidevs ? wifidevs.getHTModes() : null);
if (!hwmode_values.length)
hwmode_values = L.toArray(devinfo.hwmodes);
@ -1959,8 +1982,8 @@ var CBIWifiFrequencyValue = form.Value.extend({
const config_chval = cfgvals ? cfgvals[2] : uci.get('wireless', config_section, 'channel');
const cfg_chval = devcfg.channel || config_chval;
const cfg_bandval = devcfg.band || uci.get('wireless', config_section, 'band');
const htval = isQcaWifiHwtype(hwtype) ? (cfg_htval || devinfo.htmode) : (devinfo.htmode || cfg_htval);
const hwval = isQcaWifiHwtype(hwtype) ? (cfg_hwval || devinfo.hwmode) : (devinfo.hwmode || cfg_hwval);
const htval = cfg_htval || devcfg.htmode || devinfo.htmode;
const hwval = cfg_hwval || devcfg.hwmode || devinfo.hwmode;
const chval = cfg_chval || devinfo.channel;
const bandval = cfg_bandval || getConfiguredBand(hwtype, hwval, chval, null, htval);
const forceSelectValue = function(sel, value) {