Compare commits

..

1601 Commits

Author SHA1 Message Date
世界
5b29fd3be4 ccm,ocm: add request ID context to HTTP request logging 2026-03-14 16:14:24 +08:00
世界
016e5e1b12 service/ocm: add default OpenAI-Beta header and log websocket error body
The upstream OpenAI WebSocket endpoint requires the
OpenAI-Beta: responses_websockets=2026-02-06 header. Set it
automatically when the client doesn't provide it.

Also capture and log the response body on non-429 WebSocket
handshake failures to surface the actual error from upstream.
2026-03-14 16:07:58 +08:00
世界
92b3bde862 ccm,ocm: strip reverse proxy headers from upstream responses 2026-03-14 15:58:33 +08:00
世界
64f7349fca service/ocm: unify websocket logging with HTTP request logging 2026-03-14 15:52:27 +08:00
世界
527372ba74 ccm,ocm: auto-detect plan weight for external credentials via status endpoint 2026-03-14 15:44:04 +08:00
世界
c6c07cb52f service/ccm: update oauth token URL and remove unnecessary Accept header 2026-03-14 15:19:59 +08:00
世界
913f033d1a ccm,ocm: improve balancer least_used with plan-weighted scoring and reset urgency
Scale remaining capacity by plan weight (Pro=1, Max 5x=5, Max 20x=10
for CCM; Plus=1, Pro=10 for OCM) so higher-tier accounts contribute
proportionally more. Factor in weekly reset proximity so credentials
about to reset are preferred ("use it or lose it").

Auto-detect plan weight from subscriptionType + rateLimitTier (CCM)
or plan_type (OCM). Fetch /api/oauth/profile when rateLimitTier is
missing from the credential file. External credentials accept a
manual plan_weight option.
2026-03-14 15:10:13 +08:00
世界
688f8cc4ef service/ocm: only log new credential assignments and add websocket logging 2026-03-14 14:44:24 +08:00
世界
de51879ae9 service/ccm: only log new credential assignments and show context window in model 2026-03-14 14:31:21 +08:00
世界
2943e8e5f0 ccm,ocm: mark credentials unusable on usage poll failure and trigger poll on upstream error 2026-03-14 14:31:21 +08:00
世界
e2b2af8322 service/ccm: allow extended context (1m) for all credentials
1m context is now available to all subscribers and no longer
consumes Extra Usage.
2026-03-14 13:48:23 +08:00
世界
c8d8d0a3e7 service/ccm: reject fast-mode external credentials 2026-03-13 23:31:19 +08:00
世界
8cd7713ca9 ccm,ocm: fix reserveWeekly default and remove dead reserve fields 2026-03-13 23:29:06 +08:00
世界
566abb00cd ccm,ocm: add limit options and fix aggregated utilization scaling
Add limit_5h and limit_weekly options as alternatives to reserve_5h
and reserve_weekly for capping credential utilization. The two are
mutually exclusive per window.

Fix computeAggregatedUtilization to scale per-credential utilization
relative to each credential's cap before averaging, so external users
see correct available capacity regardless of per-credential caps.

Fix pickLeastUsed to compare remaining capacity (cap - utilization)
instead of raw utilization, ensuring fair comparison across credentials
with different caps.
2026-03-13 23:13:44 +08:00
世界
ae7550b465 ocm: preserve websocket rate limit event fields 2026-03-13 22:24:08 +08:00
世界
63d4cdffef ocm: rewrite codex.rate_limits WebSocket events for external users
The HTTP path rewrites utilization headers for external users via
rewriteResponseHeadersForExternalUser to show aggregated values.
The WebSocket upgrade headers were also rewritten, but in-band
codex.rate_limits events were forwarded unmodified, leaking
per-credential utilization to external users.
2026-03-13 21:54:47 +08:00
世界
5516d7b045 ccm,ocm: fix passive usage update for WebSocket connections
WebSocket 101 upgrade responses do not include utilization headers
(confirmed via codex CLI source). Rate limit data is delivered
exclusively through in-band events (codex.rate_limits and error
events with status 429).

Previously, updateStateFromHeaders unconditionally bumped lastUpdated
even when no utilization headers were found, which suppressed polling
and left credential utilization permanently stale during WebSocket
sessions.

- Only bump lastUpdated when actual utilization data is parsed
- Parse in-band codex.rate_limits events to update credential state
- Detect in-band 429 error events to markRateLimited
- Fix WebSocket 429 retry to update old credential state before retry
2026-03-13 21:42:05 +08:00
世界
c639c27cdb ccm,ocm: fix reverse session shutdown race 2026-03-13 21:31:23 +08:00
世界
f0022f59a2 ccm,ocm: block utilization decrease within same rate-limit window
updateStateFromHeaders unconditionally applied header utilization
values even when they were lower than the current state, causing
poll-sourced values to be overwritten by stale header values.

Parse reset timestamps before utilization and only allow decreases
when the reset timestamp changes (indicating a new rate-limit
window). Also add math.Ceil to CCM external credential for
consistency with default credential.
2026-03-13 21:06:32 +08:00
世界
9e7d863ee7 ccm,ocm: fix connector-side bufio data loss in reverse proxy
connectorConnect() creates a bufio.NewReader to read the HTTP 101
upgrade response, but then passes the raw conn to yamux.Server().
If TCP coalesces the 101 response with initial yamux frames, the
bufio reader over-reads into its buffer and those bytes are lost
to yamux, causing session failure.

Wrap the bufio.Reader and raw conn into a bufferedConn so yamux
reads through the buffer first.
2026-03-13 21:06:32 +08:00
世界
d5c6c6aed2 ccm,ocm: fix data race on reverseContext/reverseCancel
InterfaceUpdated() writes reverseContext and reverseCancel without
synchronization while connectorLoop/connectorConnect goroutines
read them concurrently. close() also accesses reverseCancel without
a lock.

Fix by extending reverseAccess mutex to protect these fields:
- Add getReverseContext()/resetReverseContext() methods
- Pass context as parameter to connectorConnect
- Merge close() into a single lock acquisition
- Use resetReverseContext() in InterfaceUpdated()
2026-03-13 21:06:32 +08:00
世界
4d89d732e2 ccm,ocm: reset connector backoff after successful connection
The consecutiveFailures counter in connectorLoop never resets,
causing backoff to permanently cap at 30-45s even after a
connection that served successfully for hours.

Reset the counter when connectorConnect ran for at least one
minute, indicating a successful session rather than a transient
dial/handshake failure.
2026-03-13 21:05:50 +08:00
世界
f6821be8a3 ccm,ocm: unify HTTP request retry with fast retry and exponential backoff 2026-03-13 20:05:54 +08:00
世界
03b01efe49 Fix reverse external credential handling 2026-03-13 19:36:51 +08:00
世界
16aeba8ec0 ccm,ocm: add reverse proxy support for external credentials
Allow two CCM/OCM instances to share credentials when only one has a
public IP, using yamux-multiplexed reverse connections.

Three credential modes:
- Normal: URL set, reverse=false — standard HTTP proxy
- Receiver: URL empty — waits for incoming reverse connection
- Connector: URL set, reverse=true — dials out to establish connection

Extend InterfaceUpdated to services so network changes trigger
reverse connection reconnection.
2026-03-13 18:51:02 +08:00
世界
283a5aacee ccm,ocm: strip reverse proxy headers before forwarding to upstream 2026-03-13 04:54:11 +08:00
世界
8d852bba9b ccm,ocm,ssmapi: fix HTTP/2 over TLS with h2c handler
aTLS.NewListener returns *LazyConn, not *tls.Conn, so Go's
http.Server cannot detect TLS via type assertion and falls back
to HTTP/1.x. When ALPN negotiates h2, the client sends HTTP/2
frames that the server fails to parse, causing HTTP 520 errors
behind Cloudflare.

Wrap HTTP handlers with h2c.NewHandler to intercept the HTTP/2
client preface and dispatch to http2.Server.ServeConn, consistent
with DERP, v2rayhttp, naive, and v2raygrpclite services.
2026-03-13 04:52:31 +08:00
世界
29c8794f45 ccm,ocm: check credential file writability before token refresh
Refuse to refresh tokens when the credential file is not writable,
preventing server-side invalidation of the old refresh token that
would make the credential permanently unusable after restart.
2026-03-13 03:27:42 +08:00
世界
c8d593503f ccm,ocm: watch credential_path and allow delayed credentials 2026-03-13 02:47:06 +08:00
世界
a8934be7cd ccm/ocm: Add external credential support for cross-instance usage sharing
Extract credential interface from *defaultCredential to support both
default (OAuth) and external (remote proxy) credential types. External
credentials proxy requests to a remote ccm/ocm instance with bearer
token auth, poll a /status endpoint for utilization, and parse
aggregated rate limit headers from responses.

Add allow_external_usage user flag to control whether balancer/fallback
providers may select external credentials. Add status endpoint
(/ccm/v1/status, /ocm/v1/status) returning averaged utilization across
eligible credentials. Rewrite response rate limit headers for external
users with aggregated values.
2026-03-13 01:47:45 +08:00
世界
7aef716ebc ccm/ocm: Add multi-credential support with balancer and fallback strategies 2026-03-13 00:50:04 +08:00
世界
7df171ff20 Bump version 2026-03-11 21:31:42 +08:00
世界
46eda3e96f cronet-go: Update chromium to 145.0.7632.159 2026-03-11 21:31:42 +08:00
世界
727a9d18d6 documentation: Update descriptions for neighbor rules 2026-03-11 21:31:42 +08:00
世界
20f60b8c7b Add macOS support for MAC and hostname rule items 2026-03-11 21:31:42 +08:00
世界
84b0ddff7f Add Android support for MAC and hostname rule items 2026-03-11 21:31:42 +08:00
世界
811ea13b73 Add MAC and hostname rule items 2026-03-11 21:31:41 +08:00
世界
bdb90f0a01 Bump version 2026-03-11 21:30:11 +08:00
世界
c9ab6458fa tun:Fix auto_redirect dropping SO_BINDTODEVICE traffic 2026-03-11 21:30:11 +08:00
世界
16a249f672 tailscale: Fix system interface rules 2026-03-11 21:30:03 +08:00
世界
54468a1a2a platform: Add f-droid update helpers 2026-03-11 20:41:29 +08:00
世界
8289bbd846 Add Alpine APK packaging to CI build
Add fpm-based Alpine APK packaging alongside existing DEB/RPM/Pacman
packages. Alpine APKs use `linux` in the filename to distinguish from
OpenWrt APKs which use the `openwrt` prefix.
2026-03-11 20:41:29 +08:00
世界
49c450d942 ccm/ocm: Fix missing metering for 1M context and /fast mode
CCM: Fix 1M context detection - use prefix match for versioned
beta strings (e.g. "context-1m-2025-08-07") and include cache
tokens in the 200K threshold check per Anthropic billing docs.

OCM: Add GPT-5.4 family pricing (standard/priority/flex) with
extended context (>272K) premium pricing support. Add context
window tracking to usage combinations, mirroring CCM's pattern.
Update normalizeGPT5Model defaults to latest known models.
2026-03-11 20:41:29 +08:00
世界
a7ee943216 Fix tailscale connections 2026-03-11 00:27:15 +08:00
世界
8bb4c4dd32 documentation: Update ocm/ccm examples 2026-03-10 22:04:12 +08:00
世界
67621ee6ba Fix OCM websocket proxy lifecycle and headers 2026-03-10 22:04:11 +08:00
世界
a09ffe6a0f ccm/ocm: Add by_user_and_week cost summary 2026-03-10 22:04:11 +08:00
世界
e0be8743f6 ocm: Add Responses WebSocket API proxy and fix client config docs
Support the OpenAI Responses WebSocket API (`wss://.../v1/responses`)
for bidirectional frame proxying with usage tracking.
Fix Codex CLI client config examples to use profiles and correct flags.

Update openai-go v3.24.0 → v3.26.0.
2026-03-10 22:04:11 +08:00
世界
0b04528803 tailscaile: Fix using TUN auto redirect with tailscale system interface 2026-03-10 22:04:11 +08:00
世界
65875e6dac tailscale: Use system dialer for system interface
* Revert "Fix netstack TCP connections with system interface
2026-03-10 19:50:16 +08:00
世界
4d6fb1d38d Fix legacy DNS client_subnet options not working 2026-03-09 20:18:47 +08:00
世界
305b930d90 release: Fix default config 2026-03-09 20:18:43 +08:00
世界
bc3884ca91 release: Add openwrt apk build 2026-03-09 20:18:40 +08:00
世界
df0bf927e4 Fix missing with_gvisor build tag for tailscale 2026-03-09 20:18:28 +08:00
世界
efe20ea51c release: Backport Go 1.25 to macOS 10.13 2026-03-09 20:13:36 +08:00
世界
e21a72fcd1 Fix websocket connection and goroutine leaks in Clash API
Co-authored-by: traitman <112139837+traitman@users.noreply.github.com>
2026-03-09 20:06:34 +08:00
世界
e1477bd065 documentation: Update cronet-go descriptions 2026-03-09 20:06:34 +08:00
世界
aa495fce38 Fix local DNS transport CNAME chain broken with systemd-resolved
Replace D-Bus ResolveRecord API with direct raw DNS queries to upstream
servers obtained from systemd-resolved's per-interface link properties.
2026-03-09 20:06:34 +08:00
世界
9cd60c28c0 tailscale: Fix inbound UDP packet connection 2026-03-09 20:06:34 +08:00
Heng lu
2ba896c5ac Fix netns fd leak in ListenNetworkNamespace 2026-03-09 20:06:34 +08:00
Oleg Artyomov
1d388547ee service/ccm: strip Accept-Encoding before forwarding to avoid untracked usage
When clients (e.g. Node.js Anthropic SDK) explicitly set Accept-Encoding: gzip,
Go's http.Transport does not transparently decompress the response body, because
it only does so when it added the header itself. This causes CCM's json.Unmarshal
to receive raw gzip bytes, silently failing to parse usage data and leaving the
usage counter unchanged.

Fix: remove Accept-Encoding from the outgoing proxy request. Transport adds it
automatically and transparently decompresses response.Body before CCM reads it.

Wire compression (CCM→Anthropic) is preserved — Transport still negotiates gzip.
Only CCM→localhost path is affected; compression on loopback has no practical
benefit.
2026-03-09 20:06:34 +08:00
世界
e343cec4d5 Fix legacy DNS defaults on final transport 2026-03-09 20:06:34 +08:00
世界
d58efc5d01 cronet-go: Fix library search path 2026-03-09 20:06:34 +08:00
世界
4b26ab16fb Bump version 2026-03-07 16:13:23 +08:00
世界
0e27312eda Update Go to 1.25.8 2026-03-07 16:13:23 +08:00
世界
4e0a953b98 sing: Revert "Relax domain name validation to support non-standard characters" 2026-03-07 15:44:40 +08:00
世界
27c5b0b1af Fix DNS exchange failure and recursion deadlock in connector
Co-authored-by: everyx <lunt.luo@gmail.com>
2026-03-06 15:31:22 +08:00
dyhkwong
84019b06d9 Fix v2ray HTTP transport server 2026-03-06 10:13:39 +08:00
世界
7fd21f8bf4 Bump version 2026-03-05 21:46:27 +08:00
世界
88695b0d1f Rename branches and update release workflows
stable-next → oldstable, main-next → stable, dev-next → testing, new unstable
2026-03-05 21:12:02 +08:00
世界
fb269c9032 tun: Fix darwin batch loop not exit on EBADF 2026-03-05 20:38:19 +08:00
世界
e62dc7bfa2 Fix rule_set_ip_cidr_accept_empty not working 2026-03-04 11:48:22 +08:00
世界
f295e195b5 tailscale: Fix netstack TCP connections with system interface 2026-03-03 22:06:54 +08:00
世界
ab76062a41 Fix fake-ip address allocation 2026-03-03 21:37:24 +08:00
世界
d14417d392 Fix naive client close 2026-03-03 21:21:09 +08:00
世界
96c5c27610 sing: reject IP literals in IsDomainName 2026-03-03 21:21:09 +08:00
世界
91f92bee49 release: Unify default build tags and linker flags into shared files
Move hardcoded build tags and ldflags from Makefile, Dockerfile, CI
workflows, and local build scripts into canonical files under release/:

- release/DEFAULT_BUILD_TAGS (Linux common archs, Darwin, Android)
- release/DEFAULT_BUILD_TAGS_WINDOWS (includes with_purego)
- release/DEFAULT_BUILD_TAGS_OTHERS (no with_naive_outbound)
- release/LDFLAGS (shared linker flags)
2026-03-03 21:21:09 +08:00
世界
1803471e02 endpoint: Fix UDP resolved destination 2026-03-02 13:55:26 +08:00
世界
3de56d344e Update external dependencies 2026-03-02 06:53:10 +08:00
世界
c71abbdfb8 Update dependencies 2026-03-02 06:52:35 +08:00
世界
ed15121e95 sing: Relax domain name validation to support non-standard characters 2026-03-01 19:45:19 +08:00
世界
46c6945da5 documentation: Update mkdcos-material 2026-03-01 18:37:31 +08:00
traitman
1beb4cb002 clash-api: Fix websocket connection not closed after config reload via SIGHUP
Co-authored-by: TraitMan <traitman@maildog.top>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-03-01 12:30:43 +08:00
dyhkwong
4c65fea1ac Fix IPv6 local DNS on Windows 2026-03-01 12:30:43 +08:00
世界
8ae93a98e5 Remove overdue deprecated features 2026-03-01 12:30:43 +08:00
世界
6da7e538e1 Bump version 2026-02-28 14:42:39 +08:00
世界
13e6ba4cb2 Update tfo-go 2026-02-27 19:55:32 +08:00
世界
93b7328c3f Fix missing Tailscale in ProxyDisplayName 2026-02-27 19:39:52 +08:00
世界
11dc5bcbe1 Fixes in cronet-go 2026-02-27 19:39:52 +08:00
世界
fa3ab87b11 platform: Fix gorelease build 2026-02-27 15:07:16 +08:00
世界
9bd9e9a58b dialer: use KeepAliveConfig for TCP keepalive 2026-02-27 14:58:06 +08:00
世界
9d6dee7451 release: Fix pacman package 2026-02-27 14:58:06 +08:00
世界
9c2cdc7203 Fix per-outbound bind_interface 2026-02-27 14:58:06 +08:00
世界
65150f5cc3 platform: Improve OOM killer for iOS 2026-02-27 14:58:06 +08:00
世界
21a1512e6c tailscale: Fix AdvertiseTags 2026-02-27 14:58:06 +08:00
世界
cf4791f1ad platform: Improve iOS OOM killer 2026-02-26 14:13:32 +08:00
世界
0bc66e5a56 service/ccm,ocm: Fixes and improvements 2026-02-26 13:36:46 +08:00
世界
d48236da94 Fix wireguard reserved 2026-02-24 15:49:52 +08:00
世界
4c05d7b888 Add advertise tags support for Tailscale endpoint 2026-02-24 15:31:57 +08:00
世界
94ed42caf1 Bump version 2026-02-23 18:17:47 +08:00
世界
e0c18cc3d4 tun: Fix nftablesCreateLocalAddressSets 2026-02-23 18:17:47 +08:00
世界
0817c25f4c release: Fix Docker build for loong64 and mipsle 2026-02-23 16:31:19 +08:00
世界
7745a97cca daemon: Fix started service leak 2026-02-23 14:49:58 +08:00
世界
9bcd715d31 Bump version 2026-02-21 13:55:31 +08:00
世界
6a95c66bc7 Pin Go version to 1.25.7 2026-02-21 13:55:31 +08:00
世界
b5800847ae More linux builds for naive 2026-02-21 13:55:31 +08:00
世界
aa85cbb86e Treat H3 RequestCanceled as closed 2026-02-21 09:31:11 +08:00
世界
c59991420e Minor fixes for naive 2026-02-18 01:26:29 +08:00
世界
c0304b8362 Bump version 2026-02-16 12:46:43 +08:00
世界
d1f1271a02 quic-go: Minor fixes 2026-02-16 12:46:29 +08:00
世界
de4fdbe553 platform: Add semver helper 2026-02-16 11:28:54 +08:00
世界
804606042f Bump version 2026-02-15 21:13:55 +08:00
世界
53f2db3f97 platform: Add windows build 2026-02-15 21:10:44 +08:00
世界
1f2fdec89d release: Fix update_apple_version command 2026-02-15 21:09:14 +08:00
世界
8714c157c9 Fix matching multi predefined 2026-02-15 21:09:06 +08:00
世界
657fba4ca5 Fix matching rule-set invert 2026-02-15 21:08:33 +08:00
世界
0a69621207 wireguard: Fix missing fallback for gso 2026-02-15 21:08:26 +08:00
世界
58ccf82e0b Bump version 2026-02-09 15:50:51 +08:00
世界
ceab244329 tuic: Fix udp context 2026-02-09 15:50:51 +08:00
世界
58fcdceca2 Fix naive padding 2026-02-09 15:50:51 +08:00
世界
98af3c0ad6 experimental: New FFI 2026-02-09 15:50:51 +08:00
世界
172a9d5e4e Standardize gomobile usages 2026-02-07 15:52:26 +08:00
世界
aba8346bd6 Fix DNS cache lock goroutine leak
The cache deduplication in Client.Exchange uses a channel-based lock
per DNS question. Waiting goroutines blocked on <-cond without context
awareness, causing them to accumulate indefinitely when the owning
goroutine's transport call stalls. Add select on ctx.Done() so waiters
respect context cancellation and timeouts.
2026-02-06 22:28:35 +08:00
世界
d8e269e0ac socks: Fix "Fix missing UDP timeout" 2026-02-06 22:26:45 +08:00
世界
c45ea8dfac Recover from bbolt panics on corrupted database
When bbolt encounters corrupted page data at runtime, it panics
instead of returning an error. Wrap all DB transactions with
recover to catch these panics, delete the corrupted database
file, and reopen a fresh one.
2026-02-06 19:35:32 +08:00
世界
a2d313c59b Bump version 2026-02-05 20:28:25 +08:00
世界
15722b06dd Update Go to 1.25.7 2026-02-05 17:49:06 +08:00
世界
d230dae0a5 Fix vmess crash 2026-02-05 17:23:49 +08:00
世界
e11dbf3a8e bufio: Refactor copy 2026-02-05 12:03:03 +08:00
世界
baa9f29f0d documentation: Update release changelog 2026-02-05 12:03:03 +08:00
世界
55b6e7dbfe socks: Fix missing UDP timeout 2026-02-05 12:03:03 +08:00
世界
a05e05a47c Fix random iproute2 table index was incorrectly removed 2026-02-02 14:15:55 +08:00
世界
c1dc6cb0fb Bump version 2026-02-01 12:29:57 +08:00
世界
432fe1b3c9 Disable rp filter atomically 2026-02-01 10:49:12 +08:00
世界
8dd8897fd8 Fix varbin serialization 2026-02-01 10:48:05 +08:00
世界
ff58edb1c1 Bump version 2026-01-30 14:05:29 +08:00
世界
79bab39502 Fix auto_redirect fallback rule 2026-01-30 11:42:56 +08:00
世界
a4d5d59901 Minor fixes 2026-01-29 13:40:34 +08:00
世界
1af14a0237 Remove varbin usages 2026-01-29 13:40:34 +08:00
世界
944a9986d9 release: Always build tailscale for iOS and tvOS 2026-01-29 13:40:34 +08:00
Balthild
60a1e4c866 Add acmedns support 2026-01-17 20:52:43 +08:00
世界
5d67c131fa documentation: Bump version 2026-01-17 19:21:19 +08:00
世界
b9cc87d35a Skip strict routing in Windows versions below Windows 10 2026-01-17 19:21:19 +08:00
世界
490d501257 Fix trafficontrol Manager 2026-01-17 19:16:56 +08:00
世界
725e4adc46 release: Update android command 2026-01-17 19:16:56 +08:00
世界
4a14d39cad release: Log build ID during TestFlight publishing 2026-01-17 19:16:56 +08:00
世界
8ec58c96f5 Fix naive outbound on iOS 2026-01-17 19:15:56 +08:00
世界
e8450b2e61 platform: Refactor CommandClient & Connections 2026-01-17 05:50:39 +08:00
世界
30c3855e4b Fix logic issues with BBR impl 2026-01-17 05:50:16 +08:00
世界
ccf90aee8a release: Improve publish_testflight 2026-01-17 05:50:08 +08:00
世界
e6c03fd448 Update quic-go to v0.59.0 2026-01-17 05:50:07 +08:00
世界
e0f1cdf464 platform: Uniq network interfaces 2026-01-17 05:49:57 +08:00
世界
8d88c6532f Add dial option bind_address_no_port 2026-01-17 05:49:56 +08:00
世界
3890bd2be7 platform: Display k based bytes 2026-01-17 05:49:45 +08:00
世界
6cd1eb9b94 Fix tailscale endpoint 2026-01-17 05:49:35 +08:00
世界
f196b7a583 tailscale: Add system interface support 2026-01-17 05:49:24 +08:00
世界
bd9935eebb platform: Fix gomobile build 2026-01-17 05:49:13 +08:00
世界
0e0e838ff5 platform: Update apple build comamnds 2026-01-17 05:49:13 +08:00
世界
0caebd3171 platform: Improve interface 2026-01-17 05:49:12 +08:00
世界
7d2944eba9 Downgrade quic-go to v0.57.1 2026-01-17 05:49:12 +08:00
世界
a5db2feb5e Fix linux musl builds 2026-01-17 05:48:59 +08:00
世界
708ceb3d29 Fix openwrt builds 2026-01-17 05:48:59 +08:00
世界
157e33f2a4 Add kmod-nft-queue dependency for openwrt package 2026-01-17 05:48:59 +08:00
世界
1d4fb83313 Fix nfqueue fallback 2026-01-17 05:48:58 +08:00
世界
85f5f6cebb Disable multipath TCP by default via GODEBUG 2026-01-17 05:48:58 +08:00
世界
6a750f4522 Fix missing relay support for Tailscale 2026-01-17 05:48:57 +08:00
世界
46c2cc37c3 cronet: Fix windows DNS hijack 2026-01-17 05:48:42 +08:00
世界
aa8dd6e44f Fix DNS transports 2026-01-17 05:48:41 +08:00
世界
4e94a64dcc platform: Expose process info 2026-01-17 05:48:41 +08:00
世界
494990f914 Update bypass action behavior for auto redirect 2026-01-17 05:48:41 +08:00
世界
95ccb837d3 platform: Add GetStartedAt for StartedService 2026-01-17 05:48:40 +08:00
世界
24b33a43fc documentation: Format changes header 2026-01-17 05:48:40 +08:00
世界
8ae16aa452 Add format_docs command for documentation trailing space formatting 2026-01-17 05:48:39 +08:00
世界
bf4a9edc89 Fix panic when closing Box before Start with file log output 2026-01-17 05:48:39 +08:00
世界
78b4eac974 Add pre-match support for auto redirect 2026-01-17 05:48:39 +08:00
世界
a34868468f Fix cronet on iOS 2026-01-17 05:48:38 +08:00
世界
e392c70b6f Ignore darwin IP_DONTFRAG error when not supported 2026-01-17 05:48:37 +08:00
世界
511d1bb3fa Update tailscale to v1.92.4 2026-01-17 05:48:28 +08:00
世界
4273ffa77e Update cronet-go to v143.0.7499.109-1 2026-01-17 05:48:17 +08:00
世界
f5ccf746ea platform: Split library for Android SDK 21 and 23 2026-01-17 05:48:16 +08:00
世界
b2d90b7d86 Fix missing RootPoolFromContext and TimeFuncFromContext in HTTP clients 2026-01-17 05:48:16 +08:00
世界
e0a78fde07 documentation: Minor fixes 2026-01-17 05:48:16 +08:00
世界
203f4134b0 documentation: Add Wi-Fi state shared page 2026-01-17 05:48:16 +08:00
世界
c2b697a778 Fix missing build constraints for linux wifi state monitor 2026-01-17 05:48:15 +08:00
世界
ddec2ab282 Update dependencies 2026-01-17 05:48:15 +08:00
世界
35ff7d1fb4 Update quic-go to v0.58.0 2026-01-17 05:48:04 +08:00
世界
cba18635c8 Add Chrome Root Store certificate option
Adds `chrome` as a new certificate store option alongside `mozilla`.
Both stores filter out China-based CA certificates.
2026-01-17 05:47:54 +08:00
世界
0d8c7a9c5d Fix cronet-go crash 2026-01-17 05:47:54 +08:00
世界
faff3174a3 Add trace logging for lifecycle calls
Log start/close operations with timing information for debugging.
2026-01-17 05:47:54 +08:00
世界
2fc1b672cc documentation: Minor fixes 2026-01-17 05:47:54 +08:00
世界
143983b585 Remove certificate_public_key_sha256 for naive 2026-01-17 05:47:54 +08:00
世界
4afdf4153a platform: Use new crash log api 2026-01-17 05:47:54 +08:00
世界
750dc9c3e0 Fix naive network 2026-01-17 05:47:53 +08:00
世界
48b7adde7d Add QUIC support for naiveproxy 2026-01-17 05:47:52 +08:00
世界
0585f6d065 Add ECH support for NaiveProxy outbound and tls.ech.query_server_name option
- Enable ECH for NaiveProxy outbound with DNS resolver integration
- Add query_server_name option to override domain for ECH HTTPS record queries
- Update cronet-go dependency and remove windows_386 support
2026-01-17 05:47:42 +08:00
世界
8101a7b0bd Fix naiveproxy build 2026-01-17 05:47:42 +08:00
世界
e8620587dd Add OpenAI Codex Multiplexer service 2026-01-17 05:47:42 +08:00
世界
a89680fa2d Update pricing for CCM service 2026-01-17 05:47:42 +08:00
世界
b919039c43 release: Upload only other apks 2026-01-17 05:47:42 +08:00
世界
9b0960bb5a Fix bugs and add UoT option for naiveproxy outbound 2026-01-17 05:47:41 +08:00
世界
ad7b982242 Add naiveproxy outbound 2026-01-17 05:47:41 +08:00
世界
7e68013b05 Apply ping destination filter for Windows 2026-01-17 05:47:33 +08:00
世界
ac427b98f4 platform: Add UsePlatformWIFIMonitor to gRPC interface
Align dev-next-grpc with wip2 by adding UsePlatformWIFIMonitor()
to the new PlatformInterface, allowing platform clients to indicate
they handle WIFI monitoring themselves.
2026-01-17 05:47:32 +08:00
世界
a5fb467db2 daemon: Add clear logs 2026-01-17 05:47:32 +08:00
世界
a930356b04 Revert "Stop using DHCP on iOS and tvOS" 2026-01-17 05:47:32 +08:00
世界
5bc0dfa9dd platform: Refactoring libbox to use gRPC-based protocol 2026-01-17 05:47:32 +08:00
世界
743b460e51 Add Windows WI-FI state support 2026-01-17 05:47:27 +08:00
世界
8d8ca282a1 Add Linux WI-FI state support
Support monitoring WIFI state on Linux through:
- NetworkManager (D-Bus)
- IWD (D-Bus)
- wpa_supplicant (control socket)
- ConnMan (D-Bus)
2026-01-17 05:47:04 +08:00
世界
cd56eaaba2 Add more tcp keep alive options
Also update default TCP keep-alive initial period from 10 minutes to 5 minutes.
2026-01-17 05:47:04 +08:00
世界
e92938364d Update quic-go to v0.57.1 2026-01-17 05:46:52 +08:00
世界
1c4614318e Fix read credentials for ccm service 2026-01-17 05:46:24 +08:00
世界
0f5cda4169 Add claude code multiplexer service 2026-01-17 05:46:23 +08:00
世界
d87c9fd242 Fix compatibility with MPTCP 2026-01-17 05:46:23 +08:00
世界
fce21607bd Use a more conservative strategy for resolving with systemd-resolved for local DNS server 2026-01-17 05:46:23 +08:00
世界
3dc285be8c Fix missing mTLS support in client options 2026-01-17 05:46:23 +08:00
世界
79bbce3db3 Add curve preferences, pinned public key SHA256 and mTLS for TLS options 2026-01-17 05:46:22 +08:00
世界
dfd95b2615 Fix WireGuard input packet 2026-01-17 05:46:22 +08:00
世界
ab0869c972 Update tfo-go to latest 2026-01-17 05:46:21 +08:00
世界
9ac0539ffd Remove compatibility codes 2026-01-17 05:46:14 +08:00
世界
cb4deb0c20 Do not use linkname by default to simplify debugging 2026-01-17 05:46:14 +08:00
世界
6b90b61358 documentation: Update chinese translations 2026-01-17 05:46:14 +08:00
世界
ed1ee4c3a4 Update quic-go to v0.55.0 2026-01-17 05:46:13 +08:00
世界
7f3ea8dbd8 Update WireGuard and Tailscale 2026-01-17 05:46:02 +08:00
世界
12b055989b Fix preConnectionCopy 2026-01-17 05:46:01 +08:00
世界
49056b5060 Fix ping domain 2026-01-17 05:46:01 +08:00
世界
c530995832 release: Fix linux build 2026-01-17 05:46:01 +08:00
世界
60d81a73d9 Improve ktls rx error handling 2026-01-17 05:46:01 +08:00
世界
e9c46cc359 Improve compatibility for kTLS 2026-01-17 05:46:00 +08:00
世界
9110851af3 ktls: Add warning for inappropriate scenarios 2026-01-17 05:44:43 +08:00
世界
107f92381b Add support for kTLS
Reference: https://gitlab.com/go-extension/tls
2026-01-17 05:44:42 +08:00
世界
f84129ca79 Add proxy support for ICMP echo request 2026-01-17 05:44:41 +08:00
世界
44fafcef73 Fix resolve using resolved 2026-01-17 05:44:29 +08:00
世界
a5e09fcd43 documentation: Update behavior of local DNS server on darwin 2026-01-17 05:44:29 +08:00
世界
387b42c9c2 Remove use of ldflags -checklinkname=0 on darwin 2026-01-17 05:44:29 +08:00
世界
044eb728cb Fix legacy DNS config 2026-01-17 05:44:29 +08:00
世界
2be8a45f14 Fix rule-set format 2026-01-17 05:44:29 +08:00
世界
1336987756 documentation: Remove outdated icons 2026-01-17 05:44:29 +08:00
世界
e3473d3de0 documentation: Improve local DNS server 2026-01-17 05:44:28 +08:00
世界
bba92146b1 Stop using DHCP on iOS and tvOS
We do not have the `com.apple.developer.networking.multicast` entitlement and are unable to obtain it for non-technical reasons.
2026-01-17 05:44:28 +08:00
世界
48f84b31d6 Improve local DNS server on darwin
We mistakenly believed that `libresolv`'s `search` function worked correctly in NetworkExtension, but it seems only `getaddrinfo` does.

This commit changes the behavior of the `local` DNS server in NetworkExtension to prefer DHCP, falling back to `getaddrinfo` if DHCP servers are unavailable.

It's worth noting that `prefer_go` does not disable DHCP since it respects Dial Fields, but `getaddrinfo` does the opposite. The new behavior only applies to NetworkExtension, not to all scenarios (primarily command-line binaries) as it did previously.

In addition, this commit also improves the DHCP DNS server to use the same robust query logic as `local`.
2026-01-17 05:44:28 +08:00
世界
1c846df903 Use resolved in local DNS server if available 2026-01-17 05:44:28 +08:00
xchacha20-poly1305
0bd98a300f Fix rule set version 2026-01-17 05:44:27 +08:00
世界
87eaf3ce6e documentation: Add preferred_by route rule item 2026-01-17 05:44:27 +08:00
世界
239e6ec701 Add preferred_by route rule item 2026-01-17 05:44:27 +08:00
世界
5be1887f92 documentation: Add interface address rule items 2026-01-17 05:44:27 +08:00
世界
65264afdf9 Add interface address rule items 2026-01-17 05:44:26 +08:00
世界
fecdbf20de Fix ECH retry support 2026-01-17 05:44:26 +08:00
neletor
1f03080540 Add support for ech retry configs 2026-01-17 05:44:26 +08:00
Zephyruso
737162e75a Add /dns/flush-clash meta api 2026-01-17 05:44:26 +08:00
世界
51ce402dbb Bump version 2026-01-17 05:10:56 +08:00
世界
8b404b5a4c Update Go to 1.25.6 2026-01-17 05:10:56 +08:00
世界
3ce94d50dd Update uTLS to v1.8.2 2026-01-17 04:54:18 +08:00
世界
29d56fca9c Update smux to v1.5.50 & Fix h2mux RST_STREAM on half-close 2026-01-17 04:17:14 +08:00
世界
ab18010ee1 Bump version 2026-01-12 20:38:21 +08:00
世界
e69c202c79 Fix logic issues with BBR impl 2026-01-12 20:34:04 +08:00
世界
0a812f2a46 Bump version 2026-01-07 15:13:35 +08:00
Gavin Luo
fffe9fc566 Fix reset buffer in dhcp response loop
Previously, the buffer was not reset within the response loop. If a packet
handle failed or completed, the buffer retained its state. Specifically,
if `ReadPacketFrom` returned `io.ErrShortBuffer`, the error was ignored
via `continue`, but the buffer remained full. This caused the next
read attempt to immediately fail with the same error, creating a tight
busy-wait loop that consumed 100% CPU.

Validates `buffer.Reset()` is called at the start of each iteration to
ensure a clean state for 'ReadPacketFrom'.
2026-01-05 17:46:59 +08:00
世界
6fdf27a701 Fix Tailscale endpoint using wrong source IP with advertise_routes 2026-01-04 22:14:54 +08:00
Bruce Wayne
7fa7d4f0a9 ducumentation: update Shadowsocks inbound documentation for SSM API 2026-01-02 19:18:52 +08:00
世界
f511ebc1d4 Fix lint errors 2026-01-02 19:17:53 +08:00
世界
84bbdc2eba Revert "Pin gofumpt and golangci-lint versions"
This reverts commit d9d7f7880d.
2026-01-02 19:14:13 +08:00
世界
568612fc70 Fix duplicate tag detection for empty tags
Closes https://github.com/SagerNet/sing-box/issues/3665
2026-01-02 19:14:13 +08:00
世界
d78828fd81 Fix quic sniffer 2026-01-02 19:14:13 +08:00
世界
f56d9ab945 Bump version 2025-12-25 14:47:10 +08:00
世界
86fabd6a22 Update Mozilla certificates 2025-12-25 14:42:18 +08:00
世界
24a1e7cee4 Ignore darwin IP_DONTFRAG error when not supported 2025-12-25 14:40:48 +08:00
世界
223dd8bb1a Fix TCP DNS response buffer 2025-12-22 13:51:00 +08:00
世界
68448de7d0 Fix missing RootPoolFromContext and TimeFuncFromContext in HTTP clients 2025-12-22 13:50:57 +08:00
世界
1ebff74c21 Fix DNS cache not working when domain strategy is set
The cache lookup was performed before rule matching, using the caller's
strategy (usually AsIS/0) instead of the resolved strategy. This caused
cache misses when ipv4_only was configured globally but the cache lookup
expected both A and AAAA records.

Remove LookupCache and ExchangeCache from Router, as the cache checks
inside client.Lookup and client.Exchange already handle caching correctly
after rule matching with the proper strategy and transport.
2025-12-21 16:59:10 +08:00
世界
f0cd3422c1 Bump version 2025-12-14 00:09:19 +08:00
世界
e385a98ced Update Go to 1.25.5 2025-12-13 20:11:29 +08:00
世界
670f32baee Fix naive inbound 2025-12-12 21:19:28 +08:00
世界
2747a00ba2 Fix tailscale destination 2025-12-01 15:02:04 +08:00
世界
48e76038d0 Update Go to 1.25.4 2025-11-16 09:53:10 +08:00
世界
6421252d44 release: Fix windows7 build 2025-11-16 09:09:34 +08:00
世界
216c4c8bd4 Fix adapter handler 2025-11-16 08:34:46 +08:00
世界
5841d410a1 ssm-api: Fix save cache 2025-11-04 11:00:43 +08:00
Kumiko as a Service
63c8207d7a Use --no-cache --upgrade option in apk add
No need for separate upgrade / cache cleanup steps.

Signed-off-by: Kumiko as a Service <Dreista@users.noreply.github.com>
2025-11-04 11:00:41 +08:00
世界
54ed58499d Bump version 2025-10-27 18:04:24 +08:00
世界
b1bdc18c85 Fix socks response 2025-10-27 18:03:05 +08:00
世界
a38030cc0b Fix memory leak in hysteria2 2025-10-24 10:52:08 +08:00
世界
4626aa2cb0 Bump version 2025-10-21 21:39:28 +08:00
世界
5a40b673a4 Update dependencies 2025-10-21 21:39:28 +08:00
世界
541f63fee4 redirect: Fix compatibility with /product/bin/su 2025-10-21 21:27:15 +08:00
世界
5de6f4a14f Fix tailscale not enforcing NoLogsNoSupport 2025-10-16 22:30:08 +08:00
世界
5658830077 Fix trailing dot handling in local DNS transport 2025-10-16 21:43:12 +08:00
世界
0e50edc009 documentation: Add appreciate for Warp 2025-10-16 21:43:12 +08:00
世界
444f454810 Bump version 2025-10-14 23:43:36 +08:00
世界
d0e1fd6c7e Update Go to 1.25.3 2025-10-14 23:43:36 +08:00
世界
17b4d1e010 Update uTLS to v1.8.1 2025-10-14 23:40:19 +08:00
世界
06791470c9 Fix DNS reject panic 2025-10-14 23:40:19 +08:00
世界
ef14c8ca0e Disable TCP slow open for anytls
Fixes #3459
2025-10-14 23:40:19 +08:00
世界
36dc883c7c Fix DNS negative caching to comply with RFC 2308 2025-10-09 23:45:23 +08:00
Mahdi
6557bd7029 Fix dns cache in lookup 2025-10-09 23:45:23 +08:00
世界
41b30c91d9 Improve HTTPS DNS transport 2025-10-09 23:45:23 +08:00
世界
0f767d5ce1 Update .gitignore 2025-10-07 13:37:11 +08:00
世界
328a6de797 Bump version 2025-10-05 17:58:21 +08:00
Mahdi
886be6414d Fix dns truncate 2025-10-05 17:58:21 +08:00
世界
9362d3cab3 Attempt to fix leak in quic-go 2025-10-01 11:59:17 +08:00
世界
ced2e39dbf Update dependencies 2025-10-01 11:55:33 +08:00
anytls
2159d8877b Update anytls v0.0.11
Co-authored-by: anytls <anytls>
2025-10-01 10:23:15 +08:00
世界
cb7dba3eff release: Improve publish testflight 2025-10-01 10:22:44 +08:00
世界
d9d7f7880d Pin gofumpt and golangci-lint versions
As we don't want to remove naked returns
2025-09-23 16:33:42 +08:00
世界
a031aaf2c0 Do not reset network on sleep or wake 2025-09-23 16:17:44 +08:00
世界
4bca951773 Fix adguard matcher 2025-09-23 16:12:29 +08:00
世界
140735dbde Fix websocket log handling 2025-09-23 16:12:29 +08:00
世界
714a68bba1 Update .gitignore 2025-09-23 16:12:29 +08:00
世界
573c6179ab Bump version 2025-09-13 13:16:13 +08:00
世界
510bf05e36 Fix UDP exchange for local/dhcp DNS servers 2025-09-13 12:26:48 +08:00
世界
ae852e0be4 Bump version 2025-09-13 03:09:10 +08:00
世界
1955002ed8 Do not cache DNS responses with empty answers 2025-09-13 03:04:08 +08:00
世界
44559fb7b9 Bump version 2025-09-13 00:07:57 +08:00
世界
0977c5cf73 release: Disable Apple platform CI builds, since `-allowProvisioningUpdates is broken by Apple 2025-09-13 00:07:57 +08:00
世界
07697bf931 release: Fix xcode build 2025-09-12 22:57:44 +08:00
世界
5d1d1a1456 Fix TCP exchange for local/dhcp DNS servers 2025-09-12 21:58:48 +08:00
世界
146383499e Fix race codes 2025-09-12 21:58:48 +08:00
世界
e81a76fdf9 Fix DNS exchange 2025-09-12 18:05:02 +08:00
世界
de13137418 Fix auto redirect 2025-09-12 11:04:12 +08:00
世界
e42b818c2a Fix dhcp fetch 2025-09-12 11:03:13 +08:00
世界
fcde0c94e0 Bump version 2025-09-10 22:57:24 +08:00
世界
1af83e997d Update Go to 1.25.1 2025-09-10 22:57:24 +08:00
世界
59ee7be72a Fix SyscallVectorisedPacketWriter 2025-09-10 22:46:41 +08:00
世界
c331ee3d5c Fix timeout check 2025-09-10 22:42:40 +08:00
世界
36babe4bef Fix hysteria2 handshake timeout 2025-09-09 18:03:18 +08:00
世界
c5f2cea802 Prevent panic when wintun dll fails to load 2025-09-09 14:49:00 +08:00
世界
8a200bf913 Fix auto redirect output 2025-09-09 14:29:40 +08:00
世界
f16468e74f Fix ipv6 tproxy listener 2025-09-09 14:16:40 +08:00
世界
79c0b9f51d Fix tls options ignored in mixed inbounds 2025-09-08 19:45:52 +08:00
世界
f98a3a4f65 Treat requests with OPT extra but no options as simple requests 2025-09-08 09:12:30 +08:00
世界
b14cecaeb2 Fix DNS packet size 2025-09-08 09:12:30 +08:00
世界
2594745ef8 Fix DNS client 2025-09-08 09:12:30 +08:00
世界
cc3041322e Fix DNS cache 2025-09-08 09:12:30 +08:00
世界
f352f84483 Fix read address 2025-09-05 15:16:14 +08:00
世界
cbf48e9b8c Fix multiple sniff 2025-09-03 20:09:05 +08:00
世界
0ef7e8eca2 Fix route.default_interface not taking effect 2025-09-02 18:00:02 +08:00
世界
1a18e43a88 Fix linux icmp routes 2025-09-02 17:55:48 +08:00
世界
6849288d6d Fix typo in TestSniffUQUICChrome115 2025-09-02 17:55:26 +08:00
世界
2edfed7d91 Improve DHCP DNS server 2025-09-02 17:55:26 +08:00
世界
30c069f5b7 Fix local DNS server on legacy windows 2025-09-02 17:55:26 +08:00
世界
649163cb7b Fix domain strategy not taking effect 2025-09-02 17:35:27 +08:00
世界
980e96250b Bump version 2025-08-28 12:11:30 +08:00
世界
963bc4b647 Enforce Tailscale NoLogsNoSupport 2025-08-28 10:30:13 +08:00
世界
031f25c1c1 Deprecate common/atomic 2025-08-25 19:49:12 +08:00
世界
b40f642fa4 Bump version 2025-08-21 09:43:47 +08:00
世界
22782ca6fc Fix outbound start 2025-08-21 09:41:31 +08:00
世界
1468d83895 Make realityClientConnWrapper replaceable 2025-08-20 16:26:27 +08:00
世界
97f0dc8a60 Bump version 2025-08-20 09:20:41 +08:00
dyhkwong
ee02532ab5 Fix tlsfragment fallback writeAndWaitAck 2025-08-20 09:20:41 +08:00
世界
f1dd0dba78 Make ReadWaitConn reader replaceable 2025-08-20 09:18:03 +08:00
wwqgtxx
f4ed684146 Update cast using in sing-vmess 2025-08-20 08:45:09 +08:00
wwqgtxx
83f02d0bfb Make utlsConnWrapper replaceable 2025-08-20 08:45:09 +08:00
wwqgtxx
52fa5f20a3 Make realityConnWrapper replaceable 2025-08-20 08:45:09 +08:00
世界
f462ce5615 Update tfo-go 2025-08-19 21:56:05 +08:00
世界
cef3e538ba Fix failed DNS responses being incorrectly rejected 2025-08-19 11:14:46 +08:00
世界
acda4ce985 Fix bind_interface not working with auto_redirect 2025-08-17 14:48:01 +08:00
世界
354ece2bdf Fix resolved service 2025-08-16 00:09:29 +08:00
世界
de10bb00a9 Fix ssm-api 2025-08-15 15:05:37 +08:00
世界
fdc181106d Fix atomic pointer usages 2025-08-15 15:05:34 +08:00
renovate[bot]
8752b631bd [dependencies] Update golang Docker tag to v1.25 (#3276)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-15 12:45:43 +08:00
世界
378e39f70c Update golangci-lint to v2 2025-08-13 23:37:40 +08:00
renovate[bot]
043a2e7a07 [dependencies] Update github-actions 2025-08-13 22:12:19 +08:00
世界
7e190e92ca Fix build with Go 1.25 2025-08-13 22:08:35 +08:00
世界
5eb318ba06 Update Go to 1.25 2025-08-13 22:08:35 +08:00
世界
4a209f1afb Fix h2mux close check 2025-08-13 21:04:01 +08:00
世界
c0ac3c748c Reduce default MTU for android 2025-08-13 11:48:44 +08:00
世界
a65d3e040a platform: Fix context 2025-08-13 11:26:32 +08:00
世界
2358efe44a release: Fix android build 2025-08-11 22:11:14 +08:00
世界
09d3b8f2c2 release: Fix repo 2025-08-11 22:11:14 +08:00
yu
531de77124 documentation: Fix tun address format 2025-08-11 22:11:13 +08:00
Kismet
44981fd803 documentation: Fix typo 2025-08-11 22:11:13 +08:00
世界
4fb5ac292b Bump version 2025-08-10 20:06:28 +08:00
Sentsuki
0e23a3d7c2 documentation: Fix Rcode's migration guide
Signed-off-by: Sentsuki <52487960+Sentsuki@users.noreply.github.com>
2025-08-10 20:06:28 +08:00
Oleksandr Redko
76ee64ae50 Simplify slice to array conversion 2025-08-10 20:06:28 +08:00
Me0wo
e1dbcccab5 documentation: Fix typo
Signed-off-by: Me0wo <152751263+Sn0wo2@users.noreply.github.com>
2025-08-10 20:06:28 +08:00
Youfu Zhang
fba802effd Fix libresolv initialization
Fixes: 9533031891 ("Update libresolv usage")

Signed-off-by: Youfu Zhang <zhangyoufu@gmail.com>
2025-08-10 20:06:28 +08:00
世界
9495b56772 Update Go to 1.24.6 2025-08-08 17:07:56 +08:00
世界
a8434b176f Fix SyscallVectorisedWriter 2025-08-08 16:08:47 +08:00
世界
ef0004400d Fix legacy domain resolver deprecated warning incorrectly suppressed for direct outbound 2025-08-07 13:56:35 +08:00
世界
0a63049845 android: Add workaround for tailscale pidfd crash 2025-08-07 12:54:19 +08:00
世界
2dcb86941f Bump version 2025-08-04 08:54:00 +08:00
世界
5c6eb89cfb Fix udp listener write back 2025-08-04 08:54:00 +08:00
世界
5b92eeb3bf Fix auto redirect panic 2025-08-01 16:50:54 +08:00
wwqgtxx
3518ce083b Fix packetaddr panic 2025-08-01 16:50:54 +08:00
世界
f13c54afc1 Fix vless write 2025-07-28 08:01:52 +08:00
世界
3388efe65a Fix ssm-api 2025-07-28 08:01:52 +08:00
世界
a11384b286 Fix time service wrapper 2025-07-24 09:21:08 +08:00
dyhkwong
9dd9fb27cd Fix disable_sni nil time func 2025-07-24 09:21:08 +08:00
世界
0f2035149c Remove dependency on circl 2025-07-23 11:26:06 +08:00
世界
cba364204a Fix VectorisedReadWaiter on windows 2025-07-23 11:26:06 +08:00
世界
4e17788549 Update dependencies 2025-07-23 11:26:06 +08:00
世界
18a6719893 Fix IndexTLSServerName 2025-07-23 11:26:06 +08:00
dyhkwong
687343f6ca Fix disable_sni not working with custom RootCAs 2025-07-22 19:20:09 +08:00
世界
e061538c30 Update Go to 1.24.5 2025-07-21 10:10:02 +08:00
世界
a6375c7530 Fix data corruption in direct copy 2025-07-21 10:09:59 +08:00
世界
45fa18a2e3 Fix vision crash 2025-07-20 18:31:05 +08:00
世界
534cccce91 Fix DNS upgrade 2025-07-18 21:46:03 +08:00
世界
72dbcd3ad4 Improve darwin tun performance 2025-07-18 21:23:04 +08:00
世界
5533094984 Fix UDP DNS buffer size 2025-07-18 12:20:33 +08:00
世界
ae2ecd6002 Increase default mtu to 65535 2025-07-12 14:48:04 +08:00
世界
0098a2adc5 Improve direct copy 2025-07-12 14:48:04 +08:00
世界
c0dd4a3f07 Fix DNS reject check 2025-07-08 13:14:46 +08:00
世界
497ddb5829 Improve copy 2025-07-08 13:14:46 +08:00
世界
811ff93549 Increase default mtu under network extension to 4064 2025-07-08 13:14:46 +08:00
世界
96df69bcdc release: Fix publish testflight 2025-07-08 13:14:46 +08:00
世界
6cfa2b8b86 Improve darwin tun performance 2025-07-08 13:14:46 +08:00
世界
eea1e701b7 Improve nftables rules for openwrt 2025-07-08 13:14:46 +08:00
世界
455e5de74d Fixed DoH server recover from conn freezes 2025-07-08 13:14:45 +08:00
世界
9533031891 Update libresolv usage 2025-07-08 13:14:45 +08:00
yu
80f8ea6849 documentation: Update client configuration manual 2025-07-08 13:14:45 +08:00
yanwo
50eadb00c7 documentation: Fix typo
Signed-off-by: yanwo <ogilvy@gmail.com>
2025-07-08 13:14:45 +08:00
anytinz
d4012bd0b2 documentation: Fix wrong SideStore loopback ip 2025-07-08 13:14:45 +08:00
世界
a902e9f9f6 Revert "release: Add IPA build"
After testing, it seems that since extensions are not handled correctly, it cannot be installed by SideStore.
2025-07-08 13:14:45 +08:00
世界
da3ba573d8 release: Add IPA build 2025-07-08 13:14:45 +08:00
世界
bea9048cfe Add API to dump AdGuard rules 2025-07-08 13:14:44 +08:00
Sukka
fc0f5ed83a Improve AdGuard rule-set parser 2025-07-08 13:14:44 +08:00
Restia-Ashbell
c0588c30d7 Add ECH support for uTLS 2025-07-08 13:14:44 +08:00
世界
24c940c51c Improve TLS fragments 2025-07-08 13:14:44 +08:00
世界
407ee08d8a Add cache support for ssm-api 2025-07-08 13:14:44 +08:00
世界
756585fb2a Fix service will not be closed 2025-07-08 13:14:44 +08:00
世界
5662784afb Add loopback address support for tun 2025-07-08 13:14:44 +08:00
世界
3801901726 Fix tproxy listener 2025-07-08 13:14:43 +08:00
世界
7d58174f1f Fix systemd package 2025-07-08 13:14:43 +08:00
世界
d339f85087 Fix missing home for derp service 2025-07-08 13:14:43 +08:00
Zero Clover
b6a114f7f4 documentation: Fix services 2025-07-08 13:14:43 +08:00
世界
e586ef070e Fix dns.client_subnet ignored 2025-07-08 13:14:43 +08:00
世界
71a76e9ecb documentation: Minor fixes 2025-07-08 13:14:42 +08:00
世界
1d66474022 Fix tailscale forward 2025-07-08 13:14:42 +08:00
世界
3934e53476 Minor fixes 2025-07-08 13:14:42 +08:00
世界
0146fbfc40 Add SSM API service 2025-07-08 13:14:42 +08:00
世界
6ee3117755 Add resolved service and DNS server 2025-07-08 13:14:41 +08:00
世界
e2440a569e Add DERP service 2025-07-08 13:14:41 +08:00
世界
7a1eee78df Add service component type 2025-07-08 13:14:41 +08:00
世界
e3c8c0705f Fix tproxy tcp control 2025-07-08 13:14:40 +08:00
愚者
886d427337 release: Fix build tags for android
Signed-off-by: 愚者 <11926619+FansChou@users.noreply.github.com>
2025-07-08 13:14:40 +08:00
世界
d5432b4c27 prevent creation of bind and mark controls on unsupported platforms 2025-07-08 13:14:40 +08:00
PuerNya
42064fe7ec documentation: Fix description of reject DNS action behavior 2025-07-08 13:14:40 +08:00
Restia-Ashbell
7cee76f9a6 Fix TLS record fragment 2025-07-08 13:14:39 +08:00
世界
ed5b2f2997 Add missing accept_routes option for Tailscale 2025-07-08 13:14:39 +08:00
世界
3b480de38a Add TLS record fragment support 2025-07-08 13:14:38 +08:00
世界
f990630ccc Fix set edns0 client subnet 2025-07-08 13:14:38 +08:00
世界
d33614d6a0 Update minor dependencies 2025-07-08 13:14:38 +08:00
世界
b3866bcea0 Update certmagic and providers 2025-07-08 13:14:38 +08:00
世界
26ec73c71b Update protobuf and grpc 2025-07-08 13:14:38 +08:00
世界
c3403c5413 Add control options for listeners 2025-07-08 13:14:38 +08:00
世界
3b6ddcae37 Update quic-go to v0.52.0 2025-07-08 13:14:19 +08:00
世界
dbdcce20a8 Update utls to v1.7.2 2025-07-08 13:12:35 +08:00
世界
e7ef1b2368 Handle EDNS version downgrade 2025-07-08 13:12:35 +08:00
世界
ce32d1c2c3 documentation: Fix anytls padding scheme description 2025-07-08 13:12:34 +08:00
安容
596b66f397 Report invalid DNS address early 2025-07-08 13:12:34 +08:00
世界
d4fd43cf6f Fix wireguard listen_port 2025-07-08 13:12:34 +08:00
世界
6c377f16e7 clash-api: Add more meta api 2025-07-08 13:12:34 +08:00
世界
349db7baec Fix DNS lookup 2025-07-08 13:12:33 +08:00
世界
1f3097da00 Fix fetch ECH configs 2025-07-08 13:12:33 +08:00
reletor
0b4b5e6f0f documentation: Minor fixes 2025-07-08 13:12:33 +08:00
caelansar
245273e6c1 Fix callback deletion in UDP transport 2025-07-08 13:12:32 +08:00
世界
54a0004de6 documentation: Try to make the play review happy 2025-07-08 13:12:32 +08:00
世界
6a211f6ed6 Fix missing handling of legacy domain_strategy options 2025-07-08 13:12:32 +08:00
世界
aadb44ebd6 Improve local DNS server 2025-07-08 13:12:32 +08:00
anytls
9b0db6ab15 Update anytls
Co-authored-by: anytls <anytls>
2025-07-08 13:12:31 +08:00
世界
5b363c347f Fix DNS dialer 2025-07-08 13:12:31 +08:00
世界
cdea3f63d4 release: Skip override version for iOS 2025-07-08 13:12:31 +08:00
iikira
40a6260f6e Fix UDP DNS server crash
Signed-off-by: iikira <i2@mail.iikira.com>
2025-07-08 13:12:31 +08:00
ReleTor
a5e47f4e0f Fix fetch ECH configs 2025-07-08 13:12:30 +08:00
世界
ac7bc587cb Allow direct outbounds without domain_resolver 2025-07-08 13:12:30 +08:00
世界
4e11a3585a Fix Tailscale dialer 2025-07-08 13:12:30 +08:00
dyhkwong
63d3e9f6e5 Fix DNS over QUIC stream close 2025-07-08 13:12:30 +08:00
anytls
d115e36ed8 Update anytls
Co-authored-by: anytls <anytls>
2025-07-08 13:12:30 +08:00
Rambling2076
af56b1a950 Fix missing with_tailscale in Dockerfile
Signed-off-by: Rambling2076 <Rambling2076@proton.me>
2025-07-08 13:12:29 +08:00
世界
f9999a76fe Fail when default DNS server not found 2025-07-08 13:12:28 +08:00
世界
42eb3841a1 Update gVisor to 20250319.0 2025-07-08 13:12:28 +08:00
世界
fb622ccbdf Explicitly reject detour to empty direct outbounds 2025-07-08 13:12:28 +08:00
世界
d2dc3ddf72 Add netns support 2025-07-08 13:12:28 +08:00
世界
e8499452f8 Add wildcard name support for predefined records 2025-07-08 13:12:27 +08:00
世界
e0a6b31c03 Remove map usage in options 2025-07-08 13:12:27 +08:00
世界
7c923209ad Fix unhandled DNS loop 2025-07-08 13:12:27 +08:00
世界
bca2bd2fa1 Add wildcard-sni support for shadow-tls inbound 2025-07-08 13:12:26 +08:00
k9982874
fa99ca2757 Add ntp protocol sniffing 2025-07-08 13:12:26 +08:00
世界
7073f2a272 option: Fix marshal legacy DNS options 2025-07-08 13:12:26 +08:00
世界
390e30ae7b Make domain_resolver optional when only one DNS server is configured 2025-07-08 13:12:26 +08:00
世界
23cf8c49e0 Fix DNS lookup context pollution 2025-07-08 13:12:25 +08:00
世界
b17a024f6c Fix http3 DNS server connecting to wrong address 2025-07-08 13:12:25 +08:00
Restia-Ashbell
1ed21085bb documentation: Fix typo 2025-07-08 13:12:25 +08:00
anytls
56409ff269 Update sing-anytls
Co-authored-by: anytls <anytls>
2025-07-08 13:12:24 +08:00
k9982874
0c523980ff Fix hosts DNS server 2025-07-08 13:12:24 +08:00
世界
32873d06bc Fix UDP DNS server crash 2025-07-08 13:12:24 +08:00
世界
4accaccf77 documentation: Fix missing ip_accept_any DNS rule option 2025-07-08 13:12:23 +08:00
世界
ff416aacaf Fix anytls dialer usage 2025-07-08 13:12:23 +08:00
世界
b97947e8ac Move predefined DNS server to rule action 2025-07-08 13:12:23 +08:00
世界
dfcd9fb8c3 Fix domain resolver on direct outbound 2025-07-08 13:12:22 +08:00
Zephyruso
803811568e Fix missing AnyTLS display name 2025-07-08 13:12:22 +08:00
anytls
50b0bd5c39 Update sing-anytls
Co-authored-by: anytls <anytls>
2025-07-08 13:12:22 +08:00
Estel
2d02b2b1cf documentation: Fix typo
Signed-off-by: Estel <callmebedrockdigger@gmail.com>
2025-07-08 13:12:22 +08:00
TargetLocked
456fbecf16 Fix parsing legacy DNS options 2025-07-08 13:12:21 +08:00
世界
668923c392 Fix DNS fallback 2025-07-08 13:12:21 +08:00
世界
c51e9cbe06 documentation: Fix missing hosts DNS server 2025-07-08 13:12:20 +08:00
anytls
60b451e6cf Add MinIdleSession option to AnyTLS outbound
Co-authored-by: anytls <anytls>
2025-07-08 13:12:20 +08:00
ReleTor
3e35390d8f documentation: Minor fixes 2025-07-08 13:12:20 +08:00
libtry486
f2dad289fb documentation: Fix typo
fix typo

Signed-off-by: libtry486 <89328481+libtry486@users.noreply.github.com>
2025-07-08 13:12:20 +08:00
Alireza Ahmadi
b4a8fa59f5 Fix Outbound deadlock 2025-07-08 13:12:19 +08:00
世界
73de2a7d07 documentation: Fix AnyTLS doc 2025-07-08 13:12:19 +08:00
anytls
1699a7ce33 Add AnyTLS protocol 2025-07-08 13:12:19 +08:00
世界
7743c6e881 Migrate to stdlib ECH support 2025-07-08 13:12:19 +08:00
世界
9a5f69f435 Add fallback local DNS server for iOS 2025-07-08 13:12:18 +08:00
世界
5c4211e849 Get darwin local DNS server from libresolv 2025-07-08 13:12:18 +08:00
世界
c1189e2a7b Improve resolve action 2025-07-08 13:12:18 +08:00
世界
f18889369f Add back port hopping to hysteria 1 2025-07-08 13:12:17 +08:00
xchacha20-poly1305
91c7b638e8 Remove single quotes of raw Moziila certs 2025-07-08 13:12:17 +08:00
世界
6f793a0273 Add Tailscale endpoint 2025-07-08 13:12:16 +08:00
世界
0f6c417c3c Build legacy binaries with latest Go 2025-07-08 13:12:16 +08:00
世界
c830e9a634 documentation: Remove outdated icons 2025-07-08 13:12:16 +08:00
世界
e809623ec9 documentation: Certificate store 2025-07-08 13:12:16 +08:00
世界
061276902b documentation: TLS fragment 2025-07-08 13:12:15 +08:00
世界
fa6f7d396e documentation: Outbound domain resolver 2025-07-08 13:12:15 +08:00
世界
23666a9230 documentation: Refactor DNS 2025-07-08 13:12:15 +08:00
世界
17576e9f66 Add certificate store 2025-07-08 13:12:14 +08:00
世界
90ec9c8bcb Add TLS fragment support 2025-07-08 13:12:14 +08:00
世界
988ac62a1b refactor: Outbound domain resolver 2025-07-08 13:12:14 +08:00
世界
3016338e34 refactor: DNS 2025-07-08 13:12:14 +08:00
世界
bc35aca017 Bump version 2025-07-08 13:11:13 +08:00
世界
281d52a1ea Fix hy2 server crash 2025-07-08 13:11:13 +08:00
世界
b8502759b5 Fix DNS reject check 2025-07-07 13:57:37 +08:00
世界
6f804adf39 Fix v2rayhttp crash 2025-07-03 21:48:10 +08:00
Kyson
36db31c55a documentation: Fix typo
Co-authored-by: chenqixin <chenqixin@bytedance.com>
2025-06-29 18:54:05 +08:00
世界
4dbbf59c82 Fix logger for acme 2025-06-29 18:44:40 +08:00
世界
832eb4458d release: Fix xcode version 2025-06-29 18:44:40 +08:00
dyhkwong
2cf989d306 Fix inbound with v2ray transport missing InboundOptions 2025-06-25 13:20:00 +08:00
世界
7d3ee29bd0 Also skip duplicate sniff for TCP 2025-06-21 12:57:27 +08:00
世界
cba0e46aba Fix log for rejected connections 2025-06-21 12:57:26 +08:00
世界
9b8ab3e61e Bump version 2025-06-19 11:57:44 +08:00
dyhkwong
47f18e823a Fix: macOS udp find process should use unspecified fallback
be8d63ba8f
2025-06-18 08:34:59 +08:00
世界
2d1b824b62 Fix gLazyConn race 2025-06-17 14:24:11 +08:00
世界
d511698f3f Fix slowOpenConn 2025-06-12 08:05:04 +08:00
世界
cb435ea232 Fix default network strategy 2025-06-12 08:05:04 +08:00
世界
43a9016c83 Fix leak in hijack-dns 2025-06-06 14:28:09 +08:00
世界
255068fd40 Bump version 2025-06-04 23:32:10 +08:00
世界
098a00b025 Fix v2ray websocket transport 2025-06-04 23:23:36 +08:00
世界
dba0b5276b Bump version 2025-06-04 20:06:38 +08:00
Sentsuki
78ae935468 documentation: Fix typo
Signed-off-by: Sentsuki <52487960+Sentsuki@users.noreply.github.com>
2025-06-04 20:06:38 +08:00
Mahdi
3ea5f76470 Fix nil logger at v2rayhttp server 2025-06-04 20:06:20 +08:00
世界
b4d294c05e Fix TUIC read buffer 2025-06-04 20:03:51 +08:00
世界
83cf5f5c6a Fix ws closed error message 2025-05-27 14:30:07 +08:00
世界
e7b3a8eebe Fix vmess read request 2025-05-27 14:11:05 +08:00
世界
ee3a42a67e Fix none method read buffer 2025-05-27 14:03:48 +08:00
世界
50227c0f5f Fix sniff action 2025-05-26 18:24:35 +08:00
世界
bc5eb1e1a5 Fix RoutePacketConnectionEx 2025-05-24 08:14:43 +08:00
世界
995267a042 Remove wrong ALPNs in DOH/DOH3 2025-05-24 08:00:13 +08:00
世界
41226a6075 Fix interface finder 2025-05-23 10:57:38 +08:00
世界
81d32181ce Fix update route address set 2025-05-20 19:46:54 +08:00
世界
c5ecca3938 Bump version 2025-05-18 16:48:44 +08:00
世界
900888731c Fix DNS reject response 2025-05-13 18:05:31 +08:00
世界
13e648e4b1 Fix set edns0 subnet 2025-05-07 15:12:17 +08:00
世界
aff12ff671 Bump version 2025-05-05 09:37:47 +08:00
世界
101fb88255 Fix allocator put 2025-05-05 09:37:44 +08:00
世界
8b489354e4 Undeprecate the block outbound 2025-05-04 18:45:53 +08:00
世界
7dea6eb7a6 Fix missing read waiter for cancelers 2025-05-04 18:14:21 +08:00
世界
af1bfe4e3e Make rule_set.format optional 2025-05-04 18:14:21 +08:00
世界
d574e9eb52 Update smux to v1.5.34 2025-04-30 19:39:15 +08:00
世界
2d7df1e1f2 Fix hysteria bytes format 2025-04-29 20:45:19 +08:00
世界
1c0ffcf5b1 Fix counter position in auto redirect dnat rules 2025-04-28 11:20:23 +08:00
世界
348cc39975 Bump version 2025-04-27 21:33:31 +08:00
世界
987899f94a Fix usages of wireguard listener 2025-04-27 21:29:23 +08:00
世界
d8b2d5142f Fix panic on some stupid input 2025-04-25 16:03:58 +08:00
世界
134802d1ee Fix ssh outbound 2025-04-25 16:03:57 +08:00
世界
e5e81b4de1 Fix wireguard listening 2025-04-25 16:03:57 +08:00
世界
300c961efa option: Fix listable again and again 2025-04-25 16:03:57 +08:00
世界
7c7f512405 option: Fix omitempty reject method 2025-04-25 16:03:57 +08:00
世界
03e8d029c2 release: Fix apt-get install 2025-04-25 16:03:57 +08:00
世界
787b5f1931 Fix set wireguard reserved on Linux 2025-04-25 16:03:57 +08:00
世界
56a7624618 Fix vmess working with zero uuids 2025-04-25 16:03:57 +08:00
世界
3a84acf122 Fix hysteria1 server panic 2025-04-25 16:03:57 +08:00
世界
f600e02e47 Fix DNS crash 2025-04-25 16:03:57 +08:00
世界
e6d19de58a Fix overriding address 2025-04-22 14:55:44 +08:00
dyhkwong
f2bbf6b2aa Fix sniffer errors override each others
* Fix sniffer errors override each others

* Do not return ErrNeedMoreData if header is not expected
2025-04-22 14:44:55 +08:00
dyhkwong
c54d50fd36 Fix websocket detour
Signed-off-by: trimgop <20010323+trimgop@users.noreply.github.com>
Co-authored-by: trimgop <20010323+trimgop@users.noreply.github.com>
2025-04-22 14:44:34 +08:00
世界
6a051054db release: Fix packages 2025-04-19 19:12:01 +08:00
世界
49498f6439 Bump version 2025-04-18 08:54:40 +08:00
世界
144a890c71 release: Add openwrt packages 2025-04-18 08:54:40 +08:00
世界
afb4993445 Fix urltest outbound 2025-04-18 08:54:40 +08:00
世界
4c9455b944 Fix wireguard endpoint 2025-04-18 08:54:40 +08:00
世界
5fdc051a08 Fix override_port in direct inbound 2025-04-16 17:04:13 +08:00
世界
cb68a40c43 documentation: Update actual behaviors of auto_redirect and strict_route 2025-04-12 13:06:16 +08:00
纳西妲 · Nahida
023218e6e7 Fix build will fail when use space to split each tag 2025-04-12 13:06:16 +08:00
世界
2a24b94b8d Minor fixes 2025-04-12 13:06:15 +08:00
世界
c6531cf184 Fix NTP service 2025-04-12 13:06:15 +08:00
世界
d4fa0ed349 Improve auto redirect 2025-04-12 13:06:10 +08:00
世界
10874d2dc4 Bump version 2025-04-08 14:34:09 +08:00
Fei1Yang
5adaf1ac75 Mark config file as noreplace for rpm 2025-04-08 14:21:08 +08:00
世界
9668ea69b8 Fix windows process searcher 2025-04-08 14:16:27 +08:00
testing
ae9bc7acf1 documentation: Fix typo
Signed-off-by: testing <58134720+testing765@users.noreply.github.com>
2025-04-08 14:16:23 +08:00
世界
594ee480a2 option: Fix listable 2025-04-08 14:16:23 +08:00
世界
a15b5a2463 Fix no_drop not work 2025-04-08 14:16:23 +08:00
Mahdi
991e755789 Fix conn copy 2025-04-08 14:16:22 +08:00
世界
97d41ffde8 Improve pause management 2025-04-08 14:16:22 +08:00
世界
24af0766ac Fix uTP sniffer 2025-04-08 14:16:22 +08:00
世界
af17eaa537 Improve sniffer 2025-04-08 14:16:22 +08:00
世界
3adc10a797 Fix hysteria2 close 2025-04-08 14:16:22 +08:00
xchacha20-poly1305
5eeef6b28e Fix multiple trackers 2025-04-08 14:16:22 +08:00
世界
f4c29840c3 Fix DNS sniffer 2025-03-31 20:45:04 +08:00
世界
47fc3ebda4 Add duplicate tag check 2025-03-29 23:10:22 +08:00
世界
9774a659b0 Fix DoQ / truncate DNS message 2025-03-29 17:41:22 +08:00
世界
2e4a6de4e7 release: Fix read tag 2025-03-27 20:30:57 +08:00
世界
a530e424e9 Bump version 2025-03-27 18:17:39 +08:00
世界
0bfd487ee9 Fix udpnat2 handler again 2025-03-27 18:17:39 +08:00
世界
6aae834493 release: Fix workflow 2025-03-27 18:17:39 +08:00
世界
f56131f38e Make linter happy 2025-03-24 20:38:42 +08:00
世界
273a11d550 Fix crash on udpnat2 handler 2025-03-24 18:14:32 +08:00
世界
ae8ce75e41 Fix websocket crash 2025-03-24 17:44:14 +08:00
世界
d6d94b689f release: Replace goreleaser build with scripts 2025-03-24 13:48:37 +08:00
世界
30d785f1ee release: Use fake goreleaser key 2025-03-21 22:25:51 +08:00
世界
db5ec3cdfc Fix connectionCopyEarly 2025-03-21 10:51:16 +08:00
世界
9aca54d039 Fix socks5 UDP 2025-03-16 14:46:44 +08:00
世界
d55d5009c2 Fix processing multiple sniffs 2025-03-16 09:21:54 +08:00
世界
4f3ee61104 Fix copy early conn 2025-03-15 08:09:04 +08:00
世界
96eb98c00a Fix httpupgrade crash 2025-03-14 17:17:28 +08:00
世界
68ce9577c6 Fix context in v2ray http transports 2025-03-14 17:07:17 +08:00
世界
3ae036e997 Downgrade goreleaser to stable since nfpm fixed 2025-03-13 18:53:19 +08:00
世界
5da2d1d470 release: Fix goreleaser version 2025-03-12 16:15:50 +08:00
世界
8e2baf40f1 Bump version 2025-03-11 20:18:34 +08:00
世界
c24c40dfee platform: Fix android start 2025-03-11 20:18:34 +08:00
世界
32e52ce1ed Fix udp nat for fakeip 2025-03-11 19:09:27 +08:00
世界
ed46438359 release: Use nightly goreleaser to fix rpm bug 2025-03-11 13:29:08 +08:00
世界
0b5490d5a3 Fix resolve domain for WireGuard 2025-03-11 12:02:25 +08:00
Tal Rasha
2d73ef511d Fix grpclite memory leak
Co-authored-by: talrasha007 <talrasha007@gmail.om>
2025-03-10 14:48:02 +08:00
Mahdi
63e6c85f6f Fix shadowsocks UoT 2025-03-10 14:47:59 +08:00
世界
8946a6d2d0 release: Use latest goreleaser 2025-03-09 15:27:04 +08:00
世界
d3132645fb documentation: Fix description of the UoT protocol 2025-03-09 15:26:42 +08:00
世界
373f158fe0 Fix download external ui with query params 2025-03-09 15:26:36 +08:00
世界
ce36835fab Fix override destination 2025-03-09 15:25:06 +08:00
世界
619fa671d7 Skip binding to the default interface as it will fail on some Android devices 2025-02-26 07:25:35 +08:00
世界
eb07c7a79e Bump version 2025-02-24 07:27:55 +08:00
Gavin Luo
7eb3535094 release: Fix systemd permissions 2025-02-24 07:27:55 +08:00
世界
93b68312cf platform: Add update WIFI state func 2025-02-23 08:35:30 +08:00
世界
97ce666e43 Fix http.FileServer short write 2025-02-23 08:35:30 +08:00
世界
4000e1e66d release: Fix update android version 2025-02-23 08:35:30 +08:00
世界
270740e859 Fix crash on route address set update 2025-02-23 08:35:30 +08:00
世界
6cad142cfe Bump Go to go1.24 2025-02-23 08:35:30 +08:00
世界
093013687c Fix sniff QUIC hidden in three or more packets 2025-02-18 18:14:59 +08:00
世界
ff31c469a0 Override version 2025-02-11 15:55:15 +08:00
世界
fbe390268c Bump version 2025-02-11 01:32:14 +08:00
世界
07ac01dcb7 platform: Update NDK to r28 2025-02-11 01:32:14 +08:00
ReleTor
badfdb62cd documentation: Fixes 2025-02-11 01:32:14 +08:00
printfer
986a410b30 documentation: Fix migration links 2025-02-11 01:32:14 +08:00
世界
9db2d58545 Fix override address 2025-02-11 01:32:14 +08:00
世界
4eed46ac59 Fix respond ICMP echo 2025-02-10 15:12:10 +08:00
世界
abc38d1dab Fix udpnat2 crash 2025-02-10 15:11:26 +08:00
世界
8d6c4f1289 release: Skip testflight when another build in review 2025-02-06 12:02:47 +08:00
世界
a2d40eb8b8 Fix override UDP destination 2025-02-06 11:20:35 +08:00
世界
17b502bb4b Update dependencies 2025-02-06 09:08:52 +08:00
世界
a0d4421085 Update quic-go to v0.49.0 2025-02-06 08:50:21 +08:00
世界
0d443072d1 Fix panic in auto-redirect initialize 2025-02-06 08:49:25 +08:00
世界
c9fb99b799 Fix missing ENOTCONN in IsClosed check 2025-02-06 08:48:49 +08:00
世界
92d245ad04 Bump version 2025-02-05 09:59:52 +08:00
世界
0908627297 Fix crash on remote rule-set stop 2025-02-05 08:58:10 +08:00
世界
7f79458b4f Minor updates 2025-02-01 19:49:33 +08:00
世界
9b4c11ba95 Fix rule-set not closed 2025-02-01 19:49:33 +08:00
世界
27c31eac5d Fix local rule-set not updated 2025-02-01 19:42:21 +08:00
世界
bab8dc0b82 Fix missing handshake for early conn 2025-01-31 12:57:35 +08:00
世界
d09d2fb665 Bump version 2025-01-30 15:09:54 +08:00
世界
e64cf3b7df Do not set address sets to routes on Apple platforms
Network Extension was observed to stop for unknown reasons
2025-01-27 13:40:39 +08:00
世界
9b73222314 documentation: Bump version 2025-01-27 10:53:14 +08:00
世界
3923b57abf release: Update NDK to r28-beta3 2025-01-27 10:53:14 +08:00
世界
4807e64609 documentation: Fix typo 2025-01-27 10:04:07 +08:00
世界
eeb37d89f1 Fix rule-set upgrade command 2025-01-27 09:50:27 +08:00
世界
08c1ec4b7e Fix legacy routes 2025-01-27 09:50:27 +08:00
HystericalDragon
6b4cf67add Fix endpoints not close 2025-01-27 09:50:27 +08:00
世界
e65926fd08 Fix tests 2025-01-13 15:14:30 +08:00
世界
f2ec319fe1 Fix system time 2025-01-13 15:14:30 +08:00
世界
32377a61b7 Add port hopping for hysteria2 2025-01-13 15:14:30 +08:00
世界
7aac801ccd tun: Set address sets to routes 2025-01-13 15:14:30 +08:00
世界
96fdf59ee4 Fix default dialer on legacy xiaomi systems 2025-01-13 15:14:30 +08:00
世界
50b8f3ab94 Add rule-set merge command 2025-01-13 15:14:30 +08:00
世界
ff7aaf977b Fix DNS match 2025-01-13 15:14:30 +08:00
世界
9a1efbe54d Fix domain strategy 2025-01-13 15:14:30 +08:00
世界
906c21f458 Fix time service 2025-01-13 15:14:30 +08:00
世界
d5e7af7a7e Fix socks5 UDP implementation 2025-01-13 15:14:30 +08:00
世界
4d41f03bd5 clash-api: Fix missing endpoints 2025-01-13 15:14:30 +08:00
世界
30704a15a7 hysteria2: Add more masquerade options 2025-01-13 15:14:30 +08:00
世界
83889178ed Improve timeouts 2025-01-13 15:14:30 +08:00
世界
1d2720bf5e Add UDP timeout route option 2025-01-13 15:14:30 +08:00
世界
c4b6d0eadb Make GSO adaptive 2025-01-13 15:14:30 +08:00
世界
0c66888691 Fix lint 2025-01-13 15:14:30 +08:00
世界
68781387fe refactor: WireGuard endpoint 2025-01-13 15:14:30 +08:00
世界
fd299a0961 refactor: connection manager 2025-01-13 15:14:30 +08:00
世界
285a82050c documentation: Fix typo 2025-01-13 15:14:30 +08:00
世界
2dbb8c55c9 Add override destination to route options 2025-01-13 15:14:30 +08:00
世界
effcf39469 Add dns.cache_capacity 2025-01-13 15:14:30 +08:00
世界
9db9484863 Refactor multi networks strategy 2025-01-13 15:14:30 +08:00
世界
ca813f461b documentation: Remove unused titles 2025-01-13 15:14:30 +08:00
世界
bb46cdb2b3 Add multi network dialing 2025-01-13 15:14:30 +08:00
世界
dcb10c21a1 documentation: Merge route options to route actions 2025-01-13 15:14:29 +08:00
世界
05ea0ca00e Add network_[type/is_expensive/is_constrained] rule items 2025-01-13 15:14:29 +08:00
世界
c098f282b1 Merge route options to route actions 2025-01-13 15:14:29 +08:00
世界
ecf82d197c refactor: Platform Interfaces 2025-01-13 15:14:29 +08:00
世界
9afe75586a refactor: Extract services form router 2025-01-13 15:14:29 +08:00
世界
a1be455202 refactor: Modular network manager 2025-01-13 15:14:29 +08:00
世界
19fb214226 refactor: Modular inbound/outbound manager 2025-01-13 15:14:29 +08:00
世界
28ec898a8c documentation: Add rule action 2025-01-13 15:14:29 +08:00
世界
467b1bbeeb documentation: Update the scheduled removal time of deprecated features 2025-01-13 15:14:29 +08:00
世界
02ab8ce806 documentation: Remove outdated icons 2025-01-13 15:14:29 +08:00
世界
ce69e620e9 Migrate bad options to library 2025-01-13 15:14:29 +08:00
世界
1133cf3ef5 Implement udp connect 2025-01-13 15:14:29 +08:00
世界
59a607e303 Implement new deprecated warnings 2025-01-13 15:14:29 +08:00
世界
313be3d7a4 Improve rule actions 2025-01-13 15:14:29 +08:00
世界
4fe40fcee0 Remove unused reject methods 2025-01-13 15:14:29 +08:00
世界
e233fd4fe5 refactor: Modular inbounds/outbounds 2025-01-13 15:14:29 +08:00
世界
9f7683818f Implement dns-hijack 2025-01-13 15:14:29 +08:00
世界
179e3cb2f5 Implement resolve(server) 2025-01-13 15:14:29 +08:00
世界
41b960552d Implement TCP and ICMP rejects 2025-01-13 15:14:29 +08:00
世界
8304295c48 Crazy sekai overturns the small pond 2025-01-13 15:14:29 +08:00
世界
253b41936e Bump version 2025-01-11 20:58:00 +08:00
世界
ce5b4b06b5 auto-redirect: Fix fetch interfaces 2025-01-07 21:24:52 +08:00
世界
50f5006c43 Fix leak in reality server 2025-01-07 17:27:10 +08:00
世界
e42ff22c2e quic: Fix source not unwrapped 2025-01-07 17:27:10 +08:00
世界
578571b972 Bump version 2025-01-02 15:21:13 +08:00
世界
935beca45d Fix check interface 2025-01-01 12:19:56 +08:00
世界
3e246f1173 Fix vmess mux leak 2025-01-01 12:18:56 +08:00
世界
1bc27a32c2 Fix linter configuration 2025-01-01 12:18:56 +08:00
世界
bc2e3960e4 Enable fix stack for Android 7 and 9 2024-12-28 15:06:57 +08:00
世界
9c4ab0bf33 Bump version 2024-12-21 17:20:56 +08:00
世界
27bdef34c7 release: Fix create app store version 2024-12-21 17:20:56 +08:00
世界
3c00099ed4 Fix process rule check 2024-12-21 17:15:41 +08:00
世界
2babf07f9a Fix wireguard 2024-12-21 16:07:19 +08:00
世界
4795ed712b release: Fix android version 2024-12-21 16:07:19 +08:00
世界
d4cd564dbe release: Fix check tag 2024-12-21 16:07:19 +08:00
世界
1676e13d3e Bump version 2024-12-17 13:43:17 +08:00
世界
50576084c6 release: Add publish testflight 2024-12-17 13:43:17 +08:00
世界
3a94e792a2 documentation: Fix uid range example 2024-12-15 13:56:25 +08:00
世界
9f69f41f68 Update http file server usage 2024-12-15 13:56:25 +08:00
世界
e6847ff50e Add locale support ford deprecated messages 2024-12-15 02:57:08 +08:00
世界
2ac2589d14 release: Fix publish testflight 2024-12-15 02:57:08 +08:00
世界
64a94e8144 release: Fix UpdateBuildForAppStoreVersion 2024-12-14 20:18:36 +08:00
世界
3ed8a5c5d1 wireguard: Fix windows tun read 2024-12-14 20:18:36 +08:00
世界
0a922c6fe3 release: Fix Xcode version 2024-12-14 20:18:36 +08:00
世界
52f3a4226c release: Add app store connect actions 2024-12-14 20:18:36 +08:00
世界
483d9fa503 release: Fix check prerelease 2024-12-14 20:18:36 +08:00
世界
dd9de694f8 release: Update macOS project version atomically 2024-12-14 20:18:36 +08:00
世界
5cdf5c1d9e release: Fix play release 2024-12-14 20:18:36 +08:00
世界
cec7e47086 Add workaround for bulkBarrierPreWrite: unaligned arguments panic 2024-12-14 20:18:36 +08:00
世界
1e6a3f1f0b release: Update debug iOS library build 2024-12-12 14:51:47 +08:00
世界
f0b6818b4c Add workaround for golang/go#68760 2024-12-10 10:30:42 +08:00
世界
3032317918 release: Add workflow build 2024-12-10 09:25:11 +08:00
世界
db22f61846 Update NDK to r28-rc1 2024-12-09 15:11:38 +08:00
世界
8c3a98faa2 wireguard: Fix set reserved 2024-12-05 17:58:09 +08:00
世界
1e787cb607 Fix initial traffic value 2024-12-03 21:43:56 +08:00
世界
558585b01d release: Set upload threads to 5 2024-12-03 17:36:19 +08:00
世界
6e7ecbd4f5 Fix wireguard listen 2024-11-30 12:20:48 +08:00
世界
5a661cde67 clashapi: Remove traffic loop 2024-11-28 12:57:56 +08:00
世界
3cc0e87cfb Bump version 2024-11-27 10:45:24 +08:00
世界
effea5a2b3 Update quic-go to v0.48.2 2024-11-27 10:37:00 +08:00
世界
7f168c5ec6 release: Clean before iOS build 2024-11-27 10:35:20 +08:00
Zephyruso
0e9129ee3f clashapi: Add mode list 2024-11-27 10:34:47 +08:00
世界
1086d5e665 Fix test tags 2024-11-23 12:20:12 +08:00
世界
d9102ba599 Fix build on bsd systems 2024-11-23 12:16:46 +08:00
世界
17019f1729 Bump version 2024-11-20 12:03:42 +08:00
世界
6be07ed51f Fix start watcher 2024-11-20 11:32:53 +08:00
世界
af58e3bec0 Fix debug listener 2024-11-20 11:32:53 +08:00
世界
e58b549d0f Fix "Fix reloading of tls.certificate_path, tls.key_path and tls.ech.key_path" 2024-11-18 19:03:24 +08:00
zeetex
1d81996ceb Fix reloading of tls.certificate_path, tls.key_path and tls.ech.key_path 2024-11-18 14:09:54 +08:00
世界
97c47e72c4 Update dependencies 2024-11-18 14:07:00 +08:00
世界
122be275b0 release: Notarize macos standalone manually with --no-s3-acceleration 2024-11-18 14:07:00 +08:00
世界
0bb1132034 selector: Fix crash before start 2024-11-18 14:07:00 +08:00
世界
de14337b4b Fix deprecated check 2024-11-18 14:07:00 +08:00
世界
1e07633914 Downgrade NDK to 26.2.11394342 2024-11-18 13:10:06 +08:00
世界
e3e203844e Fix decompile rule-set 2024-11-18 13:10:06 +08:00
世界
84a102a6ef Fix mux stream accept 2024-11-09 12:26:49 +08:00
世界
f1c76c4dde Fix deprecated version check 2024-11-08 20:31:29 +08:00
世界
8df0aa5719 Downgrade NDK to r26d 2024-11-07 19:58:53 +08:00
世界
21faadb992 Uniq deprecated notes 2024-11-07 19:58:53 +08:00
世界
88099a304a platform: Add SendNotification 2024-11-06 12:53:32 +08:00
世界
f504fb0d46 Revert "platform: Add openURL event"
This reverts commit 718cffea9a.
2024-11-05 20:03:39 +08:00
世界
1d517b6ca5 platform: Add link flags 2024-11-05 18:28:13 +08:00
世界
b702d0b67a Update dependencies 2024-11-05 18:28:03 +08:00
世界
a001e30d8b platform: Remove SetTraceback("all") 2024-11-05 18:28:02 +08:00
世界
cdb93f0bb2 Fix "Fix metadata context" 2024-11-05 18:27:51 +08:00
世界
718cffea9a platform: Add openURL event 2024-11-05 18:27:51 +08:00
世界
9585c53e9f release: Add upload dSYMs 2024-10-30 15:35:20 +08:00
世界
d66d5cd457 Add deprecated warnings 2024-10-30 14:01:28 +08:00
世界
8c143feec8 Increase timeouts 2024-10-30 14:01:28 +08:00
世界
419058f466 Update NDK version 2024-10-30 14:01:13 +08:00
世界
1a6047a61b Fix metadata context 2024-10-30 14:01:13 +08:00
世界
327bb35ddd Rename HTTP start context 2024-10-30 14:01:13 +08:00
世界
6ed9a06394 Fix rule-set format 2024-10-25 22:12:47 +08:00
世界
b80ec55ba0 Bump version 2024-10-16 21:11:31 +08:00
世界
08718112ae Retry system forwarder listen 2024-10-16 20:47:26 +08:00
TsingShui
956ee361df Fix corrected improper use of reader and bReader
Co-authored-by: x_123 <x@a>
2024-10-16 20:45:18 +08:00
世界
e93d0408be documentation: Fix release notes 2024-10-16 20:45:13 +08:00
世界
137832ff3e Bump version 2024-10-13 21:17:59 +08:00
世界
3ede29fb6d documentation: Improve theme 2024-10-13 13:07:18 +08:00
世界
82ab68b542 build: Fix find NDK 2024-10-13 13:07:18 +08:00
renovate[bot]
e55723d84d [dependencies] Update actions/checkout digest to eef6144 2024-10-13 13:07:18 +08:00
世界
2f4d2d97f9 auto-redirect: Let fw4 take precedence over prerouting 2024-10-13 13:07:18 +08:00
世界
926d6f769e Update utls to v1.6.7 2024-10-13 13:07:02 +08:00
srk24
846777cd0c Add process_path_regex rule type 2024-10-13 13:07:02 +08:00
世界
06533b7a3b clash-api: Add PNA support 2024-10-13 13:07:02 +08:00
世界
4a95558c53 Add RDP sniffer 2024-10-13 13:07:02 +08:00
世界
e39a28ed5a Add SSH sniffer 2024-10-13 13:07:02 +08:00
世界
b2c708a3e6 Write close error to log 2024-10-13 13:07:02 +08:00
世界
a9209bb3e5 Add AdGuard DNS filter support 2024-10-13 13:07:02 +08:00
世界
9dc3bb975a Improve QUIC sniffer 2024-10-13 13:07:02 +08:00
世界
3a7acaa92a Add inline rule-set & Add reload for local rule-set 2024-10-13 13:07:02 +08:00
世界
6bebe2483b Unique rule-set names 2024-10-13 13:07:02 +08:00
世界
93cf134995 Add accept empty DNS rule option 2024-10-13 13:07:02 +08:00
世界
ff7d8c9ba8 Add custom options for TUN auto-route and auto-redirect 2024-10-13 13:07:02 +08:00
世界
50f07b42f6 Improve base DNS transports & Minor fixes 2024-10-13 13:07:02 +08:00
世界
db3a0c636d Add auto-redirect & Improve auto-route 2024-10-13 13:07:02 +08:00
世界
fec38f85cd Add rule-set decompile command 2024-10-13 13:07:02 +08:00
世界
dcb0141646 Add IP address support for rule-set match match 2024-10-13 13:07:02 +08:00
世界
f4f5a3c925 Improve usages of json.Unmarshal 2024-10-13 13:07:02 +08:00
世界
9b8d6c1b73 Bump rule-set version 2024-10-13 13:07:02 +08:00
世界
2f776168de Implement read deadline for QUIC based UDP inbounds 2024-10-13 13:07:02 +08:00
世界
923d3222b0 WTF is this 2024-10-13 13:07:01 +08:00
世界
bda93d516b platform: Fix clash server reload on android 2024-10-13 13:06:57 +08:00
世界
7eec3fb57a platform: Add log update interval 2024-10-13 13:06:57 +08:00
世界
b1d75812c5 platform: Prepare connections list 2024-10-13 13:06:55 +08:00
世界
d44e7d9834 Drop support for go1.18 and go1.19 2024-10-07 04:58:48 +08:00
世界
369bc7cea3 Add DTLS sniffer 2024-10-07 04:58:48 +08:00
iosmanthus
4b7a83da16 Introduce bittorrent related protocol sniffers
* Introduce bittorrent related protocol sniffers

including, sniffers of
1. BitTorrent Protocol (TCP)
2. uTorrent Transport Protocol (UDP)

Signed-off-by: iosmanthus <myosmanthustree@gmail.com>
Co-authored-by: 世界 <i@sekai.icu>
2024-10-07 04:58:48 +08:00
世界
0f7154afbd Update workflow to go1.23 2024-10-07 04:58:47 +08:00
世界
a06d10c3bc Bump version 2024-10-07 04:34:48 +08:00
世界
63cc6cc76c Fix Makefile 2024-10-07 04:34:48 +08:00
世界
d55c5b5cab documentation: Update package status 2024-10-07 04:34:48 +08:00
世界
b624c2dcc7 Fix context used by DNS outbounds 2024-10-07 04:34:48 +08:00
世界
9415444ebd Fix base path not applied to local rule-sets 2024-10-07 04:34:48 +08:00
世界
95606191d8 Add completions for linux packages 2024-10-07 04:34:48 +08:00
世界
e586d9e9bc Bump version 2024-09-20 23:37:06 +08:00
世界
8c7eaa4477 Fix docker build 2024-09-20 23:37:06 +08:00
世界
8464c8cb7c Fix version script 2024-09-20 21:10:15 +08:00
世界
39d7127651 Revert "Fix stream sniffer" 2024-09-20 20:40:02 +08:00
世界
e2077009c4 documentation: Update client status 2024-09-20 20:13:55 +08:00
世界
700a8eb425 Minor fixes 2024-09-20 20:13:14 +08:00
世界
3b0cba0852 Fix wireguard start 2024-09-20 20:12:52 +08:00
世界
f5554dd8b8 Bump version 2024-09-18 07:04:29 +08:00
世界
4d0362d530 Update macOS build workflow 2024-09-17 22:01:05 +08:00
世界
97ccd2ca04 documentation: Add sponsors page 2024-09-17 18:47:33 +08:00
世界
1ed6654ad4 Add mips64 build 2024-09-15 12:12:25 +08:00
世界
5385f75f53 documentation: Update build requirements 2024-09-15 12:10:00 +08:00
世界
ad97d4e11f Fix disconnected interface selected as default in windows 2024-09-15 11:59:32 +08:00
世界
09d4e91b77 Fix cached conn eats up read deadlines 2024-09-15 11:56:04 +08:00
Monica
3dbdda9555 documentation: Fix dial.zh.md
The Chinese documentation incorrectly stated that the default value for the domain_strategy field in the direct outbound module is dns.strategy. The correct value should be inbound.domain_strategy, as specified in the English documentation. This commit corrects the Chinese documentation to align with the accurate behavior described in the English version.

Signed-off-by: Monica <1379531829@qq.com>
2024-09-15 11:53:03 +08:00
世界
1f4ed6ff8f documentation: Update client status 2024-09-13 10:09:08 +08:00
世界
6ddbe19bc0 platform: Update bundle id 2024-09-12 17:55:53 +08:00
世界
d7205ecc60 Fix Makefile 2024-09-12 17:55:53 +08:00
世界
9e243e0ff9 gomobile: Fix go mod version 2024-09-10 23:05:13 +08:00
世界
02bc3e0a0a Update quic-go to v0.47.0 2024-09-09 14:45:46 +08:00
世界
87be6dc235 Update README 2024-09-09 08:48:35 +08:00
世界
c1c30976dc Improve docker workflow 2024-09-08 11:11:47 +08:00
世界
9bac18bcd1 wireguard: Fix events chan leak 2024-09-08 10:07:12 +08:00
世界
ceda5cc95d clash-api: Fix bad redirect 2024-09-08 10:07:07 +08:00
世界
27d6b63e71 Fix stream sniffer 2024-09-08 10:07:07 +08:00
世界
b57abcc73c tfo: Fix build with go1.23 2024-08-27 11:24:51 +08:00
世界
f1147965dd documentation: Fix missing zh headline 2024-08-21 18:52:38 +08:00
世界
45f3234c73 documentation: Update package status 2024-08-21 11:39:07 +08:00
Mingye Wang
aae3fded32 documentation: Two updates
* Copyedit documentation

Close #1378

* remove yum, go full on dnf

fixes #2049
2024-08-21 11:32:43 +08:00
世界
090494faf5 Do not close bug and enhancement issues 2024-08-21 08:09:15 +08:00
世界
db5719e22f Fix no error return when empty DNS cache retrieved 2024-08-20 23:23:48 +08:00
世界
064fb9b873 Fix direct dialer not resolving domain 2024-08-20 21:02:52 +08:00
世界
f6a1e123fc Make linter happy 2024-08-19 18:42:02 +08:00
世界
3066dfe3b3 Bump version 2024-08-19 10:43:09 +08:00
Liu Bingyan
1128fdd8c7 documentation: Update injectable description 2024-08-19 07:40:31 +08:00
世界
cfd9879b17 documentation: Remove unused 2024-08-19 06:39:33 +08:00
世界
9ceb660c57 documentation: Announce that our apps on Apple platforms are no longer available 2024-08-19 06:39:33 +08:00
世界
7d00d7df28 Update quic-go to v0.46.0 2024-08-19 06:25:15 +08:00
世界
21b1ac26b9 Fix UDP conn stuck on sniff
This change only avoids permanent hangs. We need to implement read deadlines for UDP conns in 1.10 for server inbounds.
2024-08-18 11:27:12 +08:00
世界
7fec8d842e Update golangci-lint configuration 2024-08-18 11:17:01 +08:00
世界
07c678fb85 Fix crash on Android when using process rules 2024-08-18 10:23:28 +08:00
世界
baecfc7778 Update quic-go to v0.45.2 2024-08-18 10:23:17 +08:00
世界
07de36ecdb Fix tests 2024-08-03 12:46:38 +08:00
世界
2c8a8303cd dns: Minor fixes 2024-07-27 11:04:57 +08:00
ruokeqx
e5991cae0b Use unix.SysctlRaw for macOS 2024-07-22 12:42:22 +08:00
世界
1349acfd5a Fix panic caused by possible generation of duplicate keys for domain_suffix 2024-07-17 16:02:17 +08:00
世界
98ff897f35 Fix reset outbounds 2024-07-13 17:46:22 +08:00
ayooh
6144c8e340 Fix default end value of the rangeItem 2024-07-13 17:45:32 +08:00
HystericalDragon
c8caac9f67 Fix v2rayquic default NextProtos (#1934) 2024-07-11 23:34:09 +08:00
printfer
81e9eda357 documentation: Fix typo 2024-07-07 16:09:41 +08:00
世界
7cba3da108 shadowsocks: Fix packet process for AEAD multi-user server 2024-07-05 17:09:09 +08:00
世界
82d06b43e7 vless: Fix missing deadline interfaces 2024-07-05 17:08:10 +08:00
世界
a7ac91f573 Move legacy vless implemetation to library 2024-07-03 20:17:32 +08:00
世界
0540a95a43 Fix v2ray-plugin 2024-07-02 14:08:17 +08:00
世界
94707dfcdd Add name to errors from v2ray HTTP transports 2024-07-02 14:08:17 +08:00
世界
8a17043502 Fix command output for rule-set match 2024-06-26 12:26:35 +08:00
世界
b0aaa86806 Minor fixes 2024-06-25 13:14:45 +08:00
世界
8a2d3fbb28 Update quic-go to v0.45.1 & Filter HTTPS ipv4/6hint 2024-06-24 11:07:32 +08:00
renovate[bot]
4652019608 [dependencies] Update docker/build-push-action action to v6 2024-06-24 10:24:13 +08:00
世界
06fa5abf63 Fix non-IP queries accepted by address filter rules 2024-06-24 10:13:16 +08:00
世界
996fbbf0c3 docs: Switch to venv 2024-06-24 10:10:49 +08:00
renovate[bot]
142ff1b455 [dependencies] Update actions/checkout digest to 692973e 2024-06-17 13:05:54 +08:00
世界
74d662f7a3 Fix always create package manager 2024-06-11 21:16:57 +08:00
世界
085f603377 Bump version 2024-06-09 13:20:56 +08:00
世界
460fae83dc Fix quic-go is caching dialErr since v0.43.0 2024-06-09 13:15:40 +08:00
世界
bb9bd9bff6 Fix package manager start order 2024-06-09 07:21:50 +08:00
世界
c2354ebf25 Bump version 2024-06-08 22:00:46 +08:00
世界
c1f4755c4e Fix parse UDP DNS server with addr:port address 2024-06-08 22:00:46 +08:00
世界
0ca5909b06 Stop passing device sleep events on Android and Apple platforms 2024-06-08 22:00:46 +08:00
世界
e77a8114c5 Fix rule-set start order 2024-06-08 22:00:46 +08:00
renovate[bot]
f1393235ff [dependencies] Update actions/checkout digest to a5ac7e5 2024-06-08 22:00:46 +08:00
renovate[bot]
bdba2365de [dependencies] Update goreleaser/goreleaser-action action to v6 2024-06-08 18:58:22 +08:00
世界
ce0da5b557 Fix typo 2024-06-08 18:58:14 +08:00
世界
3853201412 Bump version 2024-06-07 19:07:46 +08:00
世界
7003ef40a3 Fix wireguard start 2024-06-07 19:07:46 +08:00
世界
59ec92228c Fix repeatedly no route logs 2024-06-07 15:03:48 +08:00
世界
0eeb2da323 Fix reset HTTP3 DNS transport 2024-06-06 22:28:43 +08:00
世界
977b0fac02 Fix get source address from X-Forwarded-For 2024-06-06 22:21:37 +08:00
世界
51964801ff Fix enforced power listener on windows 2024-06-06 22:20:23 +08:00
世界
e08c052fc9 Fix wireguard client bind 2024-06-06 22:20:21 +08:00
世界
53927d8bbd Remove logs in router initialize 2024-06-06 22:20:19 +08:00
世界
968b9bc217 Fix crash on *bsd 2024-06-06 22:20:17 +08:00
lgjint
69dc87aa6d Fix set KDE6 system proxy 2024-06-06 22:20:14 +08:00
Fei1Yang
4193df375f build: Remove vendor in RPM packages 2024-05-27 19:28:15 +08:00
世界
5ff7006326 Bump version 2024-05-25 11:30:43 +08:00
世界
a89107ea9d documentation: Bump version 2024-05-23 14:57:45 +08:00
世界
9ffdbba2ed documentation: Add manuel for mitigating tunnelvision attacks 2024-05-23 14:57:27 +08:00
世界
65c71049ea documentation: Update DNS manual 2024-05-23 14:57:26 +08:00
世界
7d4e6a7f4e dialer: Allow nil router 2024-05-23 14:57:26 +08:00
世界
d612620c5d Add rule-set match command 2024-05-23 14:57:26 +08:00
世界
8a9a77a438 Add bypass_domain and search_domain platform HTTP proxy options 2024-05-23 14:57:26 +08:00
世界
a2098c18e1 Handle includeAllNetworks 2024-05-23 14:57:26 +08:00
世界
cf2181dd3a Update gVisor to 20240422.0 2024-05-23 14:57:15 +08:00
世界
5899e95ff1 Update quic-go to v0.43.1 2024-05-21 15:12:05 +08:00
世界
d7160c19cf Fixed order for Clash modes 2024-05-21 15:12:05 +08:00
世界
da9e22b4e6 Add custom prefix support in EDNS0 client subnet options 2024-05-21 15:12:05 +08:00
气息
0e120f8a44 Fix DNS exchange index
Signed-off-by: 气息 <qdshizh@gmail.com>
2024-05-21 15:12:05 +08:00
dyhkwong
d918863ac5 Always disable cache for fake-ip servers 2024-05-21 15:12:04 +08:00
PuerNya
2ae192305c Always disable cache for fake-ip DNS transport if independent_cache disabled 2024-05-21 15:12:03 +08:00
世界
71d1879bd6 Fix missing rule_set_ipcidr_match_source item in DNS rules 2024-05-21 15:12:03 +08:00
世界
917514e09f Improve DNS truncate behavior 2024-05-21 15:12:03 +08:00
世界
5327aeaea4 Fix DNS fallthrough incorrectly 2024-05-21 15:12:03 +08:00
世界
93ae3f7a1e Add rejected DNS response cache support 2024-05-21 15:12:03 +08:00
世界
f24a2aed7d Add support for client-subnet DNS options 2024-05-21 15:12:03 +08:00
世界
0517ceef76 Add address filter support for DNS rules 2024-05-21 15:12:02 +08:00
世界
830ea46932 Fix timezone for Android and iOS 2024-05-21 15:11:52 +08:00
世界
cd0fcd5ddc Improve loopback detector 2024-05-21 15:11:52 +08:00
世界
003176f069 Remove unused fakeip packet conn 2024-05-21 15:11:52 +08:00
世界
71d92518c1 Set the default TCP keep alive period 2024-05-21 15:11:52 +08:00
世界
b5dcd6bf59 Migrate ntp service to library 2024-05-21 15:11:52 +08:00
世界
11c7b4a866 Handle Windows power events 2024-05-21 15:11:52 +08:00
世界
ee14135298 Improve domain suffix match behavior
For historical reasons, sing-box's `domain_suffix` rule matches literal prefixes instead of the same as other projects.

This change modifies the behavior of `domain_suffix`: If the rule value is prefixed with `.`,
the behavior is unchanged, otherwise it matches `(domain|.+\.domain)` instead.
2024-05-21 15:11:41 +08:00
世界
cbcf005f37 Remove PROCESS_NAME_NATIVE dwFlag in process query output
The `process_path` rule of sing-box is inherited from Clash,
the original code uses the local system's path format (e.g. `\Device\HarddiskVolume1\folder\program.exe`),
but when the device has multiple disks, the HarddiskVolume serial number is not stable.

This change make QueryFullProcessImageNameW output a Win32 path (such as `C:\folder\program.exe`),
which will disrupt the existing `process_path` use cases in Windows.
2024-05-18 17:22:14 +08:00
世界
daee0b154e badtls: Support uTLS and TLS ECH for read waiter 2024-05-18 17:22:14 +08:00
世界
d530c724c0 Bump version 2024-05-18 16:53:05 +08:00
世界
7f698c1104 Fix hysteria2 panic 2024-05-18 16:20:32 +08:00
renovate[bot]
7a4a44c6d2 [dependencies] Update golangci/golangci-lint-action action to v6 2024-05-10 19:49:20 +08:00
世界
44277e5dd2 Fix urltest logic 2024-05-10 17:41:20 +08:00
世界
1f470c69c4 Update docker workflow 2024-05-03 19:33:20 +08:00
世界
742adacce7 Bump version 2024-05-03 17:38:26 +08:00
世界
32e1d5a5e2 documentation: Update package status 2024-05-03 17:38:26 +08:00
世界
cb9f4ce597 Minor fixes 2024-05-03 15:34:47 +08:00
世界
4b1a6185ba Fix DNF repo 2024-04-29 23:13:04 +08:00
世界
8d85c92356 Fix multiplex client dialer context 2024-04-29 11:58:20 +08:00
世界
c6164c9eca Fix usage of github actions 2024-04-29 11:58:20 +08:00
dyhkwong
3c85b8bc48 Fix fake-ip mapping 2024-04-29 11:58:20 +08:00
HystericalDragon
8b8fb4344c Remove unused encoder 2024-04-29 11:58:20 +08:00
renovate[bot]
e85a38e059 [dependencies] Update golangci/golangci-lint-action action to v5 2024-04-29 11:58:20 +08:00
renovate[bot]
f3ac91673a [dependencies] Update actions/checkout digest to 0ad4b8f 2024-04-29 11:58:20 +08:00
世界
0f1e58b917 documentation: Update TestFlight 2024-04-29 11:58:20 +08:00
世界
c4cfe24aef documentation: Fix strict_route description 2024-04-29 11:58:20 +08:00
世界
3d73b159ba Fix linux workflow 2024-04-29 11:58:20 +08:00
世界
0ae1afef44 Bump version 2024-04-23 14:14:40 +08:00
世界
a5e2a4073b Update dependencies 2024-04-23 14:03:55 +08:00
世界
b6cb3948a3 Fix initial packet MTU for QUIC protocols 2024-04-23 11:12:31 +08:00
世界
7b0f5061dc Fix loopback detector 2024-04-17 21:45:54 +08:00
世界
76f20482f7 Fix linux repo 2024-04-12 22:52:37 +08:00
世界
e735a5bdc8 Update dependencies 2024-04-12 22:52:37 +08:00
世界
70381e93c8 Bump version 2024-04-08 11:55:41 +08:00
世界
07a40716e8 Update dependencies 2024-04-08 11:45:00 +08:00
世界
5fea5956db Fix linux network monitor 2024-04-08 11:45:00 +08:00
世界
d20a389043 Fix timer usage 2024-04-08 11:45:00 +08:00
世界
4a4180bde5 Fixes for QUIC protocols 2024-04-08 11:44:55 +08:00
世界
7ecb6daabb Update dependencies 2024-03-29 04:52:06 +00:00
世界
712bdd9ae5 Fix fury release 2024-03-25 10:44:57 +08:00
世界
a3b74591a7 Fix syscall packet read waiter 2024-03-25 01:49:59 +08:00
世界
2f4abc6523 Fix canceler 2024-03-24 19:12:07 +08:00
dyhkwong
965ab075d9 Fix source_ip_is_private matching 2024-03-24 19:06:02 +08:00
世界
ed2f8b9637 Bump version 2024-03-23 20:50:45 +08:00
世界
0f71ce5120 tun: Fix GSO batch size 2024-03-22 14:54:57 +08:00
世界
f8085ab111 Fix Makefile 2024-03-21 23:32:02 +08:00
世界
f61b272cbf Fix WireGuard client bind 2024-03-20 10:52:24 +08:00
世界
59d437b9d2 Use local legacy compiler 2024-03-19 13:53:34 +08:00
世界
a7338fdc2b Fix DNS panic 2024-03-19 12:17:32 +08:00
Puqns67
d88860928e Fix the installation location of service files in prebuilded package 2024-03-19 12:13:59 +08:00
世界
20a2e38f47 Fix build for F-Droid 2024-03-17 21:43:22 +08:00
世界
acd438be23 Fix fury workflow 2024-03-17 21:43:22 +08:00
世界
e27fb51b54 Bump version 2024-03-16 12:05:58 +08:00
世界
adc38b26eb Fix Makefile 2024-03-15 18:06:37 +08:00
世界
7e943e743a Fix darwin interface monitor 2024-03-15 18:06:37 +08:00
世界
ceffcc0ad2 platform: Improve stop on apple platforms 2024-03-15 18:06:37 +08:00
世界
fdc451f7c6 platform: Fix missing log on apple platforms 2024-03-15 18:06:37 +08:00
世界
b48c471e6a Add repo release 2024-03-15 18:06:37 +08:00
世界
4b1fabd007 Fix lint workflow 2024-03-13 19:39:59 +08:00
世界
2b5eb1c59e Add sponsor link to issue template 2024-03-13 19:39:59 +08:00
世界
e2d3862e64 platform: reset network on invalid power events 2024-03-13 19:39:59 +08:00
世界
4f5e7b974d Fix crash in HTTP proxy server again 2024-03-10 16:53:58 +08:00
世界
21dedddd93 Update dependencies 2024-03-08 23:36:33 +08:00
世界
e02502bec0 Update go 1.20 2024-03-08 23:31:42 +08:00
世界
ba67633ee8 Fix missing source address in inbound logs in QUIC inbounds 2024-03-07 10:47:16 +08:00
世界
7fd9abe802 Bump version 2024-03-05 13:14:38 +08:00
世界
78a5f59202 documentation: Add link to F-Droid 2024-03-05 13:13:31 +08:00
世界
8d0da685d2 Update dependencies 2024-03-02 14:29:28 +08:00
世界
e6644f784e Fix crash in HTTP proxy server 2024-03-02 14:28:47 +08:00
世界
2b93b74d38 Fix SO_BINDTOIFINDEX usage 2024-02-29 13:03:05 +08:00
世界
dd52c26ae1 Don't return error in WireGurad client bind 2024-02-28 15:28:38 +08:00
世界
f288e3898b Bump version 2024-02-28 15:10:28 +08:00
世界
1bc893a73a documentation: Update privacy policy to make Play Store happy 2024-02-28 15:10:28 +08:00
世界
7359fdf195 platform: Upgrade NDK to the latest LTS version 2024-02-28 15:10:28 +08:00
世界
02b7041de6 Update dependencies 2024-02-26 22:53:31 +08:00
世界
96ac931b11 Fix network/interface monitor 2024-02-26 22:53:31 +08:00
世界
3077a82650 Fix reproducible builds 2024-02-24 23:19:31 +08:00
世界
de998c5119 Fix docker workflow 2024-02-24 22:49:07 +08:00
世界
d32c30c4b7 documentation: Bump version 2024-02-24 13:20:43 +08:00
世界
4823023806 Remove invalid archlinux packages from release 2024-02-24 13:20:43 +08:00
Aleksandr Razumov
bb355d17b2 Add riscv64 to platform list in release 2024-02-24 13:20:43 +08:00
hiddify
aaf30bf92b platform: Fix group update interval not taking effect 2024-02-24 13:20:43 +08:00
hiddify
f8c400cffc platform: Remove duplicated close 2024-02-24 13:20:43 +08:00
hatune-miku
3c24411e14 documentation: Fix navigation menu 2024-02-24 13:20:42 +08:00
世界
4a44aa3c21 Fix HTTP inbound 2024-02-24 13:20:42 +08:00
世界
8db2ae0c83 Fix documentation 2024-02-24 13:20:42 +08:00
世界
80d1aebcb7 platform: Unify client versions 2024-02-24 13:20:27 +08:00
世界
5583e01c99 platform: Export NeedWIFIState for Android 2024-02-18 14:24:21 +08:00
世界
bca0b86549 Copy DNS message struct instead of deep copy 2024-02-10 23:49:09 +08:00
世界
8332878cdc Fix destination IP CIDR match in DNS 2024-02-10 22:37:42 +08:00
世界
d0ba69ad22 Fix TUN unaligned panic on windows 2024-02-10 21:22:02 +08:00
世界
31b8834427 documentation: Fix description for with_ech 2024-02-10 12:01:09 +08:00
renovate[bot]
d0f7a59e9b [dependencies] Update golang Docker tag to v1.22 2024-02-10 11:54:40 +08:00
renovate[bot]
71e7d517a8 [dependencies] Update github-actions 2024-02-10 11:54:31 +08:00
世界
e6885e9967 platform: Ignore momentary pause on iOS 2024-02-09 13:57:47 +08:00
世界
e2090923db Bump Go version 2024-02-08 20:31:40 +08:00
世界
46be319976 Fix external controller crash before started 2024-02-08 20:31:40 +08:00
renovate[bot]
b27bc45cf2 [dependencies] Update actions/cache action to v4 2024-02-03 15:08:41 +08:00
世界
3d735281f4 documentation: Bump version 2024-02-02 17:51:40 +08:00
世界
8760a0d94d Fix android interface monitor 2024-02-02 17:51:40 +08:00
世界
2239b59933 Remove duplicated rules 2024-02-02 14:30:27 +08:00
世界
425a63f59d Fix rawConn not closed for hy/hy2 2024-02-02 14:30:27 +08:00
世界
b85725c009 urltest: Remember the last choice when there is no valid result 2024-02-02 11:27:36 +08:00
世界
17aebc56c1 Fix UDP DNS response not truncated 2024-02-01 14:43:59 +08:00
Devman
f76b21b02c Fix mobile build on windows
gobind executable name is not exactly `gobind` on windows it's `gobind.exe`

Signed-off-by: Devman <85770917+amir-devman@users.noreply.github.com>
2024-02-01 12:07:39 +08:00
kkocdko
704545a2ec Fix rule description header of domain_suffix
I read other rule_item_xxx.go files, they are all snake case. This description is showed on dashboard like yacd.

Signed-off-by: kkocdko <31189892+kkocdko@users.noreply.github.com>
2024-02-01 10:42:12 +08:00
dyhkwong
dc7b7afc06 Fix loop back detector 2024-02-01 10:41:38 +08:00
世界
e478d3c2dc Update workflow 2024-01-24 12:21:18 +08:00
世界
c8318058bb documentation: Bump version 2024-01-23 11:57:28 +08:00
世界
abca2118e7 documentation: Update geosite usage 2024-01-23 11:57:28 +08:00
世界
a8ee41715a platform: Add service error wrapper for macOS system extension 2024-01-22 19:00:09 +08:00
世界
94f76d6671 Update dependencies 2024-01-22 14:37:21 +08:00
世界
bf6cc8903c Fix missing write result in TFO open 2024-01-19 11:22:13 +08:00
世界
1b15e1692a Add sponsor button 2024-01-19 11:22:13 +08:00
世界
017372db25 Fix missing loopback detect 2024-01-19 11:22:12 +08:00
世界
216a0380fe documentation: Bump version 2024-01-16 05:50:07 +08:00
Noob Zhang
71b9e4ff17 Add network-online.target in .service files
This helps the daemon work better on IoT devices
like RaspberryPi.
According to systemd's documentation,
`network.target` means there has already been
a network manager started, but the network may
not be "up". On most PCs this does not matter
because the network will turn to "up" almost
immidiately. The IoT devices' network interface
may not be set up quickly enough, so they may
meet that the sing-box daemon is started before
network is ready, which results that sing-box
cannot find a working route. The workaround
of this is restarting sing-box daemon but it
absolutely is not the perfect solution.
As `network-online.target` must be triggered by
network manager after you configured it, I keep
`network.target` so there will be no change to
those who do not enabled proper trigger service
like `NetworkManager-wait-online.service`.

See also: https://systemd.io/NETWORK_ONLINE/
2024-01-16 05:50:07 +08:00
printfer
9b7deb5246 Enhanced light/dark mode support in mkdocs configuration 2024-01-16 05:50:07 +08:00
世界
a850a73e1a Fix missing upstream func for read wait conn 2024-01-16 05:50:07 +08:00
世界
c4d9be9e0d Fix rule match 2024-01-14 13:01:57 +08:00
世界
f31c604b3d documentation: Bump version 2024-01-09 12:22:22 +08:00
世界
4c8a50a52b Fix TLS conn cast for vision 2024-01-09 12:22:22 +08:00
世界
b326e60998 Update dependencies 2024-01-09 12:22:22 +08:00
世界
11bec79a06 documentation: Bump version 2024-01-05 11:17:49 +08:00
世界
16eff06c37 documentation: remove usages of category-companies@cn since merged into cn 2024-01-03 12:21:53 +08:00
世界
2911eba236 documentation: Update package managers 2024-01-03 12:21:48 +08:00
世界
2e607118c3 Remove unnecessary context wrappers 2024-01-03 12:21:48 +08:00
世界
89c723e3e4 Improve read wait interface &
Refactor Authenticator interface to struct &
Update smux &
Update gVisor to 20231204.0 &
Update quic-go to v0.40.1 &
Update wireguard-go &
Add GSO support for TUN/WireGuard &
Fix router pre-start &
Fix bind forwarder to interface for systems stack
2024-01-03 12:21:47 +08:00
世界
35fd9de3ff Make generated files have SUDO_USER's permissions if possible. 2024-01-03 12:21:47 +08:00
世界
6ddcd3954d Refactor inbound/outbound options struct 2024-01-03 12:21:47 +08:00
世界
36b0f2e91a Improve configuration merge 2024-01-03 12:21:47 +08:00
世界
fe053e26b5 Update uTLS to 1.5.4 2024-01-03 12:21:47 +08:00
世界
269434cfe6 Update tfo-go 2024-01-03 12:21:47 +08:00
世界
88495a24dc Update gomobile and add tag 2024-01-03 12:21:46 +08:00
世界
d131a7c10a Update cloudflare-tls to go1.21.5 2024-01-03 12:21:46 +08:00
世界
744a5d703b Make type check strict 2024-01-03 12:21:46 +08:00
世界
09421b6378 Remove comparable limit for Listable 2024-01-03 12:21:46 +08:00
世界
21283b554a Avoid opening log output before start &
Replace tracing logs with task monitor
2024-01-03 12:21:46 +08:00
世界
25810b50c1 Update documentation 2024-01-03 12:21:37 +08:00
世界
f1e3a59db3 Add idle_timeout for URLTest outbound 2024-01-03 12:21:37 +08:00
世界
a99deb2cb5 Skip internal fake-ip queries 2024-01-03 12:21:37 +08:00
世界
38d28e0763 Migrate contentjson and badjson to library &
Add omitempty in format
2024-01-03 12:21:37 +08:00
世界
e09a94bb9e Update documentation 2024-01-03 12:21:36 +08:00
世界
a21c5324fd Independent source_ip_is_private and ip_is_private rules 2024-01-03 12:21:36 +08:00
世界
4b43acfec0 Add rule-set 2024-01-03 12:21:36 +08:00
世界
7df151e820 Update buffer usage 2024-01-03 12:21:36 +08:00
世界
5948ffb965 Allow nested logical rules 2024-01-03 12:21:36 +08:00
世界
bf4e556f67 Migrate to independent cache file 2024-01-03 12:21:36 +08:00
世界
e3f8567690 documentation: Bump version 2024-01-02 14:31:53 +08:00
世界
40c7f3e170 Fix geoip close 2024-01-02 14:31:23 +08:00
世界
c506255e0f Fix grpc lite transport encoding 2024-01-01 16:16:58 +08:00
世界
87c6fd4c0f Fix h2mux request context 2024-01-01 16:15:49 +08:00
世界
19c445d28e documentation: Bump version 2023-12-29 18:00:40 +08:00
世界
9119a5209b dcoumentation: Fix description of cipher_suites 2023-12-29 18:00:40 +08:00
世界
46c8d6e61f Fix pprof URL path 2023-12-29 18:00:40 +08:00
世界
ea17c2786d Update dependencies 2023-12-27 10:37:18 +08:00
世界
12ababd911 Fix mux test 2023-12-27 10:30:19 +08:00
世界
0523845833 Update issue reporting templates
Enhanced the issue reporting templates for both English and Chinese versions by adding more structured and comprehensive guideline checkboxes. This aims to ensure contributors provide sufficient and beneficial information for reproducing and resolving issues, thereby improving the quality of reports and making issue tracking more efficient.
2023-12-26 19:05:19 +08:00
renovate[bot]
57794919fa [dependencies] Update actions/upload-artifact action to v4 2023-12-26 19:04:51 +08:00
世界
f5bb5cf343 Fix missing marshal for udp_timeout 2023-12-26 10:52:46 +08:00
世界
3eed614dea Fix ACME ALPN conflict 2023-12-26 09:02:58 +08:00
世界
76a295a660 Fix missing nil check for URLTest 2023-12-26 09:02:58 +08:00
世界
082e3fb8df Fix V2Ray transport path validation behavior 2023-12-26 09:02:58 +08:00
世界
a0cab4f563 Fix websocket client initialize 2023-12-22 20:38:06 +08:00
世界
aeb7308e81 documentation: Bump version 2023-12-21 15:25:19 +08:00
世界
bb1ebfda83 documentation: Fix link format 2023-12-21 15:24:05 +08:00
世界
c05c798221 Fix missing UDP timeout for QUIC protocols 2023-12-21 15:16:36 +08:00
世界
55b1bcc6a5 Migrate udp_timeout from seconds to duration format 2023-12-21 14:50:33 +08:00
世界
d6eddce420 Fix missing handshake timeout for multiplex 2023-12-21 14:21:59 +08:00
世界
4bf057139b Fix DNS dial context 2023-12-21 14:19:27 +08:00
世界
a1b28b8282 Try to fix HTTP server leak again 2023-12-21 14:16:16 +08:00
世界
d0aaf71770 Fix direct UDP override 2023-12-18 21:48:59 +08:00
世界
2f31202c6b documentation: Update shadowsocks example
Remove the information on password generation for `2022-blake3-aes-128-gcm` cipher from the Server Example section in the shadowsocks.md file as it is no longer needed.
2023-12-14 21:05:41 +08:00
renovate[bot]
e4cc510712 [dependencies] Update github-actions 2023-12-14 15:14:22 +08:00
世界
e329bf6865 Update dependencies 2023-12-14 15:09:03 +08:00
世界
2badcec765 platform: Fix check config 2023-12-12 20:26:41 +08:00
世界
e71c13b1a2 documentation: Bump version 2023-12-12 13:54:23 +08:00
世界
a959a67ed3 FIx error handling for netlink banned in Android 2023-12-12 13:54:23 +08:00
世界
a1044af579 platform: Fix VPN route 2023-12-11 21:59:59 +08:00
世界
a64b57451a Update dependencies 2023-12-11 21:40:11 +08:00
世界
f0e2318cbd Fix auto-route IPv6 on darwin 2023-12-11 21:39:29 +08:00
世界
ebec308fd8 documentation: Bump version 2023-12-09 00:22:27 +08:00
世界
ca094587be Fix incorrect dependency 2023-12-09 00:22:27 +08:00
世界
ca3b86c781 Update bug report template 2023-12-08 21:56:30 +08:00
世界
5a1d0047b9 documentation: Bump version 2023-12-08 21:38:34 +08:00
世界
4669854039 Fix method check for v2ray HTTP transport 2023-12-08 21:12:52 +08:00
世界
2eecdc38a4 Fix gun conn setup 2023-12-08 21:10:36 +08:00
世界
83581b7c1a Update dependencies 2023-12-08 11:18:52 +08:00
世界
d346f0023d Fix HTTP connect client again 2023-12-08 11:18:52 +08:00
世界
47b7a29cbd Fix fallback packet conn 2023-12-06 18:47:50 +08:00
世界
cffc07579d Fix missing omitempty for NTP server fields 2023-12-05 18:32:21 +08:00
世界
0ef268637e Prevent nil LocalAddr or RemoteAddr 2023-12-05 15:11:09 +08:00
世界
50f5a76380 Update dependencies 2023-12-04 21:06:15 +08:00
世界
20ca05dd36 Fix crash on websocket concurrent request 2023-12-04 20:42:01 +08:00
世界
5a792b186a documentation: Bump version 2023-12-03 16:54:49 +08:00
世界
3f458064a3 Fix not set Host header for HTTP outbound 2023-12-03 16:54:49 +08:00
世界
5269231df0 Fix URLTest outbound 2023-12-02 17:55:58 +08:00
世界
fc8e49994c documentation: Bump version 2023-12-01 20:39:01 +08:00
世界
e911d4aa4b Fix URLTest group early start 2023-12-01 20:39:01 +08:00
世界
01f6e70bc5 Fix deadline usage 2023-12-01 20:39:01 +08:00
世界
5f1e39a42c documentation: Bump version 2023-11-29 21:01:28 +08:00
世界
4f7770e254 Update dependencies 2023-11-29 21:01:19 +08:00
世界
e8c4c942c0 documentation: Bump version & Refactor docs 2023-11-28 11:21:57 +08:00
世界
253976d6c0 Add wifi_ssid and wifi_bssid route and DNS rules 2023-11-28 11:21:44 +08:00
世界
f0571b4122 Update quic-go to v0.40.0 2023-11-28 11:21:44 +08:00
世界
1b71e52e90 Migrate multiplex and UoT server to inbound & Add tcp-brutal support for multiplex 2023-11-28 11:21:44 +08:00
世界
6d24be23da Add support for v2ray http upgrade transport 2023-11-28 11:21:44 +08:00
世界
2a45c178fa Add exclude route support for tun &
Update gVisor to 20231113.0
2023-11-28 11:21:43 +08:00
世界
81e214812f Add udp_disable_domain_unmapping inbound listen option 2023-11-28 11:21:43 +08:00
世界
4d23773a25 Migrate to gobwas/ws 2023-11-28 11:21:43 +08:00
世界
40a0b69918 Fix dhcp reset 2023-11-28 11:20:48 +08:00
世界
a7b37c5953 documentation: Bump version 2023-11-24 20:58:48 +08:00
世界
03663a5093 Fix cachefile permission 2023-11-24 20:58:48 +08:00
世界
b08226a850 Fix "Fix HTTP server leak" 2023-11-24 19:58:31 +08:00
世界
edbae5dc4d Fix missing UDP user context on TUIC/Hysteria2 inbounds 2023-11-24 19:56:43 +08:00
世界
0f8ad0234b Remove unused code 2023-11-24 19:55:53 +08:00
世界
661eadc3bd documentation: Bump version 2023-11-21 10:22:44 +08:00
世界
50c1290567 Update dependencies 2023-11-21 10:22:44 +08:00
世界
eaccc9759a Fix platform API check 2023-11-21 10:22:44 +08:00
世界
925214869b Add test for ss2022 EIH 2023-11-20 18:36:44 +08:00
世界
6a2bfd26d0 Fix QUIC sniffer 2023-11-16 22:48:16 +08:00
世界
72a81afb76 Fix "Fix Linux IPv6 auto route rules" 2023-11-16 18:25:07 +08:00
世界
240abe204c Fix zero TTL was incorrectly reset 2023-11-16 18:25:07 +08:00
世界
7c49196792 build: Fix bad environment key 2023-11-16 01:42:24 +08:00
嫦悅
3a2808cff6 documentation: Fix typo
The old meaning is wrong. Correct the meaning according to the English documentation and the actual effect of the option.

Signed-off-by: 嫦悅 <lomombwlo@gmail.com>
2023-11-16 01:12:26 +08:00
guangwu
005d6cf4cf chore: unnecessary use of fmt.Sprintf 2023-11-16 01:10:52 +08:00
世界
36dff630d6 documentation: Bump version 2023-11-15 14:10:37 +08:00
世界
1825869124 platform: Refactor log interface 2023-11-15 14:10:37 +08:00
世界
3cadc90375 Fix TUIC authentication failed error message 2023-11-14 20:15:41 +08:00
renovate[bot]
2c6967d7f9 dependencies: Update actions/checkout digest to b4ffde6
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-14 20:15:41 +08:00
世界
fe866b123a Fix sing-tun version 2023-11-14 20:15:41 +08:00
世界
cbef1b1e59 Remove apk build due to missing openrc configuration 2023-11-14 20:15:41 +08:00
世界
e21f84932c build: Fix missing linux/386 2023-11-14 10:35:27 +08:00
0x7d274284
7a679bc328 Fix Dockerfile
Keep the .git folder at compile time to get the correct version

Signed-off-by: 0x7d274284 <112329548+0x7d274284@users.noreply.github.com>
2023-11-14 10:34:38 +08:00
世界
6635dd9abc documentation: Bump version 2023-11-13 21:50:36 +08:00
世界
ce164724ea build: Add apk and archlinux package builds 2023-11-13 15:28:28 +08:00
世界
a3ef7a7d88 Fix trojan-go mux context
Co-authored-by: maskedeken <maskedeken@yahoo.com>
Co-authored-by: 世界 <i@sekai.icu>
2023-11-13 14:12:35 +08:00
世界
71218ef0d3 build: Unify build tags 2023-11-13 14:12:35 +08:00
世界
e777b4c6dc Fix not closing outConn
Co-authored-by: Mahdi-zarei  <mahdi.zrei@gmail.com>
Co-authored-by: 世界 <i@sekai.icu>
2023-11-13 13:54:03 +08:00
世界
6815f94180 build: Update Go to 1.20.11 for legacy builds 2023-11-13 13:47:15 +08:00
世界
b013acd89d tun: Fix broadcast filter not applied to mixed stack 2023-11-13 13:35:10 +08:00
世界
f7c2eb6e76 Fix v2ray ws crash 2023-11-13 13:34:31 +08:00
世界
3ef9b1b343 Update protobuf generated binary 2023-11-10 10:56:25 +08:00
SakuraWald
2224c68959 Fix issue template 2023-11-10 10:36:46 +08:00
Kumiko as a Service
bb7d03d1db Use golang's cross-compilation capabilities 2023-11-10 10:36:32 +08:00
世界
50036924e8 documentation: Bump version 2023-11-10 10:24:18 +08:00
世界
c2c3f7284f Revert "Fix Host ignored in v2ray websocket transport"
This reverts commit aaa6702863.
2023-11-09 16:55:47 +08:00
世界
f6fee53676 Fix mux client close 2023-11-09 16:55:47 +08:00
世界
63b8e8ed23 platform: Increase HTTP timeout to 15s 2023-11-09 16:55:47 +08:00
世界
6ae86eda98 build: Update gradle command 2023-11-09 16:55:47 +08:00
世界
267d9617b7 build: Fix tag calculate 2023-11-07 22:27:37 +08:00
世界
0a06ccae50 platform: Fix legacy code 2023-11-07 22:14:23 +08:00
世界
8de0fad9f5 documentation: Bump version 2023-11-07 10:20:05 +08:00
世界
e05bf6308e Fix build script 2023-11-07 10:20:05 +08:00
世界
a20a0cb455 Add broadcast filter 2023-11-07 10:20:05 +08:00
世界
d29f7475d2 documentation: Bump version 2023-11-06 19:37:45 +08:00
世界
aaa6702863 Fix Host ignored in v2ray websocket transport 2023-11-05 23:27:11 +08:00
世界
bb928f096a Fix missing default next proto in hysteria2 2023-11-05 23:11:40 +08:00
johnthecoderpro
9f01d5c5b4 Fix download geo resources 2023-11-05 16:03:15 +08:00
世界
11629a931b Update release script 2023-11-05 16:02:18 +08:00
世界
126f825241 Update dependencies 2023-11-05 16:02:10 +08:00
世界
998cc7bd22 Add multicast filter for tun 2023-11-04 08:04:17 +08:00
世界
3efccaa8f5 Update dependencies 2023-10-31 18:24:58 +08:00
世界
d57b35ec30 documentation: Add privacy policy for android 2023-10-31 17:32:18 +08:00
世界
e82dab027d documentation: Bump version 2023-10-30 13:59:49 +08:00
世界
9350f3983b docs: Remove obsolete fields 2023-10-30 13:59:49 +08:00
世界
53b123241f android: Add build info tools for debug 2023-10-30 12:41:24 +08:00
世界
97286eea1e Add TLS self sign generate command 2023-10-30 12:41:23 +08:00
世界
343e24969d Add brutal debug option for Hysteria2 2023-10-30 12:41:23 +08:00
世界
31c294d998 Update BBR and Hysteria congestion control & Migrate legacy Hysteria protocol to library 2023-10-30 12:41:22 +08:00
世界
3b161ab30c Fix netip.Prefix usage 2023-10-30 12:41:22 +08:00
septs
41fd1778a7 Improve HTTP headers option 2023-10-30 12:41:22 +08:00
septs
ac930cf1aa Improve naive auth logical 2023-10-30 12:41:22 +08:00
世界
e143fc510d Update gVisor to 20230814.0 2023-10-30 12:41:21 +08:00
世界
bea177a4cd Improve linux bind interface 2023-10-30 12:41:21 +08:00
世界
aa05a4d050 Remove deprecated features 2023-10-30 12:41:21 +08:00
世界
a8112ff824 Update workflows 2023-10-30 12:40:52 +08:00
世界
a7710c3845 documentation: Bump version 2023-10-30 10:42:42 +08:00
世界
cb2e15f8a7 Fix UDP domain NAT 2023-10-28 21:41:39 +08:00
世界
23aa8a0543 Add legacy builds for old Windows and macOS versions 2023-10-26 14:02:24 +08:00
世界
edf7d046eb Fix outbound not found message 2023-10-26 14:02:19 +08:00
世界
de0b5cc1c2 Fix Linux IPv6 auto route rules 2023-10-26 12:02:00 +08:00
世界
2686e8afea Fix TUIC server TLS config not started 2023-10-26 12:01:28 +08:00
世界
d9853ca2be Update dependencies 2023-10-26 11:57:18 +08:00
世界
b617eb5adf documentation: Bump version 2023-10-23 14:09:09 +08:00
世界
ddf38799e2 makefile: Fix release command 2023-10-23 14:09:06 +08:00
世界
5291d43dc8 makefile: Add -allowProvisioningUpdates to Apple build commands 2023-10-21 17:51:00 +08:00
世界
a634830d85 Fix invalid address check in UoT conn 2023-10-21 17:23:30 +08:00
世界
e5d191ca73 Add retry for bbolt open 2023-10-14 19:24:05 +08:00
世界
2371f0fd51 Update dependencies 2023-10-14 17:36:35 +08:00
世界
cfdce7a96f Fix bbolt panic on arm32 2023-10-14 17:36:13 +08:00
世界
dc8ac01dec documentation: Bump version 2023-10-11 12:05:31 +08:00
世界
5f18738b2b Fix task cancel context 2023-10-11 12:05:27 +08:00
世界
7b4e4ca2d0 Fix compatibility with Android 14 2023-10-10 15:09:49 +08:00
世界
01ba4668b6 platform: Also reset connections on wake 2023-10-10 15:08:18 +08:00
dyhkwong
e782d21806 Fix connect domain for IP outbound 2023-10-10 15:08:16 +08:00
世界
00155d61fc documentation: Bump version 2023-10-07 10:04:10 +08:00
世界
8f2273a2b4 documentation: Bump version 2023-10-06 17:10:53 +08:00
世界
0d0526afa2 Update dependencies 2023-10-06 17:10:31 +08:00
世界
ac2d07b61a Fix UDP dialer network 2023-10-03 11:08:31 +08:00
世界
d35487f422 Fix ip_version does not take effect 2023-10-03 11:01:25 +08:00
世界
2749f4a013 documentation: Bump version 2023-10-03 09:22:29 +08:00
世界
45c679648e platform: Fix log server 2023-10-03 09:22:29 +08:00
世界
5f2f7fc8b9 Fix HTTP inbound leak 2023-10-01 14:39:58 +08:00
世界
83c79102cf Update xcode version script 2023-10-01 14:05:51 +08:00
世界
8b95292e53 Update dependencies 2023-09-30 22:34:54 +08:00
世界
3de7a2ddd3 Fix concurrent access on task returnError 2023-09-30 21:52:11 +08:00
Shanoa Ice
8437a6cb4e Fix set KDE system proxy 2023-09-30 16:44:58 +08:00
世界
9c4d08c6e1 documentation: Bump version 2023-09-28 16:02:54 +08:00
世界
e26096085e Remove invalid code 2023-09-28 15:52:05 +08:00
世界
2f1b2199c5 Fix DHCPv4 listen address 2023-09-27 18:25:23 +08:00
世界
af791db01f documentation: Bump version 2023-09-27 14:16:04 +08:00
世界
abcf030d89 Update dependencies 2023-09-27 14:16:04 +08:00
世界
7840dc73e3 Fix resolve dialer 2023-09-27 13:17:16 +08:00
世界
df9050400e android: Fix netlink check 2023-09-26 17:41:07 +08:00
世界
fdd38d6cf8 documentation: Bump version 2023-09-25 21:56:32 +08:00
世界
9891fd672f Reject SOCKS4 unauthenticated request 2023-09-25 21:16:14 +08:00
世界
92a84ee112 Update dependencies 2023-09-25 17:56:21 +08:00
世界
992331f17e documentation: Bump version 2023-09-24 14:40:03 +08:00
世界
4fb227ed86 Fix payload not return to pool 2023-09-24 14:40:03 +08:00
世界
5a1ddea100 Fix dns log 2023-09-24 12:38:33 +08:00
世界
fbaa2f9de9 Fix merge command 2023-09-24 12:12:35 +08:00
世界
97ab9bb194 Fix shadow-tls user context 2023-09-24 12:12:35 +08:00
世界
61ac141124 Improve URLTest delay calculate 2023-09-23 20:27:43 +08:00
世界
d4d49d9df5 Fix ACME DNS01 DNS challenge 2023-09-23 20:26:46 +08:00
世界
c60a944aac Fix missing context in geo resources download 2023-09-23 20:26:46 +08:00
世界
17584c245f documentation: Bump version 2023-09-22 12:08:09 +08:00
世界
6e84b694a4 Minor fixes 2023-09-22 12:07:39 +08:00
世界
34a93171f0 Update dependencies 2023-09-20 22:20:40 +08:00
世界
678f6ef72f Fix debug.SetMemoryLimit usage 2023-09-20 22:01:58 +08:00
世界
ae8187ed15 documentation: Bump version 2023-09-20 14:15:54 +08:00
世界
12dd1ac87f Improve conntrack 2023-09-20 14:15:00 +08:00
世界
85c8f00885 documentation: Bump version 2023-09-19 19:59:29 +08:00
世界
e7b7ae811f Add merge command 2023-09-19 19:59:07 +08:00
世界
a9743b77f6 Don't return success as an error when the lookup result is empty 2023-09-19 19:05:23 +08:00
世界
4068871d97 Update quic-go 2023-09-19 18:33:44 +08:00
世界
f05afcea39 Update dependencies 2023-09-19 18:33:06 +08:00
renovate[bot]
688e9daef4 [dependencies] Update github-actions
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-17 13:12:03 +08:00
世界
64edacffb7 Bump version 2023-09-17 01:09:48 +08:00
世界
743df5373b Fix hysteria2 mbps calculation 2023-09-17 01:09:48 +08:00
世界
e80084316d Fix panic on create system proxy failed 2023-09-17 01:09:41 +08:00
世界
9dcd427743 Fix v2ray websocket transport 2023-09-17 01:09:41 +08:00
世界
d17e93384b Add ACME DNS01 challenge support via libdns 2023-09-17 01:09:41 +08:00
世界
c1ffcf365e Fix test 2023-09-16 23:45:47 +08:00
世界
3040e97222 Fix gomobile build on macOS 2023-09-16 00:09:45 +08:00
世界
5f063fb0b5 Update Makefile 2023-09-16 00:09:41 +08:00
世界
a7dadd8671 documentation: Update Clash default_mode description 2023-09-16 00:09:41 +08:00
世界
c320be75a7 Add interrupt support for outbound groups 2023-09-16 00:09:41 +08:00
世界
bd7adcbb7e Migrate QUIC wrapper and protocol implementations to library 2023-09-16 00:09:41 +08:00
世界
1d6d3edec5 documentation: Update changelog for stable versions 2023-09-15 17:34:39 +08:00
世界
46bfeb574c documentation: Bump version 2023-09-12 15:57:33 +08:00
世界
a1449ee40e Mark deprecated features 2023-09-12 13:26:23 +08:00
世界
8cb41b5fa6 documentation: Add hysteria2 2023-09-12 13:26:22 +08:00
世界
53475c7390 Add hysteria2 protocol 2023-09-12 13:26:21 +08:00
世界
5d8af150a7 Improve system proxy API 2023-09-12 13:26:21 +08:00
Hiddify
69499a51a5 Add KDE set system proxy support
Co-authored-by: Hiddify <114227601+hiddify1@users.noreply.github.com>
2023-09-12 13:26:20 +08:00
世界
4c050d7f4b Add ECH support for QUIC based protocols 2023-09-12 13:26:20 +08:00
世界
533fca9fa3 documentation: Update TLS ECH struct 2023-09-12 13:26:20 +08:00
世界
187bf2f7bc Enable with_ech by default 2023-09-12 13:26:19 +08:00
世界
983a4222ad Add ECH keypair generator 2023-09-12 13:26:19 +08:00
世界
2ea506aeb8 Remove legacy NTP usages 2023-09-12 13:26:19 +08:00
世界
5b343d4c72 Improve ECH support 2023-09-12 13:26:19 +08:00
世界
be61ca64d4 Fix SOCKS outbound 2023-09-12 13:26:03 +08:00
世界
efe33cf48d Fix QUIC DNS 2023-09-12 13:14:21 +08:00
世界
fe8d46cce5 Fix TFO async write 2023-09-09 19:52:13 +08:00
世界
b1f289bce5 Fix TUIC context 2023-09-09 11:41:04 +08:00
世界
a8beb80876 Update Makefile 2023-09-07 22:37:55 +08:00
世界
ff209471d8 Fix QUIC defragger 2023-09-07 21:35:25 +08:00
unknown
806f7d0a2b Fix QUIC stream usage 2023-09-07 21:34:36 +08:00
世界
6b943caf37 Reject invalid connection 2023-09-07 21:34:36 +08:00
世界
4ea2d460f4 Fix router close 2023-09-07 21:34:35 +08:00
世界
c84c18f960 platform: Fix crash on android 2023-09-07 21:34:35 +08:00
世界
1402bdab41 Fix connect domain for IP outbounds 2023-09-07 21:34:35 +08:00
renovate[bot]
7082cf277e [dependencies] Update actions/checkout action to v4 2023-09-07 21:34:35 +08:00
世界
b9310154a7 clash-api: Move default mode to first 2023-09-07 21:34:35 +08:00
世界
55c34e3fb0 platform: Improve client 2023-09-07 21:34:35 +08:00
世界
68f2202eec documentation: Bump version 2023-08-31 23:32:44 +08:00
renovate[bot]
5057e50bb8 [dependencies] Update actions/stale action to v8
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-31 23:32:44 +08:00
世界
23e1a69955 Update Makefile 2023-08-31 23:32:44 +08:00
世界
b83c6c9d20 Fix return nil addr in conn 2023-08-30 21:28:03 +08:00
世界
67deac6d44 Fix hysteria packet write 2023-08-30 18:23:17 +08:00
世界
ea3731162b Update Makefile 2023-08-30 18:23:17 +08:00
世界
c75e32e722 documentation: Update changelog 2023-08-29 10:26:11 +08:00
世界
e7b35be5f6 Update Makefile 2023-08-29 10:26:11 +08:00
世界
5a309266f0 Fix TUIC client 2023-08-28 19:58:02 +08:00
世界
05669eaaad documentation: Update changelog 2023-08-25 20:33:16 +08:00
世界
e91a6e5439 Update dependencies 2023-08-25 20:21:14 +08:00
世界
43f72a6419 Add store_mode and platform Clash mode selector 2023-08-25 20:21:14 +08:00
世界
6dcacf3b5e Fix sniffer 2023-08-24 21:53:06 +08:00
世界
edad4d1ce7 Update dependencies 2023-08-24 11:31:29 +08:00
世界
262842c87d Fix test 2023-08-22 11:22:13 +08:00
世界
376f527742 documentation: Bump version 2023-08-21 18:24:31 +08:00
世界
c0bbb3849d Fix TUIC UDP 2023-08-21 18:16:38 +08:00
世界
738c25d818 Fix Makefile 2023-08-21 18:14:27 +08:00
dyhkwong
027af4d4ee Fix SOCKS5 UDP sniffing accidentially enabled 2023-08-20 19:06:37 +08:00
世界
6011f4483a Remove bad scripts 2023-08-20 17:57:39 +08:00
世界
fc22466e3b documentation: Bump version 2023-08-20 17:29:22 +08:00
世界
975e13a313 Add [include/exclude]_interface iproute2 options 2023-08-20 17:29:22 +08:00
世界
f46732bc0e Add udp over stream support for TUIC 2023-08-20 14:15:22 +08:00
世界
5c5c25e3ad Update tfo-go 2023-08-20 13:32:07 +08:00
世界
53a0bf2d11 minor: Remove unused parameter 2023-08-20 13:32:07 +08:00
renovate[bot]
7b79d98f59 [dependencies] Update golang Docker tag to v1.21 2023-08-20 13:31:56 +08:00
世界
1dd2c26f31 Fix UDP route 2023-08-20 13:31:55 +08:00
armv9
d14170348d Fix process search with fakeip 2023-08-20 13:31:55 +08:00
世界
9f94b21687 platform: Add group expand status 2023-08-20 13:31:55 +08:00
世界
cf57e46d69 Add new issue template 2023-08-20 13:31:49 +08:00
世界
b459001600 Update documentation 2023-08-20 13:31:48 +08:00
世界
73267fd6ad Fix ci build 2023-08-20 13:31:48 +08:00
世界
1019ecfdcf Add TCP MultiPath support 2023-08-20 13:31:48 +08:00
世界
81b847faca Pause recurring tasks when no network 2023-08-20 13:31:48 +08:00
世界
ce4c76cdd2 documentation: Add TUIC 2023-08-20 13:31:47 +08:00
世界
917420e79a Add TUIC protocol 2023-08-20 13:31:40 +08:00
世界
0b14dc3228 Update quic-go 2023-08-20 13:31:40 +08:00
世界
cbdaf3272b Update dependencies 2023-08-20 13:31:36 +08:00
世界
d51ab2b0a7 Fix missing HandshakeConn interface 2023-08-20 13:31:31 +08:00
世界
1363e16312 platform: Enable Clash API support by default 2023-08-20 13:31:29 +08:00
世界
f43d0141f3 Save fakeip metadata immediately 2023-08-20 13:31:27 +08:00
世界
90b3aad83a Fix network monitor 2023-08-20 13:30:50 +08:00
世界
2675aff98a Fix tag detect 2023-08-07 22:07:24 +08:00
世界
09ffa2c66e documentation: Update changelog 2023-08-05 21:37:08 +08:00
世界
9fba4f02b6 Update dependencies 2023-08-05 21:37:08 +08:00
世界
59987747e5 platform: Improve status 2023-08-04 21:10:32 +08:00
世界
c40140bbae Fix UDP async write 2023-08-04 12:38:51 +08:00
世界
2123b216c0 Fix http proxy server again 2023-08-04 12:38:51 +08:00
世界
1983f54907 platform: Add sleep support for NetworkExtension 2023-08-04 12:38:51 +08:00
世界
8d629ef323 documentation: Update changelog 2023-07-31 09:49:07 +08:00
世界
f57bee2f4b Update quic-go 2023-07-31 09:49:07 +08:00
世界
679739683e Update dependencies 2023-07-31 09:07:32 +08:00
世界
4fcce1f073 Fix http proxy server 2023-07-31 09:04:55 +08:00
世界
ff14220e08 platform: Update profile binary format 2023-07-30 20:55:27 +08:00
世界
a7b7a5c3c5 documentation:Add tvOS page 2023-07-29 21:09:15 +08:00
世界
b054441f34 platform: Add support for tvOS 2023-07-29 21:01:43 +08:00
世界
1e31d26e03 documentation: Update installation 2023-07-29 21:01:43 +08:00
世界
ffe515d0e0 documentation: Update changelog 2023-07-25 13:21:15 +08:00
世界
aad021f521 Fix gVisor UDP 2023-07-25 08:28:24 +08:00
世界
4a986459ee documentation: Update changelog 2023-07-24 17:56:39 +08:00
世界
9532d0cba4 Fix cache file 2023-07-24 16:51:03 +08:00
世界
cadc34f3ad Add support for macOS system extension 2023-07-24 16:51:03 +08:00
世界
db23a48b36 Update dependencies 2023-07-23 14:23:51 +08:00
世界
407cf68e59 Fix certmagic usage 2023-07-20 20:03:20 +08:00
世界
e0058ca9c5 Fix fakeip unsaved state 2023-07-20 19:59:04 +08:00
世界
8140af01aa Fix download geo resources 2023-07-20 19:41:22 +08:00
世界
98bf696d01 Fix cache file 2023-07-20 19:41:22 +08:00
世界
e075bb5c8d Update changelog 2023-07-19 14:59:30 +08:00
世界
c6baabedef Update dependencies 2023-07-19 14:45:30 +08:00
世界
6e6998dab7 Improve platform status 2023-07-16 14:08:45 +08:00
世界
1a29c23263 documentation: Update changelog 2023-07-15 14:53:48 +08:00
世界
0f87396ab6 Fix abx reader for some malformed formats 2023-07-15 14:46:15 +08:00
世界
ffde948860 Update support page 2023-07-15 14:46:09 +08:00
世界
69b5dbdcc3 Build memory limiter for android 2023-07-15 14:46:06 +08:00
世界
1121517755 Fix hysteria inbound context 2023-07-11 20:58:20 +08:00
世界
6879def619 Fix test 2023-07-11 15:44:03 +08:00
世界
5c0f6d0a6f Fix vmess legacy server 2023-07-11 15:44:03 +08:00
世界
d74abbd20e Fix v2ray websocket transport 2023-07-11 15:44:03 +08:00
世界
120dae4eed Fix fakeip lookup 2023-07-11 15:44:02 +08:00
世界
bb651db2d2 platform: Fix output format 2023-07-09 12:24:43 +08:00
世界
e929dde13e documentation: Update changelog 2023-07-09 07:54:08 +08:00
世界
9d75385bbb Fix async FakeIP save 2023-07-08 16:21:11 +08:00
世界
1c526feec1 Update dependencies 2023-07-08 16:19:28 +08:00
世界
7df26986de documentation: Update changelog 2023-07-07 14:23:45 +08:00
世界
5f2d23a12d Fix multi error 2023-07-07 13:57:28 +08:00
世界
d9e65c0969 Fix syscall copy packet 2023-07-07 13:42:42 +08:00
世界
ec1160924f Fix save FakeIP cache 2023-07-07 12:51:38 +08:00
世界
230e8f895d Fix close quic.Listener 2023-07-07 12:43:22 +08:00
世界
af79378734 Update dependencies 2023-07-03 21:47:32 +08:00
世界
07ce5e0d22 Remove stack buffer usage 2023-07-03 21:45:32 +08:00
世界
9c8565cf21 platform: Add group interface 2023-07-02 18:46:41 +08:00
世界
5ad0ea2b5a Try fix StreamDomainNameQuery 2023-07-02 16:38:38 +08:00
世界
e482053c8a documentation: Update changelog 2023-06-27 11:31:24 +08:00
世界
945713d886 Update dependencies 2023-06-27 11:13:30 +08:00
世界
9bb62ad6b5 Check duplicated outbound tag 2023-06-26 18:47:52 +08:00
世界
c2bda9fbde Fix DNS rewrite_ttl logic 2023-06-23 16:12:25 +08:00
世界
1d1db62a44 Prevent write packet IPv6 packet to tun IPv4 connection 2023-06-21 13:29:24 +08:00
世界
39405373f8 documentation: Update changelog 2023-06-19 14:11:41 +08:00
世界
22a7988d3f Update dependencies 2023-06-19 14:11:41 +08:00
世界
b2092fafb7 Fix ios build
Cannot use errno as method and variable due to conflict with objc
2023-06-19 14:11:40 +08:00
世界
cc7b5d8280 Unwrap 4in6 address received by client packet conn 2023-06-19 13:29:41 +08:00
世界
702d96a738 platform: Improve local DNS transport 2023-06-18 10:00:32 +08:00
世界
b9f34f1309 documentation: Update changelog 2023-06-17 12:21:18 +08:00
世界
07724a0ddd Update dependencies 2023-06-17 12:17:43 +08:00
世界
83c3454685 Update quic-go 2023-06-15 14:52:25 +08:00
世界
7d263eb733 documentation: Update changelog 2023-06-14 16:28:44 +08:00
世界
222687d9c5 Update goreleaser usage 2023-06-14 09:39:13 +08:00
世界
07d3652e30 Build with_dhcp by default 2023-06-14 09:14:45 +08:00
世界
8d5b9d240a Fix outbound start sequence 2023-06-13 22:38:05 +08:00
世界
4f12eba944 Fix hysteria outbound 2023-06-13 21:41:33 +08:00
世界
a7f77d59c1 Update documentation 2023-06-11 22:38:37 +08:00
shadow750d6
597248130f Reconnect once if hysteria request fails
This allows graceful recovery when network isn't good enough.

[Original hysteria source
code](13d46da998/core/cs/client.go (L182))
has similar mechanism.
2023-06-11 22:21:32 +08:00
世界
3c2c9cf317 Migrate gVisor to fork 2023-06-11 22:07:36 +08:00
世界
e572b9d0cd Update dependencies 2023-06-11 20:58:59 +08:00
世界
52e9059a8d Fix fakeip routing 2023-06-11 20:58:59 +08:00
世界
0cb9cff690 Fix shadowsocks none client 2023-06-08 10:23:39 +08:00
世界
c0669cb2a5 Fix TLS 1.2 support for shadow-tls client 2023-06-07 21:03:39 +08:00
世界
c5902f2473 Fix using v2ray websocket transport with detour 2023-06-07 21:03:21 +08:00
世界
22028602e8 Improve read waiter interface 2023-06-07 21:03:11 +08:00
世界
bd54608473 Fix DNS outbound 2023-06-07 21:03:08 +08:00
世界
3741394269 Add cache_id option for Clash cache file 2023-06-07 21:03:06 +08:00
世界
6266d2df7e Fix shadowsocks AEAD UDP server 2023-06-07 21:02:52 +08:00
世界
01dfba722a Use API to create windows firewall rule 2023-06-07 21:01:29 +08:00
世界
f8d5f01665 Reimplemented shadowsocks client 2023-06-07 20:57:07 +08:00
Hellojack
ad999d4791 Fix UVariantLen usage 2023-06-07 20:56:57 +08:00
世界
6f1b258501 Improve DNS caching 2023-06-07 20:56:55 +08:00
世界
f949ddc0ab Set TCP keepalive for WireGuard gVisor TCP connections 2023-06-07 20:53:26 +08:00
Weltolk
f53007cbf3 documentation: Fix fakeip link broken 2023-06-07 20:53:13 +08:00
世界
c287731df9 Improve direct copy 2023-06-07 20:53:00 +08:00
世界
bc32c78d03 Improve multiplex 2023-06-07 20:46:34 +08:00
世界
daee0db7bb clash-api: Reset outbounds in DELETE /connections 2023-06-07 20:45:39 +08:00
世界
91fbf4c79b Add multiplexer for VLESS outbound 2023-06-07 20:45:39 +08:00
世界
54d9ef2f2a Update gVisor to 20230417.0 2023-06-07 20:45:25 +08:00
世界
e056d4502b Add debug http server 2023-06-07 20:45:25 +08:00
世界
98c2c439aa Add filemanager api 2023-06-07 20:45:25 +08:00
世界
b6068cea6b Update wireguard-go 2023-06-07 20:38:30 +08:00
世界
9c9affa719 Ignore system tun stack bind interface error 2023-06-07 20:35:02 +08:00
世界
8eb7dd0059 Improve VLESS request 2023-06-07 20:35:00 +08:00
世界
a62ad44883 Add deadline interface 2023-06-07 20:34:56 +08:00
世界
2850354070 shadowsocks: Multi-user support for legacy AEAD inbound
Signed-off-by: wwqgtxx <wwqgtxx@gmail.com>
2023-06-07 20:34:54 +08:00
世界
0a4abcbbc8 Add headers option for HTTP outbound 2023-06-07 20:34:52 +08:00
世界
b491c350ae URLTest improvements 2023-06-07 20:33:56 +08:00
世界
1fbe7c54bf Fix wireguard reconnect 2023-06-07 20:33:53 +08:00
世界
9d32fc9bd1 Use HTTPS URLTest source 2023-06-07 20:33:50 +08:00
世界
542612129d clash-api: Add Clash.Meta APIs 2023-06-07 20:33:41 +08:00
世界
750f87bb0a clash api: download clash-dashboard if external-ui directory is empty 2023-06-07 20:33:06 +08:00
世界
e168de79c7 Add multi-peer support for wireguard outbound 2023-06-07 20:33:04 +08:00
世界
9bca5a517f Add fakeip support 2023-06-07 20:31:26 +08:00
世界
aa94cfb876 Refactor rules 2023-06-07 20:28:21 +08:00
世界
52b776b561 Add dns reverse mapping 2023-06-07 20:19:46 +08:00
世界
c74d3a53d4 documentation: Update changelog 2023-05-19 15:48:35 +08:00
世界
fe7ac80a6c Update dependencies 2023-05-19 15:48:35 +08:00
世界
e50b334b9a Fix uTLS ALPN 2023-05-19 15:41:18 +08:00
XYenon
a0d8e374fb Fix incorrect use of sort.Slice 2023-05-19 15:40:48 +08:00
Larvan2
d3a67cb5ae Enable mkdocs search in documentation
Signed-off-by: Larvan2 <78135608+Larvan2@users.noreply.github.com>
2023-05-19 15:40:42 +08:00
世界
e69e98b185 Fix documentation 2023-05-19 15:40:13 +08:00
世界
5e1499d67b Update badtls 2023-05-19 15:39:40 +08:00
世界
e8dad1afeb Fix grpc request 2023-04-22 19:51:04 +08:00
世界
6ce4e31fc8 documentation: Update changelog 2023-04-22 08:26:09 +08:00
世界
d2d4faf520 Revert LRU cache changes 2023-04-22 08:23:49 +08:00
世界
438de36749 Make v2ray http2 conn public 2023-04-22 08:14:55 +08:00
世界
df0eef770e Fix http response check
Co-authored-by: armv9 <48624112+arm64v8a@users.noreply.github.com>
2023-04-22 08:14:55 +08:00
世界
bbdd495ed5 Fix v2ray-plugin TLS server name
Co-authored-by: armv9 <48624112+arm64v8a@users.noreply.github.com>
2023-04-21 17:48:36 +08:00
世界
d686172854 Fix grpc lite request host
Co-authored-by: armv9 <48624112+arm64v8a@users.noreply.github.com>
2023-04-21 17:48:36 +08:00
H1JK
e1d96cb64e Add BaseContext to http servers 2023-04-21 17:48:29 +08:00
H1JK
d5f94b65b7 Fix gRPC service name escape 2023-04-21 17:48:29 +08:00
Hellojack
ec2d0b6b3c Remove TLS requirement for gRPC client 2023-04-21 17:48:29 +08:00
世界
3a92bf993d platform: Add UsePlatformAutoDetectInterfaceControl 2023-04-21 17:47:17 +08:00
armv9
ec13965fd0 Fix HTTP sniffer 2023-04-19 21:58:58 +08:00
世界
ddf747006e Update to uuid v5 2023-04-19 21:58:58 +08:00
世界
4382093868 Prepare deadline interface 2023-04-19 21:58:58 +08:00
世界
a5322850b3 documentation: Update client notes 2023-04-19 21:58:58 +08:00
世界
407b08975c Remove legacy warnings 2023-04-19 21:58:58 +08:00
世界
c7067ff5e8 Fix default interface monitor for darwin 2023-04-19 21:58:58 +08:00
世界
9b2384b296 Add udpnat test 2023-04-19 21:43:13 +08:00
世界
b498a22972 Fix interface monitor for android 2023-04-19 21:42:40 +08:00
世界
20e9da5c67 Fix udp timeout 2023-04-19 21:42:10 +08:00
世界
ec8974673b Fix platform interface monitor & Fix system tun stack for ios 2023-04-19 21:40:03 +08:00
世界
5e6e7923e4 Fix shadowsocksr build 2023-04-17 18:06:43 +08:00
世界
de1b5971e1 Update documentation 2023-04-16 16:28:41 +08:00
世界
5c20d0b4d5 Update dependencies 2023-04-16 16:28:41 +08:00
世界
9df96ac7f1 Fix deadline usage on websocket conn 2023-04-16 16:28:40 +08:00
世界
87cd925144 Fix conntrack return pointer 2023-04-14 21:00:45 +08:00
世界
fecb796000 android: Remove Seq.Delete warning 2023-04-14 21:00:40 +08:00
世界
cfb6c804aa Print sniff result 2023-04-14 21:00:01 +08:00
世界
11c50c7558 Fix processing domain address in packet 2023-04-14 20:59:57 +08:00
世界
34cc7f176e Fix parsing query in http path 2023-04-14 20:59:16 +08:00
Xiaokang Wang (Shelikhoo)
b54da9c6af Fix '?' at end of WebSocket path get escaped
This fix align sing-box's behaviour with V2Ray when it comes to processing ? at the end of WebSocket's path.
2023-04-14 20:58:12 +08:00
世界
f44f86b832 Fix workflows 2023-04-14 20:57:08 +08:00
世界
4ebf40f582 Fix find process user 2023-04-14 20:56:58 +08:00
世界
53e4302143 Fix set HTTP TLS ALPN 2023-04-14 20:56:55 +08:00
世界
cf778eda4f Fix v2ray http transport server read request 2023-04-14 20:56:51 +08:00
世界
bb63429079 Update cancel context usage 2023-04-14 20:56:16 +08:00
世界
f7f9a7ae20 Fix write log to stderr 2023-04-14 20:55:57 +08:00
世界
8699412a4c platform: Add stderr redirect 2023-04-14 20:55:45 +08:00
世界
0d7aa19cd1 Fix write http status after response sent 2023-04-14 20:55:20 +08:00
世界
50a7295360 Replace usages of uber/atomic 2023-04-14 20:55:05 +08:00
世界
e57b6ae98d Update dependencies 2023-04-14 20:54:56 +08:00
世界
6843970536 Add loopback check 2023-04-08 09:13:50 +08:00
世界
62425ad3e4 Add close monitor 2023-04-08 08:10:03 +08:00
世界
e1e217854e Add start and close track message 2023-04-08 08:09:28 +08:00
世界
5bf177b021 platform: Fix build on windows 2023-04-07 21:10:16 +08:00
世界
72dbf2e2b4 documentation: Update changelog 2023-04-07 19:18:26 +08:00
世界
46c318c6fe Fix v2ray HTTP/1.1 transport compatibility 2023-04-07 18:20:07 +08:00
世界
05bb1b88c3 dns: Fix rewrite TTL 2023-04-07 16:19:34 +08:00
世界
5176ea9fe0 Update dependencies 2023-04-07 16:19:34 +08:00
世界
36d349acd2 dns: Fix calculate TTL 2023-04-07 13:12:16 +08:00
世界
4feee983b5 Update reality protocol 2023-04-06 19:05:05 +08:00
世界
9b12e3e389 Update client documentation 2023-04-06 12:51:26 +08:00
世界
afd3464216 Minor fixes 2023-04-05 21:41:06 +08:00
世界
8b64446274 platform: Fixes and improvements 2023-04-05 19:54:20 +08:00
世界
28aa4c4d1f Refactor log factory constructor 2023-04-03 20:24:13 +08:00
世界
0be3cdc8fb platform: Add http client 2023-04-03 15:12:44 +08:00
armv9
f8be484019 conntrack: Fix missing tracking for udp conn 2023-04-02 12:06:03 +08:00
世界
35f03f092d Improve UDP domain destination NAT 2023-04-02 12:05:59 +08:00
世界
c3d7401ead platform: Add check config func 2023-04-02 10:35:03 +08:00
世界
4db7eb9d9e documentation: Update changelog 2023-03-31 16:29:08 +08:00
世界
fd4efd6104 Fix dns transport read 2023-03-31 14:31:35 +08:00
世界
19a35ec6a4 Fix http2 transport close 2023-03-31 14:31:35 +08:00
世界
2012c0ca1e Update release scripts 2023-03-31 14:31:35 +08:00
世界
187421c754 Append time to session log 2023-03-31 14:31:35 +08:00
世界
b3fb86d415 Accept "any" outbound in dns rule 2023-03-31 14:31:35 +08:00
世界
88fafd4e30 Fix dns routing context 2023-03-31 09:14:04 +08:00
世界
8056932f9c Update documentation 2023-03-27 08:23:01 +08:00
世界
c8af003bfc Update dependencies 2023-03-27 08:22:56 +08:00
世界
4999441a85 Fix missing default host in v2ray http transport`s request 2023-03-27 08:20:59 +08:00
世界
09b001e795 Revert remove install shell 2023-03-27 08:20:55 +08:00
世界
3b3a251008 Update LICENSE 2023-03-27 08:20:51 +08:00
世界
2e4eb9aa39 Update dockerfile 2023-03-24 08:29:11 +08:00
世界
77fd284703 documentation: Update changelog 2023-03-24 08:04:36 +08:00
世界
0a4517f4b7 Update dependencies 2023-03-24 07:06:45 +08:00
世界
4395db3206 documentation: Update set_system_proxy usage 2023-03-23 21:27:50 +08:00
世界
dd5b0abc67 Fix slow open 2023-03-23 17:14:38 +08:00
世界
466800aa3a Fix wireguard mutex 2023-03-23 15:43:17 +08:00
世界
4328c535a9 Improve timeout canceler 2023-03-23 15:39:12 +08:00
世界
f9516709da Update documentation 2023-03-23 07:54:24 +08:00
世界
5dce722879 Update dependencies 2023-03-23 07:49:14 +08:00
世界
9324a39d4e Fix import format 2023-03-20 23:01:54 +08:00
世界
84904c5206 Create working directory if not exists 2023-03-20 19:33:00 +08:00
世界
fe4b429fc2 hysteria: Accept inbound configuration without users 2023-03-20 19:22:46 +08:00
世界
f680d0acaf Add with_reality_server to release build tags 2023-03-20 17:36:59 +08:00
世界
4baff5aeb1 documentation: Update changelog 2023-03-20 17:32:59 +08:00
世界
f25296fb23 Update dependencies 2023-03-20 17:27:48 +08:00
世界
e717852c73 Fix optional listen address 2023-03-19 20:46:22 +08:00
世界
13dc70f649 Fix make build 2023-03-19 16:57:07 +08:00
世界
46040a71c3 Fix vision padding overflow 2023-03-19 10:25:35 +08:00
世界
0558b3fc5c ntp: Add write_to_system service option 2023-03-18 23:11:40 +08:00
世界
99b2ab5526 Add command to fetch a URL 2023-03-18 21:02:29 +08:00
世界
e5f3bb6344 Add command to connect an address 2023-03-18 20:27:38 +08:00
世界
c7f89ad88e Add multiple configuration support 2023-03-18 20:27:38 +08:00
世界
e0d9f79445 Fix test 2023-03-18 17:02:55 +08:00
世界
b6dbb69fc4 Fix write nil in buffered vectorised writer 2023-03-18 16:32:28 +08:00
世界
b76fabee65 documentation: Fix broken link 2023-03-18 16:31:15 +08:00
世界
872bcfd1c0 readme: Add packaging status 2023-03-17 17:58:08 +08:00
世界
b033c13ca2 documentation: Update stable changelog 2023-03-17 17:58:08 +08:00
renovate[bot]
2db188f3a1 dependencies: Update actions/setup-go action to v4
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-17 17:58:08 +08:00
世界
11de271c8f Add experimental debug options 2023-03-17 17:58:08 +08:00
世界
40c800c57c documentation: Fix typo 2023-03-17 13:34:36 +08:00
世界
91b0540e95 documentation: Update UoT application support status 2023-03-17 13:28:05 +08:00
世界
ce6d186345 Update documentation 2023-03-17 13:07:22 +08:00
世界
32bc4450a7 Update dependencies 2023-03-17 12:59:12 +08:00
世界
43f31b40ba Update UoT protocol 2023-03-17 12:57:48 +08:00
世界
a3a5185b15 platform: Fix bytes format 2023-03-16 11:28:54 +08:00
世界
14a0f180c8 ios: Add with_quic tag in build 2023-03-16 11:28:54 +08:00
世界
cc9cb0b477 platform: Add oom killer 2023-03-16 11:28:54 +08:00
世界
2cb0e37f50 platform: Add low memory interface 2023-03-16 00:36:04 +08:00
世界
dbd5be55b0 tun: Create gVisor stack by default in Apple Network Extension 2023-03-15 21:50:18 +08:00
世界
f674b4fbd5 Fix build embed tor for mobile 2023-03-15 20:59:45 +08:00
世界
5a4e8fea81 Fix lint 2023-03-15 14:56:06 +08:00
世界
78e02b52ca Update UoT protocol 2023-03-15 14:52:32 +08:00
世界
ffdaae90d7 Update dependencies 2023-03-15 11:59:15 +08:00
世界
c77681ea17 Fix close platform tun 2023-03-13 19:47:00 +08:00
世界
d824390167 Fix cross make build 2023-03-13 19:46:20 +08:00
世界
70cf681ff2 Remove length limit on short_id for reality TLS config 2023-03-13 19:46:16 +08:00
wwqgtxx
b004b9ec81 Fix stack wireguard device returning non-nil interface containing nil pointer 2023-03-13 19:46:16 +08:00
世界
657b05fd96 Print command to shell error 2023-03-13 19:46:16 +08:00
世界
caad60da45 Apply --disable-color to global logger 2023-03-13 11:23:00 +08:00
世界
7d22cf9b45 Support $schema in configuration file 2023-03-13 10:58:29 +08:00
世界
5cb178ca93 Update documentation 2023-03-12 23:07:38 +08:00
世界
16788008b6 Update dependencies 2023-03-12 23:07:24 +08:00
世界
6ec7a33046 Fix make install 2023-03-11 19:24:19 +08:00
世界
6af9c2b3ca Add health check support for http-based v2ray transport 2023-03-11 15:49:02 +08:00
世界
bdc620dab1 Fix http server usage 2023-03-11 15:05:07 +08:00
世界
a88820af31 Fix missing default shadowtls version 2023-03-11 10:12:46 +08:00
世界
3688f2e114 Update documentation 2023-03-10 11:15:00 +08:00
世界
a183958d53 Update dependencies 2023-03-09 23:24:05 +08:00
世界
1c8a9e91b7 Generate version during compilation 2023-03-09 23:24:05 +08:00
世界
325f6c71ff Fix windows interface monitor 2023-03-09 16:00:57 +08:00
世界
6c6c0792ad Update reality and uTLS 2023-03-09 10:51:01 +08:00
世界
9264f2307c Fix link broken in installation documentation page 2023-03-08 15:56:41 +08:00
世界
87dd328700 Fix build check 2023-03-08 15:23:46 +08:00
世界
0cec92dd0f Update documentation 2023-03-08 14:59:09 +08:00
世界
e88afa9665 Fix vmess server buffer 2023-03-07 17:07:37 +08:00
世界
3883a81315 Check constant.Version before build release 2023-03-06 16:34:44 +08:00
Dmitry R
c919ad079a systemd: Add reload command 2023-03-06 16:32:54 +08:00
世界
83593aee70 Fix vless read cache 2023-03-06 11:19:38 +08:00
1110 changed files with 111396 additions and 26286 deletions

31
.fpm_openwrt Normal file
View File

@@ -0,0 +1,31 @@
-s dir
--name sing-box
--category net
--license GPL-3.0-or-later
--description "The universal proxy platform."
--url "https://sing-box.sagernet.org/"
--maintainer "nekohasekai <contact-git@sekai.icu>"
--no-deb-generate-changes
--config-files /etc/config/sing-box
--config-files /etc/sing-box/config.json
--depends ca-bundle
--depends kmod-inet-diag
--depends kmod-tun
--depends firewall4
--depends kmod-nft-queue
--before-remove release/config/openwrt.prerm
release/config/config.json=/etc/sing-box/config.json
release/config/openwrt.conf=/etc/config/sing-box
release/config/openwrt.init=/etc/init.d/sing-box
release/config/openwrt.keep=/lib/upgrade/keep.d/sing-box
release/completions/sing-box.bash=/usr/share/bash-completion/completions/sing-box.bash
release/completions/sing-box.fish=/usr/share/fish/vendor_completions.d/sing-box.fish
release/completions/sing-box.zsh=/usr/share/zsh/site-functions/_sing-box
LICENSE=/usr/share/licenses/sing-box/LICENSE

23
.fpm_pacman Normal file
View File

@@ -0,0 +1,23 @@
-s dir
--name sing-box
--category net
--license GPL-3.0-or-later
--description "The universal proxy platform."
--url "https://sing-box.sagernet.org/"
--maintainer "nekohasekai <contact-git@sekai.icu>"
--config-files etc/sing-box/config.json
--after-install release/config/sing-box.postinst
release/config/config.json=/etc/sing-box/config.json
release/config/sing-box.service=/usr/lib/systemd/system/sing-box.service
release/config/sing-box@.service=/usr/lib/systemd/system/sing-box@.service
release/config/sing-box.sysusers=/usr/lib/sysusers.d/sing-box.conf
release/config/sing-box.rules=usr/share/polkit-1/rules.d/sing-box.rules
release/config/sing-box-split-dns.xml=/usr/share/dbus-1/system.d/sing-box-split-dns.conf
release/completions/sing-box.bash=/usr/share/bash-completion/completions/sing-box.bash
release/completions/sing-box.fish=/usr/share/fish/vendor_completions.d/sing-box.fish
release/completions/sing-box.zsh=/usr/share/zsh/site-functions/_sing-box
LICENSE=/usr/share/licenses/sing-box/LICENSE

25
.fpm_systemd Normal file
View File

@@ -0,0 +1,25 @@
-s dir
--name sing-box
--category net
--license GPL-3.0-or-later
--description "The universal proxy platform."
--url "https://sing-box.sagernet.org/"
--maintainer "nekohasekai <contact-git@sekai.icu>"
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
--no-deb-generate-changes
--config-files /etc/sing-box/config.json
--after-install release/config/sing-box.postinst
release/config/config.json=/etc/sing-box/config.json
release/config/sing-box.service=/usr/lib/systemd/system/sing-box.service
release/config/sing-box@.service=/usr/lib/systemd/system/sing-box@.service
release/config/sing-box.sysusers=/usr/lib/sysusers.d/sing-box.conf
release/config/sing-box.rules=usr/share/polkit-1/rules.d/sing-box.rules
release/config/sing-box-split-dns.xml=/usr/share/dbus-1/system.d/sing-box-split-dns.conf
release/completions/sing-box.bash=/usr/share/bash-completion/completions/sing-box.bash
release/completions/sing-box.fish=/usr/share/fish/vendor_completions.d/sing-box.fish
release/completions/sing-box.zsh=/usr/share/zsh/site-functions/_sing-box
LICENSE=/usr/share/licenses/sing-box/LICENSE

1
.github/CRONET_GO_VERSION vendored Normal file
View File

@@ -0,0 +1 @@
ea7cd33752aed62603775af3df946c1b83f4b0b3

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
github: nekohasekai

View File

@@ -1,70 +1,88 @@
name: Bug Report
description: "Create a report to help us improve."
name: Bug report
description: "Report sing-box bug"
body:
- type: checkboxes
id: terms
- type: dropdown
attributes:
label: Welcome
label: Operating system
description: Operating system type
options:
- label: Yes, I'm using the latest major release. Only such installations are supported.
required: true
- label: Yes, I'm using the latest Golang release. Only such installations are supported.
required: true
- label: Yes, I've searched similar issues on GitHub and didn't find any.
required: true
- label: Yes, I've included all information below (version, **FULL** config, **FULL** log, etc).
required: true
- type: textarea
id: problem
attributes:
label: Description of the problem
placeholder: Your problem description
- iOS
- macOS
- Apple tvOS
- Android
- Windows
- Linux
- Others
validations:
required: true
- type: textarea
id: version
- type: input
attributes:
label: Version of sing-box
value: |-
<details>
```console
$ sing-box version
# Paste output here
```
</details>
label: System version
description: Please provide the operating system version
validations:
required: true
- type: textarea
id: config
- type: dropdown
attributes:
label: Server and client configuration file
value: |-
<details>
```console
# paste json here
```
</details>
label: Installation type
description: Please provide the sing-box installation type
options:
- Original sing-box Command Line
- sing-box for iOS Graphical Client
- sing-box for macOS Graphical Client
- sing-box for Apple tvOS Graphical Client
- sing-box for Android Graphical Client
- Third-party graphical clients that advertise themselves as using sing-box (Windows)
- Third-party graphical clients that advertise themselves as using sing-box (Android)
- Others
validations:
required: true
- type: textarea
id: log
- type: input
attributes:
label: Server and client log file
value: |-
<details>
```console
# paste log here
```
</details>
description: Graphical client version
label: If you are using a graphical client, please provide the version of the client.
- type: textarea
attributes:
label: Version
description: If you are using the original command line program, please provide the output of the `sing-box version` command.
render: shell
- type: textarea
attributes:
label: Description
description: Please provide a detailed description of the error.
validations:
required: true
- type: textarea
attributes:
label: Reproduction
description: Please provide the steps to reproduce the error, including the configuration files and procedures that can locally (not dependent on the remote server) reproduce the error using the original command line program of sing-box.
validations:
required: true
- type: textarea
attributes:
label: Logs
description: |-
In addition, if you encounter a crash with the graphical client, please also provide crash logs.
For Apple platform clients, please check `Settings - View Service Log` for crash logs.
For the Android client, please check the `/sdcard/Android/data/io.nekohasekai.sfa/files/stderr.log` file for crash logs.
render: shell
- type: checkboxes
id: supporter
attributes:
label: Supporter
options:
- label: I am a [sponsor](https://github.com/sponsors/nekohasekai/)
- type: checkboxes
attributes:
label: Integrity requirements
description: |-
Please check all of the following options to prove that you have read and understood the requirements, otherwise this issue will be closed.
Sing-box is not a project aimed to please users who can't make any meaningful contributions and gain unethical influence. If you deceive here to deliberately waste the time of the developers, you will be permanently blocked.
options:
- label: I confirm that I have read the documentation, understand the meaning of all the configuration items I wrote, and did not pile up seemingly useful options or default values.
required: true
- label: I confirm that I have provided the server and client configuration files and process that can be reproduced locally, instead of a complicated client configuration file that has been stripped of sensitive data.
required: true
- label: I confirm that I have provided the simplest configuration that can be used to reproduce the error I reported, instead of depending on remote servers, TUN, graphical interface clients, or other closed-source software.
required: true
- label: I confirm that I have provided the complete configuration files and logs, rather than just providing parts I think are useful out of confidence in my own intelligence.
required: true

View File

@@ -0,0 +1,88 @@
name: 错误反馈
description: "提交 sing-box 漏洞"
body:
- type: dropdown
attributes:
label: 操作系统
description: 请提供操作系统类型
options:
- iOS
- macOS
- Apple tvOS
- Android
- Windows
- Linux
- 其他
validations:
required: true
- type: input
attributes:
label: 系统版本
description: 请提供操作系统版本
validations:
required: true
- type: dropdown
attributes:
label: 安装类型
description: 请提供该 sing-box 安装类型
options:
- sing-box 原始命令行程序
- sing-box for iOS 图形客户端程序
- sing-box for macOS 图形客户端程序
- sing-box for Apple tvOS 图形客户端程序
- sing-box for Android 图形客户端程序
- 宣传使用 sing-box 的第三方图形客户端程序 (Windows)
- 宣传使用 sing-box 的第三方图形客户端程序 (Android)
- 其他
validations:
required: true
- type: input
attributes:
description: 图形客户端版本
label: 如果您使用图形客户端程序,请提供该程序版本。
- type: textarea
attributes:
label: 版本
description: 如果您使用原始命令行程序,请提供 `sing-box version` 命令的输出。
render: shell
- type: textarea
attributes:
label: 描述
description: 请提供错误的详细描述。
validations:
required: true
- type: textarea
attributes:
label: 重现方式
description: 请提供重现错误的步骤,必须包括可以在本地(不依赖与远程服务器)使用 sing-box 原始命令行程序重现错误的配置文件与流程。
validations:
required: true
- type: textarea
attributes:
label: 日志
description: |-
此外,如果您遭遇图形界面应用程序崩溃,请附加提供崩溃日志。
对于 Apple 平台图形客户端程序,请检查 `Settings - View Service Log` 以导出崩溃日志。
对于 Android 图形客户端程序,请检查 `/sdcard/Android/data/io.nekohasekai.sfa/files/stderr.log` 文件以导出崩溃日志。
render: shell
- type: checkboxes
id: supporter
attributes:
label: 支持我们
options:
- label: 我已经 [赞助](https://github.com/sponsors/nekohasekai/)
- type: checkboxes
attributes:
label: 完整性要求
description: |-
请勾选以下所有选项以证明您已经阅读并理解了以下要求,否则该 issue 将被关闭。
sing-box 不是讨好无法作出任何意义上的贡献的最终用户并获取非道德影响力的项目,如果您在此处欺骗以故意浪费开发者的时间,您将被永久封锁。
options:
- label: 我保证阅读了文档,了解所有我编写的配置文件项的含义,而不是大量堆砌看似有用的选项或默认值。
required: true
- label: 我保证提供了可以在本地重现该问题的服务器、客户端配置文件与流程,而不是一个脱敏的复杂客户端配置文件。
required: true
- label: 我保证提供了可用于重现我报告的错误的最简配置而不是依赖远程服务器、TUN、图形界面客户端或者其他闭源软件。
required: true
- label: 我保证提供了完整的配置文件与日志,而不是出于对自身智力的自信而仅提供了部分认为有用的部分。
required: true

81
.github/build_alpine_apk.sh vendored Executable file
View File

@@ -0,0 +1,81 @@
#!/usr/bin/env bash
set -e -o pipefail
ARCHITECTURE="$1"
VERSION="$2"
BINARY_PATH="$3"
OUTPUT_PATH="$4"
if [ -z "$ARCHITECTURE" ] || [ -z "$VERSION" ] || [ -z "$BINARY_PATH" ] || [ -z "$OUTPUT_PATH" ]; then
echo "Usage: $0 <architecture> <version> <binary_path> <output_path>"
exit 1
fi
PROJECT=$(cd "$(dirname "$0")/.."; pwd)
# Convert version to APK format:
# 1.13.0-beta.8 -> 1.13.0_beta8-r0
# 1.13.0-rc.3 -> 1.13.0_rc3-r0
# 1.13.0 -> 1.13.0-r0
APK_VERSION=$(echo "$VERSION" | sed -E 's/-([a-z]+)\.([0-9]+)/_\1\2/')
APK_VERSION="${APK_VERSION}-r0"
ROOT_DIR=$(mktemp -d)
trap 'rm -rf "$ROOT_DIR"' EXIT
# Binary
install -Dm755 "$BINARY_PATH" "$ROOT_DIR/usr/bin/sing-box"
# Config files
install -Dm644 "$PROJECT/release/config/config.json" "$ROOT_DIR/etc/sing-box/config.json"
install -Dm755 "$PROJECT/release/config/sing-box.initd" "$ROOT_DIR/etc/init.d/sing-box"
install -Dm644 "$PROJECT/release/config/sing-box.confd" "$ROOT_DIR/etc/conf.d/sing-box"
# Service files
install -Dm644 "$PROJECT/release/config/sing-box.service" "$ROOT_DIR/usr/lib/systemd/system/sing-box.service"
install -Dm644 "$PROJECT/release/config/sing-box@.service" "$ROOT_DIR/usr/lib/systemd/system/sing-box@.service"
# Completions
install -Dm644 "$PROJECT/release/completions/sing-box.bash" "$ROOT_DIR/usr/share/bash-completion/completions/sing-box.bash"
install -Dm644 "$PROJECT/release/completions/sing-box.fish" "$ROOT_DIR/usr/share/fish/vendor_completions.d/sing-box.fish"
install -Dm644 "$PROJECT/release/completions/sing-box.zsh" "$ROOT_DIR/usr/share/zsh/site-functions/_sing-box"
# License
install -Dm644 "$PROJECT/LICENSE" "$ROOT_DIR/usr/share/licenses/sing-box/LICENSE"
# APK metadata
PACKAGES_DIR="$ROOT_DIR/lib/apk/packages"
mkdir -p "$PACKAGES_DIR"
# .conffiles
cat > "$PACKAGES_DIR/.conffiles" <<'EOF'
/etc/conf.d/sing-box
/etc/init.d/sing-box
/etc/sing-box/config.json
EOF
# .conffiles_static (sha256 checksums)
while IFS= read -r conffile; do
sha256=$(sha256sum "$ROOT_DIR$conffile" | cut -d' ' -f1)
echo "$conffile $sha256"
done < "$PACKAGES_DIR/.conffiles" > "$PACKAGES_DIR/.conffiles_static"
# .list (all files, excluding lib/apk/packages/ metadata)
(cd "$ROOT_DIR" && find . -type f -o -type l) \
| sed 's|^\./|/|' \
| grep -v '^/lib/apk/packages/' \
| sort > "$PACKAGES_DIR/.list"
# Build APK
apk mkpkg \
--info "name:sing-box" \
--info "version:${APK_VERSION}" \
--info "description:The universal proxy platform." \
--info "arch:${ARCHITECTURE}" \
--info "license:GPL-3.0-or-later with name use or association addition" \
--info "origin:sing-box" \
--info "url:https://sing-box.sagernet.org/" \
--info "maintainer:nekohasekai <contact-git@sekai.icu>" \
--files "$ROOT_DIR" \
--output "$OUTPUT_PATH"

80
.github/build_openwrt_apk.sh vendored Executable file
View File

@@ -0,0 +1,80 @@
#!/usr/bin/env bash
set -e -o pipefail
ARCHITECTURE="$1"
VERSION="$2"
BINARY_PATH="$3"
OUTPUT_PATH="$4"
if [ -z "$ARCHITECTURE" ] || [ -z "$VERSION" ] || [ -z "$BINARY_PATH" ] || [ -z "$OUTPUT_PATH" ]; then
echo "Usage: $0 <architecture> <version> <binary_path> <output_path>"
exit 1
fi
PROJECT=$(cd "$(dirname "$0")/.."; pwd)
# Convert version to APK format:
# 1.13.0-beta.8 -> 1.13.0_beta8-r0
# 1.13.0-rc.3 -> 1.13.0_rc3-r0
# 1.13.0 -> 1.13.0-r0
APK_VERSION=$(echo "$VERSION" | sed -E 's/-([a-z]+)\.([0-9]+)/_\1\2/')
APK_VERSION="${APK_VERSION}-r0"
ROOT_DIR=$(mktemp -d)
trap 'rm -rf "$ROOT_DIR"' EXIT
# Binary
install -Dm755 "$BINARY_PATH" "$ROOT_DIR/usr/bin/sing-box"
# Config files
install -Dm644 "$PROJECT/release/config/config.json" "$ROOT_DIR/etc/sing-box/config.json"
install -Dm644 "$PROJECT/release/config/openwrt.conf" "$ROOT_DIR/etc/config/sing-box"
install -Dm755 "$PROJECT/release/config/openwrt.init" "$ROOT_DIR/etc/init.d/sing-box"
install -Dm644 "$PROJECT/release/config/openwrt.keep" "$ROOT_DIR/lib/upgrade/keep.d/sing-box"
# Completions
install -Dm644 "$PROJECT/release/completions/sing-box.bash" "$ROOT_DIR/usr/share/bash-completion/completions/sing-box.bash"
install -Dm644 "$PROJECT/release/completions/sing-box.fish" "$ROOT_DIR/usr/share/fish/vendor_completions.d/sing-box.fish"
install -Dm644 "$PROJECT/release/completions/sing-box.zsh" "$ROOT_DIR/usr/share/zsh/site-functions/_sing-box"
# License
install -Dm644 "$PROJECT/LICENSE" "$ROOT_DIR/usr/share/licenses/sing-box/LICENSE"
# APK metadata
PACKAGES_DIR="$ROOT_DIR/lib/apk/packages"
mkdir -p "$PACKAGES_DIR"
# .conffiles
cat > "$PACKAGES_DIR/.conffiles" <<'EOF'
/etc/config/sing-box
/etc/sing-box/config.json
EOF
# .conffiles_static (sha256 checksums)
while IFS= read -r conffile; do
sha256=$(sha256sum "$ROOT_DIR$conffile" | cut -d' ' -f1)
echo "$conffile $sha256"
done < "$PACKAGES_DIR/.conffiles" > "$PACKAGES_DIR/.conffiles_static"
# .list (all files, excluding lib/apk/packages/ metadata)
(cd "$ROOT_DIR" && find . -type f -o -type l) \
| sed 's|^\./|/|' \
| grep -v '^/lib/apk/packages/' \
| sort > "$PACKAGES_DIR/.list"
# Build APK
apk mkpkg \
--info "name:sing-box" \
--info "version:${APK_VERSION}" \
--info "description:The universal proxy platform." \
--info "arch:${ARCHITECTURE}" \
--info "license:GPL-3.0-or-later" \
--info "origin:sing-box" \
--info "url:https://sing-box.sagernet.org/" \
--info "maintainer:nekohasekai <contact-git@sekai.icu>" \
--info "depends:ca-bundle kmod-inet-diag kmod-tun firewall4 kmod-nft-queue" \
--info "provider-priority:100" \
--script "pre-deinstall:${PROJECT}/release/config/openwrt.prerm" \
--files "$ROOT_DIR" \
--output "$OUTPUT_PATH"

28
.github/deb2ipk.sh vendored Executable file
View File

@@ -0,0 +1,28 @@
#!/usr/bin/env bash
# mod from https://gist.github.com/pldubouilh/c5703052986bfdd404005951dee54683
set -e -o pipefail
PROJECT=$(dirname "$0")/../..
TMP_PATH=`mktemp -d`
cp $2 $TMP_PATH
pushd $TMP_PATH
DEB_NAME=`ls *.deb`
ar x $DEB_NAME
mkdir control
pushd control
tar xf ../control.tar.gz
rm md5sums
sed "s/Architecture:\\ \w*/Architecture:\\ $1/g" ./control -i
cat control
tar czf ../control.tar.gz ./*
popd
DEB_NAME=${DEB_NAME%.deb}
tar czf $DEB_NAME.ipk control.tar.gz data.tar.gz debian-binary
popd
cp $TMP_PATH/$DEB_NAME.ipk $3
rm -r $TMP_PATH

View File

@@ -6,7 +6,7 @@
":disableRateLimiting"
],
"baseBranches": [
"dev-next"
"unstable"
],
"golang": {
"enabled": false

45
.github/setup_go_for_macos1013.sh vendored Executable file
View File

@@ -0,0 +1,45 @@
#!/usr/bin/env bash
set -euo pipefail
VERSION="1.25.8"
PATCH_COMMITS=(
"afe69d3cec1c6dcf0f1797b20546795730850070"
"1ed289b0cf87dc5aae9c6fe1aa5f200a83412938"
)
CURL_ARGS=(
-fL
--silent
--show-error
)
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
CURL_ARGS+=(-H "Authorization: Bearer ${GITHUB_TOKEN}")
fi
mkdir -p "$HOME/go"
cd "$HOME/go"
wget "https://dl.google.com/go/go${VERSION}.darwin-arm64.tar.gz"
tar -xzf "go${VERSION}.darwin-arm64.tar.gz"
#cp -a go go_bootstrap
mv go go_osx
cd go_osx
# these patch URLs only work on golang1.25.x
# that means after golang1.26 release it must be changed
# see: https://github.com/SagerNet/go/commits/release-branch.go1.25/
# revert:
# 33d3f603c1: "cmd/link/internal/ld: use 12.0.0 OS/SDK versions for macOS linking"
# 937368f84e: "crypto/x509: change how we retrieve chains on darwin"
for patch_commit in "${PATCH_COMMITS[@]}"; do
curl "${CURL_ARGS[@]}" "https://github.com/SagerNet/go/commit/${patch_commit}.diff" | patch --verbose -p 1
done
# Rebuild is not needed: we build with CGO_ENABLED=1, so Apple's external
# linker handles LC_BUILD_VERSION via MACOSX_DEPLOYMENT_TARGET, and the
# stdlib (crypto/x509) is compiled from patched src automatically.
#cd src
#GOROOT_BOOTSTRAP="$HOME/go/go_bootstrap" ./make.bash
#cd ../..
#rm -rf go_bootstrap "go${VERSION}.darwin-arm64.tar.gz"

46
.github/setup_go_for_windows7.sh vendored Executable file
View File

@@ -0,0 +1,46 @@
#!/usr/bin/env bash
set -euo pipefail
VERSION="1.25.8"
PATCH_COMMITS=(
"466f6c7a29bc098b0d4c987b803c779222894a11"
"1bdabae205052afe1dadb2ad6f1ba612cdbc532a"
"a90777dcf692dd2168577853ba743b4338721b06"
"f6bddda4e8ff58a957462a1a09562924d5f3d05c"
"bed309eff415bcb3c77dd4bc3277b682b89a388d"
"34b899c2fb39b092db4fa67c4417e41dc046be4b"
)
CURL_ARGS=(
-fL
--silent
--show-error
)
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
CURL_ARGS+=(-H "Authorization: Bearer ${GITHUB_TOKEN}")
fi
mkdir -p "$HOME/go"
cd "$HOME/go"
wget "https://dl.google.com/go/go${VERSION}.linux-amd64.tar.gz"
tar -xzf "go${VERSION}.linux-amd64.tar.gz"
mv go go_win7
cd go_win7
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
# these patch URLs only work on golang1.25.x
# that means after golang1.26 release it must be changed
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.25/
# revert:
# 693def151adff1af707d82d28f55dba81ceb08e1: "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng"
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
# fixes:
# bed309eff415bcb3c77dd4bc3277b682b89a388d: "Fix os.RemoveAll not working on Windows7"
# 34b899c2fb39b092db4fa67c4417e41dc046be4b: "Revert \"os: remove 5ms sleep on Windows in (*Process).Wait\""
for patch_commit in "${PATCH_COMMITS[@]}"; do
curl "${CURL_ARGS[@]}" "https://github.com/MetaCubeX/go/commit/${patch_commit}.diff" | patch --verbose -p 1
done

14
.github/update_clients.sh vendored Executable file
View File

@@ -0,0 +1,14 @@
#!/usr/bin/env bash
PROJECTS=$(dirname "$0")/../..
function updateClient() {
pushd clients/$1
git fetch
git reset FETCH_HEAD --hard
popd
git add clients/$1
}
updateClient "apple"
updateClient "android"

13
.github/update_cronet.sh vendored Executable file
View File

@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -e -o pipefail
SCRIPT_DIR=$(dirname "$0")
PROJECTS=$SCRIPT_DIR/../..
git -C $PROJECTS/cronet-go fetch origin main
git -C $PROJECTS/cronet-go fetch origin go
go get -x github.com/sagernet/cronet-go/all@$(git -C $PROJECTS/cronet-go rev-parse origin/go)
go get -x github.com/sagernet/cronet-go@$(git -C $PROJECTS/cronet-go rev-parse origin/go)
go mod tidy
git -C $PROJECTS/cronet-go rev-parse origin/go > "$SCRIPT_DIR/CRONET_GO_VERSION"

13
.github/update_cronet_dev.sh vendored Executable file
View File

@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -e -o pipefail
SCRIPT_DIR=$(dirname "$0")
PROJECTS=$SCRIPT_DIR/../..
git -C $PROJECTS/cronet-go fetch origin dev
git -C $PROJECTS/cronet-go fetch origin go_dev
go get -x github.com/sagernet/cronet-go/all@$(git -C $PROJECTS/cronet-go rev-parse origin/go_dev)
go get -x github.com/sagernet/cronet-go@$(git -C $PROJECTS/cronet-go rev-parse origin/go_dev)
go mod tidy
git -C $PROJECTS/cronet-go rev-parse origin/dev > "$SCRIPT_DIR/CRONET_GO_VERSION"

1019
.github/workflows/build.yml vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,212 +0,0 @@
name: Debug build
on:
push:
branches:
- main-next
- dev-next
paths-ignore:
- '**.md'
- '.github/**'
- '!.github/workflows/debug.yml'
pull_request:
branches:
- main-next
- dev-next
jobs:
build:
name: Debug build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Get latest go version
id: version
run: |
echo go_version=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') >> $GITHUB_OUTPUT
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: ${{ steps.version.outputs.go_version }}
- name: Cache go module
uses: actions/cache@v3
with:
path: |
~/go/pkg/mod
key: go-${{ hashFiles('**/go.sum') }}
- name: Add cache to Go proxy
run: |
version=`git rev-parse HEAD`
mkdir build
pushd build
go mod init build
go get -v github.com/sagernet/sing-box@$version
popd
continue-on-error: true
- name: Run Test
run: |
go test -v ./...
build_go118:
name: Debug build (Go 1.18)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: 1.18.10
- name: Cache go module
uses: actions/cache@v3
with:
path: |
~/go/pkg/mod
key: go118-${{ hashFiles('**/go.sum') }}
- name: Run Test
run: make
cross:
strategy:
matrix:
include:
# windows
- name: windows-amd64
goos: windows
goarch: amd64
goamd64: v1
- name: windows-amd64-v3
goos: windows
goarch: amd64
goamd64: v3
- name: windows-386
goos: windows
goarch: 386
- name: windows-arm64
goos: windows
goarch: arm64
- name: windows-arm32v7
goos: windows
goarch: arm
goarm: 7
# linux
- name: linux-amd64
goos: linux
goarch: amd64
goamd64: v1
- name: linux-amd64-v3
goos: linux
goarch: amd64
goamd64: v3
- name: linux-386
goos: linux
goarch: 386
- name: linux-arm64
goos: linux
goarch: arm64
- name: linux-armv5
goos: linux
goarch: arm
goarm: 5
- name: linux-armv6
goos: linux
goarch: arm
goarm: 6
- name: linux-armv7
goos: linux
goarch: arm
goarm: 7
- name: linux-mips-softfloat
goos: linux
goarch: mips
gomips: softfloat
- name: linux-mips-hardfloat
goos: linux
goarch: mips
gomips: hardfloat
- name: linux-mipsel-softfloat
goos: linux
goarch: mipsle
gomips: softfloat
- name: linux-mipsel-hardfloat
goos: linux
goarch: mipsle
gomips: hardfloat
- name: linux-mips64
goos: linux
goarch: mips64
- name: linux-mips64el
goos: linux
goarch: mips64le
- name: linux-s390x
goos: linux
goarch: s390x
# darwin
- name: darwin-amd64
goos: darwin
goarch: amd64
goamd64: v1
- name: darwin-amd64-v3
goos: darwin
goarch: amd64
goamd64: v3
- name: darwin-arm64
goos: darwin
goarch: arm64
# freebsd
- name: freebsd-amd64
goos: freebsd
goarch: amd64
goamd64: v1
- name: freebsd-amd64-v3
goos: freebsd
goarch: amd64
goamd64: v3
- name: freebsd-386
goos: freebsd
goarch: 386
- name: freebsd-arm64
goos: freebsd
goarch: arm64
fail-fast: false
runs-on: ubuntu-latest
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
GOAMD64: ${{ matrix.goamd64 }}
GOARM: ${{ matrix.goarm }}
GOMIPS: ${{ matrix.gomips }}
CGO_ENABLED: 0
TAGS: with_clash_api,with_quic
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Get latest go version
id: version
run: |
echo go_version=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') >> $GITHUB_OUTPUT
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: ${{ steps.version.outputs.go_version }}
- name: Cache go module
uses: actions/cache@v3
with:
path: |
~/go/pkg/mod
key: go-${{ hashFiles('**/go.sum') }}
- name: Build
id: build
run: make
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: sing-box-${{ matrix.name }}
path: sing-box*

View File

@@ -1,45 +1,296 @@
name: Build Docker Images
name: Publish Docker Images
on:
#push:
# branches:
# - stable
# - testing
release:
types:
- published
workflow_dispatch:
inputs:
tag:
description: "The tag version you want to build"
env:
REGISTRY_IMAGE: ghcr.io/sagernet/sing-box
jobs:
build:
build_binary:
name: Build binary
if: github.event_name != 'release' || github.event.release.target_commitish != 'oldstable'
runs-on: ubuntu-latest
strategy:
fail-fast: true
matrix:
include:
# Naive-enabled builds (musl)
- { arch: amd64, naive: true, docker_platform: "linux/amd64" }
- { arch: arm64, naive: true, docker_platform: "linux/arm64" }
- { arch: "386", naive: true, docker_platform: "linux/386" }
- { arch: arm, goarm: "7", naive: true, docker_platform: "linux/arm/v7" }
- { arch: mipsle, gomips: softfloat, naive: true, docker_platform: "linux/mipsle" }
- { arch: riscv64, naive: true, docker_platform: "linux/riscv64" }
- { arch: loong64, naive: true, docker_platform: "linux/loong64" }
# Non-naive builds
- { arch: arm, goarm: "6", docker_platform: "linux/arm/v6" }
- { arch: ppc64le, docker_platform: "linux/ppc64le" }
- { arch: s390x, docker_platform: "linux/s390x" }
steps:
- name: Get commit to build
id: ref
run: |-
if [[ -z "${{ github.event.inputs.tag }}" ]]; then
ref="${{ github.ref_name }}"
else
ref="${{ github.event.inputs.tag }}"
fi
echo "ref=$ref"
echo "ref=$ref" >> $GITHUB_OUTPUT
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
ref: ${{ steps.ref.outputs.ref }}
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ~1.25.8
- name: Clone cronet-go
if: matrix.naive
run: |
set -xeuo pipefail
CRONET_GO_VERSION=$(cat .github/CRONET_GO_VERSION)
git init ~/cronet-go
git -C ~/cronet-go remote add origin https://github.com/sagernet/cronet-go.git
git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION"
git -C ~/cronet-go checkout FETCH_HEAD
git -C ~/cronet-go submodule update --init --recursive --depth=1
- name: Regenerate Debian keyring
if: matrix.naive
run: |
set -xeuo pipefail
rm -f ~/cronet-go/naiveproxy/src/build/linux/sysroot_scripts/keyring.gpg
cd ~/cronet-go
GPG_TTY=/dev/null ./naiveproxy/src/build/linux/sysroot_scripts/generate_keyring.sh
- name: Cache Chromium toolchain
if: matrix.naive
id: cache-chromium-toolchain
uses: actions/cache@v4
with:
path: |
~/cronet-go/naiveproxy/src/third_party/llvm-build/
~/cronet-go/naiveproxy/src/gn/out/
~/cronet-go/naiveproxy/src/chrome/build/pgo_profiles/
~/cronet-go/naiveproxy/src/out/sysroot-build/
key: chromium-toolchain-${{ matrix.arch }}-musl-${{ hashFiles('.github/CRONET_GO_VERSION') }}
- name: Download Chromium toolchain
if: matrix.naive
run: |
set -xeuo pipefail
cd ~/cronet-go
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl download-toolchain
- name: Set version
run: |
set -xeuo pipefail
VERSION=$(go run ./cmd/internal/read_tag)
echo "VERSION=${VERSION}" >> "${GITHUB_ENV}"
- name: Set Chromium toolchain environment
if: matrix.naive
run: |
set -xeuo pipefail
cd ~/cronet-go
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl env >> $GITHUB_ENV
- name: Set build tags
run: |
set -xeuo pipefail
if [[ "${{ matrix.naive }}" == "true" ]]; then
TAGS="$(cat release/DEFAULT_BUILD_TAGS),with_musl"
else
TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS)
fi
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
- name: Set shared ldflags
run: |
echo "LDFLAGS_SHARED=$(cat release/LDFLAGS)" >> "${GITHUB_ENV}"
- name: Build (naive)
if: matrix.naive
run: |
set -xeuo pipefail
go build -v -trimpath -o sing-box -tags "${BUILD_TAGS}" \
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${VERSION}' ${LDFLAGS_SHARED} -s -w -buildid=" \
./cmd/sing-box
env:
CGO_ENABLED: "1"
GOOS: linux
GOARCH: ${{ matrix.arch }}
GOARM: ${{ matrix.goarm }}
GOMIPS: ${{ matrix.gomips }}
- name: Build (non-naive)
if: ${{ ! matrix.naive }}
run: |
set -xeuo pipefail
go build -v -trimpath -o sing-box -tags "${BUILD_TAGS}" \
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${VERSION}' ${LDFLAGS_SHARED} -s -w -buildid=" \
./cmd/sing-box
env:
CGO_ENABLED: "0"
GOOS: linux
GOARCH: ${{ matrix.arch }}
GOARM: ${{ matrix.goarm }}
- name: Prepare artifact
run: |
platform=${{ matrix.docker_platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
# Rename binary to include arch info for Dockerfile.binary
BINARY_NAME="sing-box-${{ matrix.arch }}"
if [[ -n "${{ matrix.goarm }}" ]]; then
BINARY_NAME="${BINARY_NAME}v${{ matrix.goarm }}"
fi
mv sing-box "${BINARY_NAME}"
echo "BINARY_NAME=${BINARY_NAME}" >> $GITHUB_ENV
- name: Upload binary
uses: actions/upload-artifact@v4
with:
name: binary-${{ env.PLATFORM_PAIR }}
path: ${{ env.BINARY_NAME }}
if-no-files-found: error
retention-days: 1
build_docker:
name: Build Docker image
runs-on: ubuntu-latest
needs:
- build_binary
strategy:
fail-fast: true
matrix:
include:
- { platform: "linux/amd64" }
- { platform: "linux/arm/v6" }
- { platform: "linux/arm/v7" }
- { platform: "linux/arm64" }
- { platform: "linux/386" }
# mipsle: no base Docker image available for this platform
- { platform: "linux/ppc64le" }
- { platform: "linux/riscv64" }
- { platform: "linux/s390x" }
- { platform: "linux/loong64", base_image: "ghcr.io/loong64/alpine:edge" }
steps:
- name: Get commit to build
id: ref
run: |-
if [[ -z "${{ github.event.inputs.tag }}" ]]; then
ref="${{ github.ref_name }}"
else
ref="${{ github.event.inputs.tag }}"
fi
echo "ref=$ref"
echo "ref=$ref" >> $GITHUB_OUTPUT
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
ref: ${{ steps.ref.outputs.ref }}
fetch-depth: 0
- name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Download binary
uses: actions/download-artifact@v5
with:
name: binary-${{ env.PLATFORM_PAIR }}
path: .
- name: Prepare binary
run: |
# Find and make the binary executable
chmod +x sing-box-*
ls -la sing-box-*
- name: Setup QEMU
uses: docker/setup-qemu-action@v3
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Setup QEMU for Docker Buildx
uses: docker/setup-qemu-action@v2
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker metadata
id: metadata
uses: docker/metadata-action@v4
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/sagernet/sing-box
- name: Get tag to build
id: tag
images: ${{ env.REGISTRY_IMAGE }}
- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
with:
platforms: ${{ matrix.platform }}
context: .
file: Dockerfile.binary
build-args: |
BASE_IMAGE=${{ matrix.base_image || 'alpine' }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true
- name: Export digest
run: |
echo "latest=ghcr.io/sagernet/sing-box:latest" >> $GITHUB_OUTPUT
if [[ -z "${{ github.event.inputs.tag }}" ]]; then
echo "versioned=ghcr.io/sagernet/sing-box:${{ github.ref_name }}" >> $GITHUB_OUTPUT
else
echo "versioned=ghcr.io/sagernet/sing-box:${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
fi
- name: Build and release Docker images
uses: docker/build-push-action@v4
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
platforms: linux/386,linux/amd64,linux/arm64,linux/s390x
target: dist
tags: |
${{ steps.tag.outputs.latest }}
${{ steps.tag.outputs.versioned }}
push: true
name: digests-${{ env.PLATFORM_PAIR }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
merge:
if: github.event_name != 'push'
runs-on: ubuntu-latest
needs:
- build_docker
steps:
- name: Get commit to build
id: ref
run: |-
if [[ -z "${{ github.event.inputs.tag }}" ]]; then
ref="${{ github.ref_name }}"
else
ref="${{ github.event.inputs.tag }}"
fi
echo "ref=$ref"
echo "ref=$ref" >> $GITHUB_OUTPUT
if [[ $ref == *"-"* ]]; then
latest=latest-beta
else
latest=latest
fi
echo "latest=$latest"
echo "latest=$latest" >> $GITHUB_OUTPUT
- name: Download digests
uses: actions/download-artifact@v5
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create manifest list and push
if: github.event_name != 'push'
working-directory: /tmp/digests
run: |
docker buildx imagetools create \
-t "${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.latest }}" \
-t "${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}" \
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
- name: Inspect image
if: github.event_name != 'push'
run: |
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.latest }}
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}

View File

@@ -3,16 +3,20 @@ name: Lint
on:
push:
branches:
- main-next
- dev-next
- oldstable
- stable
- testing
- unstable
paths-ignore:
- '**.md'
- '.github/**'
- '!.github/workflows/lint.yml'
pull_request:
branches:
- main-next
- dev-next
- oldstable
- stable
- testing
- unstable
jobs:
build:
@@ -20,24 +24,17 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
fetch-depth: 0
- name: Get latest go version
id: version
run: |
echo go_version=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') >> $GITHUB_OUTPUT
- name: Setup Go
uses: actions/setup-go@v3
uses: actions/setup-go@v5
with:
go-version: ${{ steps.version.outputs.go_version }}
- name: Cache go module
uses: actions/cache@v3
with:
path: |
~/go/pkg/mod
key: go-${{ hashFiles('**/go.sum') }}
go-version: ^1.25
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
uses: golangci/golangci-lint-action@v8
with:
version: latest
version: latest
args: --timeout=30m
install-mode: binary
verify: false

255
.github/workflows/linux.yml vendored Normal file
View File

@@ -0,0 +1,255 @@
name: Build Linux Packages
on:
#push:
# branches:
# - stable
# - testing
workflow_dispatch:
inputs:
version:
description: "Version name"
required: true
type: string
forceBeta:
description: "Force beta"
required: false
type: boolean
default: false
release:
types:
- published
jobs:
calculate_version:
name: Calculate version
if: github.event_name != 'release' || github.event.release.target_commitish != 'oldstable'
runs-on: ubuntu-latest
outputs:
version: ${{ steps.outputs.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ~1.25.8
- name: Check input version
if: github.event_name == 'workflow_dispatch'
run: |-
echo "version=${{ inputs.version }}"
echo "version=${{ inputs.version }}" >> "$GITHUB_ENV"
- name: Calculate version
if: github.event_name != 'workflow_dispatch'
run: |-
go run -v ./cmd/internal/read_tag --ci --nightly
- name: Set outputs
id: outputs
run: |-
echo "version=$version" >> "$GITHUB_OUTPUT"
build:
name: Build binary
runs-on: ubuntu-latest
needs:
- calculate_version
strategy:
matrix:
include:
# Naive-enabled builds (musl)
- { os: linux, arch: amd64, naive: true, debian: amd64, rpm: x86_64, pacman: x86_64 }
- { os: linux, arch: arm64, naive: true, debian: arm64, rpm: aarch64, pacman: aarch64 }
- { os: linux, arch: "386", naive: true, debian: i386, rpm: i386 }
- { os: linux, arch: arm, goarm: "7", naive: true, debian: armhf, rpm: armv7hl, pacman: armv7hl }
- { os: linux, arch: mipsle, gomips: softfloat, naive: true, debian: mipsel, rpm: mipsel }
- { os: linux, arch: riscv64, naive: true, debian: riscv64, rpm: riscv64 }
- { os: linux, arch: loong64, naive: true, debian: loongarch64, rpm: loongarch64 }
# Non-naive builds (unsupported architectures)
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl }
- { os: linux, arch: mips64le, debian: mips64el, rpm: mips64el }
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ~1.25.8
- name: Clone cronet-go
if: matrix.naive
run: |
set -xeuo pipefail
CRONET_GO_VERSION=$(cat .github/CRONET_GO_VERSION)
git init ~/cronet-go
git -C ~/cronet-go remote add origin https://github.com/sagernet/cronet-go.git
git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION"
git -C ~/cronet-go checkout FETCH_HEAD
git -C ~/cronet-go submodule update --init --recursive --depth=1
- name: Regenerate Debian keyring
if: matrix.naive
run: |
set -xeuo pipefail
rm -f ~/cronet-go/naiveproxy/src/build/linux/sysroot_scripts/keyring.gpg
cd ~/cronet-go
GPG_TTY=/dev/null ./naiveproxy/src/build/linux/sysroot_scripts/generate_keyring.sh
- name: Cache Chromium toolchain
if: matrix.naive
id: cache-chromium-toolchain
uses: actions/cache@v4
with:
path: |
~/cronet-go/naiveproxy/src/third_party/llvm-build/
~/cronet-go/naiveproxy/src/gn/out/
~/cronet-go/naiveproxy/src/chrome/build/pgo_profiles/
~/cronet-go/naiveproxy/src/out/sysroot-build/
key: chromium-toolchain-${{ matrix.arch }}-musl-${{ hashFiles('.github/CRONET_GO_VERSION') }}
- name: Download Chromium toolchain
if: matrix.naive
run: |
set -xeuo pipefail
cd ~/cronet-go
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl download-toolchain
- name: Set Chromium toolchain environment
if: matrix.naive
run: |
set -xeuo pipefail
cd ~/cronet-go
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl env >> $GITHUB_ENV
- name: Set tag
run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f
- name: Set build tags
run: |
set -xeuo pipefail
if [[ "${{ matrix.naive }}" == "true" ]]; then
TAGS="$(cat release/DEFAULT_BUILD_TAGS),with_musl"
else
TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS)
fi
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
- name: Set shared ldflags
run: |
echo "LDFLAGS_SHARED=$(cat release/LDFLAGS)" >> "${GITHUB_ENV}"
- name: Build (naive)
if: matrix.naive
run: |
set -xeuo pipefail
mkdir -p dist
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
./cmd/sing-box
env:
CGO_ENABLED: "1"
GOOS: linux
GOARCH: ${{ matrix.arch }}
GOARM: ${{ matrix.goarm }}
GOMIPS: ${{ matrix.gomips }}
GOMIPS64: ${{ matrix.gomips }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build (non-naive)
if: ${{ ! matrix.naive }}
run: |
set -xeuo pipefail
mkdir -p dist
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
./cmd/sing-box
env:
CGO_ENABLED: "0"
GOOS: ${{ matrix.os }}
GOARCH: ${{ matrix.arch }}
GOARM: ${{ matrix.goarm }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Set mtime
run: |-
TZ=UTC touch -t '197001010000' dist/sing-box
- name: Set name
if: (! contains(needs.calculate_version.outputs.version, '-')) && !inputs.forceBeta
run: |-
echo "NAME=sing-box" >> "$GITHUB_ENV"
- name: Set beta name
if: contains(needs.calculate_version.outputs.version, '-') || inputs.forceBeta
run: |-
echo "NAME=sing-box-beta" >> "$GITHUB_ENV"
- name: Set version
run: |-
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
PKG_VERSION="${PKG_VERSION//-/\~}"
echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}"
- name: Package DEB
if: matrix.debian != ''
run: |
set -xeuo pipefail
sudo gem install fpm
sudo apt-get install -y debsigs
cp .fpm_systemd .fpm
fpm -t deb \
--name "${NAME}" \
-v "$PKG_VERSION" \
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.debian }}.deb" \
--architecture ${{ matrix.debian }} \
dist/sing-box=/usr/bin/sing-box
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
sudo patch /usr/bin/debsigs < '/tmp/debsigs.diff'
rm -rf $HOME/.gnupg
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
${{ secrets.GPG_KEY }}
EOF
debsigs --sign=origin -k ${{ secrets.GPG_KEY_ID }} --gpgopts '--pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}"' dist/*.deb
- name: Package RPM
if: matrix.rpm != ''
run: |-
set -xeuo pipefail
sudo gem install fpm
cp .fpm_systemd .fpm
fpm -t rpm \
--name "${NAME}" \
-v "$PKG_VERSION" \
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.rpm }}.rpm" \
--architecture ${{ matrix.rpm }} \
dist/sing-box=/usr/bin/sing-box
cat > $HOME/.rpmmacros <<EOF
%_gpg_name ${{ secrets.GPG_KEY_ID }}
%_gpg_sign_cmd_extra_args --pinentry-mode loopback --passphrase ${{ secrets.GPG_PASSPHRASE }}
EOF
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
${{ secrets.GPG_KEY }}
EOF
rpmsign --addsign dist/*.rpm
- name: Cleanup
run: rm dist/sing-box
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.legacy_go && '-legacy' || '' }}
path: "dist"
upload:
name: Upload builds
runs-on: ubuntu-latest
needs:
- calculate_version
- build
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
fetch-depth: 0
- name: Set tag
run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
- name: Download builds
uses: actions/download-artifact@v5
with:
path: dist
merge-multiple: true
- name: Publish packages
if: github.event_name != 'push'
run: |-
ls dist | xargs -I {} curl -F "package=@dist/{}" https://${{ secrets.FURY_TOKEN }}@push.fury.io/sagernet/

View File

@@ -1,20 +0,0 @@
name: Generate Documents
on:
push:
branches:
- dev
paths:
- docs/**
- .github/workflows/mkdocs.yml
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.x
- run: |
pip install mkdocs-material=="9.*" mkdocs-static-i18n=="0.53"
- run: |
mkdocs gh-deploy -m "{sha}" --force --ignore-version --no-history

View File

@@ -8,8 +8,9 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v7
- uses: actions/stale@v9
with:
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 5 days'
days-before-stale: 60
days-before-close: 5
days-before-close: 5
exempt-issue-labels: 'bug,enhancement'

9
.gitignore vendored
View File

@@ -1,6 +1,7 @@
/.idea/
/vendor/
/*.json
/*.srs
/*.db
/site/
/bin/
@@ -11,4 +12,12 @@
/*.jar
/*.aar
/*.xcframework/
/experimental/libbox/*.aar
/experimental/libbox/*.xcframework/
/experimental/libbox/*.nupkg
.DS_Store
/config.d/
/venv/
CLAUDE.md
AGENTS.md
/.claude/

6
.gitmodules vendored Normal file
View File

@@ -0,0 +1,6 @@
[submodule "clients/apple"]
path = clients/apple
url = https://github.com/SagerNet/sing-box-for-apple.git
[submodule "clients/android"]
path = clients/android
url = https://github.com/SagerNet/sing-box-for-android.git

View File

@@ -1,26 +1,64 @@
linters:
disable-all: true
enable:
- gofumpt
- govet
- gci
- staticcheck
- paralleltest
version: "2"
run:
skip-dirs:
- transport/simple-obfs
- transport/clashssr
- transport/cloudflaretls
- transport/shadowtls/tls
- transport/shadowtls/tls_go119
linters-settings:
gci:
custom-order: true
sections:
- standard
- prefix(github.com/sagernet/)
- default
staticcheck:
go: '1.20'
go: "1.25"
build-tags:
- with_gvisor
- with_quic
- with_dhcp
- with_wireguard
- with_utls
- with_acme
- with_clash_api
- with_tailscale
- with_ccm
- with_ocm
- badlinkname
- tfogo_checklinkname0
linters:
default: none
enable:
- govet
- ineffassign
- paralleltest
- staticcheck
settings:
staticcheck:
checks:
- all
- -S1000
- -S1008
- -S1017
- -ST1003
- -QF1001
- -QF1003
- -QF1008
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
paths:
- transport/simple-obfs
- third_party$
- builtin$
- examples$
formatters:
enable:
- gci
- gofumpt
settings:
gci:
sections:
- standard
- prefix(github.com/sagernet/)
- default
custom-order: true
exclusions:
generated: lax
paths:
- transport/simple-obfs
- third_party$
- builtin$
- examples$

View File

@@ -1,137 +0,0 @@
project_name: sing-box
builds:
- id: main
main: ./cmd/sing-box
flags:
- -v
- -trimpath
asmflags:
- all=-trimpath={{.Env.GOPATH}}
gcflags:
- all=-trimpath={{.Env.GOPATH}}
ldflags:
- -s -w -buildid=
tags:
- with_gvisor
- with_quic
- with_wireguard
- with_utls
- with_clash_api
env:
- CGO_ENABLED=0
targets:
- linux_amd64_v1
- linux_amd64_v3
- linux_arm64
- linux_arm_7
- linux_s390x
- windows_amd64_v1
- windows_amd64_v3
- windows_386
- windows_arm64
- darwin_amd64_v1
- darwin_amd64_v3
- darwin_arm64
mod_timestamp: '{{ .CommitTimestamp }}'
- id: android
main: ./cmd/sing-box
flags:
- -v
- -trimpath
asmflags:
- all=-trimpath={{.Env.GOPATH}}
gcflags:
- all=-trimpath={{.Env.GOPATH}}
ldflags:
- -s -w -buildid=
tags:
- with_gvisor
- with_quic
- with_wireguard
- with_utls
- with_clash_api
env:
- CGO_ENABLED=1
overrides:
- goos: android
goarch: arm
goarm: 7
env:
- CC=armv7a-linux-androideabi19-clang
- CXX=armv7a-linux-androideabi19-clang++
- goos: android
goarch: arm64
env:
- CC=aarch64-linux-android21-clang
- CXX=aarch64-linux-android21-clang++
- goos: android
goarch: 386
env:
- CC=i686-linux-android19-clang
- CXX=i686-linux-android19-clang++
- goos: android
goarch: amd64
goamd64: v1
env:
- CC=x86_64-linux-android21-clang
- CXX=x86_64-linux-android21-clang++
targets:
- android_arm_7
- android_arm64
- android_386
- android_amd64
mod_timestamp: '{{ .CommitTimestamp }}'
snapshot:
name_template: "{{ .Version }}.{{ .ShortCommit }}"
archives:
- id: archive
format: tar.gz
format_overrides:
- goos: windows
format: zip
wrap_in_directory: true
files:
- LICENSE
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
nfpms:
- id: package
package_name: sing-box
file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
vendor: sagernet
homepage: https://sing-box.sagernet.org/
maintainer: nekohasekai <contact-git@sekai.icu>
description: The universal proxy platform.
license: GPLv3 or later
formats:
- deb
- rpm
priority: extra
contents:
- src: release/config/config.json
dst: /etc/sing-box/config.json
type: config
- src: release/config/sing-box.service
dst: /etc/systemd/system/sing-box.service
- src: release/config/sing-box@.service
dst: /etc/systemd/system/sing-box@.service
- src: LICENSE
dst: /usr/share/licenses/sing-box/LICENSE
scripts:
postinstall: release/config/postinstall.sh
postremove: release/config/postremove.sh
source:
enabled: false
name_template: '{{ .ProjectName }}-{{ .Version }}.source'
prefix_template: '{{ .ProjectName }}-{{ .Version }}/'
checksum:
disable: true
name_template: '{{ .ProjectName }}-{{ .Version }}.checksum'
signs:
- artifacts: checksum
release:
github:
owner: SagerNet
name: sing-box
name_template: '{{ if .IsSnapshot }}{{ nightly }}{{ else }}{{ .Version }}{{ end }}'
draft: true
mode: replace

View File

@@ -1,22 +1,26 @@
FROM golang:1.20-alpine AS builder
FROM --platform=$BUILDPLATFORM golang:1.25-alpine AS builder
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
COPY . /go/src/github.com/sagernet/sing-box
WORKDIR /go/src/github.com/sagernet/sing-box
ARG TARGETOS TARGETARCH
ARG GOPROXY=""
ENV GOPROXY ${GOPROXY}
ENV CGO_ENABLED=0
ENV GOOS=$TARGETOS
ENV GOARCH=$TARGETARCH
RUN set -ex \
&& apk add git build-base \
&& export COMMIT=$(git rev-parse --short HEAD) \
&& go build -v -trimpath -tags with_quic,with_wireguard,with_acme \
&& export VERSION=$(go run ./cmd/internal/read_tag) \
&& export TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS) \
&& export LDFLAGS_SHARED=$(cat release/LDFLAGS) \
&& go build -v -trimpath -tags "$TAGS" \
-o /go/bin/sing-box \
-ldflags "-s -w -buildid=" \
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" $LDFLAGS_SHARED -s -w -buildid=" \
./cmd/sing-box
FROM alpine AS dist
FROM --platform=$TARGETPLATFORM alpine AS dist
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
RUN set -ex \
&& apk upgrade \
&& apk add bash tzdata ca-certificates \
&& rm -rf /var/cache/apk/*
&& apk add --no-cache --upgrade bash tzdata ca-certificates nftables
COPY --from=builder /go/bin/sing-box /usr/local/bin/sing-box
ENTRYPOINT ["sing-box"]
ENTRYPOINT ["sing-box"]

14
Dockerfile.binary Normal file
View File

@@ -0,0 +1,14 @@
ARG BASE_IMAGE=alpine
FROM ${BASE_IMAGE}
ARG TARGETARCH
ARG TARGETVARIANT
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
RUN set -ex \
&& if command -v apk > /dev/null; then \
apk add --no-cache --upgrade bash tzdata ca-certificates nftables; \
else \
apt-get update && apt-get install -y --no-install-recommends bash tzdata ca-certificates nftables \
&& rm -rf /var/lib/apt/lists/*; \
fi
COPY sing-box-${TARGETARCH}${TARGETVARIANT} /usr/local/bin/sing-box
ENTRYPOINT ["sing-box"]

View File

@@ -11,4 +11,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
along with this program. If not, see <http://www.gnu.org/licenses/>.
In addition, no derivative work may use the name or imply association
with this application without prior consent.

239
Makefile
View File

@@ -1,22 +1,47 @@
NAME = sing-box
COMMIT = $(shell git rev-parse --short HEAD)
TAGS ?= with_gvisor,with_quic,with_wireguard,with_utls,with_reality_server,with_clash_api
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server,with_shadowsocksr
PARAMS = -v -trimpath -tags "$(TAGS)" -ldflags "-s -w -buildid="
MAIN = ./cmd/sing-box
TAGS ?= $(shell cat release/DEFAULT_BUILD_TAGS_OTHERS)
.PHONY: test release
GOHOSTOS = $(shell go env GOHOSTOS)
GOHOSTARCH = $(shell go env GOHOSTARCH)
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest)
LDFLAGS_SHARED = $(shell cat release/LDFLAGS)
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' $(LDFLAGS_SHARED) -s -w -buildid="
MAIN_PARAMS = $(PARAMS) -tags "$(TAGS)"
MAIN = ./cmd/sing-box
PREFIX ?= $(shell go env GOPATH)
SING_FFI ?= sing-ffi
LIBBOX_FFI_CONFIG ?= ./experimental/libbox/ffi.json
.PHONY: test release docs build
build:
go build $(PARAMS) $(MAIN)
export GOTOOLCHAIN=local && \
go build $(MAIN_PARAMS) $(MAIN)
race:
export GOTOOLCHAIN=local && \
go build -race $(MAIN_PARAMS) $(MAIN)
ci_build:
export GOTOOLCHAIN=local && \
go build $(PARAMS) $(MAIN) && \
go build $(MAIN_PARAMS) $(MAIN)
generate_completions:
go run -v --tags "$(TAGS),generate,generate_completions" $(MAIN)
install:
go install $(PARAMS) $(MAIN)
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
fmt:
@gofumpt -l -w .
@gofmt -s -w .
@gci write --custom-order -s "standard,prefix(github.com/sagernet/),default" .
@gci write --custom-order -s standard -s "prefix(github.com/sagernet/)" -s "default" .
fmt_docs:
go run ./cmd/internal/format_docs
fmt_install:
go install -v mvdan.cc/gofumpt@latest
@@ -30,7 +55,7 @@ lint:
GOOS=freebsd golangci-lint run ./...
lint_install:
go install -v github.com/golangci/golangci-lint/cmd/golangci-lint@latest
go install -v github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
proto:
@go run ./cmd/internal/protogen
@@ -41,24 +66,162 @@ proto_install:
go install -v google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
snapshot:
go run ./cmd/internal/build goreleaser release --rm-dist --snapshot || exit 1
mkdir dist/release
mv dist/*.tar.gz dist/*.zip dist/*.deb dist/*.rpm dist/release
ghr --delete --draft --prerelease -p 1 nightly dist/release
rm -r dist
update_certificates:
go run ./cmd/internal/update_certificates
release:
go run ./cmd/internal/build goreleaser release --rm-dist --skip-publish || exit 1
go run ./cmd/internal/build goreleaser release --clean --skip publish
mkdir dist/release
mv dist/*.tar.gz dist/*.zip dist/*.deb dist/*.rpm dist/release
ghr --delete --draft --prerelease -p 3 $(shell git describe --tags) dist/release
rm -r dist
mv dist/*.tar.gz \
dist/*.zip \
dist/*.deb \
dist/*.rpm \
dist/*_amd64.pkg.tar.zst \
dist/*_arm64.pkg.tar.zst \
dist/release
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
rm -r dist/release
release_repo:
go run ./cmd/internal/build goreleaser release -f .goreleaser.fury.yaml --clean
release_install:
go install -v github.com/goreleaser/goreleaser@latest
go install -v github.com/tcnksm/ghr@latest
update_android_version:
go run ./cmd/internal/update_android_version
build_android:
cd ../sing-box-for-android && ./gradlew :app:clean :app:assembleOtherRelease :app:assembleOtherLegacyRelease && ./gradlew --stop
upload_android:
mkdir -p dist/release_android
cp ../sing-box-for-android/app/build/outputs/apk/other/release/*.apk dist/release_android
cp ../sing-box-for-android/app/build/outputs/apk/otherLegacy/release/*.apk dist/release_android
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release_android
rm -rf dist/release_android
release_android: lib_android update_android_version build_android upload_android
publish_android:
cd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle && ./gradlew --stop
# TODO: find why and remove `-destination 'generic/platform=iOS'`
# TODO: remove xcode clean when fix control widget fixed
build_ios:
cd ../sing-box-for-apple && \
rm -rf build/SFI.xcarchive && \
xcodebuild clean -scheme SFI && \
xcodebuild archive -scheme SFI -configuration Release -destination 'generic/platform=iOS' -archivePath build/SFI.xcarchive -allowProvisioningUpdates | xcbeautify | grep -A 10 -e "Archive Succeeded" -e "ARCHIVE FAILED" -e "❌"
upload_ios_app_store:
cd ../sing-box-for-apple && \
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
export_ios_ipa:
cd ../sing-box-for-apple && \
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Export.plist -allowProvisioningUpdates -exportPath build/SFI && \
cp build/SFI/sing-box.ipa dist/SFI.ipa
upload_ios_ipa:
cd dist && \
cp SFI.ipa "SFI-${VERSION}.ipa" && \
ghr --replace --draft --prerelease "v${VERSION}" "SFI-${VERSION}.ipa"
release_ios: build_ios upload_ios_app_store
build_macos:
cd ../sing-box-for-apple && \
rm -rf build/SFM.xcarchive && \
xcodebuild archive -scheme SFM -configuration Release -archivePath build/SFM.xcarchive -allowProvisioningUpdates | xcbeautify | grep -A 10 -e "Archive Succeeded" -e "ARCHIVE FAILED" -e "❌"
upload_macos_app_store:
cd ../sing-box-for-apple && \
xcodebuild -exportArchive -archivePath build/SFM.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
release_macos: build_macos upload_macos_app_store
build_macos_standalone:
$(MAKE) -C ../sing-box-for-apple archive_macos_standalone
build_macos_dmg:
$(MAKE) -C ../sing-box-for-apple build_macos_dmg
build_macos_pkg:
$(MAKE) -C ../sing-box-for-apple build_macos_pkg
notarize_macos_dmg:
$(MAKE) -C ../sing-box-for-apple notarize_macos_dmg
notarize_macos_pkg:
$(MAKE) -C ../sing-box-for-apple notarize_macos_pkg
upload_macos_dmg:
mkdir -p dist/SFM
cp ../sing-box-for-apple/build/SFM-Apple.dmg "dist/SFM/SFM-${VERSION}-Apple.dmg"
cp ../sing-box-for-apple/build/SFM-Intel.dmg "dist/SFM/SFM-${VERSION}-Intel.dmg"
cp ../sing-box-for-apple/build/SFM-Universal.dmg "dist/SFM/SFM-${VERSION}-Universal.dmg"
ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Apple.dmg"
ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Intel.dmg"
ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Universal.dmg"
upload_macos_pkg:
mkdir -p dist/SFM
cp ../sing-box-for-apple/build/SFM-Apple.pkg "dist/SFM/SFM-${VERSION}-Apple.pkg"
cp ../sing-box-for-apple/build/SFM-Intel.pkg "dist/SFM/SFM-${VERSION}-Intel.pkg"
cp ../sing-box-for-apple/build/SFM-Universal.pkg "dist/SFM/SFM-${VERSION}-Universal.pkg"
ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Apple.pkg"
ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Intel.pkg"
ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Universal.pkg"
upload_macos_dsyms:
mkdir -p dist/SFM
cd ../sing-box-for-apple/build/SFM.System-universal.xcarchive && zip -r SFM.dSYMs.zip dSYMs
cp ../sing-box-for-apple/build/SFM.System-universal.xcarchive/SFM.dSYMs.zip "dist/SFM/SFM-${VERSION}.dSYMs.zip"
ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}.dSYMs.zip"
release_macos_standalone: build_macos_pkg notarize_macos_pkg upload_macos_pkg upload_macos_dsyms
build_tvos:
cd ../sing-box-for-apple && \
rm -rf build/SFT.xcarchive && \
xcodebuild archive -scheme SFT -configuration Release -archivePath build/SFT.xcarchive -allowProvisioningUpdates | xcbeautify | grep -A 10 -e "Archive Succeeded" -e "ARCHIVE FAILED" -e "❌"
upload_tvos_app_store:
cd ../sing-box-for-apple && \
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
export_tvos_ipa:
cd ../sing-box-for-apple && \
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Export.plist -allowProvisioningUpdates -exportPath build/SFT && \
cp build/SFT/sing-box.ipa dist/SFT.ipa
upload_tvos_ipa:
cd dist && \
cp SFT.ipa "SFT-${VERSION}.ipa" && \
ghr --replace --draft --prerelease "v${VERSION}" "SFT-${VERSION}.ipa"
release_tvos: build_tvos upload_tvos_app_store
update_apple_version:
go run ./cmd/internal/update_apple_version
update_macos_version:
MACOS_PROJECT_VERSION=$(shell go run -v ./cmd/internal/app_store_connect next_macos_project_version) go run ./cmd/internal/update_apple_version
release_apple: lib_apple update_apple_version release_ios release_macos release_tvos release_macos_standalone
release_apple_beta: update_apple_version release_ios release_macos release_tvos
publish_testflight:
go run -v ./cmd/internal/app_store_connect publish_testflight $(filter-out $@,$(MAKECMDGOALS))
prepare_app_store:
go run -v ./cmd/internal/app_store_connect prepare_app_store
publish_app_store:
go run -v ./cmd/internal/app_store_connect publish_app_store
test:
@go test -v ./... && \
cd test && \
@@ -71,13 +234,34 @@ test_stdio:
go mod tidy && \
go test -v -tags "$(TAGS_TEST),force_stdio" .
lib:
go run ./cmd/internal/build_libbox
lib_android:
go run ./cmd/internal/build_libbox -target android
lib_apple:
go run ./cmd/internal/build_libbox -target apple
lib_windows:
$(SING_FFI) generate --config $(LIBBOX_FFI_CONFIG) --platform-type csharp
lib_android_new:
$(SING_FFI) generate --config $(LIBBOX_FFI_CONFIG) --platform-type android
lib_apple_new:
$(SING_FFI) generate --config $(LIBBOX_FFI_CONFIG) --platform-type apple
lib_install:
go get -v -d
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.0.0-20221130124640-349ebaa752ca
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.0.0-20221130124640-349ebaa752ca
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.12
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.12
docs:
venv/bin/mkdocs serve
publish_docs:
venv/bin/mkdocs gh-deploy -m "Update" --force --ignore-version --no-history
docs_install:
python3 -m venv venv
source ./venv/bin/activate && pip install --force-reinstall mkdocs-material=="9.7.2" mkdocs-static-i18n=="1.2.*"
clean:
rm -rf bin dist sing-box
@@ -86,4 +270,7 @@ clean:
update:
git fetch
git reset FETCH_HEAD --hard
git clean -fdx
git clean -fdx
%:
@:

View File

@@ -1,7 +1,17 @@
> Sponsored by [Warp](https://go.warp.dev/sing-box), built for coding with multiple AI agents
<a href="https://go.warp.dev/sing-box">
<img alt="Warp sponsorship" width="400" src="https://github.com/warpdotdev/brand-assets/raw/refs/heads/main/Github/Sponsor/Warp-Github-LG-02.png">
</a>
---
# sing-box
The universal proxy platform.
[![Packaging status](https://repology.org/badge/vertical-allrepos/sing-box.svg)](https://repology.org/project/sing-box/versions)
## Documentation
https://sing-box.sagernet.org
@@ -23,4 +33,7 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
In addition, no derivative work may use the name or imply association
with this application without prior consent.
```

21
adapter/certificate.go Normal file
View File

@@ -0,0 +1,21 @@
package adapter
import (
"context"
"crypto/x509"
"github.com/sagernet/sing/service"
)
type CertificateStore interface {
LifecycleService
Pool() *x509.CertPool
}
func RootPoolFromContext(ctx context.Context) *x509.CertPool {
store := service.FromContext[CertificateStore](ctx)
if store == nil {
return nil
}
return store.Pool()
}

18
adapter/connections.go Normal file
View File

@@ -0,0 +1,18 @@
package adapter
import (
"context"
"net"
N "github.com/sagernet/sing/common/network"
)
type ConnectionManager interface {
Lifecycle
Count() int
CloseAll()
TrackConn(conn net.Conn) net.Conn
TrackPacketConn(conn net.PacketConn) net.PacketConn
NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
}

93
adapter/dns.go Normal file
View File

@@ -0,0 +1,93 @@
package adapter
import (
"context"
"net/netip"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
"github.com/sagernet/sing/service"
"github.com/miekg/dns"
)
type DNSRouter interface {
Lifecycle
Exchange(ctx context.Context, message *dns.Msg, options DNSQueryOptions) (*dns.Msg, error)
Lookup(ctx context.Context, domain string, options DNSQueryOptions) ([]netip.Addr, error)
ClearCache()
LookupReverseMapping(ip netip.Addr) (string, bool)
ResetNetwork()
}
type DNSClient interface {
Start()
Exchange(ctx context.Context, transport DNSTransport, message *dns.Msg, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) (*dns.Msg, error)
Lookup(ctx context.Context, transport DNSTransport, domain string, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error)
ClearCache()
}
type DNSQueryOptions struct {
Transport DNSTransport
Strategy C.DomainStrategy
LookupStrategy C.DomainStrategy
DisableCache bool
RewriteTTL *uint32
ClientSubnet netip.Prefix
}
func DNSQueryOptionsFrom(ctx context.Context, options *option.DomainResolveOptions) (*DNSQueryOptions, error) {
if options == nil {
return &DNSQueryOptions{}, nil
}
transportManager := service.FromContext[DNSTransportManager](ctx)
transport, loaded := transportManager.Transport(options.Server)
if !loaded {
return nil, E.New("domain resolver not found: " + options.Server)
}
return &DNSQueryOptions{
Transport: transport,
Strategy: C.DomainStrategy(options.Strategy),
DisableCache: options.DisableCache,
RewriteTTL: options.RewriteTTL,
ClientSubnet: options.ClientSubnet.Build(netip.Prefix{}),
}, nil
}
type RDRCStore interface {
LoadRDRC(transportName string, qName string, qType uint16) (rejected bool)
SaveRDRC(transportName string, qName string, qType uint16) error
SaveRDRCAsync(transportName string, qName string, qType uint16, logger logger.Logger)
}
type DNSTransport interface {
Lifecycle
Type() string
Tag() string
Dependencies() []string
Reset()
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
}
type LegacyDNSTransport interface {
LegacyStrategy() C.DomainStrategy
LegacyClientSubnet() netip.Prefix
}
type DNSTransportRegistry interface {
option.DNSTransportOptionsRegistry
CreateDNSTransport(ctx context.Context, logger log.ContextLogger, tag string, transportType string, options any) (DNSTransport, error)
}
type DNSTransportManager interface {
Lifecycle
Transports() []DNSTransport
Transport(tag string) (DNSTransport, bool)
Default() DNSTransport
FakeIP() FakeIPTransport
Remove(tag string) error
Create(ctx context.Context, logger log.ContextLogger, tag string, outboundType string, options any) error
}

28
adapter/endpoint.go Normal file
View File

@@ -0,0 +1,28 @@
package adapter
import (
"context"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
)
type Endpoint interface {
Lifecycle
Type() string
Tag() string
Outbound
}
type EndpointRegistry interface {
option.EndpointOptionsRegistry
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, endpointType string, options any) (Endpoint, error)
}
type EndpointManager interface {
Lifecycle
Endpoints() []Endpoint
Get(tag string) (Endpoint, bool)
Remove(tag string) error
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, endpointType string, options any) error
}

View File

@@ -0,0 +1,43 @@
package endpoint
import "github.com/sagernet/sing-box/option"
type Adapter struct {
endpointType string
endpointTag string
network []string
dependencies []string
}
func NewAdapter(endpointType string, endpointTag string, network []string, dependencies []string) Adapter {
return Adapter{
endpointType: endpointType,
endpointTag: endpointTag,
network: network,
dependencies: dependencies,
}
}
func NewAdapterWithDialerOptions(endpointType string, endpointTag string, network []string, dialOptions option.DialerOptions) Adapter {
var dependencies []string
if dialOptions.Detour != "" {
dependencies = []string{dialOptions.Detour}
}
return NewAdapter(endpointType, endpointTag, network, dependencies)
}
func (a *Adapter) Type() string {
return a.endpointType
}
func (a *Adapter) Tag() string {
return a.endpointTag
}
func (a *Adapter) Network() []string {
return a.network
}
func (a *Adapter) Dependencies() []string {
return a.dependencies
}

161
adapter/endpoint/manager.go Normal file
View File

@@ -0,0 +1,161 @@
package endpoint
import (
"context"
"os"
"sync"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/taskmonitor"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
)
var _ adapter.EndpointManager = (*Manager)(nil)
type Manager struct {
logger log.ContextLogger
registry adapter.EndpointRegistry
access sync.Mutex
started bool
stage adapter.StartStage
endpoints []adapter.Endpoint
endpointByTag map[string]adapter.Endpoint
}
func NewManager(logger log.ContextLogger, registry adapter.EndpointRegistry) *Manager {
return &Manager{
logger: logger,
registry: registry,
endpointByTag: make(map[string]adapter.Endpoint),
}
}
func (m *Manager) Start(stage adapter.StartStage) error {
m.access.Lock()
defer m.access.Unlock()
if m.started && m.stage >= stage {
panic("already started")
}
m.started = true
m.stage = stage
if stage == adapter.StartStateStart {
// started with outbound manager
return nil
}
for _, endpoint := range m.endpoints {
name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]"
m.logger.Trace(stage, " ", name)
startTime := time.Now()
err := adapter.LegacyStart(endpoint, stage)
if err != nil {
return E.Cause(err, stage, " ", name)
}
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
return nil
}
func (m *Manager) Close() error {
m.access.Lock()
defer m.access.Unlock()
if !m.started {
return nil
}
m.started = false
endpoints := m.endpoints
m.endpoints = nil
monitor := taskmonitor.New(m.logger, C.StopTimeout)
var err error
for _, endpoint := range endpoints {
name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]"
m.logger.Trace("close ", name)
startTime := time.Now()
monitor.Start("close ", name)
err = E.Append(err, endpoint.Close(), func(err error) error {
return E.Cause(err, "close ", name)
})
monitor.Finish()
m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
return nil
}
func (m *Manager) Endpoints() []adapter.Endpoint {
m.access.Lock()
defer m.access.Unlock()
return m.endpoints
}
func (m *Manager) Get(tag string) (adapter.Endpoint, bool) {
m.access.Lock()
defer m.access.Unlock()
endpoint, found := m.endpointByTag[tag]
return endpoint, found
}
func (m *Manager) Remove(tag string) error {
m.access.Lock()
endpoint, found := m.endpointByTag[tag]
if !found {
m.access.Unlock()
return os.ErrInvalid
}
delete(m.endpointByTag, tag)
index := common.Index(m.endpoints, func(it adapter.Endpoint) bool {
return it == endpoint
})
if index == -1 {
panic("invalid endpoint index")
}
m.endpoints = append(m.endpoints[:index], m.endpoints[index+1:]...)
started := m.started
m.access.Unlock()
if started {
return endpoint.Close()
}
return nil
}
func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) error {
endpoint, err := m.registry.Create(ctx, router, logger, tag, outboundType, options)
if err != nil {
return err
}
m.access.Lock()
defer m.access.Unlock()
if m.started {
name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]"
for _, stage := range adapter.ListStartStages {
m.logger.Trace(stage, " ", name)
startTime := time.Now()
err = adapter.LegacyStart(endpoint, stage)
if err != nil {
return E.Cause(err, stage, " ", name)
}
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
}
if existsEndpoint, loaded := m.endpointByTag[tag]; loaded {
if m.started {
err = existsEndpoint.Close()
if err != nil {
return E.Cause(err, "close endpoint/", existsEndpoint.Type(), "[", existsEndpoint.Tag(), "]")
}
}
existsIndex := common.Index(m.endpoints, func(it adapter.Endpoint) bool {
return it == existsEndpoint
})
if existsIndex == -1 {
panic("invalid endpoint index")
}
m.endpoints = append(m.endpoints[:existsIndex], m.endpoints[existsIndex+1:]...)
}
m.endpoints = append(m.endpoints, endpoint)
m.endpointByTag[tag] = endpoint
return nil
}

View File

@@ -0,0 +1,72 @@
package endpoint
import (
"context"
"sync"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options T) (adapter.Endpoint, error)
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
registry.register(outboundType, func() any {
return new(Options)
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Endpoint, error) {
var options *Options
if rawOptions != nil {
options = rawOptions.(*Options)
}
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options))
})
}
var _ adapter.EndpointRegistry = (*Registry)(nil)
type (
optionsConstructorFunc func() any
constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Endpoint, error)
)
type Registry struct {
access sync.Mutex
optionsType map[string]optionsConstructorFunc
constructor map[string]constructorFunc
}
func NewRegistry() *Registry {
return &Registry{
optionsType: make(map[string]optionsConstructorFunc),
constructor: make(map[string]constructorFunc),
}
}
func (m *Registry) CreateOptions(outboundType string) (any, bool) {
m.access.Lock()
defer m.access.Unlock()
optionsConstructor, loaded := m.optionsType[outboundType]
if !loaded {
return nil, false
}
return optionsConstructor(), true
}
func (m *Registry) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Endpoint, error) {
m.access.Lock()
defer m.access.Unlock()
constructor, loaded := m.constructor[outboundType]
if !loaded {
return nil, E.New("outbound type not found: " + outboundType)
}
return constructor(ctx, router, logger, tag, options)
}
func (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
m.access.Lock()
defer m.access.Unlock()
m.optionsType[outboundType] = optionsConstructor
m.constructor[outboundType] = constructor
}

View File

@@ -1,50 +1,146 @@
package adapter
import (
"bytes"
"context"
"net"
"encoding/binary"
"io"
"time"
"github.com/sagernet/sing-box/common/urltest"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/observable"
"github.com/sagernet/sing/common/varbin"
)
type ClashServer interface {
Service
LifecycleService
ConnectionTracker
Mode() string
StoreSelected() bool
CacheFile() ClashCacheFile
HistoryStorage() *urltest.HistoryStorage
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule) (N.PacketConn, Tracker)
ModeList() []string
SetModeUpdateHook(hook *observable.Subscriber[struct{}])
HistoryStorage() URLTestHistoryStorage
}
type ClashCacheFile interface {
type URLTestHistory struct {
Time time.Time `json:"time"`
Delay uint16 `json:"delay"`
}
type URLTestHistoryStorage interface {
SetHook(hook *observable.Subscriber[struct{}])
LoadURLTestHistory(tag string) *URLTestHistory
DeleteURLTestHistory(tag string)
StoreURLTestHistory(tag string, history *URLTestHistory)
Close() error
}
type V2RayServer interface {
LifecycleService
StatsService() ConnectionTracker
}
type CacheFile interface {
LifecycleService
StoreFakeIP() bool
FakeIPStorage
StoreRDRC() bool
RDRCStore
LoadMode() string
StoreMode(mode string) error
LoadSelected(group string) string
StoreSelected(group string, selected string) error
LoadGroupExpand(group string) (isExpand bool, loaded bool)
StoreGroupExpand(group string, expand bool) error
LoadRuleSet(tag string) *SavedBinary
SaveRuleSet(tag string, set *SavedBinary) error
}
type Tracker interface {
Leave()
type SavedBinary struct {
Content []byte
LastUpdated time.Time
LastEtag string
}
func (s *SavedBinary) MarshalBinary() ([]byte, error) {
var buffer bytes.Buffer
err := binary.Write(&buffer, binary.BigEndian, uint8(1))
if err != nil {
return nil, err
}
_, err = varbin.WriteUvarint(&buffer, uint64(len(s.Content)))
if err != nil {
return nil, err
}
_, err = buffer.Write(s.Content)
if err != nil {
return nil, err
}
err = binary.Write(&buffer, binary.BigEndian, s.LastUpdated.Unix())
if err != nil {
return nil, err
}
_, err = varbin.WriteUvarint(&buffer, uint64(len(s.LastEtag)))
if err != nil {
return nil, err
}
_, err = buffer.WriteString(s.LastEtag)
if err != nil {
return nil, err
}
return buffer.Bytes(), nil
}
func (s *SavedBinary) UnmarshalBinary(data []byte) error {
reader := bytes.NewReader(data)
var version uint8
err := binary.Read(reader, binary.BigEndian, &version)
if err != nil {
return err
}
contentLength, err := binary.ReadUvarint(reader)
if err != nil {
return err
}
s.Content = make([]byte, contentLength)
_, err = io.ReadFull(reader, s.Content)
if err != nil {
return err
}
var lastUpdated int64
err = binary.Read(reader, binary.BigEndian, &lastUpdated)
if err != nil {
return err
}
s.LastUpdated = time.Unix(lastUpdated, 0)
etagLength, err := binary.ReadUvarint(reader)
if err != nil {
return err
}
etagBytes := make([]byte, etagLength)
_, err = io.ReadFull(reader, etagBytes)
if err != nil {
return err
}
s.LastEtag = string(etagBytes)
return nil
}
type OutboundGroup interface {
Outbound
Now() string
All() []string
}
type URLTestGroup interface {
OutboundGroup
URLTest(ctx context.Context) (map[string]uint16, error)
}
func OutboundTag(detour Outbound) string {
if group, isGroup := detour.(OutboundGroup); isGroup {
return group.Now()
}
return detour.Tag()
}
type V2RayServer interface {
Service
StatsService() V2RayStatsService
}
type V2RayStatsService interface {
RoutedConnection(inbound string, outbound string, user string, conn net.Conn) net.Conn
RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn
}

31
adapter/fakeip.go Normal file
View File

@@ -0,0 +1,31 @@
package adapter
import (
"net/netip"
"github.com/sagernet/sing/common/logger"
)
type FakeIPStore interface {
SimpleLifecycle
Contains(address netip.Addr) bool
Create(domain string, isIPv6 bool) (netip.Addr, error)
Lookup(address netip.Addr) (string, bool)
Reset() error
}
type FakeIPStorage interface {
FakeIPMetadata() *FakeIPMetadata
FakeIPSaveMetadata(metadata *FakeIPMetadata) error
FakeIPSaveMetadataAsync(metadata *FakeIPMetadata)
FakeIPStore(address netip.Addr, domain string) error
FakeIPStoreAsync(address netip.Addr, domain string, logger logger.Logger)
FakeIPLoad(address netip.Addr) (string, bool)
FakeIPLoadDomain(domain string, isIPv6 bool) (netip.Addr, bool)
FakeIPReset() error
}
type FakeIPTransport interface {
DNSTransport
Store() FakeIPStore
}

View File

@@ -0,0 +1,50 @@
package adapter
import (
"bytes"
"encoding"
"encoding/binary"
"io"
"net/netip"
"github.com/sagernet/sing/common"
)
type FakeIPMetadata struct {
Inet4Range netip.Prefix
Inet6Range netip.Prefix
Inet4Current netip.Addr
Inet6Current netip.Addr
}
func (m *FakeIPMetadata) MarshalBinary() (data []byte, err error) {
var buffer bytes.Buffer
for _, marshaler := range []encoding.BinaryMarshaler{m.Inet4Range, m.Inet6Range, m.Inet4Current, m.Inet6Current} {
data, err = marshaler.MarshalBinary()
if err != nil {
return
}
common.Must(binary.Write(&buffer, binary.BigEndian, uint16(len(data))))
buffer.Write(data)
}
data = buffer.Bytes()
return
}
func (m *FakeIPMetadata) UnmarshalBinary(data []byte) error {
reader := bytes.NewReader(data)
for _, unmarshaler := range []encoding.BinaryUnmarshaler{&m.Inet4Range, &m.Inet6Range, &m.Inet4Current, &m.Inet6Current} {
var length uint16
common.Must(binary.Read(reader, binary.BigEndian, &length))
element := make([]byte, length)
_, err := io.ReadFull(reader, element)
if err != nil {
return err
}
err = unmarshaler.UnmarshalBinary(element)
if err != nil {
return err
}
}
return nil
}

View File

@@ -6,27 +6,56 @@ import (
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
// Deprecated
type ConnectionHandler interface {
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
}
type ConnectionHandlerEx interface {
NewConnectionEx(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
}
// Deprecated: use PacketHandlerEx instead
type PacketHandler interface {
NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata InboundContext) error
}
type PacketHandlerEx interface {
NewPacketEx(buffer *buf.Buffer, source M.Socksaddr)
}
// Deprecated: use OOBPacketHandlerEx instead
type OOBPacketHandler interface {
NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, oob []byte, metadata InboundContext) error
}
type OOBPacketHandlerEx interface {
NewPacketEx(buffer *buf.Buffer, oob []byte, source M.Socksaddr)
}
// Deprecated
type PacketConnectionHandler interface {
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
}
type PacketConnectionHandlerEx interface {
NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
}
// Deprecated: use TCPConnectionHandlerEx instead
//
//nolint:staticcheck
type UpstreamHandlerAdapter interface {
N.TCPConnectionHandler
N.UDPConnectionHandler
E.Handler
}
type UpstreamHandlerAdapterEx interface {
N.TCPConnectionHandlerEx
N.UDPConnectionHandlerEx
}

View File

@@ -4,52 +4,111 @@ import (
"context"
"net"
"net/netip"
"time"
"github.com/sagernet/sing-box/common/process"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
type Inbound interface {
Service
Lifecycle
Type() string
Tag() string
}
type InjectableInbound interface {
type TCPInjectableInbound interface {
Inbound
Network() []string
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
ConnectionHandlerEx
}
type UDPInjectableInbound interface {
Inbound
PacketConnectionHandlerEx
}
type InboundRegistry interface {
option.InboundOptionsRegistry
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, inboundType string, options any) (Inbound, error)
}
type InboundManager interface {
Lifecycle
Inbounds() []Inbound
Get(tag string) (Inbound, bool)
Remove(tag string) error
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, inboundType string, options any) error
}
type InboundContext struct {
Inbound string
InboundType string
IPVersion int
IPVersion uint8
Network string
Source M.Socksaddr
Destination M.Socksaddr
Domain string
Protocol string
User string
Outbound string
// sniffer
Protocol string
Domain string
Client string
SniffContext any
SnifferNames []string
SniffError error
// cache
InboundDetour string
LastInbound string
OriginDestination M.Socksaddr
InboundOptions option.InboundOptions
// Deprecated: implement in rule action
InboundDetour string
LastInbound string
OriginDestination M.Socksaddr
RouteOriginalDestination M.Socksaddr
UDPDisableDomainUnmapping bool
UDPConnect bool
UDPTimeout time.Duration
TLSFragment bool
TLSFragmentFallbackDelay time.Duration
TLSRecordFragment bool
NetworkStrategy *C.NetworkStrategy
NetworkType []C.InterfaceType
FallbackNetworkType []C.InterfaceType
FallbackDelay time.Duration
DestinationAddresses []netip.Addr
SourceGeoIPCode string
GeoIPCode string
ProcessInfo *process.Info
ProcessInfo *ConnectionOwner
SourceMACAddress net.HardwareAddr
SourceHostname string
QueryType uint16
FakeIP bool
// dns cache
// rule cache
QueryType uint16
IPCIDRMatchSource bool
IPCIDRAcceptEmpty bool
SourceAddressMatch bool
SourcePortMatch bool
DestinationAddressMatch bool
DestinationPortMatch bool
DidMatch bool
IgnoreDestinationIPCIDRMatch bool
}
func (c *InboundContext) ResetRuleCache() {
c.IPCIDRMatchSource = false
c.IPCIDRAcceptEmpty = false
c.SourceAddressMatch = false
c.SourcePortMatch = false
c.DestinationAddressMatch = false
c.DestinationPortMatch = false
c.DidMatch = false
}
type inboundContextKey struct{}
@@ -66,11 +125,18 @@ func ContextFrom(ctx context.Context) *InboundContext {
return metadata.(*InboundContext)
}
func AppendContext(ctx context.Context) (context.Context, *InboundContext) {
metadata := ContextFrom(ctx)
if metadata != nil {
return ctx, metadata
func ExtendContext(ctx context.Context) (context.Context, *InboundContext) {
var newMetadata InboundContext
if metadata := ContextFrom(ctx); metadata != nil {
newMetadata = *metadata
}
metadata = new(InboundContext)
return WithContext(ctx, metadata), metadata
return WithContext(ctx, &newMetadata), &newMetadata
}
func OverrideContext(ctx context.Context) context.Context {
if metadata := ContextFrom(ctx); metadata != nil {
newMetadata := *metadata
return WithContext(ctx, &newMetadata)
}
return ctx
}

View File

@@ -0,0 +1,21 @@
package inbound
type Adapter struct {
inboundType string
inboundTag string
}
func NewAdapter(inboundType string, inboundTag string) Adapter {
return Adapter{
inboundType: inboundType,
inboundTag: inboundTag,
}
}
func (a *Adapter) Type() string {
return a.inboundType
}
func (a *Adapter) Tag() string {
return a.inboundTag
}

163
adapter/inbound/manager.go Normal file
View File

@@ -0,0 +1,163 @@
package inbound
import (
"context"
"os"
"sync"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/taskmonitor"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
)
var _ adapter.InboundManager = (*Manager)(nil)
type Manager struct {
logger log.ContextLogger
registry adapter.InboundRegistry
endpoint adapter.EndpointManager
access sync.Mutex
started bool
stage adapter.StartStage
inbounds []adapter.Inbound
inboundByTag map[string]adapter.Inbound
}
func NewManager(logger log.ContextLogger, registry adapter.InboundRegistry, endpoint adapter.EndpointManager) *Manager {
return &Manager{
logger: logger,
registry: registry,
endpoint: endpoint,
inboundByTag: make(map[string]adapter.Inbound),
}
}
func (m *Manager) Start(stage adapter.StartStage) error {
m.access.Lock()
if m.started && m.stage >= stage {
panic("already started")
}
m.started = true
m.stage = stage
inbounds := m.inbounds
m.access.Unlock()
for _, inbound := range inbounds {
name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]"
m.logger.Trace(stage, " ", name)
startTime := time.Now()
err := adapter.LegacyStart(inbound, stage)
if err != nil {
return E.Cause(err, stage, " ", name)
}
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
return nil
}
func (m *Manager) Close() error {
m.access.Lock()
defer m.access.Unlock()
if !m.started {
return nil
}
m.started = false
inbounds := m.inbounds
m.inbounds = nil
monitor := taskmonitor.New(m.logger, C.StopTimeout)
var err error
for _, inbound := range inbounds {
name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]"
m.logger.Trace("close ", name)
startTime := time.Now()
monitor.Start("close ", name)
err = E.Append(err, inbound.Close(), func(err error) error {
return E.Cause(err, "close ", name)
})
monitor.Finish()
m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
return nil
}
func (m *Manager) Inbounds() []adapter.Inbound {
m.access.Lock()
defer m.access.Unlock()
return m.inbounds
}
func (m *Manager) Get(tag string) (adapter.Inbound, bool) {
m.access.Lock()
inbound, found := m.inboundByTag[tag]
m.access.Unlock()
if found {
return inbound, true
}
return m.endpoint.Get(tag)
}
func (m *Manager) Remove(tag string) error {
m.access.Lock()
inbound, found := m.inboundByTag[tag]
if !found {
m.access.Unlock()
return os.ErrInvalid
}
delete(m.inboundByTag, tag)
index := common.Index(m.inbounds, func(it adapter.Inbound) bool {
return it == inbound
})
if index == -1 {
panic("invalid inbound index")
}
m.inbounds = append(m.inbounds[:index], m.inbounds[index+1:]...)
started := m.started
m.access.Unlock()
if started {
return inbound.Close()
}
return nil
}
func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) error {
inbound, err := m.registry.Create(ctx, router, logger, tag, outboundType, options)
if err != nil {
return err
}
m.access.Lock()
defer m.access.Unlock()
if m.started {
name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]"
for _, stage := range adapter.ListStartStages {
m.logger.Trace(stage, " ", name)
startTime := time.Now()
err = adapter.LegacyStart(inbound, stage)
if err != nil {
return E.Cause(err, stage, " ", name)
}
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
}
if existsInbound, loaded := m.inboundByTag[tag]; loaded {
if m.started {
err = existsInbound.Close()
if err != nil {
return E.Cause(err, "close inbound/", existsInbound.Type(), "[", existsInbound.Tag(), "]")
}
}
existsIndex := common.Index(m.inbounds, func(it adapter.Inbound) bool {
return it == existsInbound
})
if existsIndex == -1 {
panic("invalid inbound index")
}
m.inbounds = append(m.inbounds[:existsIndex], m.inbounds[existsIndex+1:]...)
}
m.inbounds = append(m.inbounds, inbound)
m.inboundByTag[tag] = inbound
return nil
}

View File

@@ -0,0 +1,72 @@
package inbound
import (
"context"
"sync"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options T) (adapter.Inbound, error)
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
registry.register(outboundType, func() any {
return new(Options)
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Inbound, error) {
var options *Options
if rawOptions != nil {
options = rawOptions.(*Options)
}
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options))
})
}
var _ adapter.InboundRegistry = (*Registry)(nil)
type (
optionsConstructorFunc func() any
constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Inbound, error)
)
type Registry struct {
access sync.Mutex
optionsType map[string]optionsConstructorFunc
constructor map[string]constructorFunc
}
func NewRegistry() *Registry {
return &Registry{
optionsType: make(map[string]optionsConstructorFunc),
constructor: make(map[string]constructorFunc),
}
}
func (m *Registry) CreateOptions(outboundType string) (any, bool) {
m.access.Lock()
defer m.access.Unlock()
optionsConstructor, loaded := m.optionsType[outboundType]
if !loaded {
return nil, false
}
return optionsConstructor(), true
}
func (m *Registry) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Inbound, error) {
m.access.Lock()
defer m.access.Unlock()
constructor, loaded := m.constructor[outboundType]
if !loaded {
return nil, E.New("outbound type not found: " + outboundType)
}
return constructor(ctx, router, logger, tag, options)
}
func (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
m.access.Lock()
defer m.access.Unlock()
m.optionsType[outboundType] = optionsConstructor
m.constructor[outboundType] = constructor
}

102
adapter/lifecycle.go Normal file
View File

@@ -0,0 +1,102 @@
package adapter
import (
"reflect"
"strings"
"time"
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
)
type SimpleLifecycle interface {
Start() error
Close() error
}
type StartStage uint8
const (
StartStateInitialize StartStage = iota
StartStateStart
StartStatePostStart
StartStateStarted
)
var ListStartStages = []StartStage{
StartStateInitialize,
StartStateStart,
StartStatePostStart,
StartStateStarted,
}
func (s StartStage) String() string {
switch s {
case StartStateInitialize:
return "initialize"
case StartStateStart:
return "start"
case StartStatePostStart:
return "post-start"
case StartStateStarted:
return "finish-start"
default:
panic("unknown stage")
}
}
type Lifecycle interface {
Start(stage StartStage) error
Close() error
}
type LifecycleService interface {
Name() string
Lifecycle
}
func getServiceName(service any) string {
if named, ok := service.(interface {
Type() string
Tag() string
}); ok {
tag := named.Tag()
if tag != "" {
return named.Type() + "[" + tag + "]"
}
return named.Type()
}
t := reflect.TypeOf(service)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
return strings.ToLower(t.Name())
}
func Start(logger log.ContextLogger, stage StartStage, services ...Lifecycle) error {
for _, service := range services {
name := getServiceName(service)
logger.Trace(stage, " ", name)
startTime := time.Now()
err := service.Start(stage)
if err != nil {
return err
}
logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
return nil
}
func StartNamed(logger log.ContextLogger, stage StartStage, services []LifecycleService) error {
for _, service := range services {
logger.Trace(stage, " ", service.Name())
startTime := time.Now()
err := service.Start(stage)
if err != nil {
return E.Cause(err, stage.String(), " ", service.Name())
}
logger.Trace(stage, " ", service.Name(), " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
return nil
}

View File

@@ -0,0 +1,52 @@
package adapter
func LegacyStart(starter any, stage StartStage) error {
if lifecycle, isLifecycle := starter.(Lifecycle); isLifecycle {
return lifecycle.Start(stage)
}
switch stage {
case StartStateInitialize:
if preStarter, isPreStarter := starter.(interface {
PreStart() error
}); isPreStarter {
return preStarter.PreStart()
}
case StartStateStart:
if starter, isStarter := starter.(interface {
Start() error
}); isStarter {
return starter.Start()
}
case StartStateStarted:
if postStarter, isPostStarter := starter.(interface {
PostStart() error
}); isPostStarter {
return postStarter.PostStart()
}
}
return nil
}
type lifecycleServiceWrapper struct {
SimpleLifecycle
name string
}
func NewLifecycleService(service SimpleLifecycle, name string) LifecycleService {
return &lifecycleServiceWrapper{
SimpleLifecycle: service,
name: name,
}
}
func (l *lifecycleServiceWrapper) Name() string {
return l.name
}
func (l *lifecycleServiceWrapper) Start(stage StartStage) error {
return LegacyStart(l.SimpleLifecycle, stage)
}
func (l *lifecycleServiceWrapper) Close() error {
return l.SimpleLifecycle.Close()
}

23
adapter/neighbor.go Normal file
View File

@@ -0,0 +1,23 @@
package adapter
import (
"net"
"net/netip"
)
type NeighborEntry struct {
Address netip.Addr
MACAddress net.HardwareAddr
Hostname string
}
type NeighborResolver interface {
LookupMAC(address netip.Addr) (net.HardwareAddr, bool)
LookupHostname(address netip.Addr) (string, bool)
Start() error
Close() error
}
type NeighborUpdateListener interface {
UpdateNeighborTable(entries []NeighborEntry)
}

60
adapter/network.go Normal file
View File

@@ -0,0 +1,60 @@
package adapter
import (
"time"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/control"
)
type NetworkManager interface {
Lifecycle
Initialize(ruleSets []RuleSet)
InterfaceFinder() control.InterfaceFinder
UpdateInterfaces() error
DefaultNetworkInterface() *NetworkInterface
NetworkInterfaces() []NetworkInterface
AutoDetectInterface() bool
AutoDetectInterfaceFunc() control.Func
ProtectFunc() control.Func
DefaultOptions() NetworkOptions
RegisterAutoRedirectOutputMark(mark uint32) error
AutoRedirectOutputMark() uint32
AutoRedirectOutputMarkFunc() control.Func
NetworkMonitor() tun.NetworkUpdateMonitor
InterfaceMonitor() tun.DefaultInterfaceMonitor
PackageManager() tun.PackageManager
NeedWIFIState() bool
WIFIState() WIFIState
UpdateWIFIState()
ResetNetwork()
}
type NetworkOptions struct {
BindInterface string
RoutingMark uint32
DomainResolver string
DomainResolveOptions DNSQueryOptions
NetworkStrategy *C.NetworkStrategy
NetworkType []C.InterfaceType
FallbackNetworkType []C.InterfaceType
FallbackDelay time.Duration
}
type InterfaceUpdateListener interface {
InterfaceUpdated()
}
type WIFIState struct {
SSID string
BSSID string
}
type NetworkInterface struct {
control.Interface
Type C.InterfaceType
DNSServers []string
Expensive bool
Constrained bool
}

View File

@@ -2,8 +2,12 @@ package adapter
import (
"context"
"net"
"net/netip"
"time"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-tun"
N "github.com/sagernet/sing/common/network"
)
@@ -13,7 +17,31 @@ type Outbound interface {
Type() string
Tag() string
Network() []string
Dependencies() []string
N.Dialer
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
}
type OutboundWithPreferredRoutes interface {
Outbound
PreferredDomain(domain string) bool
PreferredAddress(address netip.Addr) bool
}
type DirectRouteOutbound interface {
Outbound
NewDirectRouteConnection(metadata InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error)
}
type OutboundRegistry interface {
option.OutboundOptionsRegistry
CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)
}
type OutboundManager interface {
Lifecycle
Outbounds() []Outbound
Outbound(tag string) (Outbound, bool)
Default() Outbound
Remove(tag string) error
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) error
}

View File

@@ -0,0 +1,45 @@
package outbound
import (
"github.com/sagernet/sing-box/option"
)
type Adapter struct {
outboundType string
outboundTag string
network []string
dependencies []string
}
func NewAdapter(outboundType string, outboundTag string, network []string, dependencies []string) Adapter {
return Adapter{
outboundType: outboundType,
outboundTag: outboundTag,
network: network,
dependencies: dependencies,
}
}
func NewAdapterWithDialerOptions(outboundType string, outboundTag string, network []string, dialOptions option.DialerOptions) Adapter {
var dependencies []string
if dialOptions.Detour != "" {
dependencies = []string{dialOptions.Detour}
}
return NewAdapter(outboundType, outboundTag, network, dependencies)
}
func (a *Adapter) Type() string {
return a.outboundType
}
func (a *Adapter) Tag() string {
return a.outboundTag
}
func (a *Adapter) Network() []string {
return a.network
}
func (a *Adapter) Dependencies() []string {
return a.dependencies
}

317
adapter/outbound/manager.go Normal file
View File

@@ -0,0 +1,317 @@
package outbound
import (
"context"
"io"
"os"
"strings"
"sync"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/taskmonitor"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/logger"
)
var _ adapter.OutboundManager = (*Manager)(nil)
type Manager struct {
logger log.ContextLogger
registry adapter.OutboundRegistry
endpoint adapter.EndpointManager
defaultTag string
access sync.RWMutex
started bool
stage adapter.StartStage
outbounds []adapter.Outbound
outboundByTag map[string]adapter.Outbound
dependByTag map[string][]string
defaultOutbound adapter.Outbound
defaultOutboundFallback func() (adapter.Outbound, error)
}
func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry, endpoint adapter.EndpointManager, defaultTag string) *Manager {
return &Manager{
logger: logger,
registry: registry,
endpoint: endpoint,
defaultTag: defaultTag,
outboundByTag: make(map[string]adapter.Outbound),
dependByTag: make(map[string][]string),
}
}
func (m *Manager) Initialize(defaultOutboundFallback func() (adapter.Outbound, error)) {
m.defaultOutboundFallback = defaultOutboundFallback
}
func (m *Manager) Start(stage adapter.StartStage) error {
m.access.Lock()
if m.started && m.stage >= stage {
panic("already started")
}
m.started = true
m.stage = stage
if stage == adapter.StartStateStart {
if m.defaultTag != "" && m.defaultOutbound == nil {
defaultEndpoint, loaded := m.endpoint.Get(m.defaultTag)
if !loaded {
m.access.Unlock()
return E.New("default outbound not found: ", m.defaultTag)
}
m.defaultOutbound = defaultEndpoint
}
if m.defaultOutbound == nil {
directOutbound, err := m.defaultOutboundFallback()
if err != nil {
m.access.Unlock()
return E.Cause(err, "create direct outbound for fallback")
}
m.outbounds = append(m.outbounds, directOutbound)
m.outboundByTag[directOutbound.Tag()] = directOutbound
m.defaultOutbound = directOutbound
}
outbounds := m.outbounds
m.access.Unlock()
return m.startOutbounds(append(outbounds, common.Map(m.endpoint.Endpoints(), func(it adapter.Endpoint) adapter.Outbound { return it })...))
} else {
outbounds := m.outbounds
m.access.Unlock()
for _, outbound := range outbounds {
name := "outbound/" + outbound.Type() + "[" + outbound.Tag() + "]"
m.logger.Trace(stage, " ", name)
startTime := time.Now()
err := adapter.LegacyStart(outbound, stage)
if err != nil {
return E.Cause(err, stage, " ", name)
}
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
}
return nil
}
func (m *Manager) startOutbounds(outbounds []adapter.Outbound) error {
monitor := taskmonitor.New(m.logger, C.StartTimeout)
started := make(map[string]bool)
for {
canContinue := false
startOne:
for _, outboundToStart := range outbounds {
outboundTag := outboundToStart.Tag()
if started[outboundTag] {
continue
}
dependencies := outboundToStart.Dependencies()
for _, dependency := range dependencies {
if !started[dependency] {
continue startOne
}
}
started[outboundTag] = true
canContinue = true
name := "outbound/" + outboundToStart.Type() + "[" + outboundTag + "]"
if starter, isStarter := outboundToStart.(adapter.Lifecycle); isStarter {
m.logger.Trace("start ", name)
startTime := time.Now()
monitor.Start("start ", name)
err := starter.Start(adapter.StartStateStart)
monitor.Finish()
if err != nil {
return E.Cause(err, "start ", name)
}
m.logger.Trace("start ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
} else if starter, isStarter := outboundToStart.(interface {
Start() error
}); isStarter {
m.logger.Trace("start ", name)
startTime := time.Now()
monitor.Start("start ", name)
err := starter.Start()
monitor.Finish()
if err != nil {
return E.Cause(err, "start ", name)
}
m.logger.Trace("start ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
}
if len(started) == len(outbounds) {
break
}
if canContinue {
continue
}
currentOutbound := common.Find(outbounds, func(it adapter.Outbound) bool {
return !started[it.Tag()]
})
var lintOutbound func(oTree []string, oCurrent adapter.Outbound) error
lintOutbound = func(oTree []string, oCurrent adapter.Outbound) error {
problemOutboundTag := common.Find(oCurrent.Dependencies(), func(it string) bool {
return !started[it]
})
if common.Contains(oTree, problemOutboundTag) {
return E.New("circular outbound dependency: ", strings.Join(oTree, " -> "), " -> ", problemOutboundTag)
}
m.access.Lock()
problemOutbound := m.outboundByTag[problemOutboundTag]
m.access.Unlock()
if problemOutbound == nil {
return E.New("dependency[", problemOutboundTag, "] not found for outbound[", oCurrent.Tag(), "]")
}
return lintOutbound(append(oTree, problemOutboundTag), problemOutbound)
}
return lintOutbound([]string{currentOutbound.Tag()}, currentOutbound)
}
return nil
}
func (m *Manager) Close() error {
monitor := taskmonitor.New(m.logger, C.StopTimeout)
m.access.Lock()
if !m.started {
m.access.Unlock()
return nil
}
m.started = false
outbounds := m.outbounds
m.outbounds = nil
m.access.Unlock()
var err error
for _, outbound := range outbounds {
if closer, isCloser := outbound.(io.Closer); isCloser {
name := "outbound/" + outbound.Type() + "[" + outbound.Tag() + "]"
m.logger.Trace("close ", name)
startTime := time.Now()
monitor.Start("close ", name)
err = E.Append(err, closer.Close(), func(err error) error {
return E.Cause(err, "close ", name)
})
monitor.Finish()
m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
}
return nil
}
func (m *Manager) Outbounds() []adapter.Outbound {
m.access.RLock()
defer m.access.RUnlock()
return m.outbounds
}
func (m *Manager) Outbound(tag string) (adapter.Outbound, bool) {
m.access.RLock()
outbound, found := m.outboundByTag[tag]
m.access.RUnlock()
if found {
return outbound, true
}
return m.endpoint.Get(tag)
}
func (m *Manager) Default() adapter.Outbound {
m.access.RLock()
defer m.access.RUnlock()
return m.defaultOutbound
}
func (m *Manager) Remove(tag string) error {
m.access.Lock()
defer m.access.Unlock()
outbound, found := m.outboundByTag[tag]
if !found {
return os.ErrInvalid
}
delete(m.outboundByTag, tag)
index := common.Index(m.outbounds, func(it adapter.Outbound) bool {
return it == outbound
})
if index == -1 {
panic("invalid inbound index")
}
m.outbounds = append(m.outbounds[:index], m.outbounds[index+1:]...)
started := m.started
if m.defaultOutbound == outbound {
if len(m.outbounds) > 0 {
m.defaultOutbound = m.outbounds[0]
m.logger.Info("updated default outbound to ", m.defaultOutbound.Tag())
} else {
m.defaultOutbound = nil
}
}
dependBy := m.dependByTag[tag]
if len(dependBy) > 0 {
return E.New("outbound[", tag, "] is depended by ", strings.Join(dependBy, ", "))
}
dependencies := outbound.Dependencies()
for _, dependency := range dependencies {
if len(m.dependByTag[dependency]) == 1 {
delete(m.dependByTag, dependency)
} else {
m.dependByTag[dependency] = common.Filter(m.dependByTag[dependency], func(it string) bool {
return it != tag
})
}
}
if started {
return common.Close(outbound)
}
return nil
}
func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, inboundType string, options any) error {
if tag == "" {
return os.ErrInvalid
}
outbound, err := m.registry.CreateOutbound(ctx, router, logger, tag, inboundType, options)
if err != nil {
return err
}
if m.started {
name := "outbound/" + outbound.Type() + "[" + outbound.Tag() + "]"
for _, stage := range adapter.ListStartStages {
m.logger.Trace(stage, " ", name)
startTime := time.Now()
err = adapter.LegacyStart(outbound, stage)
if err != nil {
return E.Cause(err, stage, " ", name)
}
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
}
m.access.Lock()
defer m.access.Unlock()
if existsOutbound, loaded := m.outboundByTag[tag]; loaded {
if m.started {
err = common.Close(existsOutbound)
if err != nil {
return E.Cause(err, "close outbound/", existsOutbound.Type(), "[", existsOutbound.Tag(), "]")
}
}
existsIndex := common.Index(m.outbounds, func(it adapter.Outbound) bool {
return it == existsOutbound
})
if existsIndex == -1 {
panic("invalid inbound index")
}
m.outbounds = append(m.outbounds[:existsIndex], m.outbounds[existsIndex+1:]...)
}
m.outbounds = append(m.outbounds, outbound)
m.outboundByTag[tag] = outbound
dependencies := outbound.Dependencies()
for _, dependency := range dependencies {
m.dependByTag[dependency] = append(m.dependByTag[dependency], tag)
}
if tag == m.defaultTag || (m.defaultTag == "" && m.defaultOutbound == nil) {
m.defaultOutbound = outbound
if m.started {
m.logger.Info("updated default outbound to ", outbound.Tag())
}
}
return nil
}

View File

@@ -0,0 +1,72 @@
package outbound
import (
"context"
"sync"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options T) (adapter.Outbound, error)
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
registry.register(outboundType, func() any {
return new(Options)
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Outbound, error) {
var options *Options
if rawOptions != nil {
options = rawOptions.(*Options)
}
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options))
})
}
var _ adapter.OutboundRegistry = (*Registry)(nil)
type (
optionsConstructorFunc func() any
constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Outbound, error)
)
type Registry struct {
access sync.Mutex
optionsType map[string]optionsConstructorFunc
constructors map[string]constructorFunc
}
func NewRegistry() *Registry {
return &Registry{
optionsType: make(map[string]optionsConstructorFunc),
constructors: make(map[string]constructorFunc),
}
}
func (r *Registry) CreateOptions(outboundType string) (any, bool) {
r.access.Lock()
defer r.access.Unlock()
optionsConstructor, loaded := r.optionsType[outboundType]
if !loaded {
return nil, false
}
return optionsConstructor(), true
}
func (r *Registry) CreateOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Outbound, error) {
r.access.Lock()
defer r.access.Unlock()
constructor, loaded := r.constructors[outboundType]
if !loaded {
return nil, E.New("outbound type not found: " + outboundType)
}
return constructor(ctx, router, logger, tag, options)
}
func (r *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
r.access.Lock()
defer r.access.Unlock()
r.optionsType[outboundType] = optionsConstructor
r.constructors[outboundType] = constructor
}

74
adapter/platform.go Normal file
View File

@@ -0,0 +1,74 @@
package adapter
import (
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/logger"
)
type PlatformInterface interface {
Initialize(networkManager NetworkManager) error
UsePlatformAutoDetectInterfaceControl() bool
AutoDetectInterfaceControl(fd int) error
UsePlatformInterface() bool
OpenInterface(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error)
UsePlatformDefaultInterfaceMonitor() bool
CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor
UsePlatformNetworkInterfaces() bool
NetworkInterfaces() ([]NetworkInterface, error)
UnderNetworkExtension() bool
NetworkExtensionIncludeAllNetworks() bool
ClearDNSCache()
RequestPermissionForWIFIState() error
ReadWIFIState() WIFIState
SystemCertificates() []string
UsePlatformConnectionOwnerFinder() bool
FindConnectionOwner(request *FindConnectionOwnerRequest) (*ConnectionOwner, error)
UsePlatformWIFIMonitor() bool
UsePlatformNotification() bool
SendNotification(notification *Notification) error
UsePlatformNeighborResolver() bool
StartNeighborMonitor(listener NeighborUpdateListener) error
CloseNeighborMonitor(listener NeighborUpdateListener) error
}
type FindConnectionOwnerRequest struct {
IpProtocol int32
SourceAddress string
SourcePort int32
DestinationAddress string
DestinationPort int32
}
type ConnectionOwner struct {
ProcessID uint32
UserId int32
UserName string
ProcessPath string
AndroidPackageName string
}
type Notification struct {
Identifier string
TypeName string
TypeID int32
Title string
Subtitle string
Body string
OpenURL string
}
type SystemProxyStatus struct {
Available bool
Enabled bool
}

1
adapter/prestart.go Normal file
View File

@@ -0,0 +1 @@
package adapter

View File

@@ -2,82 +2,115 @@ package adapter
import (
"context"
"crypto/tls"
"net"
"net/netip"
"net/http"
"sync"
"time"
"github.com/sagernet/sing-box/common/geoip"
"github.com/sagernet/sing-dns"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/control"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/ntp"
"github.com/sagernet/sing/common/x/list"
mdns "github.com/miekg/dns"
"go4.org/netipx"
)
type Router interface {
Service
Lifecycle
ConnectionRouter
PreMatch(metadata InboundContext, context tun.DirectRouteContext, timeout time.Duration, supportBypass bool) (tun.DirectRouteDestination, error)
ConnectionRouterEx
RuleSet(tag string) (RuleSet, bool)
Rules() []Rule
NeedFindProcess() bool
NeedFindNeighbor() bool
NeighborResolver() NeighborResolver
AppendTracker(tracker ConnectionTracker)
ResetNetwork()
}
Outbounds() []Outbound
Outbound(tag string) (Outbound, bool)
DefaultOutbound(network string) Outbound
type ConnectionTracker interface {
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule, matchOutbound Outbound) net.Conn
RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule, matchOutbound Outbound) N.PacketConn
}
// Deprecated: Use ConnectionRouterEx instead.
type ConnectionRouter interface {
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
GeoIPReader() *geoip.Reader
LoadGeosite(code string) (Rule, error)
Exchange(ctx context.Context, message *mdns.Msg) (*mdns.Msg, error)
Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error)
LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error)
InterfaceFinder() control.InterfaceFinder
DefaultInterface() string
AutoDetectInterface() bool
AutoDetectInterfaceFunc() control.Func
DefaultMark() int
NetworkMonitor() tun.NetworkUpdateMonitor
InterfaceMonitor() tun.DefaultInterfaceMonitor
PackageManager() tun.PackageManager
Rules() []Rule
TimeService
ClashServer() ClashServer
SetClashServer(server ClashServer)
V2RayServer() V2RayServer
SetV2RayServer(server V2RayServer)
}
type routerContextKey struct{}
func ContextWithRouter(ctx context.Context, router Router) context.Context {
return context.WithValue(ctx, (*routerContextKey)(nil), router)
type ConnectionRouterEx interface {
ConnectionRouter
RouteConnectionEx(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
}
func RouterFromContext(ctx context.Context) Router {
metadata := ctx.Value((*routerContextKey)(nil))
if metadata == nil {
return nil
type RuleSet interface {
Name() string
StartContext(ctx context.Context, startContext *HTTPStartContext) error
PostStart() error
Metadata() RuleSetMetadata
ExtractIPSet() []*netipx.IPSet
IncRef()
DecRef()
Cleanup()
RegisterCallback(callback RuleSetUpdateCallback) *list.Element[RuleSetUpdateCallback]
UnregisterCallback(element *list.Element[RuleSetUpdateCallback])
Close() error
HeadlessRule
}
type RuleSetUpdateCallback func(it RuleSet)
type RuleSetMetadata struct {
ContainsProcessRule bool
ContainsWIFIRule bool
ContainsIPCIDRRule bool
}
type HTTPStartContext struct {
ctx context.Context
access sync.Mutex
httpClientCache map[string]*http.Client
}
func NewHTTPStartContext(ctx context.Context) *HTTPStartContext {
return &HTTPStartContext{
ctx: ctx,
httpClientCache: make(map[string]*http.Client),
}
return metadata.(Router)
}
type Rule interface {
Service
Type() string
UpdateGeosite() error
Match(metadata *InboundContext) bool
Outbound() string
String() string
func (c *HTTPStartContext) HTTPClient(detour string, dialer N.Dialer) *http.Client {
c.access.Lock()
defer c.access.Unlock()
if httpClient, loaded := c.httpClientCache[detour]; loaded {
return httpClient
}
httpClient := &http.Client{
Transport: &http.Transport{
ForceAttemptHTTP2: true,
TLSHandshakeTimeout: C.TCPTimeout,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
},
TLSClientConfig: &tls.Config{
Time: ntp.TimeFuncFromContext(c.ctx),
RootCAs: RootPoolFromContext(c.ctx),
},
},
}
c.httpClientCache[detour] = httpClient
return httpClient
}
type DNSRule interface {
Rule
DisableCache() bool
}
type InterfaceUpdateListener interface {
InterfaceUpdated() error
func (c *HTTPStartContext) Close() {
c.access.Lock()
defer c.access.Unlock()
for _, client := range c.httpClientCache {
client.CloseIdleConnections()
}
}

37
adapter/rule.go Normal file
View File

@@ -0,0 +1,37 @@
package adapter
import (
C "github.com/sagernet/sing-box/constant"
)
type HeadlessRule interface {
Match(metadata *InboundContext) bool
String() string
}
type Rule interface {
HeadlessRule
SimpleLifecycle
Type() string
Action() RuleAction
}
type DNSRule interface {
Rule
WithAddressLimit() bool
MatchAddressLimit(metadata *InboundContext) bool
}
type RuleAction interface {
Type() string
String() string
}
func IsFinalAction(action RuleAction) bool {
switch action.Type() {
case C.RuleActionTypeSniff, C.RuleActionTypeResolve:
return false
default:
return true
}
}

View File

@@ -1,6 +1,27 @@
package adapter
import (
"context"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
)
type Service interface {
Start() error
Close() error
Lifecycle
Type() string
Tag() string
}
type ServiceRegistry interface {
option.ServiceOptionsRegistry
Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) (Service, error)
}
type ServiceManager interface {
Lifecycle
Services() []Service
Get(tag string) (Service, bool)
Remove(tag string) error
Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) error
}

View File

@@ -0,0 +1,21 @@
package service
type Adapter struct {
serviceType string
serviceTag string
}
func NewAdapter(serviceType string, serviceTag string) Adapter {
return Adapter{
serviceType: serviceType,
serviceTag: serviceTag,
}
}
func (a *Adapter) Type() string {
return a.serviceType
}
func (a *Adapter) Tag() string {
return a.serviceTag
}

158
adapter/service/manager.go Normal file
View File

@@ -0,0 +1,158 @@
package service
import (
"context"
"os"
"sync"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/taskmonitor"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
)
var _ adapter.ServiceManager = (*Manager)(nil)
type Manager struct {
logger log.ContextLogger
registry adapter.ServiceRegistry
access sync.Mutex
started bool
stage adapter.StartStage
services []adapter.Service
serviceByTag map[string]adapter.Service
}
func NewManager(logger log.ContextLogger, registry adapter.ServiceRegistry) *Manager {
return &Manager{
logger: logger,
registry: registry,
serviceByTag: make(map[string]adapter.Service),
}
}
func (m *Manager) Start(stage adapter.StartStage) error {
m.access.Lock()
if m.started && m.stage >= stage {
panic("already started")
}
m.started = true
m.stage = stage
services := m.services
m.access.Unlock()
for _, service := range services {
name := "service/" + service.Type() + "[" + service.Tag() + "]"
m.logger.Trace(stage, " ", name)
startTime := time.Now()
err := adapter.LegacyStart(service, stage)
if err != nil {
return E.Cause(err, stage, " ", name)
}
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
return nil
}
func (m *Manager) Close() error {
m.access.Lock()
defer m.access.Unlock()
if !m.started {
return nil
}
m.started = false
services := m.services
m.services = nil
monitor := taskmonitor.New(m.logger, C.StopTimeout)
var err error
for _, service := range services {
name := "service/" + service.Type() + "[" + service.Tag() + "]"
m.logger.Trace("close ", name)
startTime := time.Now()
monitor.Start("close ", name)
err = E.Append(err, service.Close(), func(err error) error {
return E.Cause(err, "close ", name)
})
monitor.Finish()
m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
return nil
}
func (m *Manager) Services() []adapter.Service {
m.access.Lock()
defer m.access.Unlock()
return m.services
}
func (m *Manager) Get(tag string) (adapter.Service, bool) {
m.access.Lock()
service, found := m.serviceByTag[tag]
m.access.Unlock()
return service, found
}
func (m *Manager) Remove(tag string) error {
m.access.Lock()
service, found := m.serviceByTag[tag]
if !found {
m.access.Unlock()
return os.ErrInvalid
}
delete(m.serviceByTag, tag)
index := common.Index(m.services, func(it adapter.Service) bool {
return it == service
})
if index == -1 {
panic("invalid service index")
}
m.services = append(m.services[:index], m.services[index+1:]...)
started := m.started
m.access.Unlock()
if started {
return service.Close()
}
return nil
}
func (m *Manager) Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) error {
service, err := m.registry.Create(ctx, logger, tag, serviceType, options)
if err != nil {
return err
}
m.access.Lock()
defer m.access.Unlock()
if m.started {
name := "service/" + service.Type() + "[" + service.Tag() + "]"
for _, stage := range adapter.ListStartStages {
m.logger.Trace(stage, " ", name)
startTime := time.Now()
err = adapter.LegacyStart(service, stage)
if err != nil {
return E.Cause(err, stage, " ", name)
}
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
}
if existsService, loaded := m.serviceByTag[tag]; loaded {
if m.started {
err = existsService.Close()
if err != nil {
return E.Cause(err, "close service/", existsService.Type(), "[", existsService.Tag(), "]")
}
}
existsIndex := common.Index(m.services, func(it adapter.Service) bool {
return it == existsService
})
if existsIndex == -1 {
panic("invalid service index")
}
m.services = append(m.services[:existsIndex], m.services[existsIndex+1:]...)
}
m.services = append(m.services, service)
m.serviceByTag[tag] = service
return nil
}

View File

@@ -0,0 +1,72 @@
package service
import (
"context"
"sync"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
type ConstructorFunc[T any] func(ctx context.Context, logger log.ContextLogger, tag string, options T) (adapter.Service, error)
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
registry.register(outboundType, func() any {
return new(Options)
}, func(ctx context.Context, logger log.ContextLogger, tag string, rawOptions any) (adapter.Service, error) {
var options *Options
if rawOptions != nil {
options = rawOptions.(*Options)
}
return constructor(ctx, logger, tag, common.PtrValueOrDefault(options))
})
}
var _ adapter.ServiceRegistry = (*Registry)(nil)
type (
optionsConstructorFunc func() any
constructorFunc func(ctx context.Context, logger log.ContextLogger, tag string, options any) (adapter.Service, error)
)
type Registry struct {
access sync.Mutex
optionsType map[string]optionsConstructorFunc
constructor map[string]constructorFunc
}
func NewRegistry() *Registry {
return &Registry{
optionsType: make(map[string]optionsConstructorFunc),
constructor: make(map[string]constructorFunc),
}
}
func (m *Registry) CreateOptions(outboundType string) (any, bool) {
m.access.Lock()
defer m.access.Unlock()
optionsConstructor, loaded := m.optionsType[outboundType]
if !loaded {
return nil, false
}
return optionsConstructor(), true
}
func (m *Registry) Create(ctx context.Context, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Service, error) {
m.access.Lock()
defer m.access.Unlock()
constructor, loaded := m.constructor[outboundType]
if !loaded {
return nil, E.New("outbound type not found: " + outboundType)
}
return constructor(ctx, logger, tag, options)
}
func (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
m.access.Lock()
defer m.access.Unlock()
m.optionsType[outboundType] = optionsConstructor
m.constructor[outboundType] = constructor
}

18
adapter/ssm.go Normal file
View File

@@ -0,0 +1,18 @@
package adapter
import (
"net"
N "github.com/sagernet/sing/common/network"
)
type ManagedSSMServer interface {
Inbound
SetTracker(tracker SSMTracker)
UpdateUsers(users []string, uPSKs []string) error
}
type SSMTracker interface {
TrackConnection(conn net.Conn, metadata InboundContext) net.Conn
TrackPacketConnection(conn N.PacketConn, metadata InboundContext) N.PacketConn
}

View File

@@ -3,6 +3,6 @@ package adapter
import "time"
type TimeService interface {
Service
SimpleLifecycle
TimeFunc() func() time.Time
}

View File

@@ -4,112 +4,165 @@ import (
"context"
"net"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
type (
ConnectionHandlerFunc = func(ctx context.Context, conn net.Conn, metadata InboundContext) error
PacketConnectionHandlerFunc = func(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
ConnectionHandlerFuncEx = func(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
PacketConnectionHandlerFuncEx = func(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
)
func NewUpstreamHandler(
func NewUpstreamHandlerEx(
metadata InboundContext,
connectionHandler ConnectionHandlerFunc,
packetHandler PacketConnectionHandlerFunc,
errorHandler E.Handler,
) UpstreamHandlerAdapter {
return &myUpstreamHandlerWrapper{
connectionHandler ConnectionHandlerFuncEx,
packetHandler PacketConnectionHandlerFuncEx,
) UpstreamHandlerAdapterEx {
return &myUpstreamHandlerWrapperEx{
metadata: metadata,
connectionHandler: connectionHandler,
packetHandler: packetHandler,
errorHandler: errorHandler,
}
}
var _ UpstreamHandlerAdapter = (*myUpstreamHandlerWrapper)(nil)
var _ UpstreamHandlerAdapterEx = (*myUpstreamHandlerWrapperEx)(nil)
type myUpstreamHandlerWrapper struct {
type myUpstreamHandlerWrapperEx struct {
metadata InboundContext
connectionHandler ConnectionHandlerFunc
packetHandler PacketConnectionHandlerFunc
errorHandler E.Handler
connectionHandler ConnectionHandlerFuncEx
packetHandler PacketConnectionHandlerFuncEx
}
func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
func (w *myUpstreamHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
myMetadata := w.metadata
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
if source.IsValid() {
myMetadata.Source = source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
if destination.IsValid() {
myMetadata.Destination = destination
}
return w.connectionHandler(ctx, conn, myMetadata)
w.connectionHandler(ctx, conn, myMetadata, onClose)
}
func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
func (w *myUpstreamHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
myMetadata := w.metadata
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
if source.IsValid() {
myMetadata.Source = source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
if destination.IsValid() {
myMetadata.Destination = destination
}
return w.packetHandler(ctx, conn, myMetadata)
w.packetHandler(ctx, conn, myMetadata, onClose)
}
func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
w.errorHandler.NewError(ctx, err)
var _ UpstreamHandlerAdapterEx = (*myUpstreamContextHandlerWrapperEx)(nil)
type myUpstreamContextHandlerWrapperEx struct {
connectionHandler ConnectionHandlerFuncEx
packetHandler PacketConnectionHandlerFuncEx
}
func UpstreamMetadata(metadata InboundContext) M.Metadata {
return M.Metadata{
Source: metadata.Source,
Destination: metadata.Destination,
}
}
type myUpstreamContextHandlerWrapper struct {
connectionHandler ConnectionHandlerFunc
packetHandler PacketConnectionHandlerFunc
errorHandler E.Handler
}
func NewUpstreamContextHandler(
connectionHandler ConnectionHandlerFunc,
packetHandler PacketConnectionHandlerFunc,
errorHandler E.Handler,
) UpstreamHandlerAdapter {
return &myUpstreamContextHandlerWrapper{
func NewUpstreamContextHandlerEx(
connectionHandler ConnectionHandlerFuncEx,
packetHandler PacketConnectionHandlerFuncEx,
) UpstreamHandlerAdapterEx {
return &myUpstreamContextHandlerWrapperEx{
connectionHandler: connectionHandler,
packetHandler: packetHandler,
errorHandler: errorHandler,
}
}
func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
myMetadata := ContextFrom(ctx)
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
func (w *myUpstreamContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
_, myMetadata := ExtendContext(ctx)
if source.IsValid() {
myMetadata.Source = source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
if destination.IsValid() {
myMetadata.Destination = destination
}
return w.connectionHandler(ctx, conn, *myMetadata)
w.connectionHandler(ctx, conn, *myMetadata, onClose)
}
func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
myMetadata := ContextFrom(ctx)
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
func (w *myUpstreamContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
_, myMetadata := ExtendContext(ctx)
if source.IsValid() {
myMetadata.Source = source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
if destination.IsValid() {
myMetadata.Destination = destination
}
return w.packetHandler(ctx, conn, *myMetadata)
w.packetHandler(ctx, conn, *myMetadata, onClose)
}
func (w *myUpstreamContextHandlerWrapper) NewError(ctx context.Context, err error) {
w.errorHandler.NewError(ctx, err)
func NewRouteHandlerEx(
metadata InboundContext,
router ConnectionRouterEx,
) UpstreamHandlerAdapterEx {
return &routeHandlerWrapperEx{
metadata: metadata,
router: router,
}
}
var _ UpstreamHandlerAdapterEx = (*routeHandlerWrapperEx)(nil)
type routeHandlerWrapperEx struct {
metadata InboundContext
router ConnectionRouterEx
}
func (r *routeHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
if source.IsValid() {
r.metadata.Source = source
}
if destination.IsValid() {
r.metadata.Destination = destination
}
r.router.RouteConnectionEx(ctx, conn, r.metadata, onClose)
}
func (r *routeHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
if source.IsValid() {
r.metadata.Source = source
}
if destination.IsValid() {
r.metadata.Destination = destination
}
r.router.RoutePacketConnectionEx(ctx, conn, r.metadata, onClose)
}
func NewRouteContextHandlerEx(
router ConnectionRouterEx,
) UpstreamHandlerAdapterEx {
return &routeContextHandlerWrapperEx{
router: router,
}
}
var _ UpstreamHandlerAdapterEx = (*routeContextHandlerWrapperEx)(nil)
type routeContextHandlerWrapperEx struct {
router ConnectionRouterEx
}
func (r *routeContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
_, metadata := ExtendContext(ctx)
if source.IsValid() {
metadata.Source = source
}
if destination.IsValid() {
metadata.Destination = destination
}
r.router.RouteConnectionEx(ctx, conn, *metadata, onClose)
}
func (r *routeContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
_, metadata := ExtendContext(ctx)
if source.IsValid() {
metadata.Source = source
}
if destination.IsValid() {
metadata.Destination = destination
}
r.router.RoutePacketConnectionEx(ctx, conn, *metadata, onClose)
}

234
adapter/upstream_legacy.go Normal file
View File

@@ -0,0 +1,234 @@
package adapter
import (
"context"
"net"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
type (
// Deprecated
ConnectionHandlerFunc = func(ctx context.Context, conn net.Conn, metadata InboundContext) error
// Deprecated
PacketConnectionHandlerFunc = func(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
)
// Deprecated
//
//nolint:staticcheck
func NewUpstreamHandler(
metadata InboundContext,
connectionHandler ConnectionHandlerFunc,
packetHandler PacketConnectionHandlerFunc,
errorHandler E.Handler,
) UpstreamHandlerAdapter {
return &myUpstreamHandlerWrapper{
metadata: metadata,
connectionHandler: connectionHandler,
packetHandler: packetHandler,
errorHandler: errorHandler,
}
}
var _ UpstreamHandlerAdapter = (*myUpstreamHandlerWrapper)(nil)
// Deprecated: use myUpstreamHandlerWrapperEx instead.
//
//nolint:staticcheck
type myUpstreamHandlerWrapper struct {
metadata InboundContext
connectionHandler ConnectionHandlerFunc
packetHandler PacketConnectionHandlerFunc
errorHandler E.Handler
}
// Deprecated: use myUpstreamHandlerWrapperEx instead.
func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
myMetadata := w.metadata
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.connectionHandler(ctx, conn, myMetadata)
}
// Deprecated: use myUpstreamHandlerWrapperEx instead.
func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
myMetadata := w.metadata
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.packetHandler(ctx, conn, myMetadata)
}
// Deprecated: use myUpstreamHandlerWrapperEx instead.
func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
w.errorHandler.NewError(ctx, err)
}
// Deprecated: removed
func UpstreamMetadata(metadata InboundContext) M.Metadata {
return M.Metadata{
Source: metadata.Source.Unwrap(),
Destination: metadata.Destination.Unwrap(),
}
}
// Deprecated: Use NewUpstreamContextHandlerEx instead.
type myUpstreamContextHandlerWrapper struct {
connectionHandler ConnectionHandlerFunc
packetHandler PacketConnectionHandlerFunc
errorHandler E.Handler
}
// Deprecated: Use NewUpstreamContextHandlerEx instead.
func NewUpstreamContextHandler(
connectionHandler ConnectionHandlerFunc,
packetHandler PacketConnectionHandlerFunc,
errorHandler E.Handler,
) UpstreamHandlerAdapter {
return &myUpstreamContextHandlerWrapper{
connectionHandler: connectionHandler,
packetHandler: packetHandler,
errorHandler: errorHandler,
}
}
// Deprecated: Use NewUpstreamContextHandlerEx instead.
func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
myMetadata := ContextFrom(ctx)
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.connectionHandler(ctx, conn, *myMetadata)
}
// Deprecated: Use NewUpstreamContextHandlerEx instead.
func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
myMetadata := ContextFrom(ctx)
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.packetHandler(ctx, conn, *myMetadata)
}
// Deprecated: Use NewUpstreamContextHandlerEx instead.
func (w *myUpstreamContextHandlerWrapper) NewError(ctx context.Context, err error) {
w.errorHandler.NewError(ctx, err)
}
// Deprecated: Use ConnectionRouterEx instead.
func NewRouteHandler(
metadata InboundContext,
router ConnectionRouter,
logger logger.ContextLogger,
) UpstreamHandlerAdapter {
return &routeHandlerWrapper{
metadata: metadata,
router: router,
logger: logger,
}
}
// Deprecated: Use ConnectionRouterEx instead.
func NewRouteContextHandler(
router ConnectionRouter,
logger logger.ContextLogger,
) UpstreamHandlerAdapter {
return &routeContextHandlerWrapper{
router: router,
logger: logger,
}
}
var _ UpstreamHandlerAdapter = (*routeHandlerWrapper)(nil)
// Deprecated: Use ConnectionRouterEx instead.
//
//nolint:staticcheck
type routeHandlerWrapper struct {
metadata InboundContext
router ConnectionRouter
logger logger.ContextLogger
}
// Deprecated: Use ConnectionRouterEx instead.
func (w *routeHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
myMetadata := w.metadata
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.router.RouteConnection(ctx, conn, myMetadata)
}
// Deprecated: Use ConnectionRouterEx instead.
func (w *routeHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
myMetadata := w.metadata
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.router.RoutePacketConnection(ctx, conn, myMetadata)
}
// Deprecated: Use ConnectionRouterEx instead.
func (w *routeHandlerWrapper) NewError(ctx context.Context, err error) {
w.logger.ErrorContext(ctx, err)
}
var _ UpstreamHandlerAdapter = (*routeContextHandlerWrapper)(nil)
// Deprecated: Use ConnectionRouterEx instead.
type routeContextHandlerWrapper struct {
router ConnectionRouter
logger logger.ContextLogger
}
// Deprecated: Use ConnectionRouterEx instead.
func (w *routeContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
myMetadata := ContextFrom(ctx)
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.router.RouteConnection(ctx, conn, *myMetadata)
}
// Deprecated: Use ConnectionRouterEx instead.
func (w *routeContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
myMetadata := ContextFrom(ctx)
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.router.RoutePacketConnection(ctx, conn, *myMetadata)
}
// Deprecated: Use ConnectionRouterEx instead.
func (w *routeContextHandlerWrapper) NewError(ctx context.Context, err error) {
w.logger.ErrorContext(ctx, err)
}

View File

@@ -4,8 +4,6 @@ import (
"context"
"net"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
@@ -17,11 +15,10 @@ type V2RayServerTransport interface {
}
type V2RayServerTransportHandler interface {
N.TCPConnectionHandler
E.Handler
FallbackConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error
N.TCPConnectionHandlerEx
}
type V2RayClientTransport interface {
DialContext(ctx context.Context) (net.Conn, error)
Close() error
}

653
box.go
View File

@@ -9,192 +9,416 @@ import (
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/endpoint"
"github.com/sagernet/sing-box/adapter/inbound"
"github.com/sagernet/sing-box/adapter/outbound"
boxService "github.com/sagernet/sing-box/adapter/service"
"github.com/sagernet/sing-box/common/certificate"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/common/taskmonitor"
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/dns"
"github.com/sagernet/sing-box/dns/transport/local"
"github.com/sagernet/sing-box/experimental"
"github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/inbound"
"github.com/sagernet/sing-box/experimental/cachefile"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/outbound"
"github.com/sagernet/sing-box/protocol/direct"
"github.com/sagernet/sing-box/route"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/ntp"
"github.com/sagernet/sing/service"
"github.com/sagernet/sing/service/pause"
)
var _ adapter.Service = (*Box)(nil)
var _ adapter.SimpleLifecycle = (*Box)(nil)
type Box struct {
createdAt time.Time
router adapter.Router
inbounds []adapter.Inbound
outbounds []adapter.Outbound
logFactory log.Factory
logger log.ContextLogger
logFile *os.File
clashServer adapter.ClashServer
v2rayServer adapter.V2RayServer
done chan struct{}
createdAt time.Time
logFactory log.Factory
logger log.ContextLogger
network *route.NetworkManager
endpoint *endpoint.Manager
inbound *inbound.Manager
outbound *outbound.Manager
service *boxService.Manager
dnsTransport *dns.TransportManager
dnsRouter *dns.Router
connection *route.ConnectionManager
router *route.Router
internalService []adapter.LifecycleService
done chan struct{}
}
func New(ctx context.Context, options option.Options, platformInterface platform.Interface) (*Box, error) {
createdAt := time.Now()
logOptions := common.PtrValueOrDefault(options.Log)
type Options struct {
option.Options
Context context.Context
PlatformLogWriter log.PlatformWriter
}
func Context(
ctx context.Context,
inboundRegistry adapter.InboundRegistry,
outboundRegistry adapter.OutboundRegistry,
endpointRegistry adapter.EndpointRegistry,
dnsTransportRegistry adapter.DNSTransportRegistry,
serviceRegistry adapter.ServiceRegistry,
) context.Context {
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
service.FromContext[adapter.InboundRegistry](ctx) == nil {
ctx = service.ContextWith[option.InboundOptionsRegistry](ctx, inboundRegistry)
ctx = service.ContextWith[adapter.InboundRegistry](ctx, inboundRegistry)
}
if service.FromContext[option.OutboundOptionsRegistry](ctx) == nil ||
service.FromContext[adapter.OutboundRegistry](ctx) == nil {
ctx = service.ContextWith[option.OutboundOptionsRegistry](ctx, outboundRegistry)
ctx = service.ContextWith[adapter.OutboundRegistry](ctx, outboundRegistry)
}
if service.FromContext[option.EndpointOptionsRegistry](ctx) == nil ||
service.FromContext[adapter.EndpointRegistry](ctx) == nil {
ctx = service.ContextWith[option.EndpointOptionsRegistry](ctx, endpointRegistry)
ctx = service.ContextWith[adapter.EndpointRegistry](ctx, endpointRegistry)
}
if service.FromContext[adapter.DNSTransportRegistry](ctx) == nil {
ctx = service.ContextWith[option.DNSTransportOptionsRegistry](ctx, dnsTransportRegistry)
ctx = service.ContextWith[adapter.DNSTransportRegistry](ctx, dnsTransportRegistry)
}
if service.FromContext[adapter.ServiceRegistry](ctx) == nil {
ctx = service.ContextWith[option.ServiceOptionsRegistry](ctx, serviceRegistry)
ctx = service.ContextWith[adapter.ServiceRegistry](ctx, serviceRegistry)
}
return ctx
}
func New(options Options) (*Box, error) {
createdAt := time.Now()
ctx := options.Context
if ctx == nil {
ctx = context.Background()
}
ctx = service.ContextWithDefaultRegistry(ctx)
endpointRegistry := service.FromContext[adapter.EndpointRegistry](ctx)
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx)
serviceRegistry := service.FromContext[adapter.ServiceRegistry](ctx)
if endpointRegistry == nil {
return nil, E.New("missing endpoint registry in context")
}
if inboundRegistry == nil {
return nil, E.New("missing inbound registry in context")
}
if outboundRegistry == nil {
return nil, E.New("missing outbound registry in context")
}
if dnsTransportRegistry == nil {
return nil, E.New("missing DNS transport registry in context")
}
if serviceRegistry == nil {
return nil, E.New("missing service registry in context")
}
ctx = pause.WithDefaultManager(ctx)
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
err := applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
if err != nil {
return nil, err
}
var needCacheFile bool
var needClashAPI bool
var needV2RayAPI bool
if options.Experimental != nil {
if options.Experimental.ClashAPI != nil && options.Experimental.ClashAPI.ExternalController != "" {
needClashAPI = true
}
if options.Experimental.V2RayAPI != nil && options.Experimental.V2RayAPI.Listen != "" {
needV2RayAPI = true
}
if experimentalOptions.CacheFile != nil && experimentalOptions.CacheFile.Enabled || options.PlatformLogWriter != nil {
needCacheFile = true
}
var logFactory log.Factory
var observableLogFactory log.ObservableFactory
var logFile *os.File
var logWriter io.Writer
if logOptions.Disabled {
observableLogFactory = log.NewNOPFactory()
logFactory = observableLogFactory
} else {
switch logOptions.Output {
case "":
if platformInterface != nil {
logWriter = io.Discard
} else {
logWriter = os.Stdout
}
case "stderr":
logWriter = os.Stderr
case "stdout":
logWriter = os.Stdout
default:
var err error
logFile, err = os.OpenFile(C.BasePath(logOptions.Output), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return nil, err
}
logWriter = logFile
}
logFormatter := log.Formatter{
BaseTime: createdAt,
DisableColors: logOptions.DisableColor || logFile != nil,
DisableTimestamp: !logOptions.Timestamp && logFile != nil,
FullTimestamp: logOptions.Timestamp,
TimestampFormat: "-0700 2006-01-02 15:04:05",
}
if needClashAPI {
observableLogFactory = log.NewObservableFactory(logFormatter, logWriter, platformInterface)
logFactory = observableLogFactory
} else {
logFactory = log.NewFactory(logFormatter, logWriter, platformInterface)
}
if logOptions.Level != "" {
logLevel, err := log.ParseLevel(logOptions.Level)
if err != nil {
return nil, E.Cause(err, "parse log level")
}
logFactory.SetLevel(logLevel)
} else {
logFactory.SetLevel(log.LevelTrace)
}
if experimentalOptions.ClashAPI != nil || options.PlatformLogWriter != nil {
needClashAPI = true
}
router, err := route.NewRouter(
ctx,
logFactory,
common.PtrValueOrDefault(options.Route),
common.PtrValueOrDefault(options.DNS),
common.PtrValueOrDefault(options.NTP),
options.Inbounds,
platformInterface,
)
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
needV2RayAPI = true
}
platformInterface := service.FromContext[adapter.PlatformInterface](ctx)
var defaultLogWriter io.Writer
if platformInterface != nil {
defaultLogWriter = io.Discard
}
logFactory, err := log.New(log.Options{
Context: ctx,
Options: common.PtrValueOrDefault(options.Log),
Observable: needClashAPI,
DefaultWriter: defaultLogWriter,
BaseTime: createdAt,
PlatformWriter: options.PlatformLogWriter,
})
if err != nil {
return nil, E.Cause(err, "parse route options")
return nil, E.Cause(err, "create log factory")
}
var internalServices []adapter.LifecycleService
certificateOptions := common.PtrValueOrDefault(options.Certificate)
if C.IsAndroid || certificateOptions.Store != "" && certificateOptions.Store != C.CertificateStoreSystem ||
len(certificateOptions.Certificate) > 0 ||
len(certificateOptions.CertificatePath) > 0 ||
len(certificateOptions.CertificateDirectoryPath) > 0 {
certificateStore, err := certificate.NewStore(ctx, logFactory.NewLogger("certificate"), certificateOptions)
if err != nil {
return nil, err
}
service.MustRegister[adapter.CertificateStore](ctx, certificateStore)
internalServices = append(internalServices, certificateStore)
}
routeOptions := common.PtrValueOrDefault(options.Route)
dnsOptions := common.PtrValueOrDefault(options.DNS)
endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry)
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final)
serviceManager := boxService.NewManager(logFactory.NewLogger("service"), serviceRegistry)
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
service.MustRegister[adapter.ServiceManager](ctx, serviceManager)
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions, dnsOptions)
if err != nil {
return nil, E.Cause(err, "initialize network manager")
}
service.MustRegister[adapter.NetworkManager](ctx, networkManager)
connectionManager := route.NewConnectionManager(logFactory.NewLogger("connection"))
service.MustRegister[adapter.ConnectionManager](ctx, connectionManager)
router := route.NewRouter(ctx, logFactory, routeOptions, dnsOptions)
service.MustRegister[adapter.Router](ctx, router)
err = router.Initialize(routeOptions.Rules, routeOptions.RuleSet)
if err != nil {
return nil, E.Cause(err, "initialize router")
}
ntpOptions := common.PtrValueOrDefault(options.NTP)
var timeService *tls.TimeServiceWrapper
if ntpOptions.Enabled {
timeService = new(tls.TimeServiceWrapper)
service.MustRegister[ntp.TimeService](ctx, timeService)
}
for i, transportOptions := range dnsOptions.Servers {
var tag string
if transportOptions.Tag != "" {
tag = transportOptions.Tag
} else {
tag = F.ToString(i)
}
err = dnsTransportManager.Create(
ctx,
logFactory.NewLogger(F.ToString("dns/", transportOptions.Type, "[", tag, "]")),
tag,
transportOptions.Type,
transportOptions.Options,
)
if err != nil {
return nil, E.Cause(err, "initialize DNS server[", i, "]")
}
}
err = dnsRouter.Initialize(dnsOptions.Rules)
if err != nil {
return nil, E.Cause(err, "initialize dns router")
}
for i, endpointOptions := range options.Endpoints {
var tag string
if endpointOptions.Tag != "" {
tag = endpointOptions.Tag
} else {
tag = F.ToString(i)
}
endpointCtx := ctx
if tag != "" {
// TODO: remove this
endpointCtx = adapter.WithContext(endpointCtx, &adapter.InboundContext{
Outbound: tag,
})
}
err = endpointManager.Create(
endpointCtx,
router,
logFactory.NewLogger(F.ToString("endpoint/", endpointOptions.Type, "[", tag, "]")),
tag,
endpointOptions.Type,
endpointOptions.Options,
)
if err != nil {
return nil, E.Cause(err, "initialize endpoint[", i, "]")
}
}
inbounds := make([]adapter.Inbound, 0, len(options.Inbounds))
outbounds := make([]adapter.Outbound, 0, len(options.Outbounds))
for i, inboundOptions := range options.Inbounds {
var in adapter.Inbound
var tag string
if inboundOptions.Tag != "" {
tag = inboundOptions.Tag
} else {
tag = F.ToString(i)
}
in, err = inbound.New(
err = inboundManager.Create(
ctx,
router,
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
inboundOptions,
platformInterface,
tag,
inboundOptions.Type,
inboundOptions.Options,
)
if err != nil {
return nil, E.Cause(err, "parse inbound[", i, "]")
return nil, E.Cause(err, "initialize inbound[", i, "]")
}
inbounds = append(inbounds, in)
}
for i, outboundOptions := range options.Outbounds {
var out adapter.Outbound
var tag string
if outboundOptions.Tag != "" {
tag = outboundOptions.Tag
} else {
tag = F.ToString(i)
}
out, err = outbound.New(
ctx,
outboundCtx := ctx
if tag != "" {
// TODO: remove this
outboundCtx = adapter.WithContext(outboundCtx, &adapter.InboundContext{
Outbound: tag,
})
}
err = outboundManager.Create(
outboundCtx,
router,
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
outboundOptions)
tag,
outboundOptions.Type,
outboundOptions.Options,
)
if err != nil {
return nil, E.Cause(err, "parse outbound[", i, "]")
return nil, E.Cause(err, "initialize outbound[", i, "]")
}
outbounds = append(outbounds, out)
}
err = router.Initialize(inbounds, outbounds, func() adapter.Outbound {
out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), option.Outbound{Type: "direct", Tag: "default"})
common.Must(oErr)
outbounds = append(outbounds, out)
return out
for i, serviceOptions := range options.Services {
var tag string
if serviceOptions.Tag != "" {
tag = serviceOptions.Tag
} else {
tag = F.ToString(i)
}
err = serviceManager.Create(
ctx,
logFactory.NewLogger(F.ToString("service/", serviceOptions.Type, "[", tag, "]")),
tag,
serviceOptions.Type,
serviceOptions.Options,
)
if err != nil {
return nil, E.Cause(err, "initialize service[", i, "]")
}
}
outboundManager.Initialize(func() (adapter.Outbound, error) {
return direct.NewOutbound(
ctx,
router,
logFactory.NewLogger("outbound/direct"),
"direct",
option.DirectOutboundOptions{},
)
})
if err != nil {
return nil, err
}
var clashServer adapter.ClashServer
var v2rayServer adapter.V2RayServer
if needClashAPI {
clashServer, err = experimental.NewClashServer(router, observableLogFactory, common.PtrValueOrDefault(options.Experimental.ClashAPI))
dnsTransportManager.Initialize(func() (adapter.DNSTransport, error) {
return local.NewTransport(
ctx,
logFactory.NewLogger("dns/local"),
"local",
option.LocalDNSServerOptions{},
)
})
if platformInterface != nil {
err = platformInterface.Initialize(networkManager)
if err != nil {
return nil, E.Cause(err, "create clash api server")
return nil, E.Cause(err, "initialize platform interface")
}
router.SetClashServer(clashServer)
}
if needCacheFile {
cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
internalServices = append(internalServices, cacheFile)
}
if needClashAPI {
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
clashAPIOptions.ModeList = experimental.CalculateClashModeList(options.Options)
clashServer, err := experimental.NewClashServer(ctx, logFactory.(log.ObservableFactory), clashAPIOptions)
if err != nil {
return nil, E.Cause(err, "create clash-server")
}
router.AppendTracker(clashServer)
service.MustRegister[adapter.ClashServer](ctx, clashServer)
internalServices = append(internalServices, clashServer)
}
if needV2RayAPI {
v2rayServer, err = experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(options.Experimental.V2RayAPI))
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI))
if err != nil {
return nil, E.Cause(err, "create v2ray api server")
return nil, E.Cause(err, "create v2ray-server")
}
router.SetV2RayServer(v2rayServer)
if v2rayServer.StatsService() != nil {
router.AppendTracker(v2rayServer.StatsService())
internalServices = append(internalServices, v2rayServer)
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
}
}
if ntpOptions.Enabled {
ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions, ntpOptions.ServerIsDomain())
if err != nil {
return nil, E.Cause(err, "create NTP service")
}
ntpService := ntp.NewService(ntp.Options{
Context: ctx,
Dialer: ntpDialer,
Logger: logFactory.NewLogger("ntp"),
Server: ntpOptions.ServerOptions.Build(),
Interval: time.Duration(ntpOptions.Interval),
WriteToSystem: ntpOptions.WriteToSystem,
})
timeService.TimeService = ntpService
internalServices = append(internalServices, adapter.NewLifecycleService(ntpService, "ntp service"))
}
return &Box{
router: router,
inbounds: inbounds,
outbounds: outbounds,
createdAt: createdAt,
logFactory: logFactory,
logger: logFactory.Logger(),
logFile: logFile,
clashServer: clashServer,
v2rayServer: v2rayServer,
done: make(chan struct{}),
network: networkManager,
endpoint: endpointManager,
inbound: inboundManager,
outbound: outboundManager,
dnsTransport: dnsTransportManager,
service: serviceManager,
dnsRouter: dnsRouter,
connection: connectionManager,
router: router,
createdAt: createdAt,
logFactory: logFactory,
logger: logFactory.Logger(),
internalService: internalServices,
done: make(chan struct{}),
}, nil
}
func (s *Box) PreStart() error {
err := s.preStart()
if err != nil {
// TODO: remove catch error
defer func() {
v := recover()
if v != nil {
println(err.Error())
debug.PrintStack()
panic("panic on early close: " + fmt.Sprint(v))
}
}()
s.Close()
return err
}
s.logger.Info("sing-box pre-started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
return nil
}
func (s *Box) Start() error {
err := s.start()
if err != nil {
@@ -202,61 +426,70 @@ func (s *Box) Start() error {
defer func() {
v := recover()
if v != nil {
log.Error(E.Cause(err, "origin error"))
println(err.Error())
debug.PrintStack()
panic("panic on early close: " + fmt.Sprint(v))
println("panic on early start: " + fmt.Sprint(v))
}
}()
s.Close()
return err
}
return err
s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
return nil
}
func (s *Box) start() error {
if s.clashServer != nil {
err := s.clashServer.Start()
if err != nil {
return E.Cause(err, "start clash api server")
}
func (s *Box) preStart() error {
monitor := taskmonitor.New(s.logger, C.StartTimeout)
monitor.Start("start logger")
err := s.logFactory.Start()
monitor.Finish()
if err != nil {
return E.Cause(err, "start logger")
}
if s.v2rayServer != nil {
err := s.v2rayServer.Start()
if err != nil {
return E.Cause(err, "start v2ray api server")
}
}
for i, out := range s.outbounds {
if starter, isStarter := out.(common.Starter); isStarter {
err := starter.Start()
if err != nil {
var tag string
if out.Tag() == "" {
tag = F.ToString(i)
} else {
tag = out.Tag()
}
return E.Cause(err, "initialize outbound/", out.Type(), "[", tag, "]")
}
}
}
err := s.router.Start()
err = adapter.StartNamed(s.logger, adapter.StartStateInitialize, s.internalService) // cache-file clash-api v2ray-api
if err != nil {
return err
}
for i, in := range s.inbounds {
err = in.Start()
if err != nil {
var tag string
if in.Tag() == "" {
tag = F.ToString(i)
} else {
tag = in.Tag()
}
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
}
err = adapter.Start(s.logger, adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
if err != nil {
return err
}
err = adapter.Start(s.logger, adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router)
if err != nil {
return err
}
return nil
}
s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
func (s *Box) start() error {
err := s.preStart()
if err != nil {
return err
}
err = adapter.StartNamed(s.logger, adapter.StartStateStart, s.internalService)
if err != nil {
return err
}
err = adapter.Start(s.logger, adapter.StartStateStart, s.inbound, s.endpoint, s.service)
if err != nil {
return err
}
err = adapter.Start(s.logger, adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint, s.service)
if err != nil {
return err
}
err = adapter.StartNamed(s.logger, adapter.StartStatePostStart, s.internalService)
if err != nil {
return err
}
err = adapter.Start(s.logger, adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
if err != nil {
return err
}
err = adapter.StartNamed(s.logger, adapter.StartStateStarted, s.internalService)
if err != nil {
return err
}
return nil
}
@@ -267,45 +500,61 @@ func (s *Box) Close() error {
default:
close(s.done)
}
var errors error
for i, in := range s.inbounds {
errors = E.Append(errors, in.Close(), func(err error) error {
return E.Cause(err, "close inbound/", in.Type(), "[", i, "]")
var err error
for _, closeItem := range []struct {
name string
service adapter.Lifecycle
}{
{"service", s.service},
{"endpoint", s.endpoint},
{"inbound", s.inbound},
{"outbound", s.outbound},
{"router", s.router},
{"connection", s.connection},
{"dns-router", s.dnsRouter},
{"dns-transport", s.dnsTransport},
{"network", s.network},
} {
s.logger.Trace("close ", closeItem.name)
startTime := time.Now()
err = E.Append(err, closeItem.service.Close(), func(err error) error {
return E.Cause(err, "close ", closeItem.name)
})
s.logger.Trace("close ", closeItem.name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
for i, out := range s.outbounds {
errors = E.Append(errors, common.Close(out), func(err error) error {
return E.Cause(err, "close inbound/", out.Type(), "[", i, "]")
for _, lifecycleService := range s.internalService {
s.logger.Trace("close ", lifecycleService.Name())
startTime := time.Now()
err = E.Append(err, lifecycleService.Close(), func(err error) error {
return E.Cause(err, "close ", lifecycleService.Name())
})
s.logger.Trace("close ", lifecycleService.Name(), " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
if err := common.Close(s.router); err != nil {
errors = E.Append(errors, err, func(err error) error {
return E.Cause(err, "close router")
})
}
if err := common.Close(s.logFactory); err != nil {
errors = E.Append(errors, err, func(err error) error {
return E.Cause(err, "close log factory")
})
}
if err := common.Close(s.clashServer); err != nil {
errors = E.Append(errors, err, func(err error) error {
return E.Cause(err, "close clash api server")
})
}
if err := common.Close(s.v2rayServer); err != nil {
errors = E.Append(errors, err, func(err error) error {
return E.Cause(err, "close v2ray api server")
})
}
if s.logFile != nil {
errors = E.Append(errors, s.logFile.Close(), func(err error) error {
return E.Cause(err, "close log file")
})
}
return errors
s.logger.Trace("close logger")
startTime := time.Now()
err = E.Append(err, s.logFactory.Close(), func(err error) error {
return E.Cause(err, "close logger")
})
s.logger.Trace("close logger completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
return err
}
func (s *Box) Network() adapter.NetworkManager {
return s.network
}
func (s *Box) Router() adapter.Router {
return s.router
}
func (s *Box) Inbound() adapter.InboundManager {
return s.inbound
}
func (s *Box) Outbound() adapter.OutboundManager {
return s.outbound
}
func (s *Box) LogFactory() log.Factory {
return s.logFactory
}

1
clients/android Submodule

Submodule clients/android added at 0d31ac467f

1
clients/apple Submodule

Submodule clients/apple added at 22dcf646ce

View File

@@ -0,0 +1,452 @@
package main
import (
"context"
"net/http"
"os"
"strconv"
"strings"
"time"
"github.com/sagernet/asc-go/asc"
"github.com/sagernet/sing-box/cmd/internal/build_shared"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
)
func main() {
ctx := context.Background()
switch os.Args[1] {
case "next_macos_project_version":
err := fetchMacOSVersion(ctx)
if err != nil {
log.Fatal(err)
}
case "publish_testflight":
err := publishTestflight(ctx)
if err != nil {
log.Fatal(err)
}
case "cancel_app_store":
err := cancelAppStore(ctx, os.Args[2])
if err != nil {
log.Fatal(err)
}
case "prepare_app_store":
err := prepareAppStore(ctx)
if err != nil {
log.Fatal(err)
}
case "publish_app_store":
err := publishAppStore(ctx)
if err != nil {
log.Fatal(err)
}
default:
log.Fatal("unknown action: ", os.Args[1])
}
}
const (
appID = "6673731168"
groupID = "5c5f3b78-b7a0-40c0-bcad-e6ef87bbefda"
)
func createClient(expireDuration time.Duration) *asc.Client {
privateKey, err := os.ReadFile(os.Getenv("ASC_KEY_PATH"))
if err != nil {
log.Fatal(err)
}
tokenConfig, err := asc.NewTokenConfig(os.Getenv("ASC_KEY_ID"), os.Getenv("ASC_KEY_ISSUER_ID"), expireDuration, privateKey)
if err != nil {
log.Fatal(err)
}
return asc.NewClient(tokenConfig.Client())
}
func fetchMacOSVersion(ctx context.Context) error {
client := createClient(time.Minute)
versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
FilterPlatform: []string{"MAC_OS"},
})
if err != nil {
return err
}
var versionID string
findVersion:
for _, version := range versions.Data {
switch *version.Attributes.AppStoreState {
case asc.AppStoreVersionStateReadyForSale,
asc.AppStoreVersionStatePendingDeveloperRelease:
versionID = version.ID
break findVersion
}
}
if versionID == "" {
return E.New("no version found")
}
latestBuild, _, err := client.Builds.GetBuildForAppStoreVersion(ctx, versionID, &asc.GetBuildForAppStoreVersionQuery{})
if err != nil {
return err
}
versionInt, err := strconv.Atoi(*latestBuild.Data.Attributes.Version)
if err != nil {
return E.Cause(err, "parse version code")
}
os.Stdout.WriteString(F.ToString(versionInt+1, "\n"))
return nil
}
func publishTestflight(ctx context.Context) error {
if len(os.Args) < 3 {
return E.New("platform required: ios, macos, or tvos")
}
var platform asc.Platform
switch os.Args[2] {
case "ios":
platform = asc.PlatformIOS
case "macos":
platform = asc.PlatformMACOS
case "tvos":
platform = asc.PlatformTVOS
default:
return E.New("unknown platform: ", os.Args[2])
}
tagVersion, err := build_shared.ReadTagVersion()
if err != nil {
return err
}
tag := tagVersion.VersionString()
releaseNotes := F.ToString("sing-box ", tagVersion.String())
if len(os.Args) >= 4 {
releaseNotes = strings.Join(os.Args[3:], " ")
}
client := createClient(20 * time.Minute)
log.Info(tag, " list build IDs")
buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil)
if err != nil {
return err
}
buildIDs := common.Map(buildIDsResponse.Data, func(it asc.RelationshipData) string {
return it.ID
})
waitingForProcess := false
log.Info(string(platform), " list builds")
for {
builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{
FilterApp: []string{appID},
FilterPreReleaseVersionPlatform: []string{string(platform)},
})
if err != nil {
return err
}
build := builds.Data[0]
log.Info(string(platform), " ", tag, " found build: ", build.ID, " (", *build.Attributes.Version, ")")
if !waitingForProcess && (common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 30*time.Minute) {
log.Info(string(platform), " ", tag, " waiting for process")
time.Sleep(15 * time.Second)
continue
}
if *build.Attributes.ProcessingState != "VALID" {
waitingForProcess = true
log.Info(string(platform), " ", tag, " waiting for process: ", *build.Attributes.ProcessingState)
time.Sleep(15 * time.Second)
continue
}
log.Info(string(platform), " ", tag, " list localizations")
localizations, _, err := client.TestFlight.ListBetaBuildLocalizationsForBuild(ctx, build.ID, nil)
if err != nil {
return err
}
localization := common.Find(localizations.Data, func(it asc.BetaBuildLocalization) bool {
return *it.Attributes.Locale == "en-US"
})
if localization.ID == "" {
log.Fatal(string(platform), " ", tag, " no en-US localization found")
}
if localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == "" {
log.Info(string(platform), " ", tag, " update localization")
_, _, err = client.TestFlight.UpdateBetaBuildLocalization(ctx, localization.ID, common.Ptr(releaseNotes))
if err != nil {
return err
}
}
log.Info(string(platform), " ", tag, " publish")
response, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{build.ID})
if response != nil && (response.StatusCode == http.StatusUnprocessableEntity || response.StatusCode == http.StatusNotFound) {
log.Info("waiting for process")
time.Sleep(15 * time.Second)
continue
} else if err != nil {
return err
}
log.Info(string(platform), " ", tag, " list submissions")
betaSubmissions, _, err := client.TestFlight.ListBetaAppReviewSubmissions(ctx, &asc.ListBetaAppReviewSubmissionsQuery{
FilterBuild: []string{build.ID},
})
if err != nil {
return err
}
if len(betaSubmissions.Data) == 0 {
log.Info(string(platform), " ", tag, " create submission")
_, _, err = client.TestFlight.CreateBetaAppReviewSubmission(ctx, build.ID)
if err != nil {
if strings.Contains(err.Error(), "ANOTHER_BUILD_IN_REVIEW") {
log.Error(err)
break
}
return err
}
}
break
}
return nil
}
func cancelAppStore(ctx context.Context, platform string) error {
switch platform {
case "ios":
platform = string(asc.PlatformIOS)
case "macos":
platform = string(asc.PlatformMACOS)
case "tvos":
platform = string(asc.PlatformTVOS)
}
tag, err := build_shared.ReadTag()
if err != nil {
return err
}
client := createClient(time.Minute)
for {
log.Info(platform, " list versions")
versions, response, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
FilterPlatform: []string{string(platform)},
})
if isRetryable(response) {
continue
} else if err != nil {
return err
}
version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool {
return *it.Attributes.VersionString == tag
})
if version.ID == "" {
return nil
}
log.Info(platform, " ", tag, " get submission")
submission, response, err := client.Submission.GetAppStoreVersionSubmissionForAppStoreVersion(ctx, version.ID, nil)
if response != nil && response.StatusCode == http.StatusNotFound {
return nil
}
if isRetryable(response) {
continue
} else if err != nil {
return err
}
log.Info(platform, " ", tag, " delete submission")
_, err = client.Submission.DeleteSubmission(ctx, submission.Data.ID)
if err != nil {
return err
}
return nil
}
}
func prepareAppStore(ctx context.Context) error {
tag, err := build_shared.ReadTag()
if err != nil {
return err
}
client := createClient(time.Minute)
for _, platform := range []asc.Platform{
asc.PlatformIOS,
asc.PlatformMACOS,
asc.PlatformTVOS,
} {
log.Info(string(platform), " list versions")
versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
FilterPlatform: []string{string(platform)},
})
if err != nil {
return err
}
version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool {
return *it.Attributes.VersionString == tag
})
log.Info(string(platform), " ", tag, " list builds")
builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{
FilterApp: []string{appID},
FilterPreReleaseVersionPlatform: []string{string(platform)},
})
if err != nil {
return err
}
if len(builds.Data) == 0 {
log.Fatal(platform, " ", tag, " no build found")
}
buildID := common.Ptr(builds.Data[0].ID)
if version.ID == "" {
log.Info(string(platform), " ", tag, " create version")
newVersion, _, err := client.Apps.CreateAppStoreVersion(ctx, asc.AppStoreVersionCreateRequestAttributes{
Platform: platform,
VersionString: tag,
}, appID, buildID)
if err != nil {
return err
}
version = newVersion.Data
} else {
log.Info(string(platform), " ", tag, " check build")
currentBuild, response, err := client.Apps.GetBuildIDForAppStoreVersion(ctx, version.ID)
if err != nil {
return err
}
if response.StatusCode != http.StatusOK || currentBuild.Data.ID != *buildID {
switch *version.Attributes.AppStoreState {
case asc.AppStoreVersionStatePrepareForSubmission,
asc.AppStoreVersionStateRejected,
asc.AppStoreVersionStateDeveloperRejected:
case asc.AppStoreVersionStateWaitingForReview,
asc.AppStoreVersionStateInReview,
asc.AppStoreVersionStatePendingDeveloperRelease:
submission, _, err := client.Submission.GetAppStoreVersionSubmissionForAppStoreVersion(ctx, version.ID, nil)
if err != nil {
return err
}
if submission != nil {
log.Info(string(platform), " ", tag, " delete submission")
_, err = client.Submission.DeleteSubmission(ctx, submission.Data.ID)
if err != nil {
return err
}
time.Sleep(5 * time.Second)
}
default:
log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState))
}
log.Info(string(platform), " ", tag, " update build")
response, err = client.Apps.UpdateBuildForAppStoreVersion(ctx, version.ID, buildID)
if err != nil {
return err
}
if response.StatusCode != http.StatusNoContent {
response.Write(os.Stderr)
log.Fatal(string(platform), " ", tag, " unexpected response: ", response.Status)
}
} else {
switch *version.Attributes.AppStoreState {
case asc.AppStoreVersionStatePrepareForSubmission,
asc.AppStoreVersionStateRejected,
asc.AppStoreVersionStateDeveloperRejected:
case asc.AppStoreVersionStateWaitingForReview,
asc.AppStoreVersionStateInReview,
asc.AppStoreVersionStatePendingDeveloperRelease:
continue
default:
log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState))
}
}
}
log.Info(string(platform), " ", tag, " list localization")
localizations, _, err := client.Apps.ListLocalizationsForAppStoreVersion(ctx, version.ID, nil)
if err != nil {
return err
}
localization := common.Find(localizations.Data, func(it asc.AppStoreVersionLocalization) bool {
return *it.Attributes.Locale == "en-US"
})
if localization.ID == "" {
log.Info(string(platform), " ", tag, " no en-US localization found")
}
if localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == "" {
log.Info(string(platform), " ", tag, " update localization")
_, _, err = client.Apps.UpdateAppStoreVersionLocalization(ctx, localization.ID, &asc.AppStoreVersionLocalizationUpdateRequestAttributes{
PromotionalText: common.Ptr("Yet another distribution for sing-box, the universal proxy platform."),
WhatsNew: common.Ptr(F.ToString("sing-box ", tag, ": Fixes and improvements.")),
})
if err != nil {
return err
}
}
log.Info(string(platform), " ", tag, " create submission")
fixSubmit:
for {
_, response, err := client.Submission.CreateSubmission(ctx, version.ID)
if err != nil {
switch response.StatusCode {
case http.StatusInternalServerError:
continue
default:
return err
}
}
switch response.StatusCode {
case http.StatusCreated:
break fixSubmit
default:
return err
}
}
}
return nil
}
func publishAppStore(ctx context.Context) error {
tag, err := build_shared.ReadTag()
if err != nil {
return err
}
client := createClient(time.Minute)
for _, platform := range []asc.Platform{
asc.PlatformIOS,
asc.PlatformMACOS,
asc.PlatformTVOS,
} {
log.Info(string(platform), " list versions")
versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{
FilterPlatform: []string{string(platform)},
})
if err != nil {
return err
}
version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool {
return *it.Attributes.VersionString == tag
})
switch *version.Attributes.AppStoreState {
case asc.AppStoreVersionStatePrepareForSubmission, asc.AppStoreVersionStateDeveloperRejected:
log.Fatal(string(platform), " ", tag, " not submitted")
case asc.AppStoreVersionStateWaitingForReview,
asc.AppStoreVersionStateInReview:
log.Warn(string(platform), " ", tag, " waiting for review")
continue
case asc.AppStoreVersionStatePendingDeveloperRelease:
default:
log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState))
}
_, _, err = client.Publishing.CreatePhasedRelease(ctx, common.Ptr(asc.PhasedReleaseStateComplete), version.ID)
if err != nil {
return err
}
}
return nil
}
func isRetryable(response *asc.Response) bool {
if response == nil {
return false
}
switch response.StatusCode {
case http.StatusInternalServerError, http.StatusUnprocessableEntity:
return true
default:
return false
}
}

View File

@@ -1,6 +1,7 @@
package main
import (
"go/build"
"os"
"os/exec"
@@ -11,6 +12,10 @@ import (
func main() {
build_shared.FindSDK()
if os.Getenv("GOPATH") == "" {
os.Setenv("GOPATH", build.Default.GOPATH)
}
command := exec.Command(os.Args[1], os.Args[2:]...)
command.Stdout = os.Stdout
command.Stderr = os.Stderr

View File

@@ -5,21 +5,29 @@ import (
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
_ "github.com/sagernet/gomobile/event/key"
_ "github.com/sagernet/gomobile"
"github.com/sagernet/sing-box/cmd/internal/build_shared"
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/rw"
"github.com/sagernet/sing/common/shell"
)
var (
debugEnabled bool
target string
platform string
// withTailscale bool
)
func init() {
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
flag.StringVar(&target, "target", "android", "target platform")
flag.StringVar(&platform, "platform", "", "specify platform")
// flag.BoolVar(&withTailscale, "with-tailscale", false, "build tailscale for iOS and tvOS")
}
func main() {
@@ -30,68 +38,192 @@ func main() {
switch target {
case "android":
buildAndroid()
case "ios":
buildiOS()
case "apple":
buildApple()
}
}
var (
sharedFlags []string
debugFlags []string
sharedTags []string
darwinTags []string
// memcTags []string
notMemcTags []string
debugTags []string
)
func init() {
sharedFlags = append(sharedFlags, "-trimpath")
sharedFlags = append(sharedFlags, "-buildvcs=false")
currentTag, err := build_shared.ReadTag()
if err != nil {
currentTag = "unknown"
}
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0")
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0")
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_naive_outbound", "with_clash_api", "badlinkname", "tfogo_checklinkname0")
darwinTags = append(darwinTags, "with_dhcp", "grpcnotrace")
// memcTags = append(memcTags, "with_tailscale")
sharedTags = append(sharedTags, "with_tailscale", "ts_omit_logtail", "ts_omit_ssh", "ts_omit_drive", "ts_omit_taildrop", "ts_omit_webclient", "ts_omit_doctor", "ts_omit_capture", "ts_omit_kube", "ts_omit_aws", "ts_omit_synology", "ts_omit_bird")
notMemcTags = append(notMemcTags, "with_low_memory")
debugTags = append(debugTags, "debug")
}
type AndroidBuildConfig struct {
AndroidAPI int
OutputName string
Tags []string
}
func filterTags(tags []string, exclude ...string) []string {
excludeMap := make(map[string]bool)
for _, tag := range exclude {
excludeMap[tag] = true
}
var result []string
for _, tag := range tags {
if !excludeMap[tag] {
result = append(result, tag)
}
}
return result
}
func checkJavaVersion() {
var javaPath string
javaHome := os.Getenv("JAVA_HOME")
if javaHome == "" {
javaPath = "java"
} else {
javaPath = filepath.Join(javaHome, "bin", "java")
}
javaVersion, err := shell.Exec(javaPath, "--version").ReadOutput()
if err != nil {
log.Fatal(E.Cause(err, "check java version"))
}
if !strings.Contains(javaVersion, "openjdk 17") {
log.Fatal("java version should be openjdk 17")
}
}
func getAndroidBindTarget() string {
if platform != "" {
return platform
} else if debugEnabled {
return "android/arm64"
}
return "android"
}
func buildAndroidVariant(config AndroidBuildConfig, bindTarget string) {
args := []string{
"bind",
"-v",
"-o", config.OutputName,
"-target", bindTarget,
"-androidapi", strconv.Itoa(config.AndroidAPI),
"-javapkg=io.nekohasekai",
"-libname=box",
}
if !debugEnabled {
args = append(args, sharedFlags...)
} else {
args = append(args, debugFlags...)
}
args = append(args, "-tags", strings.Join(config.Tags, ","))
args = append(args, "./experimental/libbox")
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
command.Stdout = os.Stdout
command.Stderr = os.Stderr
err := command.Run()
if err != nil {
log.Fatal(err)
}
copyPath := filepath.Join("..", "sing-box-for-android", "app", "libs")
if rw.IsDir(copyPath) {
copyPath, _ = filepath.Abs(copyPath)
err = rw.CopyFile(config.OutputName, filepath.Join(copyPath, config.OutputName))
if err != nil {
log.Fatal(err)
}
log.Info("copied ", config.OutputName, " to ", copyPath)
}
}
func buildAndroid() {
build_shared.FindSDK()
checkJavaVersion()
args := []string{
"bind",
"-v",
"-androidapi", "21",
"-javapkg=io.nekohasekai",
"-libname=box",
}
if !debugEnabled {
args = append(args,
"-trimpath", "-ldflags=-s -w -buildid=",
"-tags", "with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api",
)
} else {
args = append(args, "-tags", "with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api,debug")
}
bindTarget := getAndroidBindTarget()
args = append(args, "./experimental/libbox")
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
command.Stdout = os.Stdout
command.Stderr = os.Stderr
err := command.Run()
if err != nil {
log.Fatal(err)
// Build main variant (SDK 23)
mainTags := append([]string{}, sharedTags...)
// mainTags = append(mainTags, memcTags...)
if debugEnabled {
mainTags = append(mainTags, debugTags...)
}
buildAndroidVariant(AndroidBuildConfig{
AndroidAPI: 23,
OutputName: "libbox.aar",
Tags: mainTags,
}, bindTarget)
const name = "libbox.aar"
copyPath := filepath.Join("..", "sing-box-for-android", "app", "libs")
if rw.FileExists(copyPath) {
copyPath, _ = filepath.Abs(copyPath)
err = rw.CopyFile(name, filepath.Join(copyPath, name))
if err != nil {
log.Fatal(err)
}
log.Info("copied to ", copyPath)
// Build legacy variant (SDK 21, no naive outbound)
legacyTags := filterTags(sharedTags, "with_naive_outbound")
// legacyTags = append(legacyTags, memcTags...)
if debugEnabled {
legacyTags = append(legacyTags, debugTags...)
}
buildAndroidVariant(AndroidBuildConfig{
AndroidAPI: 21,
OutputName: "libbox-legacy.aar",
Tags: legacyTags,
}, bindTarget)
}
func buildiOS() {
func buildApple() {
var bindTarget string
if platform != "" {
bindTarget = platform
} else if debugEnabled {
bindTarget = "ios"
} else {
bindTarget = "ios,iossimulator,tvos,tvossimulator,macos"
}
args := []string{
"bind",
"-v",
"-target", "ios,iossimulator,macos",
"-target", bindTarget,
"-libname=box",
"-tags-not-macos=with_low_memory",
}
//if !withTailscale {
// args = append(args, "-tags-macos="+strings.Join(memcTags, ","))
//}
if !debugEnabled {
args = append(
args, "-trimpath", "-ldflags=-s -w -buildid=",
"-tags", "with_gvisor,with_utls,with_clash_api,with_conntrack",
)
args = append(args, sharedFlags...)
} else {
args = append(args, "-tags", "with_gvisor,with_utls,with_clash_api,with_conntrack,debug")
args = append(args, debugFlags...)
}
tags := append(sharedTags, darwinTags...)
//if withTailscale {
// tags = append(tags, memcTags...)
//}
if debugEnabled {
tags = append(tags, debugTags...)
}
args = append(args, "-tags", strings.Join(tags, ","))
args = append(args, "./experimental/libbox")
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
@@ -102,8 +234,8 @@ func buildiOS() {
log.Fatal(err)
}
copyPath := filepath.Join("..", "sing-box-for-ios")
if rw.FileExists(copyPath) {
copyPath := filepath.Join("..", "sing-box-for-apple")
if rw.IsDir(copyPath) {
targetDir := filepath.Join(copyPath, "Libbox.xcframework")
targetDir, _ = filepath.Abs(targetDir)
os.RemoveAll(targetDir)

View File

@@ -28,7 +28,7 @@ func FindSDK() {
}
for _, path := range searchPath {
path = os.ExpandEnv(path)
if rw.FileExists(path + "/licenses/android-sdk-license") {
if rw.IsFile(filepath.Join(path, "licenses", "android-sdk-license")) {
androidSDKPath = path
break
}
@@ -48,11 +48,17 @@ func FindSDK() {
}
func findNDK() bool {
if rw.FileExists(androidSDKPath + "/ndk/25.1.8937393") {
androidNDKPath = androidSDKPath + "/ndk/25.1.8937393"
const fixedVersion = "28.0.13004108"
const versionFile = "source.properties"
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
androidNDKPath = fixedPath
return true
}
ndkVersions, err := os.ReadDir(androidSDKPath + "/ndk")
if ndkHomeEnv := os.Getenv("ANDROID_NDK_HOME"); rw.IsFile(filepath.Join(ndkHomeEnv, versionFile)) {
androidNDKPath = ndkHomeEnv
return true
}
ndkVersions, err := os.ReadDir(filepath.Join(androidSDKPath, "ndk"))
if err != nil {
return false
}
@@ -73,8 +79,10 @@ func findNDK() bool {
return true
})
for _, versionName := range versionNames {
if rw.FileExists(androidSDKPath + "/ndk/" + versionName) {
androidNDKPath = androidSDKPath + "/ndk/" + versionName
currentNDKPath := filepath.Join(androidSDKPath, "ndk", versionName)
if rw.IsFile(filepath.Join(currentNDKPath, versionFile)) {
androidNDKPath = currentNDKPath
log.Warn("reproducibility warning: using NDK version " + versionName + " instead of " + fixedVersion)
return true
}
}
@@ -85,8 +93,14 @@ var GoBinPath string
func FindMobile() {
goBin := filepath.Join(build.Default.GOPATH, "bin")
if !rw.FileExists(goBin + "/" + "gobind") {
log.Fatal("missing gomobile installation")
if runtime.GOOS == "windows" {
if !rw.IsFile(filepath.Join(goBin, "gobind.exe")) {
log.Fatal("missing gomobile installation")
}
} else {
if !rw.IsFile(filepath.Join(goBin, "gobind")) {
log.Fatal("missing gomobile installation")
}
}
GoBinPath = goBin
}

View File

@@ -0,0 +1,38 @@
package build_shared
import (
"github.com/sagernet/sing-box/common/badversion"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/shell"
)
func ReadTag() (string, error) {
currentTag, err := shell.Exec("git", "describe", "--tags").ReadOutput()
if err != nil {
return currentTag, err
}
currentTagRev, _ := shell.Exec("git", "describe", "--tags", "--abbrev=0").ReadOutput()
if currentTagRev == currentTag {
return currentTag[1:], nil
}
shortCommit, _ := shell.Exec("git", "rev-parse", "--short", "HEAD").ReadOutput()
version := badversion.Parse(currentTagRev[1:])
return version.String() + "-" + shortCommit, nil
}
func ReadTagVersionRev() (badversion.Version, error) {
currentTagRev := common.Must1(shell.Exec("git", "describe", "--tags", "--abbrev=0").ReadOutput())
return badversion.Parse(currentTagRev[1:]), nil
}
func ReadTagVersion() (badversion.Version, error) {
currentTag := common.Must1(shell.Exec("git", "describe", "--tags").ReadOutput())
currentTagRev := common.Must1(shell.Exec("git", "describe", "--tags", "--abbrev=0").ReadOutput())
version := badversion.Parse(currentTagRev[1:])
if currentTagRev != currentTag {
if version.PreReleaseIdentifier == "" {
version.Patch++
}
}
return version, nil
}

View File

@@ -0,0 +1,117 @@
package main
import (
"bytes"
"os"
"path/filepath"
"strings"
"github.com/sagernet/sing-box/log"
)
func main() {
err := filepath.Walk("docs", func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
if !strings.HasSuffix(path, ".md") {
return nil
}
return processFile(path)
})
if err != nil {
log.Fatal(err)
}
}
func processFile(path string) error {
content, err := os.ReadFile(path)
if err != nil {
return err
}
lines := strings.Split(string(content), "\n")
modified := false
result := make([]string, 0, len(lines))
inQuoteBlock := false
materialLines := []int{} // indices of :material- lines in the block
for _, line := range lines {
// Check for quote block start
if strings.HasPrefix(line, "!!! quote \"") && strings.Contains(line, "sing-box") {
inQuoteBlock = true
materialLines = nil
result = append(result, line)
continue
}
// Inside a quote block
if inQuoteBlock {
trimmed := strings.TrimPrefix(line, " ")
isMaterialLine := strings.HasPrefix(trimmed, ":material-")
isEmpty := strings.TrimSpace(line) == ""
isIndented := strings.HasPrefix(line, " ")
if isMaterialLine {
materialLines = append(materialLines, len(result))
result = append(result, line)
continue
}
// Block ends when:
// - Empty line AFTER we've seen material lines, OR
// - Non-indented, non-empty line
blockEnds := (isEmpty && len(materialLines) > 0) || (!isEmpty && !isIndented)
if blockEnds {
// Process collected material lines
if len(materialLines) > 0 {
for j, idx := range materialLines {
isLast := j == len(materialLines)-1
resultLine := strings.TrimRight(result[idx], " ")
if !isLast {
// Add trailing two spaces for non-last lines
resultLine += " "
}
if result[idx] != resultLine {
modified = true
result[idx] = resultLine
}
}
}
inQuoteBlock = false
materialLines = nil
}
}
result = append(result, line)
}
// Handle case where file ends while still in a block
if inQuoteBlock && len(materialLines) > 0 {
for j, idx := range materialLines {
isLast := j == len(materialLines)-1
resultLine := strings.TrimRight(result[idx], " ")
if !isLast {
resultLine += " "
}
if result[idx] != resultLine {
modified = true
result[idx] = resultLine
}
}
}
if modified {
newContent := strings.Join(result, "\n")
if !bytes.Equal(content, []byte(newContent)) {
log.Info("formatted: ", path)
return os.WriteFile(path, []byte(newContent), 0o644)
}
}
return nil
}

View File

@@ -0,0 +1,71 @@
package main
import (
"flag"
"os"
"github.com/sagernet/sing-box/cmd/internal/build_shared"
"github.com/sagernet/sing-box/common/badversion"
"github.com/sagernet/sing-box/log"
)
var (
flagRunInCI bool
flagRunNightly bool
)
func init() {
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
flag.BoolVar(&flagRunNightly, "nightly", false, "Run nightly")
}
func main() {
flag.Parse()
var (
versionStr string
err error
)
if flagRunNightly {
var version badversion.Version
version, err = build_shared.ReadTagVersion()
if err == nil {
versionStr = version.String()
}
} else {
versionStr, err = build_shared.ReadTag()
}
if flagRunInCI {
if err != nil {
log.Fatal(err)
}
err = setGitHubEnv("version", versionStr)
if err != nil {
log.Fatal(err)
}
} else {
if err != nil {
log.Error(err)
os.Stdout.WriteString("unknown\n")
} else {
os.Stdout.WriteString(versionStr + "\n")
}
}
}
func setGitHubEnv(name string, value string) error {
outputFile, err := os.OpenFile(os.Getenv("GITHUB_ENV"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
if err != nil {
return err
}
_, err = outputFile.WriteString(name + "=" + value + "\n")
if err != nil {
outputFile.Close()
return err
}
err = outputFile.Close()
if err != nil {
return err
}
os.Stderr.WriteString(name + "=" + value + "\n")
return nil
}

View File

@@ -0,0 +1,284 @@
package main
import (
"context"
"fmt"
"io"
"net/netip"
"os"
"os/exec"
"strings"
"syscall"
"time"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/include"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/shell"
)
var iperf3Path string
func main() {
err := main0()
if err != nil {
log.Fatal(err)
}
}
func main0() error {
err := shell.Exec("sudo", "ls").Run()
if err != nil {
return err
}
results, err := runTests()
if err != nil {
return err
}
encoder := json.NewEncoder(os.Stdout)
encoder.SetIndent("", " ")
return encoder.Encode(results)
}
func runTests() ([]TestResult, error) {
boxPaths := []string{
os.ExpandEnv("$HOME/Downloads/sing-box-1.11.15-darwin-arm64/sing-box"),
//"/Users/sekai/Downloads/sing-box-1.11.15-linux-arm64/sing-box",
"./sing-box",
}
stacks := []string{
"gvisor",
"system",
}
mtus := []int{
1500,
4064,
// 16384,
// 32768,
// 49152,
65535,
}
flagList := [][]string{
{},
}
var results []TestResult
for _, boxPath := range boxPaths {
for _, stack := range stacks {
for _, mtu := range mtus {
if strings.HasPrefix(boxPath, ".") {
for _, flags := range flagList {
result, err := testOnce(boxPath, stack, mtu, false, flags)
if err != nil {
return nil, err
}
results = append(results, *result)
}
} else {
result, err := testOnce(boxPath, stack, mtu, false, nil)
if err != nil {
return nil, err
}
results = append(results, *result)
}
}
}
}
return results, nil
}
type TestResult struct {
BoxPath string `json:"box_path"`
Stack string `json:"stack"`
MTU int `json:"mtu"`
Flags []string `json:"flags"`
MultiThread bool `json:"multi_thread"`
UploadSpeed string `json:"upload_speed"`
DownloadSpeed string `json:"download_speed"`
}
func testOnce(boxPath string, stackName string, mtu int, multiThread bool, flags []string) (result *TestResult, err error) {
testAddress := netip.MustParseAddr("1.1.1.1")
testConfig := option.Options{
Inbounds: []option.Inbound{
{
Type: C.TypeTun,
Options: &option.TunInboundOptions{
Address: []netip.Prefix{netip.MustParsePrefix("172.18.0.1/30")},
AutoRoute: true,
MTU: uint32(mtu),
Stack: stackName,
RouteAddress: []netip.Prefix{netip.PrefixFrom(testAddress, testAddress.BitLen())},
},
},
},
Route: &option.RouteOptions{
Rules: []option.Rule{
{
Type: C.RuleTypeDefault,
DefaultOptions: option.DefaultRule{
RawDefaultRule: option.RawDefaultRule{
IPCIDR: []string{testAddress.String()},
},
RuleAction: option.RuleAction{
Action: C.RuleActionTypeRouteOptions,
RouteOptionsOptions: option.RouteOptionsActionOptions{
OverrideAddress: "127.0.0.1",
},
},
},
},
},
AutoDetectInterface: true,
},
}
ctx := include.Context(context.Background())
tempConfig, err := os.CreateTemp("", "tun-bench-*.json")
if err != nil {
return
}
defer os.Remove(tempConfig.Name())
encoder := json.NewEncoderContext(ctx, tempConfig)
encoder.SetIndent("", " ")
err = encoder.Encode(testConfig)
if err != nil {
return nil, E.Cause(err, "encode test config")
}
tempConfig.Close()
var sudoArgs []string
if len(flags) > 0 {
sudoArgs = append(sudoArgs, "env")
sudoArgs = append(sudoArgs, flags...)
}
sudoArgs = append(sudoArgs, boxPath, "run", "-c", tempConfig.Name())
boxProcess := shell.Exec("sudo", sudoArgs...)
boxProcess.Stdout = &stderrWriter{}
boxProcess.Stderr = io.Discard
err = boxProcess.Start()
if err != nil {
return
}
if C.IsDarwin {
iperf3Path, err = exec.LookPath("iperf3-darwin")
} else {
iperf3Path, err = exec.LookPath("iperf3")
}
if err != nil {
return
}
serverProcess := shell.Exec(iperf3Path, "-s")
serverProcess.Stdout = io.Discard
serverProcess.Stderr = io.Discard
err = serverProcess.Start()
if err != nil {
return nil, E.Cause(err, "start iperf3 server")
}
time.Sleep(time.Second)
args := []string{"-c", testAddress.String()}
if multiThread {
args = append(args, "-P", "10")
}
uploadProcess := shell.Exec(iperf3Path, args...)
output, err := uploadProcess.Read()
if err != nil {
boxProcess.Process.Signal(syscall.SIGKILL)
serverProcess.Process.Signal(syscall.SIGKILL)
println(output)
return
}
uploadResult := common.SubstringBeforeLast(output, "iperf Done.")
uploadResult = common.SubstringBeforeLast(uploadResult, "sender")
uploadResult = common.SubstringBeforeLast(uploadResult, "bits/sec")
uploadResult = common.SubstringAfterLast(uploadResult, "Bytes")
uploadResult = strings.ReplaceAll(uploadResult, " ", "")
result = &TestResult{
BoxPath: boxPath,
Stack: stackName,
MTU: mtu,
Flags: flags,
MultiThread: multiThread,
UploadSpeed: uploadResult,
}
downloadProcess := shell.Exec(iperf3Path, append(args, "-R")...)
output, err = downloadProcess.Read()
if err != nil {
boxProcess.Process.Signal(syscall.SIGKILL)
serverProcess.Process.Signal(syscall.SIGKILL)
println(output)
return
}
downloadResult := common.SubstringBeforeLast(output, "iperf Done.")
downloadResult = common.SubstringBeforeLast(downloadResult, "receiver")
downloadResult = common.SubstringBeforeLast(downloadResult, "bits/sec")
downloadResult = common.SubstringAfterLast(downloadResult, "Bytes")
downloadResult = strings.ReplaceAll(downloadResult, " ", "")
result.DownloadSpeed = downloadResult
printArgs := []any{boxPath, stackName, mtu, "upload", uploadResult, "download", downloadResult}
if len(flags) > 0 {
printArgs = append(printArgs, "flags", strings.Join(flags, " "))
}
if multiThread {
printArgs = append(printArgs, "(-P 10)")
}
fmt.Println(printArgs...)
err = boxProcess.Process.Signal(syscall.SIGTERM)
if err != nil {
return
}
err = serverProcess.Process.Signal(syscall.SIGTERM)
if err != nil {
return
}
boxDone := make(chan struct{})
go func() {
boxProcess.Cmd.Wait()
close(boxDone)
}()
serverDone := make(chan struct{})
go func() {
serverProcess.Process.Wait()
close(serverDone)
}()
select {
case <-boxDone:
case <-time.After(2 * time.Second):
boxProcess.Process.Kill()
case <-time.After(4 * time.Second):
println("box process did not close!")
os.Exit(1)
}
select {
case <-serverDone:
case <-time.After(2 * time.Second):
serverProcess.Process.Kill()
case <-time.After(4 * time.Second):
println("server process did not close!")
os.Exit(1)
}
return
}
type stderrWriter struct{}
func (w *stderrWriter) Write(p []byte) (n int, err error) {
return os.Stderr.Write(p)
}

View File

@@ -0,0 +1,84 @@
package main
import (
"flag"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"github.com/sagernet/sing-box/cmd/internal/build_shared"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
)
var (
flagRunInCI bool
flagRunNightly bool
)
func init() {
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
flag.BoolVar(&flagRunNightly, "nightly", false, "Run nightly")
}
func main() {
flag.Parse()
newVersion := common.Must1(build_shared.ReadTag())
var androidPath string
if flagRunInCI {
androidPath = "clients/android"
} else {
androidPath = "../sing-box-for-android"
}
androidPath, err := filepath.Abs(androidPath)
if err != nil {
log.Fatal(err)
}
common.Must(os.Chdir(androidPath))
localProps := common.Must1(os.ReadFile("version.properties"))
var propsList [][]string
for _, propLine := range strings.Split(string(localProps), "\n") {
propsList = append(propsList, strings.Split(propLine, "="))
}
var (
versionUpdated bool
goVersionUpdated bool
)
for _, propPair := range propsList {
switch propPair[0] {
case "VERSION_NAME":
if propPair[1] != newVersion {
log.Info("updated version from ", propPair[1], " to ", newVersion)
versionUpdated = true
propPair[1] = newVersion
}
case "GO_VERSION":
if propPair[1] != runtime.Version() {
log.Info("updated Go version from ", propPair[1], " to ", runtime.Version())
goVersionUpdated = true
propPair[1] = runtime.Version()
}
}
}
if !(versionUpdated || goVersionUpdated) {
log.Info("version not changed")
return
} else if flagRunInCI && !flagRunNightly {
log.Fatal("version changed, commit changes first.")
}
for _, propPair := range propsList {
switch propPair[0] {
case "VERSION_CODE":
versionCode := common.Must1(strconv.ParseInt(propPair[1], 10, 64))
propPair[1] = strconv.Itoa(int(versionCode + 1))
log.Info("updated version code to ", propPair[1])
}
}
var newProps []string
for _, propPair := range propsList {
newProps = append(newProps, strings.Join(propPair, "="))
}
common.Must(os.WriteFile("version.properties", []byte(strings.Join(newProps, "\n")), 0o644))
}

View File

@@ -0,0 +1,145 @@
package main
import (
"flag"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/sagernet/sing-box/cmd/internal/build_shared"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
"howett.net/plist"
)
var flagRunInCI bool
func init() {
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
}
func main() {
flag.Parse()
newVersion := common.Must1(build_shared.ReadTagVersion())
var applePath string
if flagRunInCI {
applePath = "clients/apple"
} else {
applePath = "../sing-box-for-apple"
}
applePath, err := filepath.Abs(applePath)
if err != nil {
log.Fatal(err)
}
common.Must(os.Chdir(applePath))
projectFile := common.Must1(os.Open("sing-box.xcodeproj/project.pbxproj"))
var project map[string]any
decoder := plist.NewDecoder(projectFile)
common.Must(decoder.Decode(&project))
objectsMap := project["objects"].(map[string]any)
projectContent := string(common.Must1(os.ReadFile("sing-box.xcodeproj/project.pbxproj")))
newContent, updated0 := findAndReplace(objectsMap, projectContent, []string{"io.nekohasekai.sfavt"}, newVersion.VersionString())
newContent, updated1 := findAndReplace(objectsMap, newContent, []string{"io.nekohasekai.sfavt.standalone", "io.nekohasekai.sfavt.system"}, newVersion.String())
if updated0 || updated1 {
log.Info("updated version to ", newVersion.VersionString(), " (", newVersion.String(), ")")
}
var updated2 bool
if macProjectVersion := os.Getenv("MACOS_PROJECT_VERSION"); macProjectVersion != "" {
newContent, updated2 = findAndReplaceProjectVersion(objectsMap, newContent, []string{"SFM"}, macProjectVersion)
if updated2 {
log.Info("updated macos project version to ", macProjectVersion)
}
}
if updated0 || updated1 || updated2 {
common.Must(os.WriteFile("sing-box.xcodeproj/project.pbxproj", []byte(newContent), 0o644))
}
}
func findAndReplace(objectsMap map[string]any, projectContent string, bundleIDList []string, newVersion string) (string, bool) {
objectKeyList := findObjectKey(objectsMap, bundleIDList)
var updated bool
for _, objectKey := range objectKeyList {
matchRegexp := common.Must1(regexp.Compile(objectKey + ".*= \\{"))
indexes := matchRegexp.FindStringIndex(projectContent)
if len(indexes) < 2 {
println(projectContent)
log.Fatal("failed to find object key ", objectKey, ": ", strings.Index(projectContent, objectKey))
}
indexStart := indexes[1]
indexEnd := indexStart + strings.Index(projectContent[indexStart:], "}")
versionStart := indexStart + strings.Index(projectContent[indexStart:indexEnd], "MARKETING_VERSION = ") + 20
versionEnd := versionStart + strings.Index(projectContent[versionStart:indexEnd], ";")
version := strings.Trim(projectContent[versionStart:versionEnd], "\"")
if version == newVersion {
continue
}
updated = true
projectContent = projectContent[:versionStart] + "\"" + newVersion + "\"" + projectContent[versionEnd:]
}
return projectContent, updated
}
func findAndReplaceProjectVersion(objectsMap map[string]any, projectContent string, directoryList []string, newVersion string) (string, bool) {
objectKeyList := findObjectKeyByDirectory(objectsMap, directoryList)
var updated bool
for _, objectKey := range objectKeyList {
matchRegexp := common.Must1(regexp.Compile(objectKey + ".*= \\{"))
indexes := matchRegexp.FindStringIndex(projectContent)
if len(indexes) < 2 {
println(projectContent)
log.Fatal("failed to find object key ", objectKey, ": ", strings.Index(projectContent, objectKey))
}
indexStart := indexes[1]
indexEnd := indexStart + strings.Index(projectContent[indexStart:], "}")
versionStart := indexStart + strings.Index(projectContent[indexStart:indexEnd], "CURRENT_PROJECT_VERSION = ") + 26
versionEnd := versionStart + strings.Index(projectContent[versionStart:indexEnd], ";")
version := projectContent[versionStart:versionEnd]
if version == newVersion {
continue
}
updated = true
projectContent = projectContent[:versionStart] + newVersion + projectContent[versionEnd:]
}
return projectContent, updated
}
func findObjectKey(objectsMap map[string]any, bundleIDList []string) []string {
var objectKeyList []string
for objectKey, object := range objectsMap {
buildSettings := object.(map[string]any)["buildSettings"]
if buildSettings == nil {
continue
}
bundleIDObject := buildSettings.(map[string]any)["PRODUCT_BUNDLE_IDENTIFIER"]
if bundleIDObject == nil {
continue
}
if common.Contains(bundleIDList, bundleIDObject.(string)) {
objectKeyList = append(objectKeyList, objectKey)
}
}
return objectKeyList
}
func findObjectKeyByDirectory(objectsMap map[string]any, directoryList []string) []string {
var objectKeyList []string
for objectKey, object := range objectsMap {
buildSettings := object.(map[string]any)["buildSettings"]
if buildSettings == nil {
continue
}
infoPListFile := buildSettings.(map[string]any)["INFOPLIST_FILE"]
if infoPListFile == nil {
continue
}
for _, searchDirectory := range directoryList {
if strings.HasPrefix(infoPListFile.(string), searchDirectory+"/") {
objectKeyList = append(objectKeyList, objectKey)
}
}
}
return objectKeyList
}

View File

@@ -0,0 +1,166 @@
package main
import (
"encoding/csv"
"io"
"net/http"
"os"
"strings"
"github.com/sagernet/sing-box/log"
"golang.org/x/exp/slices"
)
func main() {
err := updateMozillaIncludedRootCAs()
if err != nil {
log.Error(err)
}
err = updateChromeIncludedRootCAs()
if err != nil {
log.Error(err)
}
}
func updateMozillaIncludedRootCAs() error {
response, err := http.Get("https://ccadb.my.salesforce-sites.com/mozilla/IncludedCACertificateReportPEMCSV")
if err != nil {
return err
}
defer response.Body.Close()
reader := csv.NewReader(response.Body)
header, err := reader.Read()
if err != nil {
return err
}
geoIndex := slices.Index(header, "Geographic Focus")
nameIndex := slices.Index(header, "Common Name or Certificate Name")
certIndex := slices.Index(header, "PEM Info")
generated := strings.Builder{}
generated.WriteString(`// Code generated by 'make update_certificates'. DO NOT EDIT.
package certificate
import "crypto/x509"
var mozillaIncluded *x509.CertPool
func init() {
mozillaIncluded = x509.NewCertPool()
`)
for {
record, err := reader.Read()
if err == io.EOF {
break
} else if err != nil {
return err
}
if record[geoIndex] == "China" {
continue
}
generated.WriteString("\n // ")
generated.WriteString(record[nameIndex])
generated.WriteString("\n")
generated.WriteString(" mozillaIncluded.AppendCertsFromPEM([]byte(`")
cert := record[certIndex]
// Remove single quotes
cert = cert[1 : len(cert)-1]
generated.WriteString(cert)
generated.WriteString("`))\n")
}
generated.WriteString("}\n")
return os.WriteFile("common/certificate/mozilla.go", []byte(generated.String()), 0o644)
}
func fetchChinaFingerprints() (map[string]bool, error) {
response, err := http.Get("https://ccadb.my.salesforce-sites.com/ccadb/AllCertificateRecordsCSVFormatv4")
if err != nil {
return nil, err
}
defer response.Body.Close()
reader := csv.NewReader(response.Body)
header, err := reader.Read()
if err != nil {
return nil, err
}
countryIndex := slices.Index(header, "Country")
fingerprintIndex := slices.Index(header, "SHA-256 Fingerprint")
chinaFingerprints := make(map[string]bool)
for {
record, err := reader.Read()
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
if record[countryIndex] == "China" {
chinaFingerprints[record[fingerprintIndex]] = true
}
}
return chinaFingerprints, nil
}
func updateChromeIncludedRootCAs() error {
chinaFingerprints, err := fetchChinaFingerprints()
if err != nil {
return err
}
response, err := http.Get("https://ccadb.my.salesforce-sites.com/ccadb/RootCACertificatesIncludedByRSReportCSV")
if err != nil {
return err
}
defer response.Body.Close()
reader := csv.NewReader(response.Body)
header, err := reader.Read()
if err != nil {
return err
}
subjectIndex := slices.Index(header, "Subject")
statusIndex := slices.Index(header, "Google Chrome Status")
certIndex := slices.Index(header, "X.509 Certificate (PEM)")
fingerprintIndex := slices.Index(header, "SHA-256 Fingerprint")
generated := strings.Builder{}
generated.WriteString(`// Code generated by 'make update_certificates'. DO NOT EDIT.
package certificate
import "crypto/x509"
var chromeIncluded *x509.CertPool
func init() {
chromeIncluded = x509.NewCertPool()
`)
for {
record, err := reader.Read()
if err == io.EOF {
break
} else if err != nil {
return err
}
if record[statusIndex] != "Included" {
continue
}
if chinaFingerprints[record[fingerprintIndex]] {
continue
}
generated.WriteString("\n // ")
generated.WriteString(record[subjectIndex])
generated.WriteString("\n")
generated.WriteString(" chromeIncluded.AppendCertsFromPEM([]byte(`")
cert := record[certIndex]
// Remove single quotes if present
if len(cert) > 0 && cert[0] == '\'' {
cert = cert[1 : len(cert)-1]
}
generated.WriteString(cert)
generated.WriteString("`))\n")
}
generated.WriteString("}\n")
return os.WriteFile("common/certificate/chrome.go", []byte(generated.String()), 0o644)
}

71
cmd/sing-box/cmd.go Normal file
View File

@@ -0,0 +1,71 @@
package main
import (
"context"
"os"
"os/user"
"strconv"
"time"
"github.com/sagernet/sing-box/experimental/deprecated"
"github.com/sagernet/sing-box/include"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/service"
"github.com/sagernet/sing/service/filemanager"
"github.com/spf13/cobra"
)
var (
globalCtx context.Context
configPaths []string
configDirectories []string
workingDir string
disableColor bool
)
var mainCommand = &cobra.Command{
Use: "sing-box",
PersistentPreRun: preRun,
}
func init() {
mainCommand.PersistentFlags().StringArrayVarP(&configPaths, "config", "c", nil, "set configuration file path")
mainCommand.PersistentFlags().StringArrayVarP(&configDirectories, "config-directory", "C", nil, "set configuration directory path")
mainCommand.PersistentFlags().StringVarP(&workingDir, "directory", "D", "", "set working directory")
mainCommand.PersistentFlags().BoolVarP(&disableColor, "disable-color", "", false, "disable color output")
}
func preRun(cmd *cobra.Command, args []string) {
globalCtx = context.Background()
sudoUser := os.Getenv("SUDO_USER")
sudoUID, _ := strconv.Atoi(os.Getenv("SUDO_UID"))
sudoGID, _ := strconv.Atoi(os.Getenv("SUDO_GID"))
if sudoUID == 0 && sudoGID == 0 && sudoUser != "" {
sudoUserObject, _ := user.Lookup(sudoUser)
if sudoUserObject != nil {
sudoUID, _ = strconv.Atoi(sudoUserObject.Uid)
sudoGID, _ = strconv.Atoi(sudoUserObject.Gid)
}
}
if sudoUID > 0 && sudoGID > 0 {
globalCtx = filemanager.WithDefault(globalCtx, "", "", sudoUID, sudoGID)
}
if disableColor {
log.SetStdLogger(log.NewDefaultFactory(context.Background(), log.Formatter{BaseTime: time.Now(), DisableColors: true}, os.Stderr, "", nil, false).Logger())
}
if workingDir != "" {
_, err := os.Stat(workingDir)
if err != nil {
filemanager.MkdirAll(globalCtx, workingDir, 0o777)
}
err = os.Chdir(workingDir)
if err != nil {
log.Fatal(err)
}
}
if len(configPaths) == 0 && len(configDirectories) == 0 {
configPaths = append(configPaths, "config.json")
}
globalCtx = include.Context(service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger())))
}

View File

@@ -26,12 +26,15 @@ func init() {
}
func check() error {
options, err := readConfig()
options, err := readConfigAndMerge()
if err != nil {
return err
}
ctx, cancel := context.WithCancel(context.Background())
instance, err := box.New(ctx, options, nil)
ctx, cancel := context.WithCancel(globalCtx)
instance, err := box.New(box.Options{
Context: ctx,
Options: options,
})
if err == nil {
instance.Close()
}

View File

@@ -5,10 +5,10 @@ import (
"os"
"path/filepath"
"github.com/sagernet/sing-box/common/json"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
"github.com/spf13/cobra"
)
@@ -33,39 +33,43 @@ func init() {
}
func format() error {
configContent, err := os.ReadFile(configPath)
optionsList, err := readConfig()
if err != nil {
return E.Cause(err, "read config")
return err
}
var options option.Options
err = options.UnmarshalJSON(configContent)
if err != nil {
return E.Cause(err, "decode config")
for _, optionsEntry := range optionsList {
optionsEntry.options, err = badjson.Omitempty(globalCtx, optionsEntry.options)
if err != nil {
return err
}
buffer := new(bytes.Buffer)
encoder := json.NewEncoder(buffer)
encoder.SetIndent("", " ")
err = encoder.Encode(optionsEntry.options)
if err != nil {
return E.Cause(err, "encode config")
}
outputPath, _ := filepath.Abs(optionsEntry.path)
if !commandFormatFlagWrite {
if len(optionsList) > 1 {
os.Stdout.WriteString(outputPath + "\n")
}
os.Stdout.WriteString(buffer.String() + "\n")
continue
}
if bytes.Equal(optionsEntry.content, buffer.Bytes()) {
continue
}
output, err := os.Create(optionsEntry.path)
if err != nil {
return E.Cause(err, "open output")
}
_, err = output.Write(buffer.Bytes())
output.Close()
if err != nil {
return E.Cause(err, "write output")
}
os.Stderr.WriteString(outputPath + "\n")
}
buffer := new(bytes.Buffer)
encoder := json.NewEncoder(buffer)
encoder.SetIndent("", " ")
err = encoder.Encode(options)
if err != nil {
return E.Cause(err, "encode config")
}
if !commandFormatFlagWrite {
os.Stdout.WriteString(buffer.String() + "\n")
return nil
}
if bytes.Equal(configContent, buffer.Bytes()) {
return nil
}
output, err := os.Create(configPath)
if err != nil {
return E.Cause(err, "open output")
}
_, err = output.Write(buffer.Bytes())
output.Close()
if err != nil {
return E.Cause(err, "write output")
}
outputPath, _ := filepath.Abs(configPath)
os.Stderr.WriteString(outputPath + "\n")
return nil
}

View File

@@ -9,9 +9,8 @@ import (
"github.com/sagernet/sing-box/log"
"github.com/gofrs/uuid"
"github.com/gofrs/uuid/v5"
"github.com/spf13/cobra"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
var commandGenerate = &cobra.Command{
@@ -22,8 +21,7 @@ var commandGenerate = &cobra.Command{
func init() {
commandGenerate.AddCommand(commandGenerateUUID)
commandGenerate.AddCommand(commandGenerateRandom)
commandGenerate.AddCommand(commandGenerateWireGuardKeyPair)
commandGenerate.AddCommand(commandGenerateRealityKeyPair)
mainCommand.AddCommand(commandGenerate)
}
@@ -92,48 +90,3 @@ func generateUUID() error {
_, err = os.Stdout.WriteString(newUUID.String() + "\n")
return err
}
var commandGenerateWireGuardKeyPair = &cobra.Command{
Use: "wg-keypair",
Short: "Generate WireGuard key pair",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
err := generateWireGuardKey()
if err != nil {
log.Fatal(err)
}
},
}
func generateWireGuardKey() error {
privateKey, err := wgtypes.GeneratePrivateKey()
if err != nil {
return err
}
os.Stdout.WriteString("PrivateKey: " + privateKey.String() + "\n")
os.Stdout.WriteString("PublicKey: " + privateKey.PublicKey().String() + "\n")
return nil
}
var commandGenerateRealityKeyPair = &cobra.Command{
Use: "reality-keypair",
Short: "Generate reality key pair",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
err := generateRealityKey()
if err != nil {
log.Fatal(err)
}
},
}
func generateRealityKey() error {
privateKey, err := wgtypes.GeneratePrivateKey()
if err != nil {
return err
}
publicKey := privateKey.PublicKey()
os.Stdout.WriteString("PrivateKey: " + base64.RawURLEncoding.EncodeToString(privateKey[:]) + "\n")
os.Stdout.WriteString("PublicKey: " + base64.RawURLEncoding.EncodeToString(publicKey[:]) + "\n")
return nil
}

View File

@@ -0,0 +1,36 @@
package main
import (
"os"
"github.com/sagernet/sing-box/common/tls"
"github.com/sagernet/sing-box/log"
"github.com/spf13/cobra"
)
var commandGenerateECHKeyPair = &cobra.Command{
Use: "ech-keypair <plain_server_name>",
Short: "Generate TLS ECH key pair",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := generateECHKeyPair(args[0])
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGenerate.AddCommand(commandGenerateECHKeyPair)
}
func generateECHKeyPair(serverName string) error {
configPem, keyPem, err := tls.ECHKeygenDefault(serverName)
if err != nil {
return err
}
os.Stdout.WriteString(configPem)
os.Stdout.WriteString(keyPem)
return nil
}

View File

@@ -0,0 +1,40 @@
package main
import (
"os"
"time"
"github.com/sagernet/sing-box/common/tls"
"github.com/sagernet/sing-box/log"
"github.com/spf13/cobra"
)
var flagGenerateTLSKeyPairMonths int
var commandGenerateTLSKeyPair = &cobra.Command{
Use: "tls-keypair <server_name>",
Short: "Generate TLS self sign key pair",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := generateTLSKeyPair(args[0])
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGenerateTLSKeyPair.Flags().IntVarP(&flagGenerateTLSKeyPairMonths, "months", "m", 1, "Valid months")
commandGenerate.AddCommand(commandGenerateTLSKeyPair)
}
func generateTLSKeyPair(serverName string) error {
privateKeyPem, publicKeyPem, err := tls.GenerateCertificate(nil, nil, time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0))
if err != nil {
return err
}
os.Stdout.WriteString(string(privateKeyPem) + "\n")
os.Stdout.WriteString(string(publicKeyPem) + "\n")
return nil
}

View File

@@ -0,0 +1,40 @@
//go:build go1.20
package main
import (
"crypto/ecdh"
"crypto/rand"
"encoding/base64"
"os"
"github.com/sagernet/sing-box/log"
"github.com/spf13/cobra"
)
var commandGenerateVAPIDKeyPair = &cobra.Command{
Use: "vapid-keypair",
Short: "Generate VAPID key pair",
Run: func(cmd *cobra.Command, args []string) {
err := generateVAPIDKeyPair()
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGenerate.AddCommand(commandGenerateVAPIDKeyPair)
}
func generateVAPIDKeyPair() error {
privateKey, err := ecdh.P256().GenerateKey(rand.Reader)
if err != nil {
return err
}
publicKey := privateKey.PublicKey()
os.Stdout.WriteString("PrivateKey: " + base64.RawURLEncoding.EncodeToString(privateKey.Bytes()) + "\n")
os.Stdout.WriteString("PublicKey: " + base64.RawURLEncoding.EncodeToString(publicKey.Bytes()) + "\n")
return nil
}

View File

@@ -0,0 +1,61 @@
package main
import (
"encoding/base64"
"os"
"github.com/sagernet/sing-box/log"
"github.com/spf13/cobra"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
func init() {
commandGenerate.AddCommand(commandGenerateWireGuardKeyPair)
commandGenerate.AddCommand(commandGenerateRealityKeyPair)
}
var commandGenerateWireGuardKeyPair = &cobra.Command{
Use: "wg-keypair",
Short: "Generate WireGuard key pair",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
err := generateWireGuardKey()
if err != nil {
log.Fatal(err)
}
},
}
func generateWireGuardKey() error {
privateKey, err := wgtypes.GeneratePrivateKey()
if err != nil {
return err
}
os.Stdout.WriteString("PrivateKey: " + privateKey.String() + "\n")
os.Stdout.WriteString("PublicKey: " + privateKey.PublicKey().String() + "\n")
return nil
}
var commandGenerateRealityKeyPair = &cobra.Command{
Use: "reality-keypair",
Short: "Generate reality key pair",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
err := generateRealityKey()
if err != nil {
log.Fatal(err)
}
},
}
func generateRealityKey() error {
privateKey, err := wgtypes.GeneratePrivateKey()
if err != nil {
return err
}
publicKey := privateKey.PublicKey()
os.Stdout.WriteString("PrivateKey: " + base64.RawURLEncoding.EncodeToString(privateKey[:]) + "\n")
os.Stdout.WriteString("PublicKey: " + base64.RawURLEncoding.EncodeToString(publicKey[:]) + "\n")
return nil
}

43
cmd/sing-box/cmd_geoip.go Normal file
View File

@@ -0,0 +1,43 @@
package main
import (
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
"github.com/oschwald/maxminddb-golang"
"github.com/spf13/cobra"
)
var (
geoipReader *maxminddb.Reader
commandGeoIPFlagFile string
)
var commandGeoip = &cobra.Command{
Use: "geoip",
Short: "GeoIP tools",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
err := geoipPreRun()
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGeoip.PersistentFlags().StringVarP(&commandGeoIPFlagFile, "file", "f", "geoip.db", "geoip file")
mainCommand.AddCommand(commandGeoip)
}
func geoipPreRun() error {
reader, err := maxminddb.Open(commandGeoIPFlagFile)
if err != nil {
return err
}
if reader.Metadata.DatabaseType != "sing-geoip" {
reader.Close()
return E.New("incorrect database type, expected sing-geoip, got ", reader.Metadata.DatabaseType)
}
geoipReader = reader
return nil
}

View File

@@ -0,0 +1,98 @@
package main
import (
"io"
"net"
"os"
"strings"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/oschwald/maxminddb-golang"
"github.com/spf13/cobra"
)
var flagGeoipExportOutput string
const flagGeoipExportDefaultOutput = "geoip-<country>.srs"
var commandGeoipExport = &cobra.Command{
Use: "export <country>",
Short: "Export geoip country as rule-set",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := geoipExport(args[0])
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGeoipExport.Flags().StringVarP(&flagGeoipExportOutput, "output", "o", flagGeoipExportDefaultOutput, "Output path")
commandGeoip.AddCommand(commandGeoipExport)
}
func geoipExport(countryCode string) error {
networks := geoipReader.Networks(maxminddb.SkipAliasedNetworks)
countryMap := make(map[string][]*net.IPNet)
var (
ipNet *net.IPNet
nextCountryCode string
err error
)
for networks.Next() {
ipNet, err = networks.Network(&nextCountryCode)
if err != nil {
return err
}
countryMap[nextCountryCode] = append(countryMap[nextCountryCode], ipNet)
}
ipNets := countryMap[strings.ToLower(countryCode)]
if len(ipNets) == 0 {
return E.New("country code not found: ", countryCode)
}
var (
outputFile *os.File
outputWriter io.Writer
)
if flagGeoipExportOutput == "stdout" {
outputWriter = os.Stdout
} else if flagGeoipExportOutput == flagGeoipExportDefaultOutput {
outputFile, err = os.Create("geoip-" + countryCode + ".json")
if err != nil {
return err
}
defer outputFile.Close()
outputWriter = outputFile
} else {
outputFile, err = os.Create(flagGeoipExportOutput)
if err != nil {
return err
}
defer outputFile.Close()
outputWriter = outputFile
}
encoder := json.NewEncoder(outputWriter)
encoder.SetIndent("", " ")
var headlessRule option.DefaultHeadlessRule
headlessRule.IPCIDR = make([]string, 0, len(ipNets))
for _, cidr := range ipNets {
headlessRule.IPCIDR = append(headlessRule.IPCIDR, cidr.String())
}
var plainRuleSet option.PlainRuleSetCompat
plainRuleSet.Version = C.RuleSetVersion2
plainRuleSet.Options.Rules = []option.HeadlessRule{
{
Type: C.RuleTypeDefault,
DefaultOptions: headlessRule,
},
}
return encoder.Encode(plainRuleSet)
}

View File

@@ -0,0 +1,31 @@
package main
import (
"os"
"github.com/sagernet/sing-box/log"
"github.com/spf13/cobra"
)
var commandGeoipList = &cobra.Command{
Use: "list",
Short: "List geoip country codes",
Run: func(cmd *cobra.Command, args []string) {
err := listGeoip()
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGeoip.AddCommand(commandGeoipList)
}
func listGeoip() error {
for _, code := range geoipReader.Metadata.Languages {
os.Stdout.WriteString(code + "\n")
}
return nil
}

View File

@@ -0,0 +1,47 @@
package main
import (
"net/netip"
"os"
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
"github.com/spf13/cobra"
)
var commandGeoipLookup = &cobra.Command{
Use: "lookup <address>",
Short: "Lookup if an IP address is contained in the GeoIP database",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := geoipLookup(args[0])
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGeoip.AddCommand(commandGeoipLookup)
}
func geoipLookup(address string) error {
addr, err := netip.ParseAddr(address)
if err != nil {
return E.Cause(err, "parse address")
}
if !N.IsPublicAddr(addr) {
os.Stdout.WriteString("private\n")
return nil
}
var code string
_ = geoipReader.Lookup(addr.AsSlice(), &code)
if code != "" {
os.Stdout.WriteString(code + "\n")
return nil
}
os.Stdout.WriteString("unknown\n")
return nil
}

View File

@@ -0,0 +1,41 @@
package main
import (
"github.com/sagernet/sing-box/common/geosite"
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
"github.com/spf13/cobra"
)
var (
commandGeoSiteFlagFile string
geositeReader *geosite.Reader
geositeCodeList []string
)
var commandGeoSite = &cobra.Command{
Use: "geosite",
Short: "Geosite tools",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
err := geositePreRun()
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGeoSite.PersistentFlags().StringVarP(&commandGeoSiteFlagFile, "file", "f", "geosite.db", "geosite file")
mainCommand.AddCommand(commandGeoSite)
}
func geositePreRun() error {
reader, codeList, err := geosite.Open(commandGeoSiteFlagFile)
if err != nil {
return E.Cause(err, "open geosite file")
}
geositeReader = reader
geositeCodeList = codeList
return nil
}

View File

@@ -0,0 +1,81 @@
package main
import (
"io"
"os"
"github.com/sagernet/sing-box/common/geosite"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/json"
"github.com/spf13/cobra"
)
var commandGeositeExportOutput string
const commandGeositeExportDefaultOutput = "geosite-<category>.json"
var commandGeositeExport = &cobra.Command{
Use: "export <category>",
Short: "Export geosite category as rule-set",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := geositeExport(args[0])
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGeositeExport.Flags().StringVarP(&commandGeositeExportOutput, "output", "o", commandGeositeExportDefaultOutput, "Output path")
commandGeoSite.AddCommand(commandGeositeExport)
}
func geositeExport(category string) error {
sourceSet, err := geositeReader.Read(category)
if err != nil {
return err
}
var (
outputFile *os.File
outputWriter io.Writer
)
if commandGeositeExportOutput == "stdout" {
outputWriter = os.Stdout
} else if commandGeositeExportOutput == commandGeositeExportDefaultOutput {
outputFile, err = os.Create("geosite-" + category + ".json")
if err != nil {
return err
}
defer outputFile.Close()
outputWriter = outputFile
} else {
outputFile, err = os.Create(commandGeositeExportOutput)
if err != nil {
return err
}
defer outputFile.Close()
outputWriter = outputFile
}
encoder := json.NewEncoder(outputWriter)
encoder.SetIndent("", " ")
var headlessRule option.DefaultHeadlessRule
defaultRule := geosite.Compile(sourceSet)
headlessRule.Domain = defaultRule.Domain
headlessRule.DomainSuffix = defaultRule.DomainSuffix
headlessRule.DomainKeyword = defaultRule.DomainKeyword
headlessRule.DomainRegex = defaultRule.DomainRegex
var plainRuleSet option.PlainRuleSetCompat
plainRuleSet.Version = C.RuleSetVersion2
plainRuleSet.Options.Rules = []option.HeadlessRule{
{
Type: C.RuleTypeDefault,
DefaultOptions: headlessRule,
},
}
return encoder.Encode(plainRuleSet)
}

View File

@@ -0,0 +1,50 @@
package main
import (
"os"
"sort"
"github.com/sagernet/sing-box/log"
F "github.com/sagernet/sing/common/format"
"github.com/spf13/cobra"
)
var commandGeositeList = &cobra.Command{
Use: "list <category>",
Short: "List geosite categories",
Run: func(cmd *cobra.Command, args []string) {
err := geositeList()
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGeoSite.AddCommand(commandGeositeList)
}
func geositeList() error {
var geositeEntry []struct {
category string
items int
}
for _, category := range geositeCodeList {
sourceSet, err := geositeReader.Read(category)
if err != nil {
return err
}
geositeEntry = append(geositeEntry, struct {
category string
items int
}{category, len(sourceSet)})
}
sort.SliceStable(geositeEntry, func(i, j int) bool {
return geositeEntry[i].items < geositeEntry[j].items
})
for _, entry := range geositeEntry {
os.Stdout.WriteString(F.ToString(entry.category, " (", entry.items, ")\n"))
}
return nil
}

View File

@@ -0,0 +1,97 @@
package main
import (
"os"
"sort"
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
"github.com/spf13/cobra"
)
var commandGeositeLookup = &cobra.Command{
Use: "lookup [category] <domain>",
Short: "Check if a domain is in the geosite",
Args: cobra.RangeArgs(1, 2),
Run: func(cmd *cobra.Command, args []string) {
var (
source string
target string
)
switch len(args) {
case 1:
target = args[0]
case 2:
source = args[0]
target = args[1]
}
err := geositeLookup(source, target)
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGeoSite.AddCommand(commandGeositeLookup)
}
func geositeLookup(source string, target string) error {
var sourceMatcherList []struct {
code string
matcher *searchGeositeMatcher
}
if source != "" {
sourceSet, err := geositeReader.Read(source)
if err != nil {
return err
}
sourceMatcher, err := newSearchGeositeMatcher(sourceSet)
if err != nil {
return E.Cause(err, "compile code: "+source)
}
sourceMatcherList = []struct {
code string
matcher *searchGeositeMatcher
}{
{
code: source,
matcher: sourceMatcher,
},
}
} else {
for _, code := range geositeCodeList {
sourceSet, err := geositeReader.Read(code)
if err != nil {
return err
}
sourceMatcher, err := newSearchGeositeMatcher(sourceSet)
if err != nil {
return E.Cause(err, "compile code: "+code)
}
sourceMatcherList = append(sourceMatcherList, struct {
code string
matcher *searchGeositeMatcher
}{
code: code,
matcher: sourceMatcher,
})
}
}
sort.SliceStable(sourceMatcherList, func(i, j int) bool {
return sourceMatcherList[i].code < sourceMatcherList[j].code
})
for _, matcherItem := range sourceMatcherList {
if matchRule := matcherItem.matcher.Match(target); matchRule != "" {
os.Stdout.WriteString("Match code (")
os.Stdout.WriteString(matcherItem.code)
os.Stdout.WriteString(") ")
os.Stdout.WriteString(matchRule)
os.Stdout.WriteString("\n")
}
}
return nil
}

View File

@@ -0,0 +1,56 @@
package main
import (
"regexp"
"strings"
"github.com/sagernet/sing-box/common/geosite"
)
type searchGeositeMatcher struct {
domainMap map[string]bool
suffixList []string
keywordList []string
regexList []string
}
func newSearchGeositeMatcher(items []geosite.Item) (*searchGeositeMatcher, error) {
options := geosite.Compile(items)
domainMap := make(map[string]bool)
for _, domain := range options.Domain {
domainMap[domain] = true
}
rule := &searchGeositeMatcher{
domainMap: domainMap,
suffixList: options.DomainSuffix,
keywordList: options.DomainKeyword,
regexList: options.DomainRegex,
}
return rule, nil
}
func (r *searchGeositeMatcher) Match(domain string) string {
if r.domainMap[domain] {
return "domain=" + domain
}
for _, suffix := range r.suffixList {
if strings.HasSuffix(domain, suffix) {
return "domain_suffix=" + suffix
}
}
for _, keyword := range r.keywordList {
if strings.Contains(domain, keyword) {
return "domain_keyword=" + keyword
}
}
for _, regexStr := range r.regexList {
regex, err := regexp.Compile(regexStr)
if err != nil {
continue
}
if regex.MatchString(domain) {
return "domain_regex=" + regexStr
}
}
return ""
}

143
cmd/sing-box/cmd_merge.go Normal file
View File

@@ -0,0 +1,143 @@
package main
import (
"bytes"
"os"
"path/filepath"
"strings"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/rw"
"github.com/spf13/cobra"
)
var commandMerge = &cobra.Command{
Use: "merge <output-path>",
Short: "Merge configurations",
Run: func(cmd *cobra.Command, args []string) {
err := merge(args[0])
if err != nil {
log.Fatal(err)
}
},
Args: cobra.ExactArgs(1),
}
func init() {
mainCommand.AddCommand(commandMerge)
}
func merge(outputPath string) error {
mergedOptions, err := readConfigAndMerge()
if err != nil {
return err
}
err = mergePathResources(&mergedOptions)
if err != nil {
return err
}
buffer := new(bytes.Buffer)
encoder := json.NewEncoder(buffer)
encoder.SetIndent("", " ")
err = encoder.Encode(mergedOptions)
if err != nil {
return E.Cause(err, "encode config")
}
if existsContent, err := os.ReadFile(outputPath); err != nil {
if string(existsContent) == buffer.String() {
return nil
}
}
err = rw.MkdirParent(outputPath)
if err != nil {
return err
}
err = os.WriteFile(outputPath, buffer.Bytes(), 0o644)
if err != nil {
return err
}
outputPath, _ = filepath.Abs(outputPath)
os.Stderr.WriteString(outputPath + "\n")
return nil
}
func mergePathResources(options *option.Options) error {
for _, inbound := range options.Inbounds {
if tlsOptions, containsTLSOptions := inbound.Options.(option.InboundTLSOptionsWrapper); containsTLSOptions {
tlsOptions.ReplaceInboundTLSOptions(mergeTLSInboundOptions(tlsOptions.TakeInboundTLSOptions()))
}
}
for _, outbound := range options.Outbounds {
switch outbound.Type {
case C.TypeSSH:
mergeSSHOutboundOptions(outbound.Options.(*option.SSHOutboundOptions))
}
if tlsOptions, containsTLSOptions := outbound.Options.(option.OutboundTLSOptionsWrapper); containsTLSOptions {
tlsOptions.ReplaceOutboundTLSOptions(mergeTLSOutboundOptions(tlsOptions.TakeOutboundTLSOptions()))
}
}
return nil
}
func mergeTLSInboundOptions(options *option.InboundTLSOptions) *option.InboundTLSOptions {
if options == nil {
return nil
}
if options.CertificatePath != "" {
if content, err := os.ReadFile(options.CertificatePath); err == nil {
options.Certificate = trimStringArray(strings.Split(string(content), "\n"))
}
}
if options.KeyPath != "" {
if content, err := os.ReadFile(options.KeyPath); err == nil {
options.Key = trimStringArray(strings.Split(string(content), "\n"))
}
}
if options.ECH != nil {
if options.ECH.KeyPath != "" {
if content, err := os.ReadFile(options.ECH.KeyPath); err == nil {
options.ECH.Key = trimStringArray(strings.Split(string(content), "\n"))
}
}
}
return options
}
func mergeTLSOutboundOptions(options *option.OutboundTLSOptions) *option.OutboundTLSOptions {
if options == nil {
return nil
}
if options.CertificatePath != "" {
if content, err := os.ReadFile(options.CertificatePath); err == nil {
options.Certificate = trimStringArray(strings.Split(string(content), "\n"))
}
}
if options.ECH != nil {
if options.ECH.ConfigPath != "" {
if content, err := os.ReadFile(options.ECH.ConfigPath); err == nil {
options.ECH.Config = trimStringArray(strings.Split(string(content), "\n"))
}
}
}
return options
}
func mergeSSHOutboundOptions(options *option.SSHOutboundOptions) {
if options.PrivateKeyPath != "" {
if content, err := os.ReadFile(os.ExpandEnv(options.PrivateKeyPath)); err == nil {
options.PrivateKey = trimStringArray(strings.Split(string(content), "\n"))
}
}
}
func trimStringArray(array []string) []string {
return common.Filter(array, func(it string) bool {
return strings.TrimSpace(it) != ""
})
}

Some files were not shown because too many files have changed in this diff Show More