Skip to content

EN Code Structure

dongle edited this page Oct 18, 2026 · 6 revisions

中文 | English

Code Structure

Repository layout for contributors and downstream developers. For conventions see AGENTS.md in the repo root. FlowWeaver originates from SniShaper and shares some technical foundations (sing-tun data plane, uTLS/ECH handling), but the codebase evolves independently.

Tech Stack

Layer Technology
Backend Go 1.27+, Wails v3 bindings, module name flowweaver
Frontend React 19 + TypeScript + MUI + Vite, @iconify/react icons (Material Symbols / MDI, bundled offline)
Core Mihomo-derived core process (built with the with_gvisor tag; TUN depends on gVisor); subscription-node dialing/latency tests reuse the mihomo adapter as a library
Packaging Windows MSIX (PFX-signed), 12-target cross-platform CLI matrix

Directory Layout

FlowWeaver/
├── main.go                 # Entry point: Wails app assembly + tray setup
├── app/                    # Go backend application layer
│   ├── app_api.go          #   App struct and all frontend-bound APIs
│   ├── app_subscription.go #   Subscription APIs (import/switch/node selection) + node/service latency probes
│   ├── app_fragment.go     #   TLS fragmentation config API (get/set/reset)
│   ├── app_system.go       #   System proxy toggle (registry writes)
│   ├── autostart_*.go      #   Autostart (Windows: Task Scheduler)
│   ├── app_tray*.go        #   Tray: build, session watch, reshow
│   └── app_update*.go      #   Version check, download and self-update install
├── core/                   # Core process management
│   ├── core_client.go      #   Core process launch, RPC client (token-authenticated)
│   ├── core_api.go         #   In-core RPC service (Ping/Shutdown require token match)
│   ├── core_runtime.go     #   Core runtime assembly (TUN, certificates, rules, subscription)
│   └── core_admin_*.go     #   Admin elevation
├── engine and shared feature packages proxy/ and {subscription,singtun,netiface,
│         certmanager,cfpool,dohresolver,rules,tlsfrag,sysproxy,evolution}/   # Moved to snishaper-core
├── pkg/                    # Local feature packages (two)
│   ├── sysproxy/           #   Thin facade over core's sysproxy.Manager
│   └── platform/           #   Platform adapters (e.g. DNS restore)
├── internal/rulesguard/    # Data-guard tests for rules/config.json
├── cli/                    # Headless CLI (build tag: headless)
│   ├── main.go             #   Entry + command dispatch
│   ├── catalog.go          #   Command catalog (single source of truth for help and TUI)
│   └── tui.go              #   Interactive panel
├── frontend/src/           # React SPA, 11 pages
│                           #   Dashboard, Subscription, Proxies, Rules, Routing,
│                           #   DNS, Evolution, Logs, Settings, About, Welcome
├── common/                 # Cross-platform helpers (open file/URL, paths, crash logs)
├── rules/config.json       # Site-group rules
├── config/settings.json    # App settings: ports, TUN (stack/adapter name), theme & appearance mode, TLS fragmentation, Cloudflare IPs, etc.
└── Platform.md / build.md  # Platform support and build matrix docs

Data Flow

Frontend (React HashRouter)
   │  Wails bindings: EventsOn subscriptions + async Go method calls
   ▼
App API (app/app_api.go)
   │
   ├──► snishaper-core/subscription  Clash subscription parsing → nodes/groups/rules
   ├──► snishaper-core/proxy          Proxy server (mode dispatch, multi-protocol dialing, subscription UDP relay)
   ├──► core/                         Core process (TUN, gVisor stack, auto-routing, DNS hijack)
   └──► snishaper-core/{certmanager,dohresolver,cfpool,gfwlist}  Certificates / DoH / CF IP pool / GFWList
  • Frontend state sync: the backend emits app:state / app:state_changed events via emitFrontendState(); the frontend subscribes and refreshes.
  • Subscription activation chain: sub use → subscription rules load through the core's proxy/rules_merge.go and hide the built-in rules → node selections persist to the subscription store → the core's proxy/subscription_dialer.go dials the selected node (TCP/UDP). Nodes carrying their raw Clash config go through the core's subscription/mihomo.go via mihomo adapter.ParseProxy (protocol/transport support identical to the Clash core; node latency tests are the core's URLTest); nodes without raw config fall back to the hand-rolled dialers.
  • TLS fragmentation settings fall back across three levels: per-rule frag > global fragment section in settings.json > tlsfrag.DefaultTLSRF* presets; NumRecords=1 with NumSegments>=2 means TCP-only fragmentation (bytes untouched, cut point moved before the SNI).
  • WireGuard tunnels run on a userspace IP stack — no kernel NIC, no routing table changes; TUN UDP traffic goes through singtun's UDP relay via the currently selected subscription node.
  • TUN data plane only sniffs domains for routing rebuild (reads the first packet for SNI when fake-ip reverse lookup fails) and never rewrites the ClientHello — the TLS transcript hash covers the entire handshake, and modifying bytes in transit causes bad record MAC errors. SNI masquerading happens only at the TLS endpoint (MITM outbound replay).
  • Core RPC isolation: the core and its client authenticate with a one-shot token (Ping/Shutdown both verify it), so overlapping instances cannot kill a freshly started core.

snishaper-core Dependency

The proxy engine comes from github.com/SnishaperTeam/snishaper-core (pinned to v0.4.0 in go.mod, no replace) instead of being forked into this repo:

  • Imported directly here: proxy, certmanager, cfpool, dohresolver, netiface, singtun, subscription, sysproxy, evolution, config, log
  • tlsfrag (core's proxy/proxy.go, proxy/tls_fragment.go) and gfwlist (core's proxy/auto_routing.go) are used inside core and are not imported here
  • pkg/sysproxy is only a thin facade: it holds one core sysproxy.Manager and forwards the original package-level helpers, so call sites are unchanged. Core's Manager keeps the original proxy snapshot and status cache per instance, and the sudo user is injected through sysproxy.WithRunAsUser
  • Product differences are injected rather than forked: config.Brand{Name: core.ProductName} (core.ProductName = "FlowWeaver"), proxy.WithProductName(core.ProductName), and subscription.WithProductName(core.ProductName) at both subscription.NewStore call sites
  • Core builds the subscription download User-Agent from config.Brand (clash-verge/v2.0 <product>). Core v0.3.0 and earlier hardcoded FlowWeaver there and logged every sniffed SNI at info level; since v0.4.0 sysproxy and evolution also live in the library (each app used to carry a byte-identical copy), so this repo requires ^v0.4.0
  • netiface is per-instance: create with netiface.NewSelector() and refresh with (*netiface.Selector).Invalidate() after TUN start/stop, replacing the old package-global netiface.InvalidateCache()

Build and Test

  • Full build: .\build_windows.ps1 -All -Silent (per-platform: -Platform windows -Arch x64 -Type cli,gui; backend/frontend only: the 3-tuple -Build windows,gui,backend, where the scope is the third element — -Build backend alone is invalid. Linux/macOS: ./build.sh; 12-target matrix in build.md)
  • Go build check: go build -tags with_gvisor ./...
  • Go tests: go test -tags with_gvisor ./... (flowweaver/app, flowweaver/common, flowweaver/core, flowweaver/internal/rulesguard)
  • Go static check: go vet -tags with_gvisor ./...
  • Frontend type check (authoritative): cd frontend && ./node_modules/.bin/tsc --noEmit
  • Headless CLI: see the build section of the CLI Reference

Contributing Notes

  • Commit messages in English, conventional-commit style (feat: / fix: / refactor: ...).
  • New UI copy must be added to the zh / en / ru i18n JSON files in the same change, with matching placeholders.
  • Any change that alters a file's purpose or adds new files must update this Wiki (zh + EN pages) in the same change.
  • Changes to security-sensitive areas (certificates, TUN, system proxy, core RPC token) require maintainer review.

Clone this wiki locally