450 Commits
Author SHA1 Message Date
wweir 6d52cc14c3 feat(admin): monitor DNS queries and filter traffic by source
Domain stats are now tracked per entry point (http/https/socks5/dns)
instead of one mixed counter, and /api/traffic accepts ?source= so each
can be inspected separately:

- Stats.record attributes events to a source; DNS queries land in the
  dns slot with no bytes, proxy connections in their protocol slot.
- Snapshot(sort, source) filters domains to one source, reporting that
  source's conns/bytes/lastSeen; source=all (default) aggregates.
- The traffic page breadcrumb gains a source switcher (all/http/https/
  socks5/dns), the view reloads on switch even while paused, and the
  DNS view hides the byte columns and labels the list as DNS queries.

Verified live: source=dns lists queried domains with query counts;
source=https lists proxied domains with bytes; all still aggregates.
2026-08-06 19:35:44 +08:00
wweir 76ab03df73 fix(admin): keep sessions alive across restarts with sliding expiry
Sessions were stored in memory with a fixed 24h TTL, so every process
restart (deploys, crashes) logged everyone out and an active session
could still lapse mid-browsing.

- Admin sessions now persist to admin.session_file (default
  /etc/sower/sessions.json, 0600, atomic temp+rename writes). Sessions
  are restored on startup, dropping already-expired tokens. IO failures
  degrade to the in-memory store.
- Sliding expiry: a session used past the halfway point of its TTL is
  refreshed instead of left to lapse, so active users stay logged in.
  Persisting only on refresh bounds disk writes to a couple per session
  per TTL.
- Login, logout, expiry purge, and refresh all sync the file.

Verified live: after systemctl restart, the pre-restart cookie still
authenticates.
2026-08-06 19:13:35 +08:00
wweir e524592f61 feat(admin): paginate and search the rules list
The rules page froze because block rules (164k+) were all transferred
and rendered at once. The list API is now paginated and searchable:

- GET /api/rules?category=X&q=...&offset=N&limit=M returns a page plus
  the total match count; q is a case-insensitive substring match
  (alloc-free scan in RuleSet.ListFiltered), limit clamps to 1000,
  default page 200.
- POST/DELETE /api/rules now return 204; the frontend refetches the
  currently loaded range so totals stay accurate.
- Rules view: server-side search box with 300ms debounce, load-more
  button appends pages, count badge shows loaded/total.

Measured on the live box: first page 0.5ms, 164k-rule scan ~22ms.
2026-08-06 19:00:09 +08:00
wweir cd5b857328 feat(admin): switchable domain sorting on the traffic page
Traffic view now sorts domains three ways via ?sort= on /api/traffic:
- bytes (default): total traffic, high to low (the former fixed order)
- recent: most recently seen first
- conns: most connections first
Invalid or missing sort falls back to bytes; the top-100 cap is kept.

Frontend gains a segmented sort switcher next to the filter; the footer
label follows the active mode. api.ts picks up the sort query param and
is committed with the user's prettier reformat (pure formatting) since
the change is entangled with it.
2026-08-06 18:51:28 +08:00
wweir f6423a617e fix(web): correct rangeNote derived value, restore rule/conn totals
Review fixes for the monitoring overhaul:

- rangeNote used $derived(() => ...) which evaluates to the function
  itself, rendering the function source in the footer; switch to
  $derived.by so it evaluates to the time-range string.
- Restore the rule-count and cumulative-connection totals that the
  Overview rewrite dropped: 规则命中 card now shows configured rule
  counts, 活跃连接 card shows cumulative per-protocol connections.
- formatUptime: show bare seconds under one minute instead of '0m 13s'.
- Reformat format.ts to the project's prettier style (tabs).
2026-08-06 17:26:29 +08:00
wweir b4cb204e3f feat(admin): OTEL metrics, real-time rates and in-process history
Monitoring overhaul for the admin console:

- Add internal/metrics: OpenTelemetry meter with Prometheus exporter
  (counters for bytes/dns/connections/rule hits, updown counter for
  active connections, histogram for connection duration, process
  gauges). Served unauthenticated at GET /metrics; an OTLP exporter
  can be added later without touching call sites.
- Fix uptime: TrafficSnapshot.Uptime was a time.Duration serialized
  as nanoseconds while the frontend renders seconds; it is now int64
  seconds. Stats is created before rule loading so uptime tracks the
  process start.
- Extend admin.Stats: per-protocol active connections (idempotent
  decrement on Close), 60s sliding-window rates, rule-hit counters,
  process resources, and a bounded in-process history ring (1h at 5s
  sampling, gap-clamped) served at GET /api/history.
- Router route observer: block/direct decisions in DialSmart, proxy
  decisions in DialProxyOnly (covers HTTP/HTTPS paths), exactly once
  per connection.
- Fix socks5 handler closing the raw conn instead of the wrapped one,
  which would have leaked active-connection counts.
- Frontend: Overview gains rate cards, active connections, rule hits,
  system stats, and four hand-rolled SVG area charts (traffic, DNS/
  conns, active per protocol, rule hits) with hover readouts.
2026-08-06 17:17:24 +08:00
wweir 0c99db7ea9 feat(web): adopt WingGate warm-paper palette for admin console
Port the "glass reading room" light theme from WingGate/web: warm
paper canvas (#eaebe5) with a soft light-falloff texture, warm ink
text, hairline warm borders, and teal (#0f766e) reserved for primary
actions, focus rings, and selection. Corner radius moves to 12px to
match. Token-only change; components consume the same semantic roles.
2026-08-06 16:16:11 +08:00
wweir 6e3c4a0fbd fix(web): switch admin console to light theme
Replace the dark-only shadcn palette with the standard light neutral
tokens and declare color-scheme: light. All components consume tokens,
so no per-component changes are needed.

Note: main.ts still adds the dark class to <html>; it is visually
harmless now that :root carries light tokens, but should be removed
when its pending formatting changes settle.
2026-08-06 15:36:42 +08:00
wweir ef8b3fc72e feat(admin): polish console interactions and visual states 2026-08-06 14:30:07 +08:00
wweir 1981c61f0c feat(admin): share port 80 with http proxy via host-based dispatch 2026-08-06 13:55:55 +08:00
wweir d4825deed2 fix(admin): harden console review findings 2026-08-06 10:30:47 +08:00
wweir 1e87298d15 feat(admin): embedded admin console for rule management and traffic stats
- internal/admin: session-cookie auth server, rule CRUD API, bounded
  per-domain traffic stats with pre-parse byte accounting
- cmd/sower: wrap proxied connections and DNS queries into stats, serve the
  admin console on its own listener
- web/: Svelte 5 + Vite admin console with shadcn-svelte UI and an advanced
  breadcrumb header (section/context dropdowns, favorites), embedded via
  go:embed and committed to web/dist
- Makefile: sower build depends on the frontend build (bun); sowerd does not
- docs: ARCHITECTURE, README, and plan updated for the admin surface
2026-08-06 08:56:33 +08:00
wweir 1daeb36d54 feat(config): add [admin] section with validation and examples
Admin is disabled by default, binds to loopback, and requires a non-empty
password when enabled. Zero-value configs skip admin validation so existing
tests and example files keep loading.
2026-08-06 08:56:27 +08:00
wweir ce5cf284b7 feat(router): add thread-safe RuleSet for runtime rule management
RuleSet retains the raw rule list alongside the suffix tree so rules can be
listed, added and removed at runtime. Removal rebuilds the tree from the
retained list; startup batch loading keeps the old single-compaction flow.
Router.BlockRule/DirectRule/ProxyRule migrate to *RuleSet.
2026-08-06 08:56:22 +08:00
wweir 48883b0783 sec(sower): strengthen auth checksum against brute force
sumChecksum used binary.Uvarint over the digest, which stops at the
first byte with a clear continuation bit: ~50% of checksums collapsed
to 7 bits of entropy (~15 bits effective), making the auth check
brute-forceable. Take a fixed 64-bit window of a SHA-256 digest
instead. Wire layout is unchanged (checksum stays 8 bytes), and the
randomized field also removes the "high bytes all zero" fingerprint.
2026-08-05 14:04:59 +08:00
wweir bbab57f2a0 fix(sowerd): retry empty reads during protocol probe
A (0, nil) Read is legal on TCP though rare; previously it aborted the
connection. Retry in a loop instead, bounded by the caller's probe
deadline.
2026-08-05 14:04:46 +08:00
wweir c76dadbda1 feat(sowerd): support WebSocket upgrade in site routing
Allow HTTP upgrade requests (WebSocket/CONNECT) through the fallback
reverse proxy instead of rejecting them with 400. Once the connection
is hijacked it is owned by the ReverseProxy upgrade goroutine, so
handleConn skips closing it; the single-conn listener is closed to
unblock Serve while the tunnel stays alive.
2026-08-05 14:04:38 +08:00
wweir e83c130881 fix(sowerd): clear probe read deadline before protocol unwrap
Previously the probe read deadline (1s absolute time set at handleConn
entry) remained active while handler.Unwrap ran its io.ReadFull on the
protocol header. A slow client could let Unwrap be interrupted by the
remaining sliver of the probe timeout, causing spurious auth/decode
failures and fallback to the fake site. Clear the deadline once at the
top of the probeMatch case, before Unwrap, so the protocol handshake
gets its own time budget consistent with the subsequent relay path.
2026-06-28 21:16:31 +08:00
wweir 62ca487087 docs: update docs and config examples for trojan removal
- ARCHITECTURE.md: remove trojan transport references, update sowerd flow
- README.md: remove trojan usage instructions, simplify remote type docs
- docs/plans/site-routing.md: update to reflect sower-only probe model
- config/*.toml,*.yaml: remove trojan from example config type options
2026-06-25 09:20:37 +08:00
wweir 474749d865 test(sowerd): add fallback, auth-failure and SNI routing tests
- TestHandleConnRoutesFallbackBySNI: verifies TLS SNI → site route
- TestHandleConnFallsBackToFakeSiteWhenSNIMisses: no-route SNI → fake_site
- TestHandleConnFallsBackAfterSowerAuthFailure: wrong password → fallback
- Add startHTTP1TCPServer and startRawTCPServer test helpers
- Fix config test: trojan → sower for TLS client hello validation
2026-06-25 09:20:34 +08:00
wweir e0874548b7 refactor: remove trojan protocol support
Trojan transport, config options, and tests are removed entirely.
The sowerd probe abstraction now handles only the sower protocol;
adding new transports requires only a new proxyProtocolHandler impl.

- transport/trojan/: deleted
- cmd/sower/proxy.go: remove trojan import and branch
- config/sower.go,sowerd.go: remove trojan from type options and validate
- transport/transport_test.go: remove trojan test cases
2026-06-25 09:20:31 +08:00
wweir 91dc8b7a87 feat(sowerd): add protocol probe abstraction and refactor handleConn
Introduce proxyProtocolHandler interface with Probe/Unwrap methods,
extracting protocol detection from handleConn into a clean pipeline:
probe first bytes → match handler → Unwrap, or fallback by SNI.

- probe.go: probeVerdict enum, proxyProtocolHandler interface,
  sowerProtocolHandler, readProtocolProbe
- handleConn now iterates handlers list, falls through to TLS SNI
  routing on probeNoMatch, and to fake_site on auth failure
- serve443 signature changed: trojan param replaced by []proxyProtocolHandler
2026-06-25 09:20:27 +08:00
wweir 8aee9da6f0 test(transport): add socks5 integration test and clean up address strings 2026-06-24 17:12:06 +08:00
wweir 8a4fb47003 sec(trojan): use constant-time password comparison and clean up IP handling 2026-06-24 17:12:01 +08:00
wweir c482623005 perf(sower): reduce md5 allocation and improve error handling 2026-06-24 17:11:59 +08:00
wweir 7286822ce9 fix(socks5): use correct reply head and improve error handling 2026-06-24 17:11:57 +08:00
wweir ad43316842 test(transport): extract common connection test helpers to internal/conntest 2026-06-24 17:11:52 +08:00
wweir ffa2dc3391 chore(deps): bump golang.org/x/crypto to v0.53.0 and related x/* deps
Upgrade the golang.org/x/* family to their current latest:
- crypto v0.49.0 -> v0.53.0 (only direct dep with an upgrade)
- net, sys, text, mod, sync, tools pulled forward as transitive deps.

No code changes: the sole x/crypto usage (autocert in cmd/sowerd) is
stable across these versions.
2026-06-24 13:37:30 +08:00
wweir 3dec49dec4 test: add regression tests for routing layer separation and fail-fast
- DNS unknown domain forwards upstream; TCP smart dial falls back proxy.
- Transparent HTTP/HTTPS uses proxy-only even when block rule matches.
- Invalid country CIDR and invalid MMDB path fail at construction.
- Access probe cache is instance-scoped; test injects fake prober.
- net.Pipe tests add timeout protection via waitForHandler helper.
- newTestRouter helper wraps NewRouter error handling.
2026-06-24 13:30:48 +08:00
wweir 8498eb1d32 feat(sower): use DialSmart/DialProxyOnly, fail-fast router construction
- Transparent HTTP/HTTPS handlers call DialProxyOnly (proxy-only path).
- SOCKS5 and explicit HTTP proxy handlers call DialSmart (smart route).
- newRouter returns error; AddCountryCIDRs failure cleans up opened MMDB.
- run() defers r.Close() for graceful MMDB reader shutdown.
2026-06-24 13:30:31 +08:00
wweir 51438c93a0 feat(router): split DialSmart/DialProxyOnly, fail-fast on bad MMDB/CIDR, add Close
- Add DialSmart (rule+detect+fallback) and DialProxyOnly (forced proxy)
  as explicit routing entry points; keep Dial as compatibility alias.
- NewRouter now returns (*Router, error); non-empty invalid MMDB path
  is a startup error instead of a silent warning.
- AddCountryCIDRs now returns error; invalid CIDR entries fail fast.
- Add Router.Close() to release geoip2.Reader on shutdown.
- Add country RWMutex so Close() does not race with concurrent reads.
- localSite checks all resolved IPs instead of only the first.
- Move accessCache from package-level global to Router instance field;
  inject accessProbeFunc for testability.
- Disable redirect following in pingClient to avoid misleading probe
  results on redirect responses.
2026-06-24 13:30:18 +08:00
wweir 0c05d04dec docs: codify DNS/transparent/smart routing layer separation in ARCHITECTURE.md
Document that DNS routing intentionally does not mirror smart TCP
routing, transparent HTTP/HTTPS listeners are proxy-only second-stage
handlers, and SOCKS5/explicit HTTP proxy entries use smart routing.
2026-06-24 13:30:06 +08:00
wweir 0547502748 feat(sowerd): route fallback sites by SNI 2026-06-24 09:24:25 +08:00
wweir 160abcf4cc docs: update agent documentation rules 2026-06-24 09:24:17 +08:00
wweir 875f80e999 refactor: replace mem cache with otter 2026-05-14 21:53:16 +08:00
wweir f23f7f6950 docs: document access probe cache 2026-05-14 21:52:58 +08:00
wweir c5b3917592 docs: add recommended sower network setups 2026-04-30 11:45:35 +08:00
wweir ad338598ae fix: harden dns proxy routing v1.5.0 2026-04-25 04:12:42 +08:00
wweir 09e4fc49fc docs: update client routing notes 2026-04-25 03:21:59 +08:00
wweir 0ef0686530 chore(release): package toml client configs 2026-04-25 03:20:41 +08:00
wweir 9c2aa3af10 chore(deps): remove unused error and hcl packages 2026-04-25 03:17:45 +08:00
wweir 5fb9dfcfee fix(router): skip empty country mmdb 2026-04-25 03:17:45 +08:00
wweir f72d88b204 fix(sower): relay transparent tls client hello 2026-04-25 03:17:35 +08:00
wweir 05f0728aa6 feat(sower): harden client rule loading 2026-04-25 03:09:45 +08:00
root d514a52497 docs: clarify transparent https routing flow v1.4.1 2026-03-31 11:53:10 +08:00
root 6f0b7d39b6 fix: preserve tls client hello in transparent https proxy 2026-03-31 11:52:56 +08:00
wweir 75156760db refactor: centralize proxy service startup handling 2026-03-31 10:14:20 +08:00
wweir 23110fc786 docs: clarify config key mapping comments 2026-03-31 10:14:15 +08:00
wweir 71eede9460 fix: align example config with snake case loader 2026-03-31 07:46:32 +08:00
wweir 549ab2a990 fix: fallback to system cache for sowerd service 2026-03-31 07:46:32 +08:00