mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-02 14:13:56 +08:00
🌈 Sync 2026-10-01 13:09:37
This commit is contained in:
1 parent
dceb6de0e4
commit
889951b6d0
116 files changed
+6376
-1093
No files matched your search
@@ -20,7 +20,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { isEmpty } from '../homeproxy-pro.uc';
|
||||
import { isEmpty, isValidCIDR } from '../homeproxy-pro.uc';
|
||||
|
||||
/* Canonical credential multiplexing for a Node. The current generator picks
|
||||
* these apart with ternaries per field (`username` vs `user` vs `password` vs
|
||||
@@ -268,11 +268,47 @@ export const Node = {
|
||||
problems = [...problems, `invalid port '${node.port}'`];
|
||||
}
|
||||
|
||||
/* TLS without server_name is a sing-box-side rejection, but the
|
||||
* rule itself is protocol-agnostic - the reality branch covers the
|
||||
* only legitimate escape - so it stays here. */
|
||||
if (node.tls.enabled === '1' && !node.tls.server_name && node.tls.reality.enabled !== '1')
|
||||
problems = [...problems, 'TLS enabled without server_name'];
|
||||
/* TLS without an explicit server_name.
|
||||
*
|
||||
* sing-box defaults an outbound's tls.server_name to the
|
||||
* outbound's server address, so a node whose address is a
|
||||
* *hostname* works fine without one: the handshake sends that
|
||||
* hostname as SNI and verifies the certificate against it. That
|
||||
* is the documented default (outbound tls.server_name defaults to
|
||||
* "server address"), not a leniency we invented.
|
||||
*
|
||||
* This check used to fire for both address shapes, and firing
|
||||
* meant die() - OutboundFactory.create() treats a non-empty
|
||||
* problems list as fatal. A trojan:// / anytls:// / hysteria2://
|
||||
* / tuic:// share link with no `sni=` parameter produces exactly
|
||||
* such a node (each parser writes `tls_sni: params.sni` and
|
||||
* nothing else), and so does the node form, whose "TLS SNI"
|
||||
* field is an optional form.Value gated only on tls=1
|
||||
* (homeproxy-pro.js:672). As a main node it then took the whole
|
||||
* client configuration down:
|
||||
*
|
||||
* node '<section>': TLS enabled without server_name
|
||||
*
|
||||
* Reproduced on a real router (2026-10-01, sing-box 1.14.2):
|
||||
* with the SNI removed from the main node, buildable() is false
|
||||
* and generate() dies - so a reload rolled back to the previous
|
||||
* configuration and a boot start refused to come up, over a node
|
||||
* sing-box would have dialed without complaint.
|
||||
*
|
||||
* The case that IS worth a problem is an IP address: the implicit
|
||||
* SNI is then the literal IP, which no real server presents a
|
||||
* matching certificate for, so the connection cannot succeed.
|
||||
* That stays fatal for a node a rule or the main node points at
|
||||
* (the existing contract for an unbuildable node) and is pruned
|
||||
* with a warning inside a urltest group
|
||||
* (generator/outbound.uc keep_candidate). reality is the escape
|
||||
* hatch: it runs its own handshake and does not use server_name.
|
||||
*/
|
||||
if (node.tls.enabled === '1' && !node.tls.server_name
|
||||
&& node.tls.reality.enabled !== '1'
|
||||
&& (isValidCIDR(node.address, 4) || isValidCIDR(node.address, 6)))
|
||||
problems = [...problems,
|
||||
`TLS enabled on IP address ${node.address} without server_name`];
|
||||
|
||||
return problems;
|
||||
},
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { isEmpty, strToBool, strToInt, strToTime, parseURL, validation, parse_port } from '../homeproxy-pro.uc';
|
||||
import { isEmpty, strToBool, strToInt, strToTime, parseURL, validation, parse_port, isValidCIDR } from '../homeproxy-pro.uc';
|
||||
|
||||
|
||||
import { get_outbound, get_resolver, get_ruleset } from './common.uc';
|
||||
@@ -72,6 +72,32 @@ function parse_dnsquery(strquery) {
|
||||
return querys;
|
||||
}
|
||||
|
||||
/* parse_dnsserver() returns null for an address it cannot make sense of, and
|
||||
* both call sites used to spread that null straight into the server object.
|
||||
* Spreading null is not a no-op in ucode - it throws
|
||||
*
|
||||
* Type error: Value (null) is not iterable
|
||||
*
|
||||
* so one unusable DNS address took the generator down with an exception that
|
||||
* names no field, and the user only saw a rollback. Reproduced on a router
|
||||
* (2026-10-01) by putting a single-label value in the China DNS field:
|
||||
* parseURL() only sets `hostname` when /sbin/validate_data accepts the value
|
||||
* as a hostname, and a dotless name ('cafe', 'beef', 'decade') is not one.
|
||||
*
|
||||
* A DNS server with no address is not something the configuration can do
|
||||
* without, so this fails the same way get_resolver()/get_ruleset() do - with
|
||||
* the field name and a usable example - instead of substituting a working
|
||||
* resolver the user did not ask for. */
|
||||
function require_dnsserver(addr, default_protocol, field) {
|
||||
const server = parse_dnsserver(addr, default_protocol);
|
||||
|
||||
if (!server)
|
||||
die(sprintf("the %s '%s' is not a usable address; use a bare IP, or a DoH/DoT URL such as https://dns.google/dns-query.\n",
|
||||
field, addr));
|
||||
|
||||
return server;
|
||||
};
|
||||
|
||||
/* --- proxy routing-mode path (bypass_mainland_china / proxy / / gfwlist) -- */
|
||||
|
||||
/* Domains whose NAPTR (qtype 35) queries bypass china-dns. Aliyun
|
||||
@@ -117,8 +143,34 @@ function bootstrap_addr(ctx) {
|
||||
if (isEmpty(addr))
|
||||
return null;
|
||||
|
||||
return (match(addr, /^[0-9]+(\.[0-9]+){3}$/) != null
|
||||
|| match(addr, /^[0-9a-fA-F:]+$/) != null) ? addr : null;
|
||||
/* isValidCIDR(), not a hand-rolled shape test.
|
||||
*
|
||||
* The pair of regexes this replaces accepted anything made only of hex
|
||||
* digits and colons - `cafe`, `face`, `abc`, `add`, `dead:beef` are
|
||||
* all perfectly good hostnames and all passed - and the IPv4 branch did
|
||||
* not range-check the octets, so 999.999.999.999 passed too. Measured
|
||||
* on the device (2026-10-01): all six returned an address.
|
||||
*
|
||||
* The cost of being wrong here is not one misbehaving resolver.
|
||||
* append_bootstrap_dns() emits the bootstrap server deliberately
|
||||
* *without* a domain_resolver of its own - existing is the whole point,
|
||||
* it is what every other server bootstraps its own hostname through -
|
||||
* so a hostname there can never be satisfied, and sing-box refuses to
|
||||
* start at all. Verified against 1.14.2 with `server: "cafe"`:
|
||||
*
|
||||
* FATAL create service: initialize DNS server[0]: missing domain
|
||||
* resolver for domain server address
|
||||
*
|
||||
* isValidCIDR() is the validator the firewall path already runs every
|
||||
* resource value through for the same reason (a value that is not a
|
||||
* real IP or CIDR reaches a root context), and it range-checks both
|
||||
* families. Reusing it keeps one definition of "is this an address".
|
||||
*
|
||||
* Behaviour for every value this generator is actually pointed at is
|
||||
* unchanged: 223.5.5.5, 1.12.12.21, fdfe::1 and the other literals the
|
||||
* option documents all still pass, and 'wan' / empty / a real hostname
|
||||
* still return null so main-dns keeps borrowing the WAN resolver. */
|
||||
return (isValidCIDR(addr, 4) || isValidCIDR(addr, 6)) ? addr : null;
|
||||
}
|
||||
|
||||
/* Append the bootstrap resolver, when there is a usable address for it.
|
||||
@@ -164,7 +216,7 @@ function append_proxy_dns(config, dm, ctx) {
|
||||
strategy: (ctx.ipv6_support !== '1') ? 'ipv4_only' : null
|
||||
},
|
||||
detour: 'main-out',
|
||||
...parse_dnsserver(ctx.dns_server, 'tcp')
|
||||
...require_dnsserver(ctx.dns_server, 'tcp', 'DNS server')
|
||||
});
|
||||
/* The DNS half of the mode's default policy. It has to name the same
|
||||
* side the route chain falls through to (ctx.proxy_fallback), or a
|
||||
@@ -213,7 +265,7 @@ function append_proxy_dns(config, dm, ctx) {
|
||||
strategy: (ctx.ipv6_support !== '1') ? 'ipv4_only' : 'prefer_ipv6'
|
||||
},
|
||||
detour: ctx.self_mark ? 'direct-out' : null,
|
||||
...parse_dnsserver(ctx.china_dns_server)
|
||||
...require_dnsserver(ctx.china_dns_server, null, 'China DNS server')
|
||||
});
|
||||
|
||||
/* Route NAPTR (qtype 35) queries for SIP/ENUM domains to the ISP
|
||||
|
||||
@@ -48,6 +48,45 @@ hp_require_singbox() {
|
||||
return 0
|
||||
}
|
||||
|
||||
# hp_require_ucode
|
||||
# Refuse to start when the installed ucode cannot load this package's own
|
||||
# modules at all. Returns 1 (and logs) when the interpreter is unusable.
|
||||
#
|
||||
# Why a check and not a documented requirement: ucode resolves `import` when a
|
||||
# module is LOADED, not when a function is called, and homeproxy-pro.uc imports
|
||||
#
|
||||
# import { access, lstat, mkdtemp, open, rmdir, unlink } from 'fs';
|
||||
#
|
||||
# so on a ucode whose fs module has no `mkdtemp` export, *every* entry point
|
||||
# into this package dies with the same one-line reference error at load time -
|
||||
# the install-time migration (etc/uci-defaults/luci-homeproxy-pro-migration),
|
||||
# both config generators, and the LuCI RPC module that imports homeproxy-pro.uc
|
||||
# too. The first of those to run is the migration, which is why the only
|
||||
# thing an affected user ever saw was a stack trace during `apk add` /
|
||||
# `opkg install` (issue #3) and never a sentence naming the requirement.
|
||||
#
|
||||
# `mkdtemp` entered ucode on 2025-11-07 (lib/fs.c, "fs: add mkdtemp() method
|
||||
# for creating temporary directories"). It replaced `mkstemp`, which every
|
||||
# older ucode does export - so this is a hard floor, not a preference, and no
|
||||
# ImmortalWrt 24.10 release can satisfy it.
|
||||
#
|
||||
# The probe is `ucode -e`, the same form tests/ucode/run.sh uses for its
|
||||
# module syntax checks. Its own output is not discarded: if this ucode is so
|
||||
# old that even the probe fails, that is exactly the information the operator
|
||||
# needs, so the error goes into the log next to the requirement.
|
||||
hp_require_ucode() {
|
||||
local probe_out
|
||||
|
||||
probe_out="$(ucode -e "import * as fs from 'fs'; exit('mkdtemp' in keys(fs) ? 0 : 1)" 2>&1)" && return 0
|
||||
|
||||
log "Error: this build needs a ucode whose fs module exports mkdtemp (added in ucode 2025-11-07, shipped in ImmortalWrt 25.12); the installed ucode does not provide it."
|
||||
log "Error: every script in this package imports it, so the service, the config generators and the LuCI RPC all fail to load on this firmware."
|
||||
[ -n "$probe_out" ] && log "Error: the capability check itself reported: ${probe_out}"
|
||||
log "Hint: this firmware's ucode is too old for this package; ImmortalWrt 25.12 or newer is required."
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# hp_crontab_drop <crontab>
|
||||
# Remove the auto-update entry from <crontab>.
|
||||
#
|
||||
|
||||
@@ -191,6 +191,16 @@ start_service() {
|
||||
# a router whose node was removed (or whose sing-box just failed its
|
||||
# version gate) keeps redirecting the LAN into a listener that no instance
|
||||
# is serving any more.
|
||||
#
|
||||
# The ucode gate comes first because it is the more fundamental one: when
|
||||
# the interpreter cannot load this package's modules, the generator below
|
||||
# would fail too, and the sing-box version line is the less useful of the
|
||||
# two messages.
|
||||
if ! hp_require_ucode; then
|
||||
hp_release_stale_intercept
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! hp_require_singbox; then
|
||||
hp_release_stale_intercept
|
||||
return 1
|
||||
|
||||
Reference in new issue
Block a user