Skip to content

Netrix 2.0.0: .NET 10, TUN IPv6, signed updates and a new brand - #1

Merged
psnwd merged 46 commits into
mainfrom
modernize-net10
Oct 11, 2026
Merged

psnwd merged 46 commits into
mainfrom
modernize-net10

Conversation

@psnwd

@psnwd psnwd commented Oct 11, 2026

Copy link
Copy Markdown
Owner

Netrix 2.0.0

Moves the project from Netch 1.9.7 to Netrix: .NET 10, helper programs rebuilt from source, new name and logo, IPv6 and update-security fixes, built-in help, and tests. Full list in CHANGELOG.md.

What's in it

New and changed behaviour

  • Web mode: sets the Windows system proxy and restores the old values on stop, even after a crash.
  • TUN IPv6: IPv6 now goes through the tunnel. If the server has no working IPv6 path, Netrix detects it at start and rejects IPv6 connections so apps fall back to IPv4 (no hang, no leak).
  • TUN speed: download and upload shown separately, read from the tunnel.
  • Stop is robust: each part stops on its own with a 15 s limit and the window never stays in "Stopping". Fixed a bug where stopping an already-dead helper process threw from its own catch block.
  • NAT test: default STUN server is now stun.1und1.de; the list only has servers that support the NAT test.
  • Help window, tooltips, Enter/Esc shortcuts, Segoe UI font, new "Global Bypass Apps" mode.

Security

  • The updater now requires a signature (ECDSA P-256, sign.ps1), not only a hash from the same release. Failed updates roll back; the asset is matched by name.

Platform and build

  • .NET 6 to .NET 10; NuGet packages refreshed; WebRequest replaced by HttpClient; netsh replaced by the IP Helper API.
  • tun2socks rebuilt from recovered source, pcap2socks vendored and built from source, aiodns on Go 1.25 with a response cache.
  • Builds go to .build\dev (default) or .build\release (-Release). CI updated to match and runs the tests.

Rebrand and cleanup

  • Netch X to Netrix (project, namespace, folders, Netrix.exe, logo and icon, repo links). The original Netch is credited in the README and NOTICE.txt (GPLv3).
  • Removed: Telegram and website links, RedirectorTester, Other/_Archive, Chinese issue templates. Chinese comments translated to English; the zh-CN interface translation is unchanged.

Things to know before merging

  • Netrix does not update an old Netch install. The startup entry, firewall rule names and single-instance ID are now "Netrix".
  • The first release must be signed, otherwise in-app update refuses it (users can still install from the release page). Needs the repository secret UPDATE_SIGNING_KEY (private key of the public key in Netrix/Services/UpdateSignature.cs).
  • Release tags can be 2.0.0 or v2.0.0.

Testing

  • dotnet test Tests\Tests.csproj -p:Platform=x64: 42 tests pass.
  • Release build produced locally with .\build.ps1 -Release (Netrix.exe reports 2.0.0).
  • Verified only in tests and rendered windows: TUN start/stop with real traffic, the new Stop behaviour with the network unplugged, Process/Share/Web modes, and the update flow have not been run end to end.
  • Not yet run: the GitHub Actions workflows (new build steps and the tag flow in release.yml).

After merging

  1. Add the UPDATE_SIGNING_KEY secret.
  2. Tag 2.0.0 (or v2.0.0) to run the release workflow, then check the release has Netrix.7z and Netrix.7z.sig.

psnwd and others added 30 commits October 10, 2026 23:42
- Target net10.0-windows; global.json rolls forward to latest feature band
- Declare Windows 8.1 as minimum OS (inet_pton/ntohs, CA1416 on SDK 10)
- CsWin32 0.1.588-beta -> 0.3.360: fix HWND, ADDRESS_FAMILY and WIN32_ERROR
  call sites; PortHelper reads the TCP table from a managed buffer (also
  stops leaking the AllocHGlobal block)
- Stun.Net 10: use Socks5UdpProxy directly (ProxyFactory overload removed)
- Bump Serilog, Fody, Threading, TraceEvent, MaxMind, TaskScheduler and
  others; drop System.Text.Encoding.CodePages (in-box)
- WinForms: hide runtime-only SyncGlobalCheckBox props from the designer
  (WFO1000); LogForm overrides OnFormClosing instead of obsolete OnClosing
- HMBSbige.SingleInstance stays on 6.0.0 (7.x API is incompatible)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Bugs found by the new tests:
- Socks5 share link wrote the password as &user=, which made parsing throw
  on the duplicate key; now &pass=
- ShareLink.ParseUri looked up util "Socks5" but its TypeName is "SOCKS"
- ShareMode.Argument was a field, so JSON dropped it; now a property
- WireGuard share link cast to VMessServer and its parse path threw;
  implement wireguard://privkey@host:port?publickey=..&address=..&mtu=..

Tests (MSTest, net10): share-link round-trips for Trojan, SS, SSR, VMess,
VLESS, Socks5, WireGuard; Mode JSON round-trips; Setting loads from {};
PortHelper finds a listening port's owner process.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Bump checkout, cache, setup-go, setup-msbuild, upload-artifact,
  action-gh-release and stale to current majors
- Replace unmaintained actions-rs/toolchain with dtolnay/rust-toolchain
- Install .NET 10 in build and release workflows, run dotnet test
- Drop vs-prerelease (projects use the stable v143 toolset)
- dependabot: gomod directory is /Other/aiodns

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
.docs holds the plan, architecture, task list, QA matrix and risks; root
CLAUDE.md imports .docs/CLAUDE.md. README now states .NET 10.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Upgrading every dependency of a 2022 tag (v5.0.16) each build is the
likeliest cause of the CI Build step failing; unverified until CI reruns.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- aiodns: miekg/dns 1.1.43 -> 1.1.73, go 1.25.0 (c-shared build verified)
- Storage/README.md: provenance + sha256 for bundled binaries
- build.ps1: GeoIP download aborts the build on failure
- TASK.md: P4 status

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
v7 exchanges int commands, not strings, and has no Received stream:
- Show request is now int command 1; handler is synchronous and posts to
  the UI thread with BeginInvoke
- identifier is plain "Netch" (the library uses it for the pipe name, so no
  Global\ prefix): single instance is now per Windows session
- drop unused Constants.Parameter.Show

Needs manual QA: launching a second copy must focus the first window.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…tions

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
CI log showed the SagerNet/gvisor fork failing with 'build constraints
exclude all Go files' under current Go. Install Go 1.18.10 (the version
upstream v2ray-core v5.0.16 CI uses) next to stable and point the
v2ray-sn script at it through GO118. aiodns keeps the stable toolchain.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
go mod tidy picked v1.18.0, whose protobytes dependency needs Go 1.20
(slice-to-array conversion) and fails under Go 1.18. v1.11.0 declares
go 1.18 and has no protobytes dependency.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
README listed WebMode but ModeType had no such value.

- ModeType.WebMode appended (persisted enum values stay stable)
- WebMode model with a ProxyOverride bypass string
- WebController sets HKCU Internet Settings to socks=host:port, notifies
  WinINet, and restores the previous values on stop
- sample mode file Storage/mode/System Proxy.json
- tests: JSON round-trip and sample file load

Only apps that honour the Windows proxy are affected. A hard crash leaves
the proxy set until cleared in Windows settings. Needs manual QA.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Previous proxy values are saved to data\webmode.restore.json while the
mode runs and put back on the next launch. Runs after the single-instance
check so a second launch cannot undo a running WebMode.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
WebUtil now uses one shared HttpClient (system proxy by default) and
HttpRequestMessage. Non-success status still throws (HttpRequestException
instead of WebException), the timeout still applies, subscription updates
still go through an optional proxy server via a per-call client. File
downloads limit the timeout to the response headers.

Tests run against a local HttpListener: UA, 404, timeout, file download
with progress, proxy.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Updater: if renaming or moving files fails, restore every *.old file
  instead of leaving a half-installed app
- MainForm: take the release asset named like the hash-table entry, not
  assets[0]; compare hashes case-insensitively; delete a download whose
  hash is wrong
- tests use a real zip (7za) and fail without the rollback
- InternalsVisibleTo Tests (GenerateAssemblyInfo is off, so via
  AssemblyInfo.cs)

The hash still comes from the same release as the file; a signature would
be needed to protect against a compromised release.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Guard: started keyword, failed keyword, Stop kills the child (runs a
  copied cmd.exe; OnStartFailed overridden so the log is not opened)
- WebController: start sets and stop restores proxy values, crash then
  RecoverAfterCrash, against a scratch registry key (KeyPath made
  internal static for this)
- V2rayConfigUtils: inbound, outbound protocol per server type, socks
  auth, vmess address and port

Drivers, admin and real binaries are not covered (NF, TUN, Pcap).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Starting a controller whose process exited before printing a started or
failed keyword waited the full 50 s timeout. Once both output pipes are
closed the process is done: stop, run OnStartFailed and throw at once.
A started keyword still wins when both happen in the same poll.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Ghidra 12.1.4 (GhidraMCP) analysis of Storage/tun2socks.bin:
- it is the private module rixcloud.moe/aiocloud/tun2socks (go1.17.1,
  c-shared), not xjasonlyu/tun2socks; source is not obtainable
- RECOVERY.md: package map, dependency versions from build info, the C ABI
  and behaviours, and what is still unrecovered
- main.go.recovered: reconstruction of package main (tun_dial/init/free/
  getUP/getDL, newRanger); not compiled, not verified
- recovered/decompiled: raw pseudo-C for main, proxies, proxies/socks,
  adns and tools

nfdriver.sys is identified (NetFilter SDK 2 v1.6, closed source) and not
reconstructed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@psnwd psnwd self-assigned this Oct 11, 2026
@psnwd psnwd added the enhancement New feature or request label Oct 11, 2026
… permissions, reusable build for releases, grouped weekly Dependabot
@psnwd
psnwd merged commit 9b98487 into main Oct 11, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant