The plane only ever existed while the daemon did. It starts at 99, after fw4 has already loaded lan→wan ACCEPT, and only reaches ArmHold after waiting out its predecessor, migrating the schema, building the engine and reading UCI — with a UPX-compressed binary decompressing off flash first. Every boot therefore had a window with no protection at all, landing exactly when Wi-Fi comes up and every client reconnects. A restart, a reload or a package upgrade opened the same window on purpose: Teardown does not consult the kill switch, and the init script guarantees the interval is non-empty. The holding plane is now persisted to /etc/shater/boot.nft on every apply and loaded by a small service at 21, right after fw4 and netifd. Its presence is the arm token: it exists only while the last applied config was enabled AND fail-closed, and goes away the moment either stops being true. Writes are content-gated — the cron reconcile runs a minute — and atomic, because the one boot that reads this file is the boot after a power cut. The service refuses to arm four ways so it can never brick a box, and its enabled-check reads /etc/rc.d directly rather than asking rc.common, which would take a blocking flock in the middle of boot. On exit the daemon re-arms only for restart and reload, read from a snapshot of rc.common's action; anything else, including an unknown one, degrades to a real stop that also disarms. An unreadable config used to leave the router bare forever: the arm call sat in the branch that requires a successful read, and nothing downstream could recover it. It now arms from the same path. A network nobody named was neither diverted nor blocked — the divert set is built from inbounds and rule sources, and the same set scopes the fail-closed drops. It is now enumerated from the interfaces whose firewall zone the operator forwards to a WAN zone — their own statement that those clients reach the internet through this box — and reported critically, by name, with both resolutions. Deliberately not closed automatically: this router cannot know a guest SSID was meant to be off the tunnel, and guessing is an outage. A device name that resolved to nothing is reported the same way, for the same reason: there is no fail-closed action available for a device we cannot name. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
102 lines
4.5 KiB
Makefile
102 lines
4.5 KiB
Makefile
#
|
|
# shater-core — data-plane glue for the Shater transparent-proxy stack (v0.2).
|
|
#
|
|
# Ships the "железно" (rock-solid) static layer that the single Go binary
|
|
# `shaterd` sits under: the procd init that supervises `shaterd run` (which
|
|
# embeds the sing-box engine + control-plane + DNS in-process), the auto-update
|
|
# cron loop, the hotplug hook that re-persists policy routing, the sysctl knobs
|
|
# TPROXY needs, one-time rt_tables seeding, and a minimal inert UCI default.
|
|
#
|
|
# Pure scripts + config => PKGARCH:=all. Nothing is compiled here.
|
|
#
|
|
|
|
include $(TOPDIR)/rules.mk
|
|
|
|
PKG_NAME:=shater-core
|
|
|
|
# Version comes from the git tag via ci/version.sh -> SHATER_PKG_VERSION /
|
|
# SHATER_PKG_RELEASE in the SDK build env (see openwrt/shaterd/Makefile for the
|
|
# full rationale — bug B4: v0.2.2…v0.2.6 all shipped as 0.2.0-r3). The literals
|
|
# are the manual/offline fallback only.
|
|
PKG_VERSION:=$(if $(SHATER_PKG_VERSION),$(SHATER_PKG_VERSION),0.2.0)
|
|
PKG_RELEASE:=$(if $(SHATER_PKG_RELEASE),$(SHATER_PKG_RELEASE),1)
|
|
|
|
PKG_MAINTAINER:=Shater <maqrota@icloud.com>
|
|
PKG_LICENSE:=GPL-2.0-or-later
|
|
|
|
include $(INCLUDE_DIR)/package.mk
|
|
|
|
define Package/shater-core
|
|
SECTION:=net
|
|
CATEGORY:=Network
|
|
TITLE:=Shater transparent-proxy data-plane glue
|
|
URL:=https://github.com/shater
|
|
# v0.2 collapses the old xrayctl + xray-core + dnsmasq-full trio into ONE Go
|
|
# binary, shaterd, which embeds the sing-box engine, the control-plane AND an
|
|
# in-process DNS server. So we no longer depend on:
|
|
# - xrayctl / xray-core -> replaced by shaterd
|
|
# - dnsmasq-full -> the engine owns the :53 hijack listener now
|
|
# We still need the kernel TPROXY modules and ip-full for policy routing:
|
|
# shaterd : the daemon our init supervises (`shaterd run`)
|
|
# kmod-nft-tproxy : kernel TPROXY (shaterd emits the `inet shater` rules)
|
|
# kmod-nft-socket : socket match used by the tproxy divert chain
|
|
# ip-full : `ip rule`/`ip route`/rt_tables for policy routing
|
|
# nftables-json : shaterd shells out to `nft`, and netplane/stats.go
|
|
# parses `nft -j list ...` — the JSON output only exists
|
|
# in the json variant (the -nft variant has no libjansson).
|
|
# fw4 already pulls it on stock images; declare it so a
|
|
# slimmed image cannot silently break counters/sets.
|
|
# ca-bundle : the daemon is CGO_ENABLED=0, so crypto/x509 has no
|
|
# host cert fallback — without /etc/ssl/certs every
|
|
# HTTPS subscription / .srs ruleset fetch fails.
|
|
DEPENDS:=+shaterd +kmod-nft-tproxy +kmod-nft-socket +ip-full +nftables-json +ca-bundle
|
|
PKGARCH:=all
|
|
endef
|
|
|
|
define Package/shater-core/description
|
|
Static data-plane glue for the Shater sing-box-based transparent proxy: procd
|
|
init (supervises `shaterd run`, which owns the engine + nft table `inet shater`
|
|
+ policy routing + in-process DNS), an auto-update cron loop with a dead-engine
|
|
watchdog, an ifup/ifdown hotplug hook that re-persists ip rules & routes,
|
|
TPROXY sysctl settings, a minimal inert UCI default (globals disabled until
|
|
configured), and idempotent first-boot setup. Designed to never break
|
|
connectivity: fully inert until explicitly enabled.
|
|
endef
|
|
|
|
# /etc/config/shater is user-editable desired state -> preserve on upgrade.
|
|
define Package/shater-core/conffiles
|
|
/etc/config/shater
|
|
endef
|
|
|
|
# Nothing to fetch or build.
|
|
define Build/Prepare
|
|
mkdir -p $(PKG_BUILD_DIR)
|
|
endef
|
|
|
|
define Build/Compile
|
|
endef
|
|
|
|
define Package/shater-core/install
|
|
$(INSTALL_DIR) $(1)/etc/init.d
|
|
$(INSTALL_BIN) ./files/etc/init.d/shater $(1)/etc/init.d/shater
|
|
$(INSTALL_BIN) ./files/etc/init.d/shater-cron $(1)/etc/init.d/shater-cron
|
|
# START=21 one-shot that loads the persisted fail-closed plane before fw4's
|
|
# `lan -> wan ACCEPT` can be the only thing on the box (the main init is
|
|
# START=99, i.e. seconds of plaintext forwarding on every boot).
|
|
$(INSTALL_BIN) ./files/etc/init.d/shater-armor $(1)/etc/init.d/shater-armor
|
|
|
|
$(INSTALL_DIR) $(1)/etc/hotplug.d/iface
|
|
$(INSTALL_BIN) ./files/etc/hotplug.d/iface/99-shater $(1)/etc/hotplug.d/iface/99-shater
|
|
|
|
$(INSTALL_DIR) $(1)/etc/sysctl.d
|
|
$(INSTALL_DATA) ./files/etc/sysctl.d/99-shater.conf $(1)/etc/sysctl.d/99-shater.conf
|
|
|
|
$(INSTALL_DIR) $(1)/etc/config
|
|
$(INSTALL_CONF) ./files/etc/config/shater $(1)/etc/config/shater
|
|
|
|
$(INSTALL_DIR) $(1)/etc/uci-defaults
|
|
$(INSTALL_BIN) ./files/etc/uci-defaults/30_shater-core $(1)/etc/uci-defaults/30_shater-core
|
|
endef
|
|
|
|
$(eval $(call BuildPackage,shater-core))
|