Compare commits

...

150 Commits

Author SHA1 Message Date
Sergei Maklagin
a47bdbe2ae Update wireguard 2026-05-29 03:55:48 +03:00
Sergei Maklagin
8e8eca01fe Resolve conflicts 2026-05-29 01:33:34 +03:00
Sergei Maklagin
41e815e18b Update sing-box core, refactor MASQUE, update XHTTP 2026-05-29 01:31:57 +03:00
世界
1086ab2563 Bump version 1.13.12 2026-05-14 15:11:09 +08:00
世界
2b995ea10a Run lint for all platforms in workflow 2026-05-13 23:39:27 +08:00
世界
5e7fd7ad78 Fix lint errors 2026-05-13 23:39:27 +08:00
世界
338aa551d1 Update golangci-lint configuration 2026-05-13 16:38:08 +08:00
世界
b6f7d62bb6 sing: Update contextjson 2026-05-13 16:28:50 +08:00
世界
76880eb46b Update naiveproxy to v148.0.7778.96-1 2026-05-13 16:28:49 +08:00
世界
b4c625543a Fix tailscale crash at start 2026-05-13 16:28:49 +08:00
世界
2f139af2d1 Fix naive inbound close 2026-05-13 16:28:49 +08:00
世界
383a1824c1 dns: Refactor reordered pool 2026-05-13 16:28:49 +08:00
世界
d166f0da8b dns: Fix conn pool leak 2026-05-13 16:28:49 +08:00
世界
6475a5e036 Skip kickWriteHandshake for server first protocols 2026-05-13 16:28:49 +08:00
世界
6548c17110 dns: Fix deadline 2026-05-13 16:28:49 +08:00
世界
d1c53d21fe release: Add replace_macos_standalone make target 2026-05-13 13:37:40 +08:00
Sergei Maklagin
b586c4f313 Remove unused file 2026-05-11 02:18:20 +03:00
Sergei Maklagin
3bd162ed6f Add new admin panel, failover, dns fallback, providers, limiters. Update XHTTP 2026-05-11 00:59:35 +03:00
Sergei Maklagin
652e0baf57 Fix go.sum 2026-05-05 12:59:07 +03:00
Sergei Maklagin
8be5c8fabe Fix legacy build 2026-05-05 12:58:01 +03:00
Sergei Maklagin
eb36c934a7 Fix examples 2026-05-03 04:14:59 +03:00
Sergei Maklagin
52edfdb059 Remove IPBlocklist and IPAllowlist 2026-05-03 04:04:16 +03:00
Sergei Maklagin
eecab479fa Fix Dockerfile 2026-05-02 15:27:46 +03:00
Sergei Maklagin
d8670a742a Merge branch 'extended' of https://github.com/shtorm-7/sing-box-extended into extended 2026-05-02 15:00:57 +03:00
Sergei Maklagin
e2ef7d83a1 Fix creating config for WARP 2026-05-02 14:54:26 +03:00
Shtorm
af75b8074f Update README.md
Signed-off-by: Shtorm <108103062+shtorm-7@users.noreply.github.com>
2026-04-30 23:39:22 +03:00
Sergei Maklagin
2e0306ae41 Disable Wireguard ICMP 2026-04-30 19:20:23 +03:00
Sergei Maklagin
ee2945ac8f Fix OverrideGateway 2026-04-30 19:19:40 +03:00
Sergei Maklagin
b2503ca860 Update chi 2026-04-30 19:18:50 +03:00
Sergei Maklagin
dcb1da8683 Fix examples 2026-04-30 13:20:45 +03:00
Sergei Maklagin
22aeb48794 Merge branch 'extended' of https://github.com/shtorm-7/sing-box-extended into extended 2026-04-30 13:14:37 +03:00
Sergei Maklagin
019103587b Update examples 2026-04-30 13:14:17 +03:00
Sergei Maklagin
3139f18bf1 Update android build tags 2026-04-30 12:17:37 +03:00
Shtorm
493538c743 Update README.md
Signed-off-by: Shtorm <108103062+shtorm-7@users.noreply.github.com>
2026-04-30 11:15:15 +03:00
Shtorm
578bc159fb Update README.md
Signed-off-by: Shtorm <108103062+shtorm-7@users.noreply.github.com>
2026-04-30 01:15:21 +03:00
Shtorm
f0c317cb0b Update README.md
Signed-off-by: Shtorm <108103062+shtorm-7@users.noreply.github.com>
2026-04-30 01:12:28 +03:00
Shtorm
32bc1a9fce Update README.md
Signed-off-by: Shtorm <108103062+shtorm-7@users.noreply.github.com>
2026-04-30 01:08:36 +03:00
Sergei Maklagin
41922ba731 Update README.md 2026-04-29 22:40:15 +03:00
Sergei Maklagin
bf8fe79a7a Fix go.mod 2026-04-29 22:16:10 +03:00
Sergei Maklagin
398b6387ab Resolve conflicts 2026-04-29 22:14:30 +03:00
Sergei Maklagin
04908a6a67 Add MTProxy, MASQUE, VPN, Link parser. Update AmneziaWG. Remove Tunneling 2026-04-29 22:11:30 +03:00
世界
ddb757a25c Reduce built-in certificate store memory 2026-04-28 07:44:20 +08:00
Sergei Maklagin
88a80e961b Fix README.md 2026-04-27 11:38:09 +03:00
Sergei Maklagin
05b82f63da Update tailscale 2026-04-26 21:31:13 +03:00
Sergei Maklagin
00c08995c7 Update sing-box core 2026-04-26 21:11:31 +03:00
世界
553cfa1f9f Bump version 2026-04-23 07:30:34 +08:00
世界
f102ef1d94 Fix process search skipped for Android again 2026-04-23 05:52:22 +08:00
世界
3312b8da50 Clean up DNS transports 2026-04-23 02:30:32 +08:00
Sergei Maklagin
09f9f114aa Update sing-box core 2026-04-22 19:23:23 +03:00
世界
a3fc14f35f Bump version 2026-04-22 13:40:36 +08:00
世界
8947cb243e sing: Fix UoT write race 2026-04-21 18:54:52 +08:00
世界
83d3a6d4e1 Hide lifecycle logs for fast operations 2026-04-21 17:15:16 +08:00
世界
71f6a2ab4e Fix process search skipped for TUN 2026-04-21 15:45:05 +08:00
世界
d942ecc904 Bump version 1.13.9 2026-04-20 09:49:39 +08:00
世界
c3de6a25fb documentation: Remove warp ads 2026-04-20 09:49:39 +08:00
世界
b3523abad5 tun: Fix multi include/exclude interfaces 2026-04-20 09:49:39 +08:00
世界
60dd7ea5c9 Simplify lifecycle logs 2026-04-20 09:49:39 +08:00
世界
e4bc459975 Skip process search for non-local source addresses 2026-04-20 09:49:39 +08:00
世界
3124cdd661 Fix windows bssid matching 2026-04-20 09:49:39 +08:00
世界
b3606e33a6 release: fix apk package file ownership 2026-04-20 09:49:39 +08:00
世界
9b72b352d5 sing: Fix UoT connect race 2026-04-20 00:10:46 +08:00
世界
fb61987d93 tun: memmod: be more resilient toward weird PE files 2026-04-19 21:54:20 +08:00
世界
a80ef94f09 Fix tailscale endpoint early-start close panic 2026-04-19 21:15:54 +08:00
世界
3a236d9c3c fswatch: Fix close 2026-04-19 20:41:33 +08:00
世界
ca76c56377 daemon: Fix registry leak 2026-04-19 20:39:38 +08:00
世界
0bd109d7bc sing: Fix interface finder 2026-04-19 20:38:52 +08:00
Sergei Maklagin
1995ba4279 Update sing-box core 2026-04-17 01:10:31 +03:00
世界
9b155ba467 Fix rdrc cache 2026-04-16 16:45:50 +08:00
世界
7ed5ef6da4 sing: Fix udpnat2 timeout 2026-04-16 16:45:38 +08:00
世界
bb3ad9c694 documentation: Fix typo 2026-04-14 16:00:47 +08:00
世界
d5adb54bc6 Bump version 2026-04-14 14:33:19 +08:00
世界
1cfcea769f Update Go to 1.25.9 2026-04-14 14:26:59 +08:00
世界
f43fc797d4 Update naiveproxy to v147.0.7727.49-1 2026-04-14 14:24:21 +08:00
世界
8e3176b789 Fix FakeIP returning error for unconfigured address family
Return SUCCESS with empty answers instead of an error when the
queried address family has no range configured. Reject configurations
where neither inet4_range nor inet6_range is set.
2026-04-14 14:15:20 +08:00
世界
025b947a24 Bump version 2026-04-10 16:23:45 +08:00
世界
76fa3c2e5e tun: Fixes 2026-04-10 14:13:06 +08:00
世界
53db1f178c Fix tailscale crash 2026-04-10 14:09:03 +08:00
世界
55ec8abf17 Fix local DNS server for Android 2026-04-10 14:08:57 +08:00
Berkay Özdemirci
5a957fd750 Fix EDNS OPT record corruption in DNS cache
The TTL computation and assignment loops treat OPT record's Hdr.Ttl
as a regular TTL, but per RFC 6891 it encodes EDNS0 metadata
(ExtRCode|Version|Flags). This corrupts cached responses causing
systemd-resolved to reject them with EDNS version 255.

Also fix pointer aliasing: storeCache() stored raw *dns.Msg pointer
so subsequent mutations by Exchange() corrupted cached data.

- Skip OPT records in all TTL loops (Exchange + loadResponse)
- Use message.Copy() in storeCache() to isolate cache from mutations
2026-04-10 14:08:24 +08:00
TargetLocked
7c3d8cf8db Fix disable tcp keep alive 2026-04-10 13:29:15 +08:00
Sergei Maklagin
2d33dee415 Update sing-box core 2026-04-06 20:54:24 +03:00
世界
813b634d08 Bump version 2026-04-06 23:09:11 +08:00
hdrover
d9b435fb62 Fix naive inbound padding bytes 2026-04-06 22:33:11 +08:00
世界
354b4b040e sing: Fix vectorised readv iovec length calculation
This does not seem to affect any actual paths in the sing-box.
2026-04-01 16:16:58 +08:00
世界
7ffdc48b49 Bump version 2026-03-30 23:03:43 +08:00
世界
e15bdf11eb sing: Minor fixes 2026-03-30 22:58:11 +08:00
世界
e3bcb06c3e platform: Add HTTPResponse.WriteToWithProgress 2026-03-30 22:42:36 +08:00
世界
84d2280960 quic: Fix protocol client close & Sync hysteria bbr fix 2026-03-30 22:42:36 +08:00
世界
4fd2532b0a Fix naive quic error message 2026-03-30 22:42:36 +08:00
Zhengchao Ding
02ccde6c71 fix(rpm): add vendor field to fpm config to avoid (none) vendor
Co-authored-by: Hyper <hypar@disroot.org>
2026-03-30 22:09:54 +08:00
世界
e98b4ad449 Fix WireGuard shutdown race crashing
Stop peer goroutines before closing the TUN device to prevent
RoutineSequentialReceiver from calling Write on a nil dispatcher.
2026-03-26 16:33:21 +08:00
世界
d09182614c Bump version 2026-03-26 13:28:33 +08:00
世界
6381de7bab route: Fix query_type never matching in rule_set headless rules 2026-03-26 13:26:18 +08:00
世界
b0c6762bc1 route: merge rule_set branches into outer rules
Treat rule_set items as merged branches instead of standalone boolean
sub-items.

Evaluate each branch inside a referenced rule-set as if it were merged
into the outer rule and keep OR semantics between branches. This lets
outer grouped fields satisfy matching groups inside a branch without
introducing a standalone outer fallback or cross-branch state union.

Keep inherited grouped state outside inverted default and logical
branches. Negated rule-set branches now evaluate !(...) against their
own conditions and only reapply the outer grouped match after negation
succeeds, so configs like outer-group && !inner-condition continue to
work.

Add regression tests for same-group merged matches, cross-group and
extra-AND failures, DNS merged-branch behaviour, and inverted merged
branches. Update the route and DNS rule docs to clarify that rule-set
branches merge into the outer rule while keeping OR semantics between
branches.
2026-03-25 14:00:29 +08:00
世界
7425100bac release: Refactor release tracks for Linux packages and Docker
Support 4 release tracks instead of 2:
- sing-box / latest (stable release)
- sing-box-beta / latest-beta (stable pre-release)
- sing-box-testing / latest-testing (testing branch)
- sing-box-oldstable / latest-oldstable (oldstable branch)

Track is detected via git branch --contains and git tag,
replacing the old version-string hyphen check.
2026-03-24 15:03:43 +08:00
世界
d454aa0fdf route: formalize nested rule_set group-state semantics
Before 795d1c289, nested rule-set evaluation reused the parent rule
match cache. In practice, this meant these fields leaked across nested
evaluation:

- SourceAddressMatch
- SourcePortMatch
- DestinationAddressMatch
- DestinationPortMatch
- DidMatch

That leak had two opposite effects.

First, it made included rule-sets partially behave like the docs'
"merged" semantics. For example, if an outer route rule had:

  rule_set = ["geosite-additional-!cn"]
  ip_cidr  = 104.26.10.0/24

and the inline rule-set matched `domain_suffix = speedtest.net`, the
inner match could set `DestinationAddressMatch = true` and the outer
rule would then pass its destination-address group check. This is why
some `rule_set + ip_cidr` combinations used to work.

But the same leak also polluted sibling rules and sibling rule-sets.
A branch could partially match one group, then fail later, and still
leave that group cache set for the next branch. This broke cases such
as gh-3485: with `rule_set = [test1, test2]`, `test1` could touch
destination-address cache before an AdGuard `@@` exclusion made the
whole branch fail, and `test2` would then run against dirty state.

795d1c289 fixed that by cloning metadata for nested rule-set/rule
evaluation and resetting the rule match cache for each branch. That
stopped sibling pollution, but it also removed the only mechanism by
which a successful nested branch could affect the parent rule's grouped
matching state.

As a result, nested rule-sets became pure boolean sub-items against the
outer rule. The previous example stopped working: the inner
`domain_suffix = speedtest.net` still matched, but the outer rule no
longer observed any destination-address-group success, so it fell
through to `final`.

This change makes the semantics explicit instead of relying on cache
side effects:

- `rule_set: ["a", "b"]` is OR
- rules inside one rule-set are OR
- each nested branch is evaluated in isolation
- failed branches contribute no grouped match state
- a successful branch contributes its grouped match state back to the
  parent rule
- grouped state from different rule-sets must not be combined together
  to satisfy one outer rule

In other words, rule-sets now behave as "OR branches whose successful
group matches merge into the outer rule", which matches the documented
intent without reintroducing cross-branch cache leakage.
2026-03-24 15:03:43 +08:00
世界
a3623eb41a tun: Fix system stack rewriting TUN subnet destinations to loopback 2026-03-23 19:38:55 +08:00
世界
72bc4c1f87 Fix DNS transport returning error for empty AAAA response
Closes #3925
2026-03-23 19:21:55 +08:00
世界
9ac1e2ff32 Match package_name in process_path rule on Android 2026-03-23 18:57:35 +08:00
世界
0045103d14 Fix package_name shared uid matching 2026-03-23 18:57:35 +08:00
世界
d2a933784c Optimize Darwin process finder 2026-03-23 18:57:35 +08:00
世界
3f05a37f65 Optimize Linux process finder 2026-03-23 18:57:35 +08:00
世界
b8e5a71450 Add process information cache to avoid duplicate lookups
PreMatch and full match phases each created a fresh InboundContext,
causing process search (expensive OS syscalls) to run twice per
connection. Use a freelru ShardedLRU cache with 200ms TTL to serve
the second lookup from cache.
2026-03-23 14:26:45 +08:00
世界
c13faa8e3c tailscale: Only set ProcessLocalIPs/ProcessSubnets for fake TUN 2026-03-23 14:16:40 +08:00
世界
7623bcd19e Fix DialerForICMPDestination 2026-03-23 13:58:55 +08:00
世界
795d1c2892 Fix nested rule-set match cache isolation 2026-03-23 12:26:19 +08:00
世界
6913b11e0a Reject removed legacy inbound fields instead of silently ignoring 2026-03-21 17:16:10 +08:00
世界
1e57c06295 daemon: Allow StartOrReloadService to recover from FATAL state 2026-03-21 13:37:14 +08:00
世界
ea464cef8d daemon: Fix CloseService leaving instance non-nil on close error 2026-03-21 13:23:57 +08:00
Andrew Novikov
a8e3cd3256 tun: Fix nfqueue not working in prerouting 2026-03-17 11:05:40 +08:00
世界
686cf1f304 documentation: Fix Chinese link anchors 2026-03-16 12:24:10 +08:00
世界
9fbfb87723 documentation: Fix unicode heading anchors 2026-03-16 12:10:32 +08:00
世界
d2fa21d07b Deprecate Socksaddr.IsFqdn: do not reject potentially valid domain names 2026-03-16 09:37:59 +08:00
世界
d3768cca36 Bump version 2026-03-15 17:56:37 +08:00
世界
0889ddd001 Fix connector canceled dial cleanup 2026-03-15 17:56:37 +08:00
深鸣
f46fbf188a documentation: Minor fixes 2026-03-15 17:56:37 +08:00
世界
f2d15139f5 tun: Fix nftables single include_uid not working 2026-03-15 16:58:34 +08:00
世界
041646b728 Fix kTLS crash 2026-03-14 21:38:38 +08:00
世界
b990de2e12 tun: Fix "Fix auto_redirect dropping SO_BINDTODEVICE traffic" 2026-03-14 21:38:38 +08:00
世界
fe585157d2 Bump version 2026-03-14 21:38:38 +08:00
世界
eed6a36e5d tun:Fix auto_redirect dropping SO_BINDTODEVICE traffic 2026-03-14 21:38:38 +08:00
世界
eb0f38544c tailscale: Fix system interface rules 2026-03-14 21:38:38 +08:00
Sergei Maklagin
cb3131b3d4 Update dependencies 2026-03-11 20:10:06 +03: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
Sergei Maklagin
47a62927f0 Merge branch 'extended' of https://github.com/shtorm-7/sing-box-extended into extended 2026-03-10 09:06:57 +03: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
Shtorm
f95b34a8a7 Update README.md
Signed-off-by: Shtorm <108103062+shtorm-7@users.noreply.github.com>
2026-02-26 02:43:48 +03:00
577 changed files with 49856 additions and 8212 deletions

View File

@@ -4,6 +4,7 @@
--license GPL-3.0-or-later --license GPL-3.0-or-later
--description "The universal proxy platform." --description "The universal proxy platform."
--url "https://sing-box.sagernet.org/" --url "https://sing-box.sagernet.org/"
--vendor SagerNet
--maintainer "nekohasekai <contact-git@sekai.icu>" --maintainer "nekohasekai <contact-git@sekai.icu>"
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues" --deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
--no-deb-generate-changes --no-deb-generate-changes

View File

@@ -1 +1 @@
cba7b9ac0399055aa49fbdc57c03c374f58e1597 2faf34666c2cc8234f10f2ab6d4c4d6104d34ae2

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

@@ -0,0 +1,94 @@
#!/usr/bin/env bash
set -e -o pipefail
prepare_apk_root() {
# apk mkpkg resolves owner/group names through --root/etc/{passwd,group}.
APK_ROOT_DIR=$(mktemp -d)
mkdir -p "$APK_ROOT_DIR/etc"
cat > "$APK_ROOT_DIR/etc/passwd" <<EOF
root:x:$(id -u):$(id -g):root:/root:/sbin/nologin
EOF
cat > "$APK_ROOT_DIR/etc/group" <<EOF
root:x:$(id -g):root
EOF
}
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)
prepare_apk_root
trap 'rm -rf "$ROOT_DIR" "$APK_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 --root "$APK_ROOT_DIR" 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"

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

@@ -0,0 +1,93 @@
#!/usr/bin/env bash
set -e -o pipefail
prepare_apk_root() {
# apk mkpkg resolves owner/group names through --root/etc/{passwd,group}.
APK_ROOT_DIR=$(mktemp -d)
mkdir -p "$APK_ROOT_DIR/etc"
cat > "$APK_ROOT_DIR/etc/passwd" <<EOF
root:x:$(id -u):$(id -g):root:/root:/sbin/nologin
EOF
cat > "$APK_ROOT_DIR/etc/group" <<EOF
root:x:$(id -g):root
EOF
}
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)
prepare_apk_root
trap 'rm -rf "$ROOT_DIR" "$APK_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 --root "$APK_ROOT_DIR" 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"

33
.github/detect_track.sh vendored Executable file
View File

@@ -0,0 +1,33 @@
#!/usr/bin/env bash
set -euo pipefail
branches=$(git branch -r --contains HEAD)
if echo "$branches" | grep -q 'origin/stable'; then
track=stable
elif echo "$branches" | grep -q 'origin/testing'; then
track=testing
elif echo "$branches" | grep -q 'origin/oldstable'; then
track=oldstable
else
echo "ERROR: HEAD is not on any known release branch (stable/testing/oldstable)" >&2
exit 1
fi
if [[ "$track" == "stable" ]]; then
tag=$(git describe --tags --exact-match HEAD 2>/dev/null || true)
if [[ -n "$tag" && "$tag" == *"-"* ]]; then
track=beta
fi
fi
case "$track" in
stable) name=sing-box; docker_tag=latest ;;
beta) name=sing-box-beta; docker_tag=latest-beta ;;
testing) name=sing-box-testing; docker_tag=latest-testing ;;
oldstable) name=sing-box-oldstable; docker_tag=latest-oldstable ;;
esac
echo "track=${track} name=${name} docker_tag=${docker_tag}" >&2
echo "TRACK=${track}" >> "$GITHUB_ENV"
echo "NAME=${name}" >> "$GITHUB_ENV"
echo "DOCKER_TAG=${docker_tag}" >> "$GITHUB_ENV"

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.9"
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"

View File

@@ -1,16 +1,35 @@
#!/usr/bin/env bash #!/usr/bin/env bash
VERSION="1.25.7" set -euo pipefail
mkdir -p $HOME/go VERSION="1.25.9"
cd $HOME/go 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" wget "https://dl.google.com/go/go${VERSION}.linux-amd64.tar.gz"
tar -xzf "go${VERSION}.linux-amd64.tar.gz" tar -xzf "go${VERSION}.linux-amd64.tar.gz"
mv go go_win7 mv go go_win7
cd go_win7 cd go_win7
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557 # modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
# this patch file only works on golang1.25.x # these patch URLs only work on golang1.25.x
# that means after golang1.26 release it must be changed # that means after golang1.26 release it must be changed
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.25/ # see: https://github.com/MetaCubeX/go/commits/release-branch.go1.25/
# revert: # revert:
@@ -18,10 +37,10 @@ cd go_win7
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7" # 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround" # 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries" # 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\""
alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"' for patch_commit in "${PATCH_COMMITS[@]}"; do
curl "${CURL_ARGS[@]}" "https://github.com/MetaCubeX/go/commit/${patch_commit}.diff" | patch --verbose -p 1
curl https://github.com/MetaCubeX/go/commit/8cb5472d94c34b88733a81091bd328e70ee565a4.diff | patch --verbose -p 1 done
curl https://github.com/MetaCubeX/go/commit/6788c4c6f9fafb56729bad6b660f7ee2272d699f.diff | patch --verbose -p 1
curl https://github.com/MetaCubeX/go/commit/a5b2168bb836ed9d6601c626f95e56c07923f906.diff | patch --verbose -p 1
curl https://github.com/MetaCubeX/go/commit/f56f1e23507e646c85243a71bde7b9629b2f970c.diff | patch --verbose -p 1

View File

@@ -47,7 +47,7 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: ~1.25.7 go-version: ~1.25.9
- name: Check input version - name: Check input version
if: github.event_name == 'workflow_dispatch' if: github.event_name == 'workflow_dispatch'
run: |- run: |-
@@ -72,27 +72,27 @@ jobs:
include: include:
- { os: linux, arch: amd64, variant: purego, naive: true } - { os: linux, arch: amd64, variant: purego, naive: true }
- { os: linux, arch: amd64, variant: glibc, naive: true } - { os: linux, arch: amd64, variant: glibc, naive: true }
- { os: linux, arch: amd64, variant: musl, naive: true, debian: amd64, rpm: x86_64, pacman: x86_64, openwrt: "x86_64" } - { os: linux, arch: amd64, variant: musl, naive: true, debian: amd64, rpm: x86_64, pacman: x86_64, alpine: x86_64, openwrt: "x86_64" }
- { os: linux, arch: arm64, variant: purego, naive: true } - { os: linux, arch: arm64, variant: purego, naive: true }
- { os: linux, arch: arm64, variant: glibc, naive: true } - { os: linux, arch: arm64, variant: glibc, naive: true }
- { os: linux, arch: arm64, variant: musl, naive: true, debian: arm64, rpm: aarch64, pacman: aarch64, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" } - { os: linux, arch: arm64, variant: musl, naive: true, debian: arm64, rpm: aarch64, pacman: aarch64, alpine: aarch64, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" }
- { os: linux, arch: "386", go386: sse2 } - { os: linux, arch: "386", go386: sse2 }
- { os: linux, arch: "386", variant: glibc, naive: true, go386: sse2 } - { os: linux, arch: "386", variant: glibc, naive: true, go386: sse2 }
- { os: linux, arch: "386", variant: musl, naive: true, go386: sse2, debian: i386, rpm: i386, openwrt: "i386_pentium4" } - { os: linux, arch: "386", variant: musl, naive: true, go386: sse2, debian: i386, rpm: i386, alpine: x86, openwrt: "i386_pentium4" }
- { os: linux, arch: arm, goarm: "7" } - { os: linux, arch: arm, goarm: "7" }
- { os: linux, arch: arm, variant: glibc, naive: true, goarm: "7" } - { os: linux, arch: arm, variant: glibc, naive: true, goarm: "7" }
- { os: linux, arch: arm, variant: musl, naive: true, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl, openwrt: "arm_cortex-a5_vfpv4 arm_cortex-a7_neon-vfpv4 arm_cortex-a7_vfpv4 arm_cortex-a8_vfpv3 arm_cortex-a9_neon arm_cortex-a9_vfpv3-d16 arm_cortex-a15_neon-vfpv4" } - { os: linux, arch: arm, variant: musl, naive: true, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl, alpine: armv7, openwrt: "arm_cortex-a5_vfpv4 arm_cortex-a7_neon-vfpv4 arm_cortex-a7_vfpv4 arm_cortex-a8_vfpv3 arm_cortex-a9_neon arm_cortex-a9_vfpv3-d16 arm_cortex-a15_neon-vfpv4" }
- { os: linux, arch: mipsle, gomips: hardfloat, naive: true, variant: glibc } - { os: linux, arch: mipsle, gomips: hardfloat, naive: true, variant: glibc }
- { os: linux, arch: mipsle, gomips: softfloat, naive: true, variant: musl, debian: mipsel, rpm: mipsel, openwrt: "mipsel_24kc mipsel_74kc mipsel_mips32" } - { os: linux, arch: mipsle, gomips: softfloat, naive: true, variant: musl, debian: mipsel, rpm: mipsel, openwrt: "mipsel_24kc mipsel_74kc mipsel_mips32" }
- { os: linux, arch: mips64le, gomips: hardfloat, naive: true, variant: glibc, debian: mips64el, rpm: mips64el } - { os: linux, arch: mips64le, gomips: hardfloat, naive: true, variant: glibc, debian: mips64el, rpm: mips64el }
- { os: linux, arch: riscv64, naive: true, variant: glibc } - { os: linux, arch: riscv64, naive: true, variant: glibc }
- { os: linux, arch: riscv64, naive: true, variant: musl, debian: riscv64, rpm: riscv64, openwrt: "riscv64_generic" } - { os: linux, arch: riscv64, naive: true, variant: musl, debian: riscv64, rpm: riscv64, alpine: riscv64, openwrt: "riscv64_generic" }
- { os: linux, arch: loong64, naive: true, variant: glibc } - { os: linux, arch: loong64, naive: true, variant: glibc }
- { os: linux, arch: loong64, naive: true, variant: musl, debian: loongarch64, rpm: loongarch64, openwrt: "loongarch64_generic" } - { os: linux, arch: loong64, naive: true, variant: musl, debian: loongarch64, rpm: loongarch64, alpine: loongarch64, openwrt: "loongarch64_generic" }
- { os: linux, arch: "386", go386: softfloat, openwrt: "i386_pentium-mmx" } - { os: linux, arch: "386", go386: softfloat, openwrt: "i386_pentium-mmx" }
- { os: linux, arch: arm, goarm: "5", openwrt: "arm_arm926ej-s arm_cortex-a7 arm_cortex-a9 arm_fa526 arm_xscale" } - { os: linux, arch: arm, goarm: "5", openwrt: "arm_arm926ej-s arm_cortex-a7 arm_cortex-a9 arm_fa526 arm_xscale" }
@@ -121,15 +121,10 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Setup Go - name: Setup Go
if: ${{ ! (matrix.legacy_win7 || matrix.legacy_go124) }} if: ${{ ! matrix.legacy_win7 }}
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: ~1.25.7 go-version: ~1.25.9
- name: Setup Go 1.24
if: matrix.legacy_go124
uses: actions/setup-go@v5
with:
go-version: ~1.24.10
- name: Cache Go for Windows 7 - name: Cache Go for Windows 7
if: matrix.legacy_win7 if: matrix.legacy_win7
id: cache-go-for-windows7 id: cache-go-for-windows7
@@ -137,9 +132,11 @@ jobs:
with: with:
path: | path: |
~/go/go_win7 ~/go/go_win7
key: go_win7_1255 key: go_win7_1258
- name: Setup Go for Windows 7 - name: Setup Go for Windows 7
if: matrix.legacy_win7 && steps.cache-go-for-windows7.outputs.cache-hit != 'true' if: matrix.legacy_win7 && steps.cache-go-for-windows7.outputs.cache-hit != 'true'
env:
GITHUB_TOKEN: ${{ github.token }}
run: |- run: |-
.github/setup_go_for_windows7.sh .github/setup_go_for_windows7.sh
- name: Setup Go for Windows 7 - name: Setup Go for Windows 7
@@ -399,6 +396,30 @@ jobs:
.github/deb2ipk.sh "$architecture" "dist/openwrt.deb" "dist/sing-box_${{ needs.calculate_version.outputs.version }}_openwrt_${architecture}.ipk" .github/deb2ipk.sh "$architecture" "dist/openwrt.deb" "dist/sing-box_${{ needs.calculate_version.outputs.version }}_openwrt_${architecture}.ipk"
done done
rm "dist/openwrt.deb" rm "dist/openwrt.deb"
- name: Install apk-tools
if: matrix.openwrt != '' || matrix.alpine != ''
run: |-
docker run --rm -v /usr/local/bin:/mnt alpine:edge sh -c "apk add --no-cache apk-tools-static && cp /sbin/apk.static /mnt/apk && chmod +x /mnt/apk"
- name: Package OpenWrt APK
if: matrix.openwrt != ''
run: |-
set -xeuo pipefail
for architecture in ${{ matrix.openwrt }}; do
.github/build_openwrt_apk.sh \
"$architecture" \
"${{ needs.calculate_version.outputs.version }}" \
"dist/sing-box" \
"dist/sing-box_${{ needs.calculate_version.outputs.version }}_openwrt_${architecture}.apk"
done
- name: Package Alpine APK
if: matrix.alpine != ''
run: |-
set -xeuo pipefail
.github/build_alpine_apk.sh \
"${{ matrix.alpine }}" \
"${{ needs.calculate_version.outputs.version }}" \
"dist/sing-box" \
"dist/sing-box_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.alpine }}.apk"
- name: Archive - name: Archive
run: | run: |
set -xeuo pipefail set -xeuo pipefail
@@ -434,22 +455,36 @@ jobs:
include: include:
- { arch: amd64 } - { arch: amd64 }
- { arch: arm64 } - { arch: arm64 }
- { arch: amd64, legacy_go124: true, legacy_name: "macos-11" } - { arch: amd64, legacy_osx: true, legacy_name: "macos-10.13" }
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Setup Go - name: Setup Go
if: ${{ ! matrix.legacy_go124 }} if: ${{ ! matrix.legacy_osx }}
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: ^1.25.3 go-version: ^1.25.3
- name: Setup Go 1.24 - name: Cache Go for macOS 10.13
if: matrix.legacy_go124 if: matrix.legacy_osx
uses: actions/setup-go@v5 id: cache-go-for-macos1013
uses: actions/cache@v4
with: with:
go-version: ~1.24.6 path: |
~/go/go_osx
key: go_osx_1258
- name: Setup Go for macOS 10.13
if: matrix.legacy_osx && steps.cache-go-for-macos1013.outputs.cache-hit != 'true'
env:
GITHUB_TOKEN: ${{ github.token }}
run: |-
.github/setup_go_for_macos1013.sh
- name: Setup Go for macOS 10.13
if: matrix.legacy_osx
run: |-
echo "PATH=$HOME/go/go_osx/bin:$PATH" >> $GITHUB_ENV
echo "GOROOT=$HOME/go/go_osx" >> $GITHUB_ENV
- name: Set tag - name: Set tag
run: |- run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV" git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
@@ -457,7 +492,7 @@ jobs:
- name: Set build tags - name: Set build tags
run: | run: |
set -xeuo pipefail set -xeuo pipefail
if [[ "${{ matrix.legacy_go124 }}" != "true" ]]; then if [[ "${{ matrix.legacy_osx }}" != "true" ]]; then
TAGS=$(cat release/DEFAULT_BUILD_TAGS) TAGS=$(cat release/DEFAULT_BUILD_TAGS)
else else
TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS) TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS)
@@ -477,6 +512,7 @@ jobs:
CGO_ENABLED: "1" CGO_ENABLED: "1"
GOOS: darwin GOOS: darwin
GOARCH: ${{ matrix.arch }} GOARCH: ${{ matrix.arch }}
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.legacy_osx && '10.13' || '' }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Set name - name: Set name
run: |- run: |-
@@ -605,7 +641,7 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: ~1.25.7 go-version: ~1.25.9
- name: Setup Android NDK - name: Setup Android NDK
id: setup-ndk id: setup-ndk
uses: nttld/setup-ndk@v1 uses: nttld/setup-ndk@v1
@@ -695,7 +731,7 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: ~1.25.7 go-version: ~1.25.9
- name: Setup Android NDK - name: Setup Android NDK
id: setup-ndk id: setup-ndk
uses: nttld/setup-ndk@v1 uses: nttld/setup-ndk@v1
@@ -794,7 +830,7 @@ jobs:
if: matrix.if if: matrix.if
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: ~1.25.7 go-version: ~1.25.9
- name: Set tag - name: Set tag
if: matrix.if if: matrix.if
run: |- run: |-

View File

@@ -19,7 +19,6 @@ env:
jobs: jobs:
build_binary: build_binary:
name: Build binary name: Build binary
if: github.event_name != 'release' || github.event.release.target_commitish != 'oldstable'
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: true fail-fast: true
@@ -56,7 +55,7 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: ~1.25.7 go-version: ~1.25.9
- name: Clone cronet-go - name: Clone cronet-go
if: matrix.naive if: matrix.naive
run: | run: |
@@ -260,13 +259,13 @@ jobs:
fi fi
echo "ref=$ref" echo "ref=$ref"
echo "ref=$ref" >> $GITHUB_OUTPUT echo "ref=$ref" >> $GITHUB_OUTPUT
if [[ $ref == *"-"* ]]; then - name: Checkout
latest=latest-beta uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
else with:
latest=latest ref: ${{ steps.ref.outputs.ref }}
fi fetch-depth: 0
echo "latest=$latest" - name: Detect track
echo "latest=$latest" >> $GITHUB_OUTPUT run: bash .github/detect_track.sh
- name: Download digests - name: Download digests
uses: actions/download-artifact@v5 uses: actions/download-artifact@v5
with: with:
@@ -286,11 +285,11 @@ jobs:
working-directory: /tmp/digests working-directory: /tmp/digests
run: | run: |
docker buildx imagetools create \ docker buildx imagetools create \
-t "${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.latest }}" \ -t "${{ env.REGISTRY_IMAGE }}:${{ env.DOCKER_TAG }}" \
-t "${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}" \ -t "${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}" \
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *) $(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
- name: Inspect image - name: Inspect image
if: github.event_name != 'push' if: github.event_name != 'push'
run: | run: |
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.latest }} docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ env.DOCKER_TAG }}
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }} docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}

View File

@@ -18,21 +18,60 @@ on:
- testing - testing
- unstable - unstable
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}-${{ inputs.build }}
cancel-in-progress: true
jobs: jobs:
build: build:
name: Build name: Lint ${{ matrix.goos }}/${{ matrix.goarch }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- goos: windows
goarch: amd64
- goos: windows
goarch: '386'
- goos: windows
goarch: arm64
- goos: linux
goarch: amd64
- goos: linux
goarch: arm64
- goos: linux
goarch: arm
- goos: linux
goarch: '386'
- goos: darwin
goarch: amd64
- goos: darwin
goarch: arm64
- goos: android
goarch: arm64
# - goos: freebsd
# goarch: amd64
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: ^1.25 go-version: ^1.25
- name: Cache go module
uses: actions/cache@v4
with:
path: |
~/go/pkg/mod
key: go-${{ hashFiles('**/go.sum') }}
- name: golangci-lint - name: golangci-lint
uses: golangci/golangci-lint-action@v8 uses: golangci/golangci-lint-action@v8
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
with: with:
version: latest version: latest
args: --timeout=30m args: --timeout=30m

View File

@@ -11,11 +11,6 @@ on:
description: "Version name" description: "Version name"
required: true required: true
type: string type: string
forceBeta:
description: "Force beta"
required: false
type: boolean
default: false
release: release:
types: types:
- published - published
@@ -23,7 +18,6 @@ on:
jobs: jobs:
calculate_version: calculate_version:
name: Calculate version name: Calculate version
if: github.event_name != 'release' || github.event.release.target_commitish != 'oldstable'
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs: outputs:
version: ${{ steps.outputs.outputs.version }} version: ${{ steps.outputs.outputs.version }}
@@ -35,7 +29,7 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: ~1.25.7 go-version: ~1.25.9
- name: Check input version - name: Check input version
if: github.event_name == 'workflow_dispatch' if: github.event_name == 'workflow_dispatch'
run: |- run: |-
@@ -78,7 +72,7 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: ~1.25.7 go-version: ~1.25.9
- name: Clone cronet-go - name: Clone cronet-go
if: matrix.naive if: matrix.naive
run: | run: |
@@ -168,14 +162,8 @@ jobs:
- name: Set mtime - name: Set mtime
run: |- run: |-
TZ=UTC touch -t '197001010000' dist/sing-box TZ=UTC touch -t '197001010000' dist/sing-box
- name: Set name - name: Detect track
if: (! contains(needs.calculate_version.outputs.version, '-')) && !inputs.forceBeta run: bash .github/detect_track.sh
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 - name: Set version
run: |- run: |-
PKG_VERSION="${{ needs.calculate_version.outputs.version }}" PKG_VERSION="${{ needs.calculate_version.outputs.version }}"

2
.gitignore vendored
View File

@@ -21,3 +21,5 @@
CLAUDE.md CLAUDE.md
AGENTS.md AGENTS.md
/.claude/ /.claude/
dist
logs

6
.gitmodules vendored
View File

@@ -1,6 +0,0 @@
[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,6 +1,6 @@
version: "2" version: "2"
run: run:
go: "1.25" go: "1.24"
build-tags: build-tags:
- with_gvisor - with_gvisor
- with_quic - with_quic
@@ -17,30 +17,29 @@ run:
linters: linters:
default: none default: none
enable: enable:
- govet
- ineffassign - ineffassign
- paralleltest - paralleltest
- staticcheck - staticcheck
- unused
- modernize
settings: settings:
modernize:
disable:
- omitzero # nested struct omitempty -> omitzero changes JSON output semantics
staticcheck: staticcheck:
checks: checks:
- all - all
- -S1000 - -QF1008 # could remove embedded field "<interface>" from selector
- -S1008 - -ST1003 # should not use ALL_CAPS in Go names; use CamelCase instead
- -S1017 - -QF1001 # could apply De Morgan's law
- -ST1003
- -QF1001
- -QF1003
- -QF1008
exclusions: exclusions:
generated: lax generated: lax
presets: presets:
- comments - comments
- common-false-positives - common-false-positives
- legacy
- std-error-handling
paths: paths:
- transport/simple-obfs - transport/simple-obfs
- \.pb\.go$
- third_party$ - third_party$
- builtin$ - builtin$
- examples$ - examples$
@@ -55,10 +54,3 @@ formatters:
- prefix(github.com/sagernet/) - prefix(github.com/sagernet/)
- default - default
custom-order: true custom-order: true
exclusions:
generated: lax
paths:
- transport/simple-obfs
- third_party$
- builtin$
- examples$

View File

@@ -20,47 +20,8 @@ builds:
- with_acme - with_acme
- with_clash_api - with_clash_api
- with_tailscale - with_tailscale
env: - with_masque
- CGO_ENABLED=0 - with_mtproxy
- GOTOOLCHAIN=local
targets:
- linux_386
- linux_amd64_v1
- linux_arm64
- linux_arm_6
- linux_arm_7
- linux_s390x
- linux_riscv64
- linux_mips
- linux_mips_softfloat
- linux_mipsle
- linux_mipsle_softfloat
- linux_mips64
- linux_mips64le
- windows_amd64_v1
- windows_386
- windows_arm64
- darwin_amd64_v1
- darwin_arm64
mod_timestamp: '{{ .CommitTimestamp }}'
- id: manager
main: ./cmd/sing-box
flags:
- -v
- -trimpath
ldflags:
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
- -s
- -buildid=
tags:
- with_gvisor
- with_quic
- with_dhcp
- with_wireguard
- with_utls
- with_acme
- with_clash_api
- with_tailscale
- with_manager - with_manager
- with_admin_panel - with_admin_panel
env: env:
@@ -74,19 +35,13 @@ builds:
- linux_arm_7 - linux_arm_7
- linux_s390x - linux_s390x
- linux_riscv64 - linux_riscv64
- linux_mips
- linux_mips_softfloat
- linux_mipsle
- linux_mipsle_softfloat
- linux_mips64
- linux_mips64le
- windows_amd64_v1 - windows_amd64_v1
- windows_386 - windows_386
- windows_arm64 - windows_arm64
- darwin_amd64_v1 - darwin_amd64_v1
- darwin_arm64 - darwin_arm64
mod_timestamp: '{{ .CommitTimestamp }}' mod_timestamp: '{{ .CommitTimestamp }}'
- id: legacy - id: mips
<<: *template <<: *template
tags: tags:
- with_gvisor - with_gvisor
@@ -97,11 +52,15 @@ builds:
- with_acme - with_acme
- with_clash_api - with_clash_api
- with_tailscale - with_tailscale
env: - with_masque
- CGO_ENABLED=0 - with_mtproxy
targets: targets:
- windows_amd64_v1 - linux_mips
- windows_386 - linux_mips_softfloat
- linux_mipsle
- linux_mipsle_softfloat
- linux_mips64
- linux_mips64le
- id: android - id: android
<<: *template <<: *template
env: env:
@@ -140,6 +99,7 @@ archives:
id: archive id: archive
builds: builds:
- main - main
- mips
- android - android
formats: formats:
- tar.gz - tar.gz
@@ -151,19 +111,6 @@ archives:
files: files:
- LICENSE - LICENSE
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}-{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}' name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}-{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
- id: archive_with_manager
builds:
- manager
formats:
- tar.gz
format_overrides:
- goos: windows
formats:
- zip
wrap_in_directory: true
files:
- LICENSE
name_template: '{{ .ProjectName }}-{{ .Version }}-with-manager-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}-{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
- id: archive-legacy - id: archive-legacy
<<: *template <<: *template
builds: builds:

View File

@@ -1,4 +1,4 @@
FROM --platform=$BUILDPLATFORM golang:1.25-alpine AS builder FROM --platform=$BUILDPLATFORM golang:1.26-alpine AS builder
LABEL maintainer="shtorm-7" LABEL maintainer="shtorm-7"
COPY . /go/src/github.com/sagernet/sing-box COPY . /go/src/github.com/sagernet/sing-box
WORKDIR /go/src/github.com/sagernet/sing-box WORKDIR /go/src/github.com/sagernet/sing-box

View File

@@ -14,12 +14,35 @@ PREFIX ?= $(shell go env GOPATH)
SING_FFI ?= sing-ffi SING_FFI ?= sing-ffi
LIBBOX_FFI_CONFIG ?= ./experimental/libbox/ffi.json LIBBOX_FFI_CONFIG ?= ./experimental/libbox/ffi.json
ADMIN_PANEL_DIR = service/admin_panel
ADMIN_PANEL_WEB = $(ADMIN_PANEL_DIR)/web
ADMIN_PANEL_DIST = $(ADMIN_PANEL_DIR)/dist
ADMIN_PANEL_TAGS = $(TAGS),with_admin_panel
DOCKER_IMAGE ?= shtorm7/sing-box-extended
DOCKER_PLATFORMS ?= linux/amd64,linux/arm64
.PHONY: test release docs build .PHONY: test release docs build
build: build:
export GOTOOLCHAIN=local && \ export GOTOOLCHAIN=local && \
go build $(MAIN_PARAMS) $(MAIN) go build $(MAIN_PARAMS) $(MAIN)
admin_panel_web:
cd $(ADMIN_PANEL_WEB) && \
npm install --no-fund --no-audit && \
npm run build
admin_panel_pack:
go run ./cmd/internal/admin_panel_pack \
-dir $(ADMIN_PANEL_DIST)
admin_panel_regen: admin_panel_web admin_panel_pack
build_admin_panel:
export GOTOOLCHAIN=local && \
go build $(PARAMS) -tags "$(ADMIN_PANEL_TAGS)" $(MAIN)
race: race:
export GOTOOLCHAIN=local && \ export GOTOOLCHAIN=local && \
go build -race $(MAIN_PARAMS) $(MAIN) go build -race $(MAIN_PARAMS) $(MAIN)
@@ -36,23 +59,17 @@ install:
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN) go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
fmt: fmt:
@gofumpt -l -w . @golangci-lint fmt
@gofmt -s -w .
@gci write --custom-order -s standard -s "prefix(github.com/sagernet/)" -s "default" .
fmt_docs: fmt_docs:
go run ./cmd/internal/format_docs go run ./cmd/internal/format_docs
fmt_install:
go install -v mvdan.cc/gofumpt@latest
go install -v github.com/daixiang0/gci@latest
lint: lint:
GOOS=linux golangci-lint run ./... GOOS=linux golangci-lint run ./...
GOOS=android golangci-lint run ./... GOOS=android golangci-lint run ./...
GOOS=windows golangci-lint run ./... GOOS=windows golangci-lint run ./...
GOOS=darwin golangci-lint run ./... GOOS=darwin golangci-lint run ./...
GOOS=freebsd golangci-lint run ./... # GOOS=freebsd golangci-lint run ./...
lint_install: lint_install:
go install -v github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest go install -v github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
@@ -84,6 +101,15 @@ release_repo:
release_install: release_install:
go install -v github.com/tcnksm/ghr@latest go install -v github.com/tcnksm/ghr@latest
release_docker:
sudo docker buildx build \
--platform $(DOCKER_PLATFORMS) \
-t $(DOCKER_IMAGE):latest \
-t $(DOCKER_IMAGE):$(VERSION) \
--push \
--network=host \
.
update_android_version: update_android_version:
go run ./cmd/internal/update_android_version go run ./cmd/internal/update_android_version
@@ -170,14 +196,31 @@ upload_macos_pkg:
ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Intel.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" ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Universal.pkg"
replace_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 "v${VERSION}" "dist/SFM/SFM-${VERSION}-Apple.pkg"
ghr --replace "v${VERSION}" "dist/SFM/SFM-${VERSION}-Intel.pkg"
ghr --replace "v${VERSION}" "dist/SFM/SFM-${VERSION}-Universal.pkg"
upload_macos_dsyms: upload_macos_dsyms:
mkdir -p dist/SFM mkdir -p dist/SFM
cd ../sing-box-for-apple/build/SFM.System-universal.xcarchive && zip -r SFM.dSYMs.zip dSYMs 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" 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" ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}.dSYMs.zip"
replace_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 "v${VERSION}" "dist/SFM/SFM-${VERSION}.dSYMs.zip"
release_macos_standalone: build_macos_pkg notarize_macos_pkg upload_macos_pkg upload_macos_dsyms release_macos_standalone: build_macos_pkg notarize_macos_pkg upload_macos_pkg upload_macos_dsyms
replace_macos_standalone: build_macos_pkg notarize_macos_pkg upload_macos_pkg upload_macos_dsyms
build_tvos: build_tvos:
cd ../sing-box-for-apple && \ cd ../sing-box-for-apple && \
rm -rf build/SFT.xcarchive && \ rm -rf build/SFT.xcarchive && \

View File

@@ -4,33 +4,44 @@ Sing-box with extended features.
## 🔥 Features ## 🔥 Features
### 🌐 Outbounds ### Outbounds
- **WARP** — Cloudflare WARP integration through WireGuard - **WARP** — Cloudflare WARP integration through WireGuard
- **Tunnel** — Protocol for creating tunnels across nodes - **MASQUE** — Cloudflare MASQUE proxy over QUIC / HTTP-2
- **Bond** — Link aggregation for increased throughput - **MTProxy** — Telegram MTProxy server with FakeTLS and domain fronting
- **Mieru** — Secure, hard to classify, hard to probe network protocol - **Mieru** — Secure, hard to classify, hard to probe network protocol
- **Failover** — Automatic outbound switching for high availability - **VPN** — Routed tunnel over any TCP sing-box protocol
- **Bond** — Link aggregation for increasing throughput
- **Fallback** — Outbound group with priority-based switching
- **Failover** — Automatic outbound switching with session recovery for high availability
### 🚦 Limiters ### DNS
- **Bandwidth Limiter** — Upload / download rate limiting - **SDNS (DNSCrypt)** — Encrypted DNS queries for enhanced privacy
- **DNS Fallback** — Sequential / parallel queries across upstream resolvers
### Limiters
- **Bandwidth Limiter** — Upload / download / bidirectional rate limiting
- **Connection Limiter** — Concurrent connection control - **Connection Limiter** — Concurrent connection control
- **Traffic Limiter** — Per-user traffic quotas
- **Rate Limiter** — Request rate limiting
### 🛡 Encryption & Obfuscation ### Encryption & Obfuscation
- **Amnezia 1.5** — WireGuard traffic obfuscation - **Amnezia 2.0** — WireGuard traffic obfuscation
- **VLESS encryption** — XRAY encryption for VLESS protocol - **VLESS encryption** — XRAY encryption for VLESS protocol
### 🔄 Transports ### Transports
- **mKCP** — Reliable UDP-based transport - **mKCP** — Reliable UDP-based transport
- **XHTTP** — Modern XRAY transport - **XHTTP** — Modern XRAY transport
### 🛠 Services ### Services
- **Admin Panel** — Web-based management interface - **Admin Panel** — Web-based management interface
- **Manager** — Management service for configuring squads, nodes, users, limiters - **Manager** — Management service for configuring users, nodes, limiters
- **Node Manager** — Service for connecting nodes to remote manager - **Manager API (HTTP/gRPC)** — HTTP and gRPC API for the Manager
- **Node Manager API** — Service for connecting nodes to remote manager
### Miscellaneous ### Miscellaneous
- **SDNS (DNSCrypt)** — Encrypted DNS queries for enhanced privacy - **Providers** — Outbound subscriptions from local files, inline lists, or remote URLs (sing-box JSON, Clash YAML, SIP008, share links)
- **Extended WireGuard options** — Advanced configuration capabilities - **Link Parser** — Outbound configured from a share link (VLESS, VMess, Shadowsocks, Trojan, Hysteria, Hysteria2, TUIC)
- **Extended WireGuard options** — Advanced configuration capabilities
- **Unified Delay** — Unified latency measurement - **Unified Delay** — Unified latency measurement
## 📚 Examples ## 📚 Examples
@@ -43,28 +54,36 @@ https://github.com/shtorm-7/sing-box-extended/tree/extended/examples
If you want to support the project, you can donate to the following addresses. If you want to support the project, you can donate to the following addresses.
### TRX (Tron) #### Tribute
**[RUB Donate](https://web.tribute.tg/d/JxY)**
**[EUR Donate](https://web.tribute.tg/d/JxZ)**
**[USD Donate](https://web.tribute.tg/d/Jy1)**
#### TRX (Tron)
``` ```
TSWU6VUZ4FcUghYDmbbhK15gRVvhvBgW3F TSWU6VUZ4FcUghYDmbbhK15gRVvhvBgW3F
``` ```
### TON #### TON
``` ```
UQAyD2UuT5kCP6lZQlhFL0hyNibDXNE4nIo_RSLVSYAtD7N1 UQAyD2UuT5kCP6lZQlhFL0hyNibDXNE4nIo_RSLVSYAtD7N1
``` ```
### Solana #### Solana
``` ```
CJu8ickwRCwNE71uVFjYf1UveyCkRp9Xo44rhPcQpeFL CJu8ickwRCwNE71uVFjYf1UveyCkRp9Xo44rhPcQpeFL
``` ```
### Bitcoin #### Bitcoin
``` ```
bc1qqx97p8k4dchqkyd47s4vf74hrqdfnmhqvcja7x bc1qqx97p8k4dchqkyd47s4vf74hrqdfnmhqvcja7x
``` ```
### Ethereum #### Ethereum
``` ```
0xAcc5919C22F2B3fAa0ec7E8BaD142da5B375FBF6 0xAcc5919C22F2B3fAa0ec7E8BaD142da5B375FBF6
``` ```
## 📄 License ## License
``` ```
Copyright (C) 2022 by nekohasekai <contact-sagernet@sekai.icu> Copyright (C) 2022 by nekohasekai <contact-sagernet@sekai.icu>

View File

@@ -68,6 +68,8 @@ type DNSTransport interface {
Type() string Type() string
Tag() string Tag() string
Dependencies() []string Dependencies() []string
// Reset closes the transport's existing connections so later requests use fresh connections.
// Exchanges that are currently using those connections may fail.
Reset() Reset()
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error) Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
} }

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"os" "os"
"sync" "sync"
"time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/taskmonitor" "github.com/sagernet/sing-box/common/taskmonitor"
@@ -12,7 +11,6 @@ import (
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
) )
var _ adapter.EndpointManager = (*Manager)(nil) var _ adapter.EndpointManager = (*Manager)(nil)
@@ -51,13 +49,12 @@ func (m *Manager) Start(stage adapter.StartStage) error {
m.access.Unlock() m.access.Unlock()
for _, endpoint := range endpoints { for _, endpoint := range endpoints {
name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]" name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]"
m.logger.Trace(stage, " ", name) done := adapter.LogElapsed(m.logger, stage, " ", name)
startTime := time.Now()
err := adapter.LegacyStart(endpoint, stage) err := adapter.LegacyStart(endpoint, stage)
done()
if err != nil { if err != nil {
return E.Cause(err, stage, " ", name) return E.Cause(err, stage, " ", name)
} }
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
} }
return nil return nil
} }
@@ -75,14 +72,13 @@ func (m *Manager) Close() error {
var err error var err error
for _, endpoint := range endpoints { for _, endpoint := range endpoints {
name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]" name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]"
m.logger.Trace("close ", name) done := adapter.LogElapsed(m.logger, "close ", name)
startTime := time.Now()
monitor.Start("close ", name) monitor.Start("close ", name)
err = E.Append(err, endpoint.Close(), func(err error) error { err = E.Append(err, endpoint.Close(), func(err error) error {
return E.Cause(err, "close ", name) return E.Cause(err, "close ", name)
}) })
monitor.Finish() monitor.Finish()
m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") done()
} }
return nil return nil
} }
@@ -133,13 +129,12 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
if m.started { if m.started {
name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]" name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]"
for _, stage := range adapter.ListStartStages { for _, stage := range adapter.ListStartStages {
m.logger.Trace(stage, " ", name) done := adapter.LogElapsed(m.logger, stage, " ", name)
startTime := time.Now()
err = adapter.LegacyStart(endpoint, stage) err = adapter.LegacyStart(endpoint, stage)
done()
if err != nil { if err != nil {
return E.Cause(err, stage, " ", name) 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 existsEndpoint, loaded := m.endpointByTag[tag]; loaded {

View File

@@ -48,6 +48,7 @@ type CacheFile interface {
RDRCStore RDRCStore
StoreWARPConfig() bool StoreWARPConfig() bool
StoreMASQUEConfig() bool
LoadMode() string LoadMode() string
StoreMode(mode string) error StoreMode(mode string) error
@@ -59,6 +60,10 @@ type CacheFile interface {
SaveRuleSet(tag string, set *SavedBinary) error SaveRuleSet(tag string, set *SavedBinary) error
LoadWARPConfig(tag string) *SavedBinary LoadWARPConfig(tag string) *SavedBinary
SaveWARPConfig(tag string, set *SavedBinary) error SaveWARPConfig(tag string, set *SavedBinary) error
LoadMASQUEConfig(tag string) *SavedBinary
SaveMASQUEConfig(tag string, set *SavedBinary) error
LoadSubscription(tag string) *SavedBinary
SaveSubscription(tag string, sub *SavedBinary) error
} }
type SavedBinary struct { type SavedBinary struct {

View File

@@ -42,16 +42,15 @@ type InboundManager interface {
} }
type InboundContext struct { type InboundContext struct {
Inbound string Inbound string
InboundType string InboundType string
IPVersion uint8 IPVersion uint8
Network string Network string
Source M.Socksaddr Source M.Socksaddr
Destination M.Socksaddr Destination M.Socksaddr
TunnelSource string Gateway *netip.Addr
TunnelDestination string User string
User string Outbound string
Outbound string
// sniffer // sniffer
@@ -104,6 +103,10 @@ type InboundContext struct {
func (c *InboundContext) ResetRuleCache() { func (c *InboundContext) ResetRuleCache() {
c.IPCIDRMatchSource = false c.IPCIDRMatchSource = false
c.IPCIDRAcceptEmpty = false c.IPCIDRAcceptEmpty = false
c.ResetRuleMatchCache()
}
func (c *InboundContext) ResetRuleMatchCache() {
c.SourceAddressMatch = false c.SourceAddressMatch = false
c.SourcePortMatch = false c.SourcePortMatch = false
c.DestinationAddressMatch = false c.DestinationAddressMatch = false

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"os" "os"
"sync" "sync"
"time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/taskmonitor" "github.com/sagernet/sing-box/common/taskmonitor"
@@ -12,7 +11,6 @@ import (
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
) )
var _ adapter.InboundManager = (*Manager)(nil) var _ adapter.InboundManager = (*Manager)(nil)
@@ -48,13 +46,12 @@ func (m *Manager) Start(stage adapter.StartStage) error {
m.access.Unlock() m.access.Unlock()
for _, inbound := range inbounds { for _, inbound := range inbounds {
name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]" name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]"
m.logger.Trace(stage, " ", name) done := adapter.LogElapsed(m.logger, stage, " ", name)
startTime := time.Now()
err := adapter.LegacyStart(inbound, stage) err := adapter.LegacyStart(inbound, stage)
done()
if err != nil { if err != nil {
return E.Cause(err, stage, " ", name) return E.Cause(err, stage, " ", name)
} }
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
} }
return nil return nil
} }
@@ -72,14 +69,13 @@ func (m *Manager) Close() error {
var err error var err error
for _, inbound := range inbounds { for _, inbound := range inbounds {
name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]" name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]"
m.logger.Trace("close ", name) done := adapter.LogElapsed(m.logger, "close ", name)
startTime := time.Now()
monitor.Start("close ", name) monitor.Start("close ", name)
err = E.Append(err, inbound.Close(), func(err error) error { err = E.Append(err, inbound.Close(), func(err error) error {
return E.Cause(err, "close ", name) return E.Cause(err, "close ", name)
}) })
monitor.Finish() monitor.Finish()
m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") done()
} }
return nil return nil
} }
@@ -133,13 +129,12 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
if m.started { if m.started {
name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]" name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]"
for _, stage := range adapter.ListStartStages { for _, stage := range adapter.ListStartStages {
m.logger.Trace(stage, " ", name) done := adapter.LogElapsed(m.logger, stage, " ", name)
startTime := time.Now()
err = adapter.LegacyStart(inbound, stage) err = adapter.LegacyStart(inbound, stage)
done()
if err != nil { if err != nil {
return E.Cause(err, stage, " ", name) 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 existsInbound, loaded := m.inboundByTag[tag]; loaded {

View File

@@ -77,26 +77,38 @@ func getServiceName(service any) string {
func Start(logger log.ContextLogger, stage StartStage, services ...Lifecycle) error { func Start(logger log.ContextLogger, stage StartStage, services ...Lifecycle) error {
for _, service := range services { for _, service := range services {
name := getServiceName(service) name := getServiceName(service)
logger.Trace(stage, " ", name) done := LogElapsed(logger, stage, " ", name)
startTime := time.Now()
err := service.Start(stage) err := service.Start(stage)
done()
if err != nil { if err != nil {
return err return err
} }
logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
} }
return nil return nil
} }
func StartNamed(logger log.ContextLogger, stage StartStage, services []LifecycleService) error { func StartNamed(logger log.ContextLogger, stage StartStage, services []LifecycleService) error {
for _, service := range services { for _, service := range services {
logger.Trace(stage, " ", service.Name()) done := LogElapsed(logger, stage, " ", service.Name())
startTime := time.Now()
err := service.Start(stage) err := service.Start(stage)
done()
if err != nil { if err != nil {
return E.Cause(err, stage.String(), " ", service.Name()) return E.Cause(err, stage.String(), " ", service.Name())
} }
logger.Trace(stage, " ", service.Name(), " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
} }
return nil return nil
} }
func LogElapsed(logger log.ContextLogger, description ...any) func() {
prefix := F.ToString(description...)
startTime := time.Now()
timer := time.AfterFunc(time.Second, func() {
logger.Trace(prefix, "...")
})
return func() {
if timer.Stop() {
return
}
logger.Trace(prefix, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
}

View File

@@ -1,6 +1,9 @@
package adapter package adapter
import ( import (
"encoding/hex"
"net"
"strings"
"time" "time"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
@@ -51,6 +54,24 @@ type WIFIState struct {
BSSID string BSSID string
} }
func NormalizeWIFIBSSID(bssid string) string {
bssid = strings.TrimSpace(bssid)
if bssid == "" {
return ""
}
parsed, err := net.ParseMAC(bssid)
if err == nil && len(parsed) == 6 {
return parsed.String()
}
if len(bssid) == 12 {
decoded, err := hex.DecodeString(bssid)
if err == nil {
return net.HardwareAddr(decoded).String()
}
}
return bssid
}
type NetworkInterface struct { type NetworkInterface struct {
control.Interface control.Interface
Type C.InterfaceType Type C.InterfaceType

View File

@@ -6,7 +6,6 @@ import (
"os" "os"
"strings" "strings"
"sync" "sync"
"time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/taskmonitor" "github.com/sagernet/sing-box/common/taskmonitor"
@@ -14,7 +13,6 @@ import (
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/logger"
) )
@@ -84,13 +82,12 @@ func (m *Manager) Start(stage adapter.StartStage) error {
m.access.Unlock() m.access.Unlock()
for _, outbound := range outbounds { for _, outbound := range outbounds {
name := "outbound/" + outbound.Type() + "[" + outbound.Tag() + "]" name := "outbound/" + outbound.Type() + "[" + outbound.Tag() + "]"
m.logger.Trace(stage, " ", name) done := adapter.LogElapsed(m.logger, stage, " ", name)
startTime := time.Now()
err := adapter.LegacyStart(outbound, stage) err := adapter.LegacyStart(outbound, stage)
done()
if err != nil { if err != nil {
return E.Cause(err, stage, " ", name) return E.Cause(err, stage, " ", name)
} }
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
} }
} }
return nil return nil
@@ -117,27 +114,25 @@ func (m *Manager) startOutbounds(outbounds []adapter.Outbound) error {
canContinue = true canContinue = true
name := "outbound/" + outboundToStart.Type() + "[" + outboundTag + "]" name := "outbound/" + outboundToStart.Type() + "[" + outboundTag + "]"
if starter, isStarter := outboundToStart.(adapter.Lifecycle); isStarter { if starter, isStarter := outboundToStart.(adapter.Lifecycle); isStarter {
m.logger.Trace("start ", name) done := adapter.LogElapsed(m.logger, "start ", name)
startTime := time.Now()
monitor.Start("start ", name) monitor.Start("start ", name)
err := starter.Start(adapter.StartStateStart) err := starter.Start(adapter.StartStateStart)
monitor.Finish() monitor.Finish()
done()
if err != nil { if err != nil {
return E.Cause(err, "start ", name) 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 { } else if starter, isStarter := outboundToStart.(interface {
Start() error Start() error
}); isStarter { }); isStarter {
m.logger.Trace("start ", name) done := adapter.LogElapsed(m.logger, "start ", name)
startTime := time.Now()
monitor.Start("start ", name) monitor.Start("start ", name)
err := starter.Start() err := starter.Start()
monitor.Finish() monitor.Finish()
done()
if err != nil { if err != nil {
return E.Cause(err, "start ", name) return E.Cause(err, "start ", name)
} }
m.logger.Trace("start ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
} }
} }
if len(started) == len(outbounds) { if len(started) == len(outbounds) {
@@ -185,14 +180,13 @@ func (m *Manager) Close() error {
for _, outbound := range outbounds { for _, outbound := range outbounds {
if closer, isCloser := outbound.(io.Closer); isCloser { if closer, isCloser := outbound.(io.Closer); isCloser {
name := "outbound/" + outbound.Type() + "[" + outbound.Tag() + "]" name := "outbound/" + outbound.Type() + "[" + outbound.Tag() + "]"
m.logger.Trace("close ", name) done := adapter.LogElapsed(m.logger, "close ", name)
startTime := time.Now()
monitor.Start("close ", name) monitor.Start("close ", name)
err = E.Append(err, closer.Close(), func(err error) error { err = E.Append(err, closer.Close(), func(err error) error {
return E.Cause(err, "close ", name) return E.Cause(err, "close ", name)
}) })
monitor.Finish() monitor.Finish()
m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") done()
} }
} }
return nil return nil
@@ -275,13 +269,12 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
if m.started { if m.started {
name := "outbound/" + outbound.Type() + "[" + outbound.Tag() + "]" name := "outbound/" + outbound.Type() + "[" + outbound.Tag() + "]"
for _, stage := range adapter.ListStartStages { for _, stage := range adapter.ListStartStages {
m.logger.Trace(stage, " ", name) done := adapter.LogElapsed(m.logger, stage, " ", name)
startTime := time.Now()
err = adapter.LegacyStart(outbound, stage) err = adapter.LegacyStart(outbound, stage)
done()
if err != nil { if err != nil {
return E.Cause(err, stage, " ", name) return E.Cause(err, stage, " ", name)
} }
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
} }
} }
m.access.Lock() m.access.Lock()

View File

@@ -1,6 +1,8 @@
package adapter package adapter
import ( import (
"net/netip"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-tun" "github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/logger"
@@ -36,6 +38,8 @@ type PlatformInterface interface {
UsePlatformNotification() bool UsePlatformNotification() bool
SendNotification(notification *Notification) error SendNotification(notification *Notification) error
MyInterfaceAddress() []netip.Addr
} }
type FindConnectionOwnerRequest struct { type FindConnectionOwnerRequest struct {
@@ -47,11 +51,11 @@ type FindConnectionOwnerRequest struct {
} }
type ConnectionOwner struct { type ConnectionOwner struct {
ProcessID uint32 ProcessID uint32
UserId int32 UserId int32
UserName string UserName string
ProcessPath string ProcessPath string
AndroidPackageName string AndroidPackageNames []string
} }
type Notification struct { type Notification struct {

51
adapter/provider.go Normal file
View File

@@ -0,0 +1,51 @@
package adapter
import (
"context"
"time"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/x/list"
)
type Provider interface {
Type() string
Tag() string
Outbounds() []Outbound
Outbound(tag string) (Outbound, bool)
UpdatedAt() time.Time
HealthCheck(ctx context.Context) (map[string]uint16, error)
RegisterCallback(callback ProviderUpdateCallback) *list.Element[ProviderUpdateCallback]
UnregisterCallback(element *list.Element[ProviderUpdateCallback])
}
type ProviderUpdater interface {
Update() error
}
type ProviderSubscriptionInfo interface {
SubscriptionInfo() SubscriptionInfo
}
type ProviderRegistry interface {
option.ProviderOptionsRegistry
CreateProvider(ctx context.Context, router Router, logFactory log.Factory, tag string, providerType string, options any) (Provider, error)
}
type ProviderManager interface {
Lifecycle
Providers() []Provider
Get(tag string) (Provider, bool)
Remove(tag string) error
Create(ctx context.Context, router Router, logFactory log.Factory, tag string, providerType string, options any) error
}
type SubscriptionInfo struct {
Upload int64
Download int64
Total int64
Expire int64
}
type ProviderUpdateCallback = func(tag string) error

267
adapter/provider/adapter.go Normal file
View File

@@ -0,0 +1,267 @@
package provider
import (
"context"
"reflect"
"sync"
"sync/atomic"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/urltest"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/batch"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/x/list"
"github.com/sagernet/sing/service"
)
type Adapter struct {
ctx context.Context
outbound adapter.OutboundManager
router adapter.Router
logFactory log.Factory
logger log.ContextLogger
providerType string
providerTag string
outbounds []adapter.Outbound
outboundsByTag map[string]adapter.Outbound
ticker *time.Ticker
checking atomic.Bool
history adapter.URLTestHistoryStorage
callbackAccess sync.Mutex
callbacks list.List[adapter.ProviderUpdateCallback]
link string
enabled bool
timeout time.Duration
interval time.Duration
}
func NewAdapter(ctx context.Context, router adapter.Router, outbound adapter.OutboundManager, logFactory log.Factory, logger log.ContextLogger, providerTag string, providerType string, options option.ProviderHealthCheckOptions) Adapter {
timeout := time.Duration(options.Timeout)
if timeout == 0 {
timeout = 3 * time.Second
}
interval := time.Duration(options.Interval)
if interval == 0 {
interval = 10 * time.Minute
}
if interval < time.Minute {
interval = time.Minute
}
return Adapter{
ctx: ctx,
outbound: outbound,
router: router,
logFactory: logFactory,
logger: logger,
providerType: providerType,
providerTag: providerTag,
enabled: options.Enabled,
link: options.URL,
timeout: timeout,
interval: interval,
}
}
func (a *Adapter) Start() error {
a.history = service.FromContext[adapter.URLTestHistoryStorage](a.ctx)
if a.history == nil {
if clashServer := service.FromContext[adapter.ClashServer](a.ctx); clashServer != nil {
a.history = clashServer.HistoryStorage()
} else {
a.history = urltest.NewHistoryStorage()
}
}
go a.loopCheck()
return nil
}
func (a *Adapter) Type() string {
return a.providerType
}
func (a *Adapter) Tag() string {
return a.providerTag
}
func (a *Adapter) Outbounds() []adapter.Outbound {
return a.outbounds
}
func (a *Adapter) Outbound(tag string) (adapter.Outbound, bool) {
if a.outboundsByTag == nil {
return nil, false
}
detour, ok := a.outboundsByTag[tag]
return detour, ok
}
func (a *Adapter) UpdateOutbounds(oldOpts []option.Outbound, newOpts []option.Outbound) {
a.removeUseless(newOpts)
var (
oldOptByTag = make(map[string]option.Outbound)
outbounds = make([]adapter.Outbound, 0, len(newOpts))
outboundsByTag = make(map[string]adapter.Outbound)
)
for _, opt := range oldOpts {
oldOptByTag[opt.Tag] = opt
}
for i, opt := range newOpts {
var tag string
if opt.Tag != "" {
tag = F.ToString(a.providerTag, "/", opt.Tag)
} else {
tag = F.ToString(a.providerTag, "/", i)
}
outbound, exist := a.outbound.Outbound(tag)
if !exist || !reflect.DeepEqual(opt, oldOptByTag[opt.Tag]) {
err := a.outbound.Create(
adapter.WithContext(a.ctx, &adapter.InboundContext{
Outbound: tag,
}),
a.router,
a.logFactory.NewLogger(F.ToString("outbound/", opt.Type, "[", tag, "]")),
tag,
opt.Type,
opt.Options,
)
if err != nil {
a.logger.Warn(err, " in ", tag, ", skip create this outbound")
continue
}
outbound, _ = a.outbound.Outbound(tag)
}
outbounds = append(outbounds, outbound)
outboundsByTag[tag] = outbound
}
if a.enabled && a.history != nil {
go a.HealthCheck(a.ctx)
}
a.outbounds = outbounds
a.outboundsByTag = outboundsByTag
}
func (a *Adapter) HealthCheck(ctx context.Context) (map[string]uint16, error) {
if a.ticker != nil {
a.ticker.Reset(a.interval)
}
return a.healthcheck(ctx)
}
func (a *Adapter) RegisterCallback(callback adapter.ProviderUpdateCallback) *list.Element[adapter.ProviderUpdateCallback] {
a.callbackAccess.Lock()
defer a.callbackAccess.Unlock()
return a.callbacks.PushBack(callback)
}
func (a *Adapter) UnregisterCallback(element *list.Element[adapter.ProviderUpdateCallback]) {
a.callbackAccess.Lock()
defer a.callbackAccess.Unlock()
a.callbacks.Remove(element)
}
func (a *Adapter) UpdateGroups() {
for element := a.callbacks.Front(); element != nil; element = element.Next() {
element.Value(a.providerTag)
}
}
func (a *Adapter) Close() error {
if a.ticker != nil {
a.ticker.Stop()
}
outbounds := a.outbounds
a.outbounds = nil
var err error
for _, ob := range outbounds {
if err2 := a.outbound.Remove(ob.Tag()); err2 != nil {
err = E.Append(err, err2, func(err error) error {
return E.Cause(err, "close outbound [", ob.Tag(), "]")
})
}
}
return err
}
func (a *Adapter) loopCheck() {
if !a.enabled {
return
}
a.ticker = time.NewTicker(a.interval)
a.healthcheck(a.ctx)
for {
select {
case <-a.ctx.Done():
return
case <-a.ticker.C:
a.healthcheck(a.ctx)
}
}
}
func (a *Adapter) healthcheck(ctx context.Context) (map[string]uint16, error) {
result := make(map[string]uint16)
if a.checking.Swap(true) {
return result, nil
}
defer a.checking.Store(false)
b, _ := batch.New(ctx, batch.WithConcurrencyNum[any](10))
var resultAccess sync.Mutex
checked := make(map[string]bool)
for _, detour := range a.outbounds {
tag := detour.Tag()
if checked[tag] {
continue
}
checked[tag] = true
b.Go(tag, func() (any, error) {
ctx, cancel := context.WithTimeout(a.ctx, a.timeout)
defer cancel()
t, err := urltest.URLTest(ctx, a.link, detour)
if err != nil {
a.logger.Debug("outbound ", tag, " unavailable: ", err)
a.history.DeleteURLTestHistory(tag)
} else {
a.logger.Debug("outbound ", tag, " available: ", t, "ms")
a.history.StoreURLTestHistory(tag, &adapter.URLTestHistory{
Time: time.Now(),
Delay: t,
})
resultAccess.Lock()
result[tag] = t
resultAccess.Unlock()
}
return nil, nil
})
}
b.Wait()
return result, nil
}
func (a *Adapter) removeUseless(newOpts []option.Outbound) {
if len(a.outbounds) == 0 {
return
}
exists := make(map[string]bool)
for i, opt := range newOpts {
var tag string
if opt.Tag != "" {
tag = F.ToString(a.providerTag, "/", opt.Tag)
} else {
tag = F.ToString(a.providerTag, "/", i)
}
exists[tag] = true
}
for _, opt := range a.outbounds {
if !exists[opt.Tag()] {
if err := a.outbound.Remove(opt.Tag()); err != nil {
a.logger.Error(err, "close outbound [", opt.Tag(), "]")
}
}
}
}

157
adapter/provider/manager.go Normal file
View File

@@ -0,0 +1,157 @@
package provider
import (
"context"
"io"
"os"
"sync"
"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"
"github.com/sagernet/sing/common/logger"
)
var _ adapter.ProviderManager = (*Manager)(nil)
type Manager struct {
logger log.ContextLogger
registry adapter.ProviderRegistry
access sync.Mutex
started bool
stage adapter.StartStage
providers []adapter.Provider
providerByTag map[string]adapter.Provider
wg sync.WaitGroup
}
func NewManager(logger logger.ContextLogger, registry adapter.ProviderRegistry) *Manager {
return &Manager{
logger: logger,
registry: registry,
providerByTag: make(map[string]adapter.Provider),
}
}
func (m *Manager) Initialize() {
}
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
providers := m.providers
m.access.Unlock()
for _, provider := range providers {
err := adapter.LegacyStart(provider, stage)
if err != nil {
return E.Cause(err, stage, " provider/", provider.Type(), "[", provider.Tag(), "]")
}
}
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
providers := m.providers
m.providers = nil
m.access.Unlock()
var err error
for _, provider := range providers {
if closer, isCloser := provider.(io.Closer); isCloser {
monitor.Start("close provider/", provider.Type(), "[", provider.Tag(), "]")
err = E.Append(err, closer.Close(), func(err error) error {
return E.Cause(err, "close provider/", provider.Type(), "[", provider.Tag(), "]")
})
monitor.Finish()
}
}
return nil
}
func (m *Manager) Providers() []adapter.Provider {
m.access.Lock()
defer m.access.Unlock()
return m.providers
}
func (m *Manager) Get(tag string) (adapter.Provider, bool) {
m.access.Lock()
provider, found := m.providerByTag[tag]
m.access.Unlock()
return provider, found
}
func (m *Manager) Remove(tag string) error {
m.access.Lock()
provider, found := m.providerByTag[tag]
if !found {
m.access.Unlock()
return os.ErrInvalid
}
delete(m.providerByTag, tag)
index := common.Index(m.providers, func(it adapter.Provider) bool {
return it == provider
})
if index == -1 {
panic("invalid provider index")
}
m.providers = append(m.providers[:index], m.providers[index+1:]...)
started := m.started
m.access.Unlock()
if started {
return common.Close(provider)
}
return nil
}
func (m *Manager) Create(ctx context.Context, router adapter.Router, logFactory log.Factory, tag string, providerType string, options any) error {
if tag == "" {
return os.ErrInvalid
}
provider, err := m.registry.CreateProvider(ctx, router, logFactory, tag, providerType, options)
if err != nil {
return err
}
m.access.Lock()
defer m.access.Unlock()
if m.started {
for _, stage := range adapter.ListStartStages {
err = adapter.LegacyStart(provider, stage)
if err != nil {
return E.Cause(err, stage, " provider/", provider.Type(), "[", provider.Tag(), "]")
}
}
}
if existsProvider, loaded := m.providerByTag[tag]; loaded {
if m.started {
err = common.Close(existsProvider)
if err != nil {
return E.Cause(err, "close provider", provider.Type(), "[", existsProvider.Tag(), "]")
}
}
existsIndex := common.Index(m.providers, func(it adapter.Provider) bool {
return it == existsProvider
})
if existsIndex == -1 {
panic("invalid provider index")
}
m.providers = append(m.providers[:existsIndex], m.providers[existsIndex+1:]...)
}
m.providers = append(m.providers, provider)
m.providerByTag[tag] = provider
return nil
}

View File

@@ -0,0 +1,72 @@
package provider
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, logFactory log.Factory, tag string, options T) (adapter.Provider, error)
func Register[Options any](registry *Registry, providerType string, constructor ConstructorFunc[Options]) {
registry.register(providerType, func() any {
return new(Options)
}, func(ctx context.Context, router adapter.Router, logFactory log.Factory, tag string, rawOptions any) (adapter.Provider, error) {
var options *Options
if rawOptions != nil {
options = rawOptions.(*Options)
}
return constructor(ctx, router, logFactory, tag, common.PtrValueOrDefault(options))
})
}
var _ adapter.ProviderRegistry = (*Registry)(nil)
type (
optionsConstructorFunc func() any
constructorFunc func(ctx context.Context, router adapter.Router, logFactory log.Factory, tag string, options any) (adapter.Provider, 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(providerType string) (any, bool) {
r.access.Lock()
defer r.access.Unlock()
optionsConstructor, loaded := r.optionsType[providerType]
if !loaded {
return nil, false
}
return optionsConstructor(), true
}
func (r *Registry) CreateProvider(ctx context.Context, router adapter.Router, logFactory log.Factory, tag string, providerType string, options any) (adapter.Provider, error) {
r.access.Lock()
defer r.access.Unlock()
constructor, loaded := r.constructors[providerType]
if !loaded {
return nil, E.New("provider type not found: '" + providerType + "'")
}
return constructor(ctx, router, logFactory, tag, options)
}
func (r *Registry) register(providerType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
r.access.Lock()
defer r.access.Unlock()
r.optionsType[providerType] = optionsConstructor
r.constructors[providerType] = constructor
}

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"os" "os"
"sync" "sync"
"time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/taskmonitor" "github.com/sagernet/sing-box/common/taskmonitor"
@@ -12,7 +11,6 @@ import (
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
) )
var _ adapter.ServiceManager = (*Manager)(nil) var _ adapter.ServiceManager = (*Manager)(nil)
@@ -46,13 +44,12 @@ func (m *Manager) Start(stage adapter.StartStage) error {
m.access.Unlock() m.access.Unlock()
for _, service := range services { for _, service := range services {
name := "service/" + service.Type() + "[" + service.Tag() + "]" name := "service/" + service.Type() + "[" + service.Tag() + "]"
m.logger.Trace(stage, " ", name) done := adapter.LogElapsed(m.logger, stage, " ", name)
startTime := time.Now()
err := adapter.LegacyStart(service, stage) err := adapter.LegacyStart(service, stage)
done()
if err != nil { if err != nil {
return E.Cause(err, stage, " ", name) return E.Cause(err, stage, " ", name)
} }
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
} }
return nil return nil
} }
@@ -70,14 +67,13 @@ func (m *Manager) Close() error {
var err error var err error
for _, service := range services { for _, service := range services {
name := "service/" + service.Type() + "[" + service.Tag() + "]" name := "service/" + service.Type() + "[" + service.Tag() + "]"
m.logger.Trace("close ", name) done := adapter.LogElapsed(m.logger, "close ", name)
startTime := time.Now()
monitor.Start("close ", name) monitor.Start("close ", name)
err = E.Append(err, service.Close(), func(err error) error { err = E.Append(err, service.Close(), func(err error) error {
return E.Cause(err, "close ", name) return E.Cause(err, "close ", name)
}) })
monitor.Finish() monitor.Finish()
m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") done()
} }
return nil return nil
} }
@@ -128,13 +124,12 @@ func (m *Manager) Create(ctx context.Context, logger log.ContextLogger, tag stri
if m.started { if m.started {
name := "service/" + service.Type() + "[" + service.Tag() + "]" name := "service/" + service.Type() + "[" + service.Tag() + "]"
for _, stage := range adapter.ListStartStages { for _, stage := range adapter.ListStartStages {
m.logger.Trace(stage, " ", name) done := adapter.LogElapsed(m.logger, stage, " ", name)
startTime := time.Now()
err = adapter.LegacyStart(service, stage) err = adapter.LegacyStart(service, stage)
done()
if err != nil { if err != nil {
return E.Cause(err, stage, " ", name) 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 existsService, loaded := m.serviceByTag[tag]; loaded {

72
box.go
View File

@@ -12,6 +12,7 @@ import (
"github.com/sagernet/sing-box/adapter/endpoint" "github.com/sagernet/sing-box/adapter/endpoint"
"github.com/sagernet/sing-box/adapter/inbound" "github.com/sagernet/sing-box/adapter/inbound"
"github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/adapter/outbound"
"github.com/sagernet/sing-box/adapter/provider"
boxService "github.com/sagernet/sing-box/adapter/service" boxService "github.com/sagernet/sing-box/adapter/service"
"github.com/sagernet/sing-box/common/certificate" "github.com/sagernet/sing-box/common/certificate"
"github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/dialer"
@@ -20,7 +21,6 @@ import (
"github.com/sagernet/sing-box/common/urltest" "github.com/sagernet/sing-box/common/urltest"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/dns" "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"
"github.com/sagernet/sing-box/experimental/cachefile" "github.com/sagernet/sing-box/experimental/cachefile"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
@@ -45,6 +45,7 @@ type Box struct {
endpoint *endpoint.Manager endpoint *endpoint.Manager
inbound *inbound.Manager inbound *inbound.Manager
outbound *outbound.Manager outbound *outbound.Manager
provider *provider.Manager
service *boxService.Manager service *boxService.Manager
dnsTransport *dns.TransportManager dnsTransport *dns.TransportManager
dnsRouter *dns.Router dnsRouter *dns.Router
@@ -65,6 +66,7 @@ func Context(
inboundRegistry adapter.InboundRegistry, inboundRegistry adapter.InboundRegistry,
outboundRegistry adapter.OutboundRegistry, outboundRegistry adapter.OutboundRegistry,
endpointRegistry adapter.EndpointRegistry, endpointRegistry adapter.EndpointRegistry,
providerRegistry adapter.ProviderRegistry,
dnsTransportRegistry adapter.DNSTransportRegistry, dnsTransportRegistry adapter.DNSTransportRegistry,
serviceRegistry adapter.ServiceRegistry, serviceRegistry adapter.ServiceRegistry,
) context.Context { ) context.Context {
@@ -83,6 +85,11 @@ func Context(
ctx = service.ContextWith[option.EndpointOptionsRegistry](ctx, endpointRegistry) ctx = service.ContextWith[option.EndpointOptionsRegistry](ctx, endpointRegistry)
ctx = service.ContextWith[adapter.EndpointRegistry](ctx, endpointRegistry) ctx = service.ContextWith[adapter.EndpointRegistry](ctx, endpointRegistry)
} }
if service.FromContext[option.ProviderOptionsRegistry](ctx) == nil ||
service.FromContext[adapter.ProviderRegistry](ctx) == nil {
ctx = service.ContextWith[option.ProviderOptionsRegistry](ctx, providerRegistry)
ctx = service.ContextWith[adapter.ProviderRegistry](ctx, providerRegistry)
}
if service.FromContext[adapter.DNSTransportRegistry](ctx) == nil { if service.FromContext[adapter.DNSTransportRegistry](ctx) == nil {
ctx = service.ContextWith[option.DNSTransportOptionsRegistry](ctx, dnsTransportRegistry) ctx = service.ContextWith[option.DNSTransportOptionsRegistry](ctx, dnsTransportRegistry)
ctx = service.ContextWith[adapter.DNSTransportRegistry](ctx, dnsTransportRegistry) ctx = service.ContextWith[adapter.DNSTransportRegistry](ctx, dnsTransportRegistry)
@@ -105,6 +112,7 @@ func New(options Options) (*Box, error) {
endpointRegistry := service.FromContext[adapter.EndpointRegistry](ctx) endpointRegistry := service.FromContext[adapter.EndpointRegistry](ctx)
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx) inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx) outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
providerRegistry := service.FromContext[adapter.ProviderRegistry](ctx)
dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx) dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx)
serviceRegistry := service.FromContext[adapter.ServiceRegistry](ctx) serviceRegistry := service.FromContext[adapter.ServiceRegistry](ctx)
@@ -117,6 +125,9 @@ func New(options Options) (*Box, error) {
if outboundRegistry == nil { if outboundRegistry == nil {
return nil, E.New("missing outbound registry in context") return nil, E.New("missing outbound registry in context")
} }
if providerRegistry == nil {
return nil, E.New("missing provider registry in context")
}
if dnsTransportRegistry == nil { if dnsTransportRegistry == nil {
return nil, E.New("missing DNS transport registry in context") return nil, E.New("missing DNS transport registry in context")
} }
@@ -182,11 +193,13 @@ func New(options Options) (*Box, error) {
endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry) endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry)
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager) inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final) outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
providerManager := provider.NewManager(logFactory.NewLogger("provider"), providerRegistry)
dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final) dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final)
serviceManager := boxService.NewManager(logFactory.NewLogger("service"), serviceRegistry) serviceManager := boxService.NewManager(logFactory.NewLogger("service"), serviceRegistry)
service.MustRegister[adapter.EndpointManager](ctx, endpointManager) service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
service.MustRegister[adapter.InboundManager](ctx, inboundManager) service.MustRegister[adapter.InboundManager](ctx, inboundManager)
service.MustRegister[adapter.OutboundManager](ctx, outboundManager) service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
service.MustRegister[adapter.ProviderManager](ctx, providerManager)
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager) service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
service.MustRegister[adapter.ServiceManager](ctx, serviceManager) service.MustRegister[adapter.ServiceManager](ctx, serviceManager)
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions) dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
@@ -277,6 +290,10 @@ func New(options Options) (*Box, error) {
return nil, E.Cause(err, "initialize inbound[", i, "]") return nil, E.Cause(err, "initialize inbound[", i, "]")
} }
} }
options.Outbounds = append(options.Outbounds, option.Outbound{
Tag: "Compatible",
Type: C.TypeDirect,
})
for i, outboundOptions := range options.Outbounds { for i, outboundOptions := range options.Outbounds {
var tag string var tag string
if outboundOptions.Tag != "" { if outboundOptions.Tag != "" {
@@ -303,6 +320,25 @@ func New(options Options) (*Box, error) {
return nil, E.Cause(err, "initialize outbound[", i, "]") return nil, E.Cause(err, "initialize outbound[", i, "]")
} }
} }
for i, providerOptions := range options.Providers {
var tag string
if providerOptions.Tag != "" {
tag = providerOptions.Tag
} else {
tag = F.ToString(i)
}
err = providerManager.Create(
ctx,
router,
logFactory,
tag,
providerOptions.Type,
providerOptions.Options,
)
if err != nil {
return nil, E.Cause(err, "initialize provider[", i, "]")
}
}
for i, serviceOptions := range options.Services { for i, serviceOptions := range options.Services {
var tag string var tag string
if serviceOptions.Tag != "" { if serviceOptions.Tag != "" {
@@ -331,11 +367,12 @@ func New(options Options) (*Box, error) {
) )
}) })
dnsTransportManager.Initialize(func() (adapter.DNSTransport, error) { dnsTransportManager.Initialize(func() (adapter.DNSTransport, error) {
return local.NewTransport( return dnsTransportRegistry.CreateDNSTransport(
ctx, ctx,
logFactory.NewLogger("dns/local"), logFactory.NewLogger("dns/local"),
"local", "local",
option.LocalDNSServerOptions{}, C.DNSTypeLocal,
&option.LocalDNSServerOptions{},
) )
}) })
if platformInterface != nil { if platformInterface != nil {
@@ -392,6 +429,7 @@ func New(options Options) (*Box, error) {
endpoint: endpointManager, endpoint: endpointManager,
inbound: inboundManager, inbound: inboundManager,
outbound: outboundManager, outbound: outboundManager,
provider: providerManager,
dnsTransport: dnsTransportManager, dnsTransport: dnsTransportManager,
service: serviceManager, service: serviceManager,
dnsRouter: dnsRouter, dnsRouter: dnsRouter,
@@ -455,11 +493,11 @@ func (s *Box) preStart() error {
if err != nil { if err != nil {
return err return err
} }
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) err = adapter.Start(s.logger, adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.provider, s.service)
if err != nil { if err != nil {
return err return err
} }
err = adapter.Start(s.logger, adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router) err = adapter.Start(s.logger, adapter.StartStateStart, s.outbound, s.provider, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router)
if err != nil { if err != nil {
return err return err
} }
@@ -479,7 +517,7 @@ func (s *Box) start() error {
if err != nil { if err != nil {
return err 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) err = adapter.Start(s.logger, adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint, s.provider, s.service)
if err != nil { if err != nil {
return err return err
} }
@@ -487,7 +525,7 @@ func (s *Box) start() error {
if err != nil { if err != nil {
return err 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) err = adapter.Start(s.logger, adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.provider, s.service)
if err != nil { if err != nil {
return err return err
} }
@@ -513,6 +551,7 @@ func (s *Box) Close() error {
{"service", s.service}, {"service", s.service},
{"endpoint", s.endpoint}, {"endpoint", s.endpoint},
{"inbound", s.inbound}, {"inbound", s.inbound},
{"provider", s.provider},
{"outbound", s.outbound}, {"outbound", s.outbound},
{"router", s.router}, {"router", s.router},
{"connection", s.connection}, {"connection", s.connection},
@@ -520,27 +559,24 @@ func (s *Box) Close() error {
{"dns-transport", s.dnsTransport}, {"dns-transport", s.dnsTransport},
{"network", s.network}, {"network", s.network},
} { } {
s.logger.Trace("close ", closeItem.name) done := adapter.LogElapsed(s.logger, "close ", closeItem.name)
startTime := time.Now()
err = E.Append(err, closeItem.service.Close(), func(err error) error { err = E.Append(err, closeItem.service.Close(), func(err error) error {
return E.Cause(err, "close ", closeItem.name) return E.Cause(err, "close ", closeItem.name)
}) })
s.logger.Trace("close ", closeItem.name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") done()
} }
for _, lifecycleService := range s.internalService { for _, lifecycleService := range s.internalService {
s.logger.Trace("close ", lifecycleService.Name()) done := adapter.LogElapsed(s.logger, "close ", lifecycleService.Name())
startTime := time.Now()
err = E.Append(err, lifecycleService.Close(), func(err error) error { err = E.Append(err, lifecycleService.Close(), func(err error) error {
return E.Cause(err, "close ", lifecycleService.Name()) return E.Cause(err, "close ", lifecycleService.Name())
}) })
s.logger.Trace("close ", lifecycleService.Name(), " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") done()
} }
s.logger.Trace("close logger") done := adapter.LogElapsed(s.logger, "close logger")
startTime := time.Now()
err = E.Append(err, s.logFactory.Close(), func(err error) error { err = E.Append(err, s.logFactory.Close(), func(err error) error {
return E.Cause(err, "close logger") return E.Cause(err, "close logger")
}) })
s.logger.Trace("close logger completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") done()
return err return err
} }
@@ -560,6 +596,10 @@ func (s *Box) Outbound() adapter.OutboundManager {
return s.outbound return s.outbound
} }
func (s *Box) Endpoint() adapter.EndpointManager {
return s.endpoint
}
func (s *Box) LogFactory() log.Factory { func (s *Box) LogFactory() log.Factory {
return s.logFactory return s.logFactory
} }

View File

@@ -0,0 +1,194 @@
// Command admin_panel_pack post-processes a directory of built SPA
// assets so it can be served straight from the Go binary via //go:embed.
// It does *not* generate any Go source any more — that responsibility
// moved to the embed directive in service/admin_panel/service.go.
//
// Three transformations happen here, all in-place inside the supplied
// directory:
//
// 1. Legacy WOFF (1.0) fallback fonts are deleted. Every browser made
// after 2014 reads WOFF2 natively, so shipping both formats roughly
// doubles the embedded font payload for no real-world benefit. The
// matching `,url(*.woff) format("woff")` segments are stripped from
// the bundled CSS in step (2) so the @font-face rules don't reference
// files that aren't shipped.
// 2. Bundled CSS is rewritten to drop those WOFF URL fragments.
// 3. Compressible text assets (.html, .css, .js, .svg, .json, .map) are
// pre-gzipped as companion `*.gz` files. The HTTP handler then either
// passes those bytes through verbatim with Content-Encoding: gzip or
// falls back to the raw file for the rare client that does not
// advertise gzip support — no on-line compression, no surprises.
// Already-compressed formats (.woff2, fonts, images) are skipped: gzip
// can't shrink them and the duplicate would only inflate the binary.
package main
import (
"bytes"
"compress/gzip"
"flag"
"fmt"
"io/fs"
"os"
"path/filepath"
"regexp"
"strings"
"time"
)
func main() {
dir := flag.String("dir", "service/admin_panel/dist", "directory of built SPA assets to post-process in place")
flag.Parse()
woffDropped, err := pruneWoff(*dir)
if err != nil {
fail("prune woff: %v", err)
}
cssRewritten, err := rewriteCSS(*dir)
if err != nil {
fail("rewrite css: %v", err)
}
stats, err := gzipText(*dir)
if err != nil {
fail("gzip text: %v", err)
}
fmt.Fprintf(os.Stderr, "post-processed %s: dropped %d woff, rewrote %d css, gzipped %d files (%d→%d bytes)\n",
*dir, woffDropped, cssRewritten, stats.gzipped, stats.totalRaw, stats.totalGz)
}
// pruneWoff deletes every legacy *.woff (WOFF 1.0) font under dir. The
// bundled CSS still references them on entry; rewriteCSS drops those
// references in a separate pass so the two operations stay independently
// testable.
func pruneWoff(dir string) (int, error) {
var n int
err := filepath.WalkDir(dir, func(p string, d fs.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if d.IsDir() || !strings.EqualFold(filepath.Ext(p), ".woff") {
return nil
}
if err := os.Remove(p); err != nil {
return err
}
n++
return nil
})
return n, err
}
// woffRefAfterRE / woffRefBeforeRE match a single WOFF 1.0 entry inside a
// Vite-bundled CSS `src:` declaration. Vite minifies the rule to
// `src:url(./X.woff2) format("woff2"),url(./X.woff) format("woff");` so the
// "after" regex (the common case) eats the *leading* comma + woff entry,
// leaving only the woff2 source. We also handle the rare reverse ordering
// in a second pass.
var (
woffRefAfterRE = regexp.MustCompile(`,\s*url\([^)]*\.woff\)\s*format\(["']woff["']\)`)
woffRefBeforeRE = regexp.MustCompile(`url\([^)]*\.woff\)\s*format\(["']woff["']\)\s*,\s*`)
)
// rewriteCSS drops every reference to a *.woff URL from every *.css file
// under dir. Pairs naturally with pruneWoff: after both passes, no font
// URL in the bundle points at a file that isn't shipped.
func rewriteCSS(dir string) (int, error) {
var n int
err := filepath.WalkDir(dir, func(p string, d fs.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if d.IsDir() || !strings.EqualFold(filepath.Ext(p), ".css") {
return nil
}
data, err := os.ReadFile(p)
if err != nil {
return err
}
out := woffRefAfterRE.ReplaceAll(data, nil)
out = woffRefBeforeRE.ReplaceAll(out, nil)
if bytes.Equal(out, data) {
return nil
}
if err := os.WriteFile(p, out, 0o644); err != nil {
return err
}
n++
return nil
})
return n, err
}
// gzipExts is the set of file extensions for which a `.gz` companion is
// generated. Anything not on this list is left alone — woff2/png/jpeg/etc.
// are already compressed, so gzip can only inflate them slightly while
// doubling the embedded payload.
var gzipExts = map[string]bool{
".html": true,
".css": true,
".js": true,
".mjs": true,
".svg": true,
".json": true,
".map": true,
".txt": true,
".xml": true,
".wasm": true,
}
type gzipStats struct {
gzipped int
totalRaw int64
totalGz int64
}
// gzipText produces a `<file>.gz` companion next to every text-like asset
// in dir, using gzip.BestCompression. The companion is dropped if the
// compressed bytes don't save at least 10 % over the raw file — same
// heuristic we used in the previous (Go-source-emitting) generation, just
// applied to disk files now.
func gzipText(dir string) (gzipStats, error) {
var stats gzipStats
err := filepath.WalkDir(dir, func(p string, d fs.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if d.IsDir() {
return nil
}
ext := strings.ToLower(filepath.Ext(p))
if ext == ".gz" || !gzipExts[ext] {
return nil
}
raw, err := os.ReadFile(p)
if err != nil {
return err
}
var buf bytes.Buffer
w, err := gzip.NewWriterLevel(&buf, gzip.BestCompression)
if err != nil {
return err
}
// Reproducible: no mtime, no OS marker.
w.ModTime = time.Time{}
w.OS = 0xff
if _, err := w.Write(raw); err != nil {
return err
}
if err := w.Close(); err != nil {
return err
}
if buf.Len() > len(raw)*9/10 {
return nil
}
stats.gzipped++
stats.totalRaw += int64(len(raw))
stats.totalGz += int64(buf.Len())
return os.WriteFile(p+".gz", buf.Bytes(), 0o644)
})
return stats, err
}
func fail(format string, args ...any) {
fmt.Fprintf(os.Stderr, "admin_panel_pack: "+format+"\n", args...)
os.Exit(1)
}

View File

@@ -63,7 +63,7 @@ func init() {
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0") 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") 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") sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_masque", "with_mtproxy", "with_utls", "with_naive_outbound", "with_clash_api", "badlinkname", "tfogo_checklinkname0")
darwinTags = append(darwinTags, "with_dhcp", "grpcnotrace") darwinTags = append(darwinTags, "with_dhcp", "grpcnotrace")
// memcTags = append(memcTags, "with_tailscale") // 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") 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")

View File

@@ -48,8 +48,8 @@ func GetRuntimeEnv(key string) (string, error) {
if readErr != nil { if readErr != nil {
return "", readErr return "", readErr
} }
envStrings := strings.Split(string(data), "\n") envStrings := strings.SplitSeq(string(data), "\n")
for _, envItem := range envStrings { for envItem := range envStrings {
envItem = strings.TrimSuffix(envItem, "\r") envItem = strings.TrimSuffix(envItem, "\r")
envKeyValue := strings.Split(envItem, "=") envKeyValue := strings.Split(envItem, "=")
if strings.EqualFold(strings.TrimSpace(envKeyValue[0]), key) { if strings.EqualFold(strings.TrimSpace(envKeyValue[0]), key) {

View File

@@ -39,7 +39,7 @@ func main() {
common.Must(os.Chdir(androidPath)) common.Must(os.Chdir(androidPath))
localProps := common.Must1(os.ReadFile("version.properties")) localProps := common.Must1(os.ReadFile("version.properties"))
var propsList [][]string var propsList [][]string
for _, propLine := range strings.Split(string(localProps), "\n") { for propLine := range strings.SplitSeq(string(localProps), "\n") {
propsList = append(propsList, strings.Split(propLine, "=")) propsList = append(propsList, strings.Split(propLine, "="))
} }
var ( var (

View File

@@ -45,10 +45,8 @@ package certificate
import "crypto/x509" import "crypto/x509"
var mozillaIncluded *x509.CertPool func newMozillaIncluded() *x509.CertPool {
pool := x509.NewCertPool()
func init() {
mozillaIncluded = x509.NewCertPool()
`) `)
for { for {
record, err := reader.Read() record, err := reader.Read()
@@ -63,14 +61,14 @@ func init() {
generated.WriteString("\n // ") generated.WriteString("\n // ")
generated.WriteString(record[nameIndex]) generated.WriteString(record[nameIndex])
generated.WriteString("\n") generated.WriteString("\n")
generated.WriteString(" mozillaIncluded.AppendCertsFromPEM([]byte(`") generated.WriteString(" pool.AppendCertsFromPEM([]byte(`")
cert := record[certIndex] cert := record[certIndex]
// Remove single quotes // Remove single quotes
cert = cert[1 : len(cert)-1] cert = cert[1 : len(cert)-1]
generated.WriteString(cert) generated.WriteString(cert)
generated.WriteString("`))\n") generated.WriteString("`))\n")
} }
generated.WriteString("}\n") generated.WriteString("\treturn pool\n}\n")
return os.WriteFile("common/certificate/mozilla.go", []byte(generated.String()), 0o644) return os.WriteFile("common/certificate/mozilla.go", []byte(generated.String()), 0o644)
} }
@@ -131,10 +129,8 @@ package certificate
import "crypto/x509" import "crypto/x509"
var chromeIncluded *x509.CertPool func newChromeIncluded() *x509.CertPool {
pool := x509.NewCertPool()
func init() {
chromeIncluded = x509.NewCertPool()
`) `)
for { for {
record, err := reader.Read() record, err := reader.Read()
@@ -152,7 +148,7 @@ func init() {
generated.WriteString("\n // ") generated.WriteString("\n // ")
generated.WriteString(record[subjectIndex]) generated.WriteString(record[subjectIndex])
generated.WriteString("\n") generated.WriteString("\n")
generated.WriteString(" chromeIncluded.AppendCertsFromPEM([]byte(`") generated.WriteString(" pool.AppendCertsFromPEM([]byte(`")
cert := record[certIndex] cert := record[certIndex]
// Remove single quotes if present // Remove single quotes if present
if len(cert) > 0 && cert[0] == '\'' { if len(cert) > 0 && cert[0] == '\'' {
@@ -161,6 +157,6 @@ func init() {
generated.WriteString(cert) generated.WriteString(cert)
generated.WriteString("`))\n") generated.WriteString("`))\n")
} }
generated.WriteString("}\n") generated.WriteString("\treturn pool\n}\n")
return os.WriteFile("common/certificate/chrome.go", []byte(generated.String()), 0o644) return os.WriteFile("common/certificate/chrome.go", []byte(generated.String()), 0o644)
} }

View File

@@ -61,16 +61,17 @@ func geoipExport(countryCode string) error {
outputFile *os.File outputFile *os.File
outputWriter io.Writer outputWriter io.Writer
) )
if flagGeoipExportOutput == "stdout" { switch flagGeoipExportOutput {
case "stdout":
outputWriter = os.Stdout outputWriter = os.Stdout
} else if flagGeoipExportOutput == flagGeoipExportDefaultOutput { case flagGeoipExportDefaultOutput:
outputFile, err = os.Create("geoip-" + countryCode + ".json") outputFile, err = os.Create("geoip-" + countryCode + ".json")
if err != nil { if err != nil {
return err return err
} }
defer outputFile.Close() defer outputFile.Close()
outputWriter = outputFile outputWriter = outputFile
} else { default:
outputFile, err = os.Create(flagGeoipExportOutput) outputFile, err = os.Create(flagGeoipExportOutput)
if err != nil { if err != nil {
return err return err

View File

@@ -43,16 +43,17 @@ func geositeExport(category string) error {
outputFile *os.File outputFile *os.File
outputWriter io.Writer outputWriter io.Writer
) )
if commandGeositeExportOutput == "stdout" { switch commandGeositeExportOutput {
case "stdout":
outputWriter = os.Stdout outputWriter = os.Stdout
} else if commandGeositeExportOutput == commandGeositeExportDefaultOutput { case commandGeositeExportDefaultOutput:
outputFile, err = os.Create("geosite-" + category + ".json") outputFile, err = os.Create("geosite-" + category + ".json")
if err != nil { if err != nil {
return err return err
} }
defer outputFile.Close() defer outputFile.Close()
outputWriter = outputFile outputWriter = outputFile
} else { default:
outputFile, err = os.Create(commandGeositeExportOutput) outputFile, err = os.Create(commandGeositeExportOutput)
if err != nil { if err != nil {
return err return err

View File

@@ -1,3 +1,5 @@
//go:build with_quic
package main package main
import ( import (

View File

@@ -29,7 +29,7 @@ type RawHalfConn struct {
func NewRawHalfConn(rawHalfConn reflect.Value, methods *Methods) (*RawHalfConn, error) { func NewRawHalfConn(rawHalfConn reflect.Value, methods *Methods) (*RawHalfConn, error) {
halfConn := &RawHalfConn{ halfConn := &RawHalfConn{
pointer: (unsafe.Pointer)(rawHalfConn.UnsafeAddr()), pointer: unsafe.Pointer(rawHalfConn.UnsafeAddr()),
methods: methods, methods: methods,
} }

View File

@@ -112,9 +112,7 @@ func IsValid(versionName string) bool {
} }
func Parse(versionName string) (version Version) { func Parse(versionName string) (version Version) {
if strings.HasPrefix(versionName, "v") { versionName = strings.TrimPrefix(versionName, "v")
versionName = versionName[1:]
}
if strings.Contains(versionName, "-") { if strings.Contains(versionName, "-") {
parts := strings.Split(versionName, "-") parts := strings.Split(versionName, "-")
versionName = parts[0] versionName = parts[0]

View File

@@ -0,0 +1,74 @@
package byteformats
import (
"fmt"
"math"
)
var (
unitNames = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
iUnitNames = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
kUnitNames = []string{"kB", "MB", "GB", "TB", "PB", "EB"}
kiUnitNames = []string{"KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
)
func formatBytes(s uint64, base float64, sizes []string) string {
if s < 10 {
return fmt.Sprintf("%d B", s)
}
e := math.Floor(logn(float64(s), base))
suffix := sizes[int(e)]
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
f := "%.0f %s"
if val < 10 {
f = "%.1f %s"
}
return fmt.Sprintf(f, val, suffix)
}
func formatKBytes(s uint64, base float64, sizes []string) string {
if s == 0 {
return fmt.Sprintf("0 %s", sizes[0])
}
e := math.Floor(logn(float64(s), base))
if e < 1 {
e = 1
}
suffix := sizes[int(e)-1]
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
f := "%.0f %s"
if val < 10 {
f = "%.1f %s"
}
return fmt.Sprintf(f, val, suffix)
}
func logn(n, b float64) float64 {
return math.Log(n) / math.Log(b)
}
func FormatBytes(s uint64) string {
return formatBytes(s, 1000, unitNames)
}
func FormatMemoryBytes(s uint64) string {
return formatBytes(s, 1024, unitNames)
}
func FormatIBytes(s uint64) string {
return formatBytes(s, 1024, iUnitNames)
}
func FormatKBytes(s uint64) string {
return formatKBytes(s, 1000, kUnitNames)
}
func FormatMemoryKBytes(s uint64) string {
return formatKBytes(s, 1024, kUnitNames)
}
func FormatKIBytes(s uint64) string {
return formatKBytes(s, 1024, kiUnitNames)
}

218
common/byteformats/json.go Normal file
View File

@@ -0,0 +1,218 @@
package byteformats
import (
"encoding/json"
"fmt"
"strconv"
"strings"
)
const (
Byte = 1 << (iota * 10)
KiByte
MiByte
GiByte
TiByte
PiByte
EiByte
)
const (
KByte = Byte * 1000
MByte = KByte * 1000
GByte = MByte * 1000
TByte = GByte * 1000
PByte = TByte * 1000
EByte = PByte * 1000
)
var unitValueTable = map[string]uint64{
"b": Byte,
"k": KByte,
"kb": KByte,
"ki": KiByte,
"kib": KiByte,
"m": MByte,
"mb": MByte,
"mi": MiByte,
"mib": MiByte,
"g": GByte,
"gb": GByte,
"gi": GiByte,
"gib": GiByte,
"t": TByte,
"tb": TByte,
"ti": TiByte,
"tib": TiByte,
"p": PByte,
"pb": PByte,
"pi": PiByte,
"pib": PiByte,
"e": EByte,
"eb": EByte,
"ei": EiByte,
"eib": EiByte,
}
var memoryUnitValueTable = map[string]uint64{
"b": Byte,
"k": KiByte,
"kb": KiByte,
"m": MiByte,
"mb": MiByte,
"g": GiByte,
"gb": GiByte,
"t": TiByte,
"tb": TiByte,
"p": PiByte,
"pb": PiByte,
"e": EiByte,
"eb": EiByte,
}
var networkUnitValueTable = map[string]uint64{
"Bps": Byte,
"Kbps": KByte / 8,
"KBps": KByte,
"Mbps": MByte / 8,
"MBps": MByte,
"Gbps": GByte / 8,
"GBps": GByte,
"Tbps": TByte / 8,
"TBps": TByte,
"Pbps": PByte / 8,
"PBps": PByte,
"Ebps": EByte / 8,
"EBps": EByte,
}
type rawBytes struct {
value uint64
unit string
unitValue uint64
}
func (b rawBytes) MarshalJSON() ([]byte, error) {
if b.unit == "" {
return json.Marshal(b.value)
}
return json.Marshal(strconv.FormatUint(b.value/b.unitValue, 10) + b.unit)
}
func parseUnit(b *rawBytes, unitTable map[string]uint64, caseSensitive bool, bytes []byte) error {
var intValue int64
err := json.Unmarshal(bytes, &intValue)
if err == nil {
b.value = uint64(intValue)
b.unit = ""
b.unitValue = 1
return nil
}
var stringValue string
err = json.Unmarshal(bytes, &stringValue)
if err != nil {
return err
}
if strings.TrimSpace(stringValue) == "" {
b.value = 0
b.unit = ""
b.unitValue = 1
return nil
}
unitIndex := 0
for i, c := range stringValue {
if c < '0' || c > '9' {
unitIndex = i
break
}
}
if unitIndex == 0 {
return fmt.Errorf("invalid format: %s", stringValue)
}
value, err := strconv.ParseUint(stringValue[:unitIndex], 10, 64)
if err != nil {
return fmt.Errorf("parse %s: %w", stringValue[:unitIndex], err)
}
rawUnit := stringValue[unitIndex:]
var unit string
if caseSensitive {
unit = strings.TrimSpace(rawUnit)
} else {
unit = strings.TrimSpace(strings.ToLower(rawUnit))
}
unitValue, loaded := unitTable[unit]
if !loaded {
return fmt.Errorf("unsupported unit: %s", rawUnit)
}
b.value = value * unitValue
b.unit = rawUnit
b.unitValue = unitValue
return nil
}
type Bytes struct {
rawBytes
}
func (b *Bytes) Value() uint64 {
if b == nil {
return 0
}
return b.value
}
func (b *Bytes) UnmarshalJSON(bytes []byte) error {
return parseUnit(&b.rawBytes, unitValueTable, false, bytes)
}
type MemoryBytes struct {
rawBytes
}
func (m *MemoryBytes) Value() uint64 {
if m == nil {
return 0
}
return m.value
}
func (m *MemoryBytes) UnmarshalJSON(bytes []byte) error {
return parseUnit(&m.rawBytes, memoryUnitValueTable, false, bytes)
}
type NetworkBytes struct {
rawBytes
}
func (n *NetworkBytes) Value() uint64 {
if n == nil {
return 0
}
return n.value
}
func (n *NetworkBytes) UnmarshalJSON(bytes []byte) error {
return parseUnit(&n.rawBytes, networkUnitValueTable, true, bytes)
}
type NetworkBytesCompat struct {
rawBytes
}
func (n *NetworkBytesCompat) Value() uint64 {
if n == nil {
return 0
}
return n.value
}
func (n *NetworkBytesCompat) UnmarshalJSON(bytes []byte) error {
err := parseUnit(&n.rawBytes, networkUnitValueTable, true, bytes)
if err != nil {
newErr := parseUnit(&n.rawBytes, unitValueTable, false, bytes)
if newErr == nil {
return nil
}
}
return err
}

View File

@@ -0,0 +1,114 @@
package byteformats_test
import (
"encoding/json"
"testing"
"github.com/sagernet/sing-box/common/byteformats"
"github.com/stretchr/testify/require"
)
func TestNetworkBytes(t *testing.T) {
t.Parallel()
testMap := map[string]uint64{
"1 Bps": byteformats.Byte,
"1 Kbps": byteformats.KByte / 8,
"1 KBps": byteformats.KByte,
"1 Mbps": byteformats.MByte / 8,
"1 MBps": byteformats.MByte,
"1 Gbps": byteformats.GByte / 8,
"1 GBps": byteformats.GByte,
"1 Tbps": byteformats.TByte / 8,
"1 TBps": byteformats.TByte,
"1 Pbps": byteformats.PByte / 8,
"1 PBps": byteformats.PByte,
"1k": byteformats.KByte,
"1m": byteformats.MByte,
}
for k, v := range testMap {
var nb byteformats.NetworkBytesCompat
require.NoError(t, json.Unmarshal([]byte("\""+k+"\""), &nb))
require.Equal(t, v, nb.Value())
b, err := json.Marshal(nb)
require.NoError(t, err)
require.Equal(t, "\""+k+"\"", string(b))
}
}
func TestMemoryBytes(t *testing.T) {
t.Parallel()
testMap := map[string]uint64{
"1 B": byteformats.Byte,
"1 KB": byteformats.KiByte,
"1 MB": byteformats.MiByte,
"1 GB": byteformats.GiByte,
"1 TB": byteformats.TiByte,
"1 PB": byteformats.PiByte,
}
for k, v := range testMap {
var mb byteformats.MemoryBytes
require.NoError(t, json.Unmarshal([]byte("\""+k+"\""), &mb))
require.Equal(t, v, mb.Value())
b, err := json.Marshal(mb)
require.NoError(t, err)
require.Equal(t, "\""+k+"\"", string(b))
}
}
func TestDefaultBytes(t *testing.T) {
t.Parallel()
testMap := map[string]uint64{
"1 B": byteformats.Byte,
"1 KB": byteformats.KByte,
"1 KiB": byteformats.KiByte,
"1 MB": byteformats.MByte,
"1 MiB": byteformats.MiByte,
"1 GB": byteformats.GByte,
"1 GiB": byteformats.GiByte,
"1 TB": byteformats.TByte,
"1 TiB": byteformats.TiByte,
"1 PB": byteformats.PByte,
"1 PiB": byteformats.PiByte,
"1 EB": byteformats.EByte,
"1 EiB": byteformats.EiByte,
"1k": byteformats.KByte,
"1m": byteformats.MByte,
"1g": byteformats.GByte,
"1t": byteformats.TByte,
"1p": byteformats.PByte,
"1e": byteformats.EByte,
"1K": byteformats.KByte,
"1M": byteformats.MByte,
"1G": byteformats.GByte,
"1T": byteformats.TByte,
"1P": byteformats.PByte,
"1E": byteformats.EByte,
"1Ki": byteformats.KiByte,
"1Mi": byteformats.MiByte,
"1Gi": byteformats.GiByte,
"1Ti": byteformats.TiByte,
"1Pi": byteformats.PiByte,
"1Ei": byteformats.EiByte,
"1KiB": byteformats.KiByte,
"1MiB": byteformats.MiByte,
"1GiB": byteformats.GiByte,
"1TiB": byteformats.TiByte,
"1PiB": byteformats.PiByte,
"1EiB": byteformats.EiByte,
"1kB": byteformats.KByte,
"1mB": byteformats.MByte,
"1gB": byteformats.GByte,
"1tB": byteformats.TByte,
"1pB": byteformats.PByte,
"1eB": byteformats.EByte,
}
for k, v := range testMap {
var mb byteformats.Bytes
require.NoError(t, json.Unmarshal([]byte("\""+k+"\""), &mb))
require.Equal(t, v, mb.Value())
b, err := json.Marshal(mb)
require.NoError(t, err)
require.Equal(t, "\""+k+"\"", string(b))
}
}

View File

@@ -4,13 +4,11 @@ package certificate
import "crypto/x509" import "crypto/x509"
var chromeIncluded *x509.CertPool func newChromeIncluded() *x509.CertPool {
pool := x509.NewCertPool()
func init() {
chromeIncluded = x509.NewCertPool()
// CN=Actalis Authentication Root CA; O=Actalis S.p.A./03358520967; L=Milan; C=IT // CN=Actalis Authentication Root CA; O=Actalis S.p.A./03358520967; L=Milan; C=IT
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE
BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w
MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290
@@ -45,7 +43,7 @@ LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg==
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=TunTrust Root CA; O=Agence Nationale de Certification Electronique; C=TN // CN=TunTrust Root CA; O=Agence Nationale de Certification Electronique; C=TN
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQEL MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQEL
BQAwYTELMAkGA1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUg BQAwYTELMAkGA1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUg
Q2VydGlmaWNhdGlvbiBFbGVjdHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJv Q2VydGlmaWNhdGlvbiBFbGVjdHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJv
@@ -80,7 +78,7 @@ d9qDRIueVSjAi1jTkD5OGwDxFa2DK5o=
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=Amazon Root CA 4; O=Amazon; C=US // CN=Amazon Root CA 4; O=Amazon; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5 MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5
MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g
Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG
@@ -95,7 +93,7 @@ CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=Amazon Root CA 1; O=Amazon; C=US // CN=Amazon Root CA 1; O=Amazon; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF
ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6
b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL
@@ -117,7 +115,7 @@ rqXRfboQnoZsG4q5WTP468SQvvG5
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=Amazon Root CA 2; O=Amazon; C=US // CN=Amazon Root CA 2; O=Amazon; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF
ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6
b24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL b24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL
@@ -150,7 +148,7 @@ n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=Amazon Root CA 3; O=Amazon; C=US // CN=Amazon Root CA 3; O=Amazon; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5 MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5
MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g
Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG
@@ -164,7 +162,7 @@ YyRIHN8wfdVoOw==
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=Certum Trusted Network CA; OU=Certum Certification Authority; O=Unizeto Technologies S.A.; C=PL // CN=Certum Trusted Network CA; OU=Certum Certification Authority; O=Unizeto Technologies S.A.; C=PL
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM
MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D
ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU
@@ -188,7 +186,7 @@ VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=Certum EC-384 CA; OU=Certum Certification Authority; O=Asseco Data Systems S.A.; C=PL // CN=Certum EC-384 CA; OU=Certum Certification Authority; O=Asseco Data Systems S.A.; C=PL
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQsw MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQsw
CQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScw CQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScw
JQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMT JQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMT
@@ -205,7 +203,7 @@ nvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k=
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=Certum Trusted Root CA; OU=Certum Certification Authority; O=Asseco Data Systems S.A.; C=PL // CN=Certum Trusted Root CA; OU=Certum Certification Authority; O=Asseco Data Systems S.A.; C=PL
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6 MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6
MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEu MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEu
MScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNV MScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNV
@@ -240,7 +238,7 @@ E2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=Certum Trusted Network CA 2; OU=Certum Certification Authority; O=Unizeto Technologies S.A.; C=PL // CN=Certum Trusted Network CA 2; OU=Certum Certification Authority; O=Unizeto Technologies S.A.; C=PL
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCB MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCB
gDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu gDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu
QS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIG QS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIG
@@ -276,7 +274,7 @@ DrW5viSP
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=Autoridad de Certificacion Firmaprofesional CIF A62634068; C=ES // CN=Autoridad de Certificacion Firmaprofesional CIF A62634068; C=ES
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UE MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UE
BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h
cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1 cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1
@@ -313,7 +311,7 @@ GbqEZycPvEJdvSRUDewdcAZfpLz6IHxV
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=ANF Secure Server Root CA; OU=ANF CA Raiz; O=ANF Autoridad de Certificacion; C=ES; SerialNumber=G63287510 // CN=ANF Secure Server Root CA; OU=ANF CA Raiz; O=ANF Autoridad de Certificacion; C=ES; SerialNumber=G63287510
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNV MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNV
BAUTCUc2MzI4NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlk BAUTCUc2MzI4NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlk
YWQgZGUgQ2VydGlmaWNhY2lvbjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNV YWQgZGUgQ2VydGlmaWNhY2lvbjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNV
@@ -349,7 +347,7 @@ tt7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw=
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=Buypass Class 2 Root CA; O=Buypass AS-983163327; C=NO // CN=Buypass Class 2 Root CA; O=Buypass AS-983163327; C=NO
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd
MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg
Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow
@@ -382,7 +380,7 @@ Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA=
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=Buypass Class 3 Root CA; O=Buypass AS-983163327; C=NO // CN=Buypass Class 3 Root CA; O=Buypass AS-983163327; C=NO
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd
MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg
Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow
@@ -415,7 +413,7 @@ u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=Certainly Root R1; O=Certainly; C=US // CN=Certainly Root R1; O=Certainly; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAw MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAw
PTELMAkGA1UEBhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2Vy PTELMAkGA1UEBhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2Vy
dGFpbmx5IFJvb3QgUjEwHhcNMjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9 dGFpbmx5IFJvb3QgUjEwHhcNMjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9
@@ -448,7 +446,7 @@ OV+KmalBWQewLK8=
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=Certainly Root E1; O=Certainly; C=US // CN=Certainly Root E1; O=Certainly; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQsw MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQsw
CQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlu CQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlu
bHkgUm9vdCBFMTAeFw0yMTA0MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJ bHkgUm9vdCBFMTAeFw0yMTA0MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJ
@@ -463,7 +461,7 @@ BtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=Certigna; O=Dhimyotis; C=FR // CN=Certigna; O=Dhimyotis; C=FR
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV
BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X
DTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ DTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ
@@ -487,7 +485,7 @@ WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg==
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=Certigna Root CA; OU=0002 48146308100036; O=Dhimyotis; C=FR // CN=Certigna Root CA; OU=0002 48146308100036; O=Dhimyotis; C=FR
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAw MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAw
WjELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAw WjELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAw
MiA0ODE0NjMwODEwMDAzNjEZMBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0x MiA0ODE0NjMwODEwMDAzNjEZMBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0x
@@ -525,7 +523,7 @@ jWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// OU=certSIGN ROOT CA; O=certSIGN; C=RO // OU=certSIGN ROOT CA; O=certSIGN; C=RO
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT
AlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD AlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD
QTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP QTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP
@@ -547,7 +545,7 @@ i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// OU=certSIGN ROOT CA G2; O=CERTSIGN SA; C=RO // OU=certSIGN ROOT CA G2; O=CERTSIGN SA; C=RO
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNV MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNV
BAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04g BAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04g
Uk9PVCBDQSBHMjAeFw0xNzAyMDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJ Uk9PVCBDQSBHMjAeFw0xNzAyMDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJ
@@ -580,7 +578,7 @@ QRBdJ3NghVdJIgc=
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=HiPKI Root CA - G1; O=Chunghwa Telecom Co., Ltd.; C=TW // CN=HiPKI Root CA - G1; O=Chunghwa Telecom Co., Ltd.; C=TW
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBP MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBP
MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0
ZC4xGzAZBgNVBAMMEkhpUEtJIFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRa ZC4xGzAZBgNVBAMMEkhpUEtJIFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRa
@@ -613,7 +611,7 @@ YDksswBVLuT1sw5XxJFBAJw/6KXf6vb/yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ==
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// OU=ePKI Root Certification Authority; O=Chunghwa Telecom Co., Ltd.; C=TW // OU=ePKI Root Certification Authority; O=Chunghwa Telecom Co., Ltd.; C=TW
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe
MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0
ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe
@@ -648,7 +646,7 @@ hNQ+IIX3Sj0rnP0qCglN6oH4EZw=
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=D-TRUST BR Root CA 1 2020; O=D-Trust GmbH; C=DE // CN=D-TRUST BR Root CA 1 2020; O=D-Trust GmbH; C=DE
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQsw MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQsw
CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS
VVNUIEJSIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5 VVNUIEJSIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5
@@ -668,7 +666,7 @@ dWNbFJWcHwHP2NVypw87
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=D-TRUST EV Root CA 1 2020; O=D-Trust GmbH; C=DE // CN=D-TRUST EV Root CA 1 2020; O=D-Trust GmbH; C=DE
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQsw MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQsw
CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS
VVNUIEVWIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5 VVNUIEVWIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5
@@ -688,7 +686,7 @@ gfM0agPnIjhQW+0ZT0MW
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=D-TRUST Root Class 3 CA 2 EV 2009; O=D-Trust GmbH; C=DE // CN=D-TRUST Root Class 3 CA 2 EV 2009; O=D-Trust GmbH; C=DE
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF
MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD
bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw
@@ -715,7 +713,7 @@ KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=D-TRUST Root Class 3 CA 2 2009; O=D-Trust GmbH; C=DE // CN=D-TRUST Root Class 3 CA 2 2009; O=D-Trust GmbH; C=DE
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF
MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD
bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha
@@ -742,7 +740,7 @@ Johw1+qRzT65ysCQblrGXnRl11z+o+I=
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=T-TeleSec GlobalRoot Class 3; OU=T-Systems Trust Center; O=T-Systems Enterprise Services GmbH; C=DE // CN=T-TeleSec GlobalRoot Class 3; OU=T-Systems Trust Center; O=T-Systems Enterprise Services GmbH; C=DE
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx
KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd
BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl
@@ -767,7 +765,7 @@ TpPDpFQUWw==
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=T-TeleSec GlobalRoot Class 2; OU=T-Systems Trust Center; O=T-Systems Enterprise Services GmbH; C=DE // CN=T-TeleSec GlobalRoot Class 2; OU=T-Systems Trust Center; O=T-Systems Enterprise Services GmbH; C=DE
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx
KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd
BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl
@@ -792,7 +790,7 @@ BSeOE6Fuwg==
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=DigiCert TLS RSA4096 Root G5; O=DigiCert, Inc.; C=US // CN=DigiCert TLS RSA4096 Root G5; O=DigiCert, Inc.; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBN MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBN
MQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMT MQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMT
HERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcN HERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcN
@@ -825,7 +823,7 @@ ovfepEWFJqgejF0pW8hL2JpqA15w8oVPbEtoL8pU9ozaMv7Da4M/OMZ+
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=DigiCert TLS ECC P384 Root G5; O=DigiCert, Inc.; C=US // CN=DigiCert TLS ECC P384 Root G5; O=DigiCert, Inc.; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQsw MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQsw
CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURp CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURp
Z2lDZXJ0IFRMUyBFQ0MgUDM4NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2 Z2lDZXJ0IFRMUyBFQ0MgUDM4NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2
@@ -841,7 +839,7 @@ DXZDjC5Ty3zfDBeWUA==
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=DigiCert Assured ID Root CA; OU=www.digicert.com; O=DigiCert Inc; C=US // CN=DigiCert Assured ID Root CA; OU=www.digicert.com; O=DigiCert Inc; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv
@@ -865,7 +863,7 @@ H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=DigiCert Assured ID Root G2; OU=www.digicert.com; O=DigiCert Inc; C=US // CN=DigiCert Assured ID Root G2; OU=www.digicert.com; O=DigiCert Inc; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv
@@ -889,7 +887,7 @@ IhNzbM8m9Yop5w==
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=DigiCert Assured ID Root G3; OU=www.digicert.com; O=DigiCert Inc; C=US // CN=DigiCert Assured ID Root G3; OU=www.digicert.com; O=DigiCert Inc; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw
CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg
@@ -906,7 +904,7 @@ JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=DigiCert Global Root CA; OU=www.digicert.com; O=DigiCert Inc; C=US // CN=DigiCert Global Root CA; OU=www.digicert.com; O=DigiCert Inc; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
@@ -930,7 +928,7 @@ CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=DigiCert Global Root G2; OU=www.digicert.com; O=DigiCert Inc; C=US // CN=DigiCert Global Root G2; OU=www.digicert.com; O=DigiCert Inc; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
@@ -954,7 +952,7 @@ MrY=
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=DigiCert Global Root G3; OU=www.digicert.com; O=DigiCert Inc; C=US // CN=DigiCert Global Root G3; OU=www.digicert.com; O=DigiCert Inc; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw
CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe
@@ -971,7 +969,7 @@ sycX
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=DigiCert High Assurance EV Root CA; OU=www.digicert.com; O=DigiCert Inc; C=US // CN=DigiCert High Assurance EV Root CA; OU=www.digicert.com; O=DigiCert Inc; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
@@ -996,7 +994,7 @@ vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=DigiCert Trusted Root G4; OU=www.digicert.com; O=DigiCert Inc; C=US // CN=DigiCert Trusted Root G4; OU=www.digicert.com; O=DigiCert Inc; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg
@@ -1030,7 +1028,7 @@ gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=QuoVadis Root CA 2; O=QuoVadis Limited; C=BM // CN=QuoVadis Root CA 2; O=QuoVadis Limited; C=BM
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x
GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv
b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV
@@ -1065,7 +1063,7 @@ ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=QuoVadis Root CA 2 G3; O=QuoVadis Limited; C=BM // CN=QuoVadis Root CA 2 G3; O=QuoVadis Limited; C=BM
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL
BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc
BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00 BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00
@@ -1098,7 +1096,7 @@ WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=QuoVadis Root CA 3 G3; O=QuoVadis Limited; C=BM // CN=QuoVadis Root CA 3 G3; O=QuoVadis Limited; C=BM
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL
BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc
BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00 BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00
@@ -1131,7 +1129,7 @@ ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=CA Disig Root R2; O=Disig a.s.; L=Bratislava; C=SK // CN=CA Disig Root R2; O=Disig a.s.; L=Bratislava; C=SK
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV
BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu
MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy
@@ -1164,7 +1162,7 @@ L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=emSign ECC Root CA - G3; OU=emSign PKI; O=eMudhra Technologies Limited; C=IN // CN=emSign ECC Root CA - G3; OU=emSign PKI; O=eMudhra Technologies Limited; C=IN
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG
EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo
bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g
@@ -1181,7 +1179,7 @@ CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=emSign Root CA - G1; OU=emSign PKI; O=eMudhra Technologies Limited; C=IN // CN=emSign Root CA - G1; OU=emSign PKI; O=eMudhra Technologies Limited; C=IN
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYD MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYD
VQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBU VQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBU
ZWNobm9sb2dpZXMgTGltaXRlZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBH ZWNobm9sb2dpZXMgTGltaXRlZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBH
@@ -1205,7 +1203,7 @@ iN66zB+Afko=
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=AffirmTrust Commercial; O=AffirmTrust; C=US // CN=AffirmTrust Commercial; O=AffirmTrust; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE
BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz
dCBDb21tZXJjaWFsMB4XDTEwMDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDEL dCBDb21tZXJjaWFsMB4XDTEwMDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDEL
@@ -1227,7 +1225,7 @@ nlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8=
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=Atos TrustedRoot 2011; O=Atos; C=DE // CN=Atos TrustedRoot 2011; O=Atos; C=DE
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE
AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG
EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM
@@ -1250,7 +1248,7 @@ KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=Atos TrustedRoot Root CA ECC TLS 2021; O=Atos; C=DE // CN=Atos TrustedRoot Root CA ECC TLS 2021; O=Atos; C=DE
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICFTCCAZugAwIBAgIQPZg7pmY9kGP3fiZXOATvADAKBggqhkjOPQQDAzBMMS4w MIICFTCCAZugAwIBAgIQPZg7pmY9kGP3fiZXOATvADAKBggqhkjOPQQDAzBMMS4w
LAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgRUNDIFRMUyAyMDIxMQ0w LAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgRUNDIFRMUyAyMDIxMQ0w
CwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTI2MjNaFw00MTA0 CwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTI2MjNaFw00MTA0
@@ -1266,7 +1264,7 @@ CCrCp1rIAjEAmeMM56PDr9NJLkaCI2ZdyQAUEv049OGYa3cpetskz2VAv9LcjBHo
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=Atos TrustedRoot Root CA RSA TLS 2021; O=Atos; C=DE // CN=Atos TrustedRoot Root CA RSA TLS 2021; O=Atos; C=DE
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFZDCCA0ygAwIBAgIQU9XP5hmTC/srBRLYwiqipDANBgkqhkiG9w0BAQwFADBM MIIFZDCCA0ygAwIBAgIQU9XP5hmTC/srBRLYwiqipDANBgkqhkiG9w0BAQwFADBM
MS4wLAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgUlNBIFRMUyAyMDIx MS4wLAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgUlNBIFRMUyAyMDIx
MQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTIxMTBaFw00 MQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTIxMTBaFw00
@@ -1299,7 +1297,7 @@ oji2jbDwN/zIIX8/syQbPYtuzE2wFg2WHYMfRsCbvUOZ58SWLs5fyQ==
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=GlobalSign; OU=GlobalSign Root CA - R6; O=GlobalSign // CN=GlobalSign; OU=GlobalSign Root CA - R6; O=GlobalSign
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg
MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh
bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx
@@ -1333,7 +1331,7 @@ JJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=GlobalSign Root E46; O=GlobalSign nv-sa; C=BE // CN=GlobalSign Root E46; O=GlobalSign nv-sa; C=BE
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYx MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYx
CzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQD CzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQD
ExNHbG9iYWxTaWduIFJvb3QgRTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAw ExNHbG9iYWxTaWduIFJvb3QgRTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAw
@@ -1348,7 +1346,7 @@ DgQWBBQxCpCPtsad0kRLgLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=GlobalSign Root R46; O=GlobalSign nv-sa; C=BE // CN=GlobalSign Root R46; O=GlobalSign nv-sa; C=BE
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUA MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUA
MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYD MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYD
VQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMy VQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMy
@@ -1381,7 +1379,7 @@ vouXsXgxT7PntgMTzlSdriVZzH81Xwj3QEUxeCp6
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=GlobalSign; OU=GlobalSign ECC Root CA - R5; O=GlobalSign // CN=GlobalSign; OU=GlobalSign ECC Root CA - R5; O=GlobalSign
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk
MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH
bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX
@@ -1397,7 +1395,7 @@ xwy8p2Fp8fc74SrL+SvzZpA3
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=GlobalSign; OU=GlobalSign Root CA - R3; O=GlobalSign // CN=GlobalSign; OU=GlobalSign Root CA - R3; O=GlobalSign
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G
A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp
Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4 Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4
@@ -1420,7 +1418,7 @@ WD9f
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=Starfield Root Certificate Authority - G2; O=Starfield Technologies, Inc.; L=Scottsdale; ST=Arizona; C=US // CN=Starfield Root Certificate Authority - G2; O=Starfield Technologies, Inc.; L=Scottsdale; ST=Arizona; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx
EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT
HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs
@@ -1445,7 +1443,7 @@ mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=Go Daddy Root Certificate Authority - G2; O=GoDaddy.com, Inc.; L=Scottsdale; ST=Arizona; C=US // CN=Go Daddy Root Certificate Authority - G2; O=GoDaddy.com, Inc.; L=Scottsdale; ST=Arizona; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx
EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT
EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp
@@ -1470,7 +1468,7 @@ LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=GlobalSign; OU=GlobalSign ECC Root CA - R4; O=GlobalSign // CN=GlobalSign; OU=GlobalSign ECC Root CA - R4; O=GlobalSign
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYD MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYD
VQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2Jh VQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2Jh
bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgw bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgw
@@ -1484,7 +1482,7 @@ bmF0774BxL4YSFlhgjICICadVGNA3jdgUM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=GTS Root R4; O=Google Trust Services LLC; C=US // CN=GTS Root R4; O=Google Trust Services LLC; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYD MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYD
VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG
A1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw A1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw
@@ -1499,7 +1497,7 @@ p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh4rsUecrNIdSUtUlD
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=GTS Root R2; O=Google Trust Services LLC; C=US // CN=GTS Root R2; O=Google Trust Services LLC; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQsw MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQsw
CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
MBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw MBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
@@ -1532,7 +1530,7 @@ JPFI/2R80L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=GTS Root R1; O=Google Trust Services LLC; C=US // CN=GTS Root R1; O=Google Trust Services LLC; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw
CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
@@ -1565,7 +1563,7 @@ bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=GTS Root R3; O=Google Trust Services LLC; C=US // CN=GTS Root R3; O=Google Trust Services LLC; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYD MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYD
VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG
A1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw A1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw
@@ -1580,7 +1578,7 @@ ZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV11RZt+cRLInUue4X
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=ACCVRAIZ1; OU=PKIACCV; O=ACCV; C=ES // CN=ACCVRAIZ1; OU=PKIACCV; O=ACCV; C=ES
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE
AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw
CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ
@@ -1626,7 +1624,7 @@ pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// OU=AC RAIZ FNMT-RCM; O=FNMT-RCM; C=ES // OU=AC RAIZ FNMT-RCM; O=FNMT-RCM; C=ES
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsx MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsx
CzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJ CzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJ
WiBGTk1ULVJDTTAeFw0wODEwMjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJ WiBGTk1ULVJDTTAeFw0wODEwMjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJ
@@ -1660,7 +1658,7 @@ uu8wd+RU4riEmViAqhOLUTpPSPaLtrM=
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS; OU=Ceres; O=FNMT-RCM; C=ES; OrganizationIdentifier=VATES-Q2826004J // CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS; OU=Ceres; O=FNMT-RCM; C=ES; OrganizationIdentifier=VATES-Q2826004J
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQsw MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQsw
CQYDVQQGEwJFUzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgw CQYDVQQGEwJFUzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgw
FgYDVQRhDA9WQVRFUy1RMjgyNjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1S FgYDVQRhDA9WQVRFUy1RMjgyNjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1S
@@ -1678,7 +1676,7 @@ v+c=
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1; OU=Kamu Sertifikasyon Merkezi - Kamu SM; O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK; L=Gebze - Kocaeli; C=TR // CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1; OU=Kamu Sertifikasyon Merkezi - Kamu SM; O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK; L=Gebze - Kocaeli; C=TR
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIx MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIx
GDAWBgNVBAcTD0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxp GDAWBgNVBAcTD0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxp
bXNlbCB2ZSBUZWtub2xvamlrIEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0w bXNlbCB2ZSBUZWtub2xvamlrIEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0w
@@ -1706,7 +1704,7 @@ lo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM=
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=HARICA TLS RSA Root CA 2021; O=Hellenic Academic and Research Institutions CA; C=GR // CN=HARICA TLS RSA Root CA 2021; O=Hellenic Academic and Research Institutions CA; C=GR
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBs MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBs
MQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl MQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl
c2VhcmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0Eg c2VhcmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0Eg
@@ -1741,7 +1739,7 @@ xw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnnkf3/W9b3raYvAwtt41dU
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=HARICA TLS ECC Root CA 2021; O=Hellenic Academic and Research Institutions CA; C=GR // CN=HARICA TLS ECC Root CA 2021; O=Hellenic Academic and Research Institutions CA; C=GR
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQsw MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQsw
CQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2Vh CQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2Vh
cmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9v cmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9v
@@ -1758,7 +1756,7 @@ nxS2PFOiTAZpffpskcYqSUXm7LcT4Tps
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=IdenTrust Commercial Root CA 1; O=IdenTrust; C=US // CN=IdenTrust Commercial Root CA 1; O=IdenTrust; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK
MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu
VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw
@@ -1791,7 +1789,7 @@ mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=ISRG Root X1; O=Internet Security Research Group; C=US // CN=ISRG Root X1; O=Internet Security Research Group; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
@@ -1824,7 +1822,7 @@ emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=ISRG Root X2; O=Internet Security Research Group; C=US // CN=ISRG Root X2; O=Internet Security Research Group; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw
CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg
R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00 R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00
@@ -1840,7 +1838,7 @@ tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=Izenpe.com; O=IZENPE S.A.; C=ES // CN=Izenpe.com; O=IZENPE S.A.; C=ES
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4 MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4
MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6 MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6
ZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD ZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD
@@ -1876,7 +1874,7 @@ QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw==
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=SZAFIR ROOT CA2; O=Krajowa Izba Rozliczeniowa S.A.; C=PL // CN=SZAFIR ROOT CA2; O=Krajowa Izba Rozliczeniowa S.A.; C=PL
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQEL MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQEL
BQAwUTELMAkGA1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6 BQAwUTELMAkGA1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6
ZW5pb3dhIFMuQS4xGDAWBgNVBAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkw ZW5pb3dhIFMuQS4xGDAWBgNVBAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkw
@@ -1899,7 +1897,7 @@ LvWpCz/UXeHPhJ/iGcJfitYgHuNztw==
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=e-Szigno Root CA 2017; O=Microsec Ltd.; L=Budapest; C=HU; OrganizationIdentifier=VATHU-23584497 // CN=e-Szigno Root CA 2017; O=Microsec Ltd.; L=Budapest; C=HU; OrganizationIdentifier=VATHU-23584497
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNV MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNV
BAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRk BAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRk
LjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJv LjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJv
@@ -1916,7 +1914,7 @@ jbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxOsvxyqltZ
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=Microsec e-Szigno Root CA 2009; O=Microsec Ltd.; L=Budapest; C=HU; EmailAddress=info@e-szigno.hu // CN=Microsec e-Szigno Root CA 2009; O=Microsec Ltd.; L=Budapest; C=HU; EmailAddress=info@e-szigno.hu
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD
VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0 VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0
ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G
@@ -1942,7 +1940,7 @@ HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=Microsoft ECC Root Certificate Authority 2017; O=Microsoft Corporation; C=US // CN=Microsoft ECC Root Certificate Authority 2017; O=Microsoft Corporation; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQsw MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQsw
CQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYD CQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYD
VQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIw VQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIw
@@ -1959,7 +1957,7 @@ iudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M=
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=Microsoft RSA Root Certificate Authority 2017; O=Microsoft Corporation; C=US // CN=Microsoft RSA Root Certificate Authority 2017; O=Microsoft Corporation; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBl MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBl
MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw
NAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 NAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
@@ -1994,7 +1992,7 @@ RA+GsCyRxj3qrg+E
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=NAVER Global Root Certification Authority; O=NAVER BUSINESS PLATFORM Corp.; C=KR // CN=NAVER Global Root Certification Authority; O=NAVER BUSINESS PLATFORM Corp.; C=KR
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEM MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEM
BQAwaTELMAkGA1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRG BQAwaTELMAkGA1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRG
T1JNIENvcnAuMTIwMAYDVQQDDClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0 T1JNIENvcnAuMTIwMAYDVQQDDClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0
@@ -2029,7 +2027,7 @@ dh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmgkpzNNIaRkPpkUZ3+/uul
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=NetLock Arany (Class Gold) Főtanúsítvány; OU=Tanúsítványkiadók (Certification Services); O=NetLock Kft.; L=Budapest; C=HU // CN=NetLock Arany (Class Gold) Főtanúsítvány; OU=Tanúsítványkiadók (Certification Services); O=NetLock Kft.; L=Budapest; C=HU
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG
EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3 EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3
MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl
@@ -2055,7 +2053,7 @@ XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E=
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=OISTE WISeKey Global Root GC CA; OU=OISTE Foundation Endorsed; O=WISeKey; C=CH // CN=OISTE WISeKey Global Root GC CA; OU=OISTE Foundation Endorsed; O=WISeKey; C=CH
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQsw MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQsw
CQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91 CQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91
bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwg bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwg
@@ -2072,7 +2070,7 @@ Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=OISTE WISeKey Global Root GB CA; OU=OISTE Foundation Endorsed; O=WISeKey; C=CH // CN=OISTE WISeKey Global Root GB CA; OU=OISTE Foundation Endorsed; O=WISeKey; C=CH
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt
MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg
Rm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i Rm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i
@@ -2096,7 +2094,7 @@ Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM=
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=Security Communication ECC RootCA1; O=SECOM Trust Systems CO.,LTD.; C=JP // CN=Security Communication ECC RootCA1; O=SECOM Trust Systems CO.,LTD.; C=JP
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYT MIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYT
AkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYD AkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYD
VQQDEyJTZWN1cml0eSBDb21tdW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYx VQQDEyJTZWN1cml0eSBDb21tdW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYx
@@ -2112,7 +2110,7 @@ be0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70eN9k=
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// OU=Security Communication RootCA2; O=SECOM Trust Systems CO.,LTD.; C=JP // OU=Security Communication RootCA2; O=SECOM Trust Systems CO.,LTD.; C=JP
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl
MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe
U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX
@@ -2135,7 +2133,7 @@ SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=Entrust Root Certification Authority; OU=www.entrust.net/CPS is incorporated by reference, (c) 2006 Entrust, Inc.; O=Entrust, Inc.; C=US // CN=Entrust Root Certification Authority; OU=www.entrust.net/CPS is incorporated by reference, (c) 2006 Entrust, Inc.; O=Entrust, Inc.; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC
VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0 VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0
Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW
@@ -2164,7 +2162,7 @@ eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=Sectigo Public Server Authentication Root E46; O=Sectigo Limited; C=GB // CN=Sectigo Public Server Authentication Root E46; O=Sectigo Limited; C=GB
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQsw MIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQsw
CQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T CQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T
ZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcN ZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcN
@@ -2180,7 +2178,7 @@ qCG76UeXlImldCBteU/IvZNeWBj7LRoAasm4PdCkT0RHlAFWovgzJQxC36oCMB3q
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=COMODO ECC Certification Authority; O=COMODO CA Limited; L=Salford; ST=Greater Manchester; C=GB // CN=COMODO ECC Certification Authority; O=COMODO CA Limited; L=Salford; ST=Greater Manchester; C=GB
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL
MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT
@@ -2198,7 +2196,7 @@ GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY=
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=COMODO Certification Authority; O=COMODO CA Limited; L=Salford; ST=Greater Manchester; C=GB // CN=COMODO Certification Authority; O=COMODO CA Limited; L=Salford; ST=Greater Manchester; C=GB
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIID0DCCArigAwIBAgIQIKTEf93f4cdTYwcTiHdgEjANBgkqhkiG9w0BAQUFADCB MIID0DCCArigAwIBAgIQIKTEf93f4cdTYwcTiHdgEjANBgkqhkiG9w0BAQUFADCB
gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV
@@ -2223,7 +2221,7 @@ R1uUq27UlTMdphVx8fiUylQ5PsE=
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=COMODO RSA Certification Authority; O=COMODO CA Limited; L=Salford; ST=Greater Manchester; C=GB // CN=COMODO RSA Certification Authority; O=COMODO CA Limited; L=Salford; ST=Greater Manchester; C=GB
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB
hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV
@@ -2259,7 +2257,7 @@ NVOFBkpdn627G190
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=USERTrust RSA Certification Authority; O=The USERTRUST Network; L=Jersey City; ST=New Jersey; C=US // CN=USERTrust RSA Certification Authority; O=The USERTRUST Network; L=Jersey City; ST=New Jersey; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB
iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl
cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV
@@ -2295,7 +2293,7 @@ jjxDah2nGN59PRbxYvnKkKj9
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=USERTrust ECC Certification Authority; O=The USERTRUST Network; L=Jersey City; ST=New Jersey; C=US // CN=USERTrust ECC Certification Authority; O=The USERTRUST Network; L=Jersey City; ST=New Jersey; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL
MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl
eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT
@@ -2313,7 +2311,7 @@ RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg=
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=Sectigo Public Server Authentication Root R46; O=Sectigo Limited; C=GB // CN=Sectigo Public Server Authentication Root R46; O=Sectigo Limited; C=GB
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBf MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBf
MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQD MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQD
Ey1TZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYw Ey1TZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYw
@@ -2347,7 +2345,7 @@ QqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=Entrust Root Certification Authority - G2; OU=See www.entrust.net/legal-terms, (c) 2009 Entrust, Inc. - for authorized use only; O=Entrust, Inc.; C=US // CN=Entrust Root Certification Authority - G2; OU=See www.entrust.net/legal-terms, (c) 2009 Entrust, Inc. - for authorized use only; O=Entrust, Inc.; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC
VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50 VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50
cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs
@@ -2374,7 +2372,7 @@ VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g==
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=Entrust Root Certification Authority - EC1; OU=See www.entrust.net/legal-terms, (c) 2012 Entrust, Inc. - for authorized use only; O=Entrust, Inc.; C=US // CN=Entrust Root Certification Authority - EC1; OU=See www.entrust.net/legal-terms, (c) 2012 Entrust, Inc. - for authorized use only; O=Entrust, Inc.; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkG MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkG
A1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3 A1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3
d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVu d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVu
@@ -2394,7 +2392,7 @@ hTcGtXsI/esni0qU+eH6p44mCOh8kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=SSL.com Root Certification Authority RSA; O=SSL Corporation; L=Houston; ST=Texas; C=US // CN=SSL.com Root Certification Authority RSA; O=SSL Corporation; L=Houston; ST=Texas; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UE MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UE
BhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQK BhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQK
DA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZp DA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZp
@@ -2430,7 +2428,7 @@ Ic2wBlX7Jz9TkHCpBB5XJ7k=
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=SSL.com TLS ECC Root CA 2022; O=SSL Corporation; C=US // CN=SSL.com TLS ECC Root CA 2022; O=SSL Corporation; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQsw MIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQsw
CQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxT CQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxT
U0wuY29tIFRMUyBFQ0MgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2 U0wuY29tIFRMUyBFQ0MgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2
@@ -2446,7 +2444,7 @@ b0Igj762TVntd00pxCAgRWSGOlDGxK0tk/UYfXLtqc/ErFc2KAhl3zx5Zn6g6g==
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=SSL.com TLS RSA Root CA 2022; O=SSL Corporation; C=US // CN=SSL.com TLS RSA Root CA 2022; O=SSL Corporation; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBO MIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBO
MQswCQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQD MQswCQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQD
DBxTU0wuY29tIFRMUyBSU0EgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloX DBxTU0wuY29tIFRMUyBSU0EgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloX
@@ -2480,7 +2478,7 @@ Mho6/4UIyYOf8kpIEFR3N+2ivEC+5BB09+Rbu7nzifmPQdjH5FCQNYA+HLhNkNPU
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=SSL.com Root Certification Authority ECC; O=SSL Corporation; L=Houston; ST=Texas; C=US // CN=SSL.com Root Certification Authority ECC; O=SSL Corporation; L=Houston; ST=Texas; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMC MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMC
VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T
U0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0 U0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0
@@ -2498,7 +2496,7 @@ gA0z5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=SSL.com EV Root Certification Authority RSA R2; O=SSL Corporation; L=Houston; ST=Texas; C=US // CN=SSL.com EV Root Certification Authority RSA R2; O=SSL Corporation; L=Houston; ST=Texas; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNV MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNV
BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UE BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UE
CgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2Vy CgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2Vy
@@ -2534,7 +2532,7 @@ mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w==
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=SSL.com EV Root Certification Authority ECC; O=SSL Corporation; L=Houston; ST=Texas; C=US // CN=SSL.com EV Root Certification Authority ECC; O=SSL Corporation; L=Houston; ST=Texas; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMC MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMC
VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T
U0wgQ29ycG9yYXRpb24xNDAyBgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZp U0wgQ29ycG9yYXRpb24xNDAyBgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZp
@@ -2552,7 +2550,7 @@ h5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg==
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=SwissSign Gold CA - G2; O=SwissSign AG; C=CH // CN=SwissSign Gold CA - G2; O=SwissSign AG; C=CH
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln
biBHb2xkIENBIC0gRzIwHhcNMDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBF biBHb2xkIENBIC0gRzIwHhcNMDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBF
@@ -2587,7 +2585,7 @@ Qc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=TWCA CYBER Root CA; OU=Root CA; O=TAIWAN-CA; C=TW // CN=TWCA CYBER Root CA; OU=Root CA; O=TAIWAN-CA; C=TW
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFjTCCA3WgAwIBAgIQQAE0jMIAAAAAAAAAATzyxjANBgkqhkiG9w0BAQwFADBQ MIIFjTCCA3WgAwIBAgIQQAE0jMIAAAAAAAAAATzyxjANBgkqhkiG9w0BAQwFADBQ
MQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290 MQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290
IENBMRswGQYDVQQDExJUV0NBIENZQkVSIFJvb3QgQ0EwHhcNMjIxMTIyMDY1NDI5 IENBMRswGQYDVQQDExJUV0NBIENZQkVSIFJvb3QgQ0EwHhcNMjIxMTIyMDY1NDI5
@@ -2621,7 +2619,7 @@ t5b5wR9iWqJDB0BeJsas7a5wFsWqynKKTbDPAYsDP27X
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=TWCA Global Root CA; OU=Root CA; O=TAIWAN-CA; C=TW // CN=TWCA Global Root CA; OU=Root CA; O=TAIWAN-CA; C=TW
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx
EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT
VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5 VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5
@@ -2654,7 +2652,7 @@ KwbQBM0=
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=TeliaSonera Root CA v1; O=TeliaSonera // CN=TeliaSonera Root CA v1; O=TeliaSonera
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw
NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv
b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD
@@ -2686,7 +2684,7 @@ SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY=
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=Telia Root CA v2; O=Telia Finland Oyj; C=FI // CN=Telia Root CA v2; O=Telia Finland Oyj; C=FI
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQx MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQx
CzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UE CzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UE
AwwQVGVsaWEgUm9vdCBDQSB2MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1 AwwQVGVsaWEgUm9vdCBDQSB2MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1
@@ -2720,7 +2718,7 @@ rBPuUBQemMc=
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=Trustwave Global ECC P384 Certification Authority; O=Trustwave Holdings, Inc.; L=Chicago; ST=Illinois; C=US // CN=Trustwave Global ECC P384 Certification Authority; O=Trustwave Holdings, Inc.; L=Chicago; ST=Illinois; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYD MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYD
VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf
BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3 BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3
@@ -2739,7 +2737,7 @@ Sw==
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=Trustwave Global ECC P256 Certification Authority; O=Trustwave Holdings, Inc.; L=Chicago; ST=Illinois; C=US // CN=Trustwave Global ECC P256 Certification Authority; O=Trustwave Holdings, Inc.; L=Chicago; ST=Illinois; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYD MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYD
VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf
BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3 BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3
@@ -2756,7 +2754,7 @@ DDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=SecureTrust CA; O=SecureTrust Corporation; C=US // CN=SecureTrust CA; O=SecureTrust Corporation; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI
MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x
FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz
@@ -2780,7 +2778,7 @@ CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
// CN=Trustwave Global Certification Authority; O=Trustwave Holdings, Inc.; L=Chicago; ST=Illinois; C=US // CN=Trustwave Global Certification Authority; O=Trustwave Holdings, Inc.; L=Chicago; ST=Illinois; C=US
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE----- pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQsw MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQsw
CQYDVQQGEwJVUzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28x CQYDVQQGEwJVUzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28x
ITAfBgNVBAoMGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1 ITAfBgNVBAoMGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1
@@ -2814,4 +2812,5 @@ h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu3R3y4G5OBVixwJAWKqQ9
EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP29FpHOTK EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP29FpHOTK
yeC2nOnOcXHebD8WpHk= yeC2nOnOcXHebD8WpHk=
-----END CERTIFICATE-----`)) -----END CERTIFICATE-----`))
return pool
} }

File diff suppressed because it is too large Load Diff

View File

@@ -22,6 +22,7 @@ var _ adapter.CertificateStore = (*Store)(nil)
type Store struct { type Store struct {
access sync.RWMutex access sync.RWMutex
storeType string
systemPool *x509.CertPool systemPool *x509.CertPool
currentPool *x509.CertPool currentPool *x509.CertPool
certificate string certificate string
@@ -31,9 +32,13 @@ type Store struct {
} }
func NewStore(ctx context.Context, logger logger.Logger, options option.CertificateOptions) (*Store, error) { func NewStore(ctx context.Context, logger logger.Logger, options option.CertificateOptions) (*Store, error) {
storeType := options.Store
if storeType == "" {
storeType = C.CertificateStoreSystem
}
var systemPool *x509.CertPool var systemPool *x509.CertPool
switch options.Store { switch storeType {
case C.CertificateStoreSystem, "": case C.CertificateStoreSystem:
systemPool = x509.NewCertPool() systemPool = x509.NewCertPool()
platformInterface := service.FromContext[adapter.PlatformInterface](ctx) platformInterface := service.FromContext[adapter.PlatformInterface](ctx)
var systemValid bool var systemValid bool
@@ -51,16 +56,13 @@ func NewStore(ctx context.Context, logger logger.Logger, options option.Certific
} }
systemPool = certPool systemPool = certPool
} }
case C.CertificateStoreMozilla: case C.CertificateStoreMozilla, C.CertificateStoreChrome:
systemPool = mozillaIncluded
case C.CertificateStoreChrome:
systemPool = chromeIncluded
case C.CertificateStoreNone: case C.CertificateStoreNone:
systemPool = nil
default: default:
return nil, E.New("unknown certificate store: ", options.Store) return nil, E.New("unknown certificate store: ", options.Store)
} }
store := &Store{ store := &Store{
storeType: storeType,
systemPool: systemPool, systemPool: systemPool,
certificate: strings.Join(options.Certificate, "\n"), certificate: strings.Join(options.Certificate, "\n"),
certificatePaths: options.CertificatePath, certificatePaths: options.CertificatePath,
@@ -124,13 +126,9 @@ func (s *Store) Pool() *x509.CertPool {
} }
func (s *Store) update() error { func (s *Store) update() error {
s.access.Lock() currentPool, err := s.newBasePool()
defer s.access.Unlock() if err != nil {
var currentPool *x509.CertPool return err
if s.systemPool == nil {
currentPool = x509.NewCertPool()
} else {
currentPool = s.systemPool.Clone()
} }
if s.certificate != "" { if s.certificate != "" {
if !currentPool.AppendCertsFromPEM([]byte(s.certificate)) { if !currentPool.AppendCertsFromPEM([]byte(s.certificate)) {
@@ -165,10 +163,30 @@ func (s *Store) update() error {
if firstErr != nil { if firstErr != nil {
return firstErr return firstErr
} }
s.access.Lock()
defer s.access.Unlock()
s.currentPool = currentPool s.currentPool = currentPool
return nil return nil
} }
func (s *Store) newBasePool() (*x509.CertPool, error) {
switch s.storeType {
case C.CertificateStoreSystem:
if s.systemPool == nil {
return x509.NewCertPool(), nil
}
return s.systemPool.Clone(), nil
case C.CertificateStoreMozilla:
return newMozillaIncluded(), nil
case C.CertificateStoreChrome:
return newChromeIncluded(), nil
case C.CertificateStoreNone:
return x509.NewCertPool(), nil
default:
return nil, E.New("unknown certificate store: ", s.storeType)
}
}
func readUniqueDirectoryEntries(dir string) ([]fs.DirEntry, error) { func readUniqueDirectoryEntries(dir string) ([]fs.DirEntry, error) {
files, err := os.ReadDir(dir) files, err := os.ReadDir(dir)
if err != nil { if err != nil {

View File

@@ -1,15 +1,12 @@
package cloudflare package cloudflare
import ( import (
"bytes"
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"net/http" "net/http"
"strings"
"time" "time"
"github.com/tidwall/gjson"
) )
type CloudflareApi struct { type CloudflareApi struct {
@@ -25,50 +22,93 @@ func NewCloudflareApi(opts ...CloudflareApiOption) *CloudflareApi {
} }
func (api *CloudflareApi) CreateProfile(ctx context.Context, publicKey string) (*CloudflareProfile, error) { func (api *CloudflareApi) CreateProfile(ctx context.Context, publicKey string) (*CloudflareProfile, error) {
request, err := http.NewRequest("POST", "https://api.cloudflareclient.com/v0i1909051800/reg", strings.NewReader( serial, err := GenerateRandomAndroidSerial()
fmt.Sprintf( if err != nil {
"{\"install_id\":\"\",\"tos\":\"%s\",\"key\":\"%s\",\"fcm_token\":\"\",\"type\":\"ios\",\"locale\":\"en_US\"}", return nil, fmt.Errorf("failed to generate serial: %v", err)
time.Now().Format("2006-01-02T15:04:05.000Z"), }
publicKey, data := Registration{
), Key: publicKey,
)) InstallID: "",
FcmToken: "",
Tos: TimeAsCfString(time.Now()),
Model: "PC",
Serial: serial,
OsVersion: "",
KeyType: KeyTypeWg,
TunType: TunTypeWg,
Locale: "en-US",
}
jsonData, err := json.Marshal(data)
if err != nil {
return nil, fmt.Errorf("failed to marshal json: %v", err)
}
request, err := http.NewRequest("POST", ApiUrl+"/"+ApiVersion+"/reg", bytes.NewBuffer(jsonData))
if err != nil { if err != nil {
return nil, err return nil, err
} }
for k, v := range Headers {
request.Header.Set(k, v)
}
response, err := api.client.Do(request.WithContext(ctx)) response, err := api.client.Do(request.WithContext(ctx))
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer response.Body.Close() defer response.Body.Close()
if response.StatusCode != 200 { if response.StatusCode != http.StatusOK {
return nil, fmt.Errorf("status code is not 200") return nil, fmt.Errorf("failed to register: %v", response.StatusCode)
}
content, err := io.ReadAll(response.Body)
if err != nil {
return nil, err
} }
profile := new(CloudflareProfile) profile := new(CloudflareProfile)
return profile, json.NewDecoder(strings.NewReader(gjson.Get(string(content), "result").Raw)).Decode(profile) return profile, json.NewDecoder(response.Body).Decode(profile)
} }
func (api *CloudflareApi) GetProfile(ctx context.Context, authToken string, id string) (*CloudflareProfile, error) { func (api *CloudflareApi) EnrollKey(ctx context.Context, authToken string, id string, keyType, tunType, publicKey string) (*CloudflareProfile, error) {
request, err := http.NewRequest("GET", "https://api.cloudflareclient.com/v0i1909051800/reg/"+id, nil) deviceUpdate := DeviceUpdate{
Name: "PC",
Key: publicKey,
KeyType: keyType,
TunType: tunType,
}
jsonData, err := json.Marshal(deviceUpdate)
if err != nil {
return nil, fmt.Errorf("failed to marshal json: %v", err)
}
request, err := http.NewRequest("PATCH", ApiUrl+"/"+ApiVersion+"/reg/"+id, bytes.NewBuffer(jsonData))
if err != nil { if err != nil {
return nil, err return nil, err
} }
for k, v := range Headers {
request.Header.Set(k, v)
}
request.Header.Set("Authorization", "Bearer "+authToken) request.Header.Set("Authorization", "Bearer "+authToken)
response, err := api.client.Do(request.WithContext(ctx)) response, err := api.client.Do(request.WithContext(ctx))
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer response.Body.Close() defer response.Body.Close()
if response.StatusCode != 200 { if response.StatusCode != http.StatusOK {
return nil, fmt.Errorf("status code is not 200") return nil, fmt.Errorf("failed to enroll key: %v", response.StatusCode)
} }
content, err := io.ReadAll(response.Body) profile := new(CloudflareProfile)
return profile, json.NewDecoder(response.Body).Decode(profile)
}
func (api *CloudflareApi) GetProfile(ctx context.Context, authToken string, id string) (*CloudflareProfile, error) {
request, err := http.NewRequest("GET", ApiUrl+"/"+ApiVersion+"/reg/"+id, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for k, v := range Headers {
request.Header.Set(k, v)
}
request.Header.Set("Authorization", "Bearer "+authToken)
response, err := api.client.Do(request.WithContext(ctx))
if err != nil {
return nil, err
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to get profile: %v", response.StatusCode)
}
profile := new(CloudflareProfile) profile := new(CloudflareProfile)
return profile, json.NewDecoder(strings.NewReader(gjson.Get(string(content), "result").Raw)).Decode(profile) return profile, json.NewDecoder(response.Body).Decode(profile)
} }

View File

@@ -0,0 +1,25 @@
package cloudflare
const (
ApiUrl = "https://api.cloudflareclient.com"
ApiVersion = "v0a4471"
ConnectSNI = "consumer-masque.cloudflareclient.com"
// unused for now
ZeroTierSNI = "zt-masque.cloudflareclient.com"
ConnectURI = "https://cloudflareaccess.com"
DefaultModel = "PC"
KeyTypeWg = "curve25519"
TunTypeWg = "wireguard"
KeyTypeMasque = "secp256r1"
TunTypeMasque = "masque"
DefaultLocale = "en_US"
DefaultEndpointH2V4 = "162.159.198.2"
DefaultEndpointH2V6 = ""
)
var Headers = map[string]string{
"User-Agent": "WARP for Android",
"CF-Client-Version": "a-6.35-4471",
"Content-Type": "application/json; charset=UTF-8",
"Connection": "Keep-Alive",
}

132
common/cloudflare/models.go Normal file
View File

@@ -0,0 +1,132 @@
package cloudflare
import (
"strings"
)
type Registration struct {
Key string `json:"key"`
InstallID string `json:"install_id"`
FcmToken string `json:"fcm_token"`
Tos string `json:"tos"`
Model string `json:"model"`
Serial string `json:"serial_number"`
OsVersion string `json:"os_version"`
KeyType string `json:"key_type"`
TunType string `json:"tunnel_type"`
Locale string `json:"locale"`
}
type CloudflareProfile struct {
ID string `json:"id"`
Type string `json:"type"`
Model string `json:"model"`
Name string `json:"name"`
Key string `json:"key"`
KeyType string `json:"key_type"`
TunType string `json:"tunnel_type"`
Account Account `json:"account"`
Config Config `json:"config"`
// WarpEnabled not set for ZeroTier
WarpEnabled bool `json:"warp_enabled,omitempty"`
// Waitlist not set for ZeroTier
Waitlist bool `json:"waitlist_enabled,omitempty"`
Created string `json:"created"`
Updated string `json:"updated"`
// Tos not set for ZeroTier
Tos string `json:"tos,omitempty"`
// Place not set for ZeroTier
Place int `json:"place,omitempty"`
Locale string `json:"locale"`
// Enabled not set for ZeroTier
Enabled bool `json:"enabled,omitempty"`
InstallID string `json:"install_id"`
// Token only set for /reg call
Token string `json:"token,omitempty"`
FcmToken string `json:"fcm_token"`
// SerialNumber not set for ZeroTier
SerialNumber string `json:"serial_number,omitempty"`
Policy Policy `json:"policy"`
}
type Account struct {
ID string `json:"id"`
AccountType string `json:"account_type"`
// Created not set for ZeroTier
Created string `json:"created,omitempty"`
// Updated not set for ZeroTier
Updated string `json:"updated,omitempty"`
// Managed only set for ZeroTier
Managed string `json:"managed,omitempty"`
// Organization only set for ZeroTier
Organization string `json:"organization,omitempty"`
// PremiumData not set for ZeroTier
PremiumData int `json:"premium_data,omitempty"`
// Quota not set for ZeroTier
Quota int `json:"quota,omitempty"`
// WarpPlus not set for ZeroTier
WarpPlus bool `json:"warp_plus,omitempty"`
// ReferralCode not set for ZeroTier
ReferralCount int `json:"referral_count,omitempty"`
// ReferralRenewalCount not set for ZeroTier
ReferralRenewalCount int `json:"referral_renewal_countdown,omitempty"`
// Role not set for ZeroTier
Role string `json:"role,omitempty"`
// License not set for ZeroTier
License string `json:"license,omitempty"`
}
type Config struct {
ClientID string `json:"client_id"`
Peers []Peer `json:"peers"`
Interface struct {
Addresses struct {
V4 string `json:"v4"`
V6 string `json:"v6"`
} `json:"addresses"`
} `json:"interface"`
Services struct {
HTTPProxy string `json:"http_proxy"`
} `json:"services"`
}
type Peer struct {
PublicKey string `json:"public_key"`
Endpoint struct {
V4 string `json:"v4"`
V6 string `json:"v6"`
Host string `json:"host"`
Ports []int `json:"ports"`
} `json:"endpoint"`
}
type Policy struct {
TunnelProtocol string `json:"tunnel_protocol"`
}
type DeviceUpdate struct {
Key string `json:"key"`
KeyType string `json:"key_type"`
TunType string `json:"tunnel_type"`
Name string `json:"name,omitempty"`
}
type APIError struct {
Result interface{} `json:"result"`
Success bool `json:"success"`
Errors []ErrorInfo `json:"errors"`
Messages []string `json:"messages"`
}
type ErrorInfo struct {
Code int `json:"code"`
Message string `json:"message"`
}
func (e *APIError) Error() string {
errors := make([]string, len(e.Errors))
for i, err := range e.Errors {
errors[i] = err.Message
}
return strings.Join(errors, ",")
}

View File

@@ -4,12 +4,14 @@ import (
"context" "context"
"net" "net"
"net/http" "net/http"
"time"
) )
type CloudflareApiOption func(api *CloudflareApi) type CloudflareApiOption func(api *CloudflareApi)
func WithDialContext(dialContext func(ctx context.Context, network, addr string) (net.Conn, error)) CloudflareApiOption { func WithDialContext(dialContext func(ctx context.Context, network, addr string) (net.Conn, error)) CloudflareApiOption {
return func(api *CloudflareApi) { return func(api *CloudflareApi) {
api.client.Timeout = 30 * time.Second
api.client.Transport = &http.Transport{ api.client.Transport = &http.Transport{
DialContext: dialContext, DialContext: dialContext,
} }

View File

@@ -1,64 +0,0 @@
package cloudflare
import "time"
type CloudflareProfile struct {
ID string `json:"id"`
Type string `json:"type"`
Name string `json:"name"`
Key string `json:"key"`
Account struct {
ID string `json:"id"`
AccountType string `json:"account_type"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
PremiumData int `json:"premium_data"`
Quota int `json:"quota"`
Usage int `json:"usage"`
WARPPlus bool `json:"warp_plus"`
ReferralCount int `json:"referral_count"`
ReferralRenewalCountdown int `json:"referral_renewal_countdown"`
Role string `json:"role"`
License string `json:"license"`
TTL time.Time `json:"ttl"`
} `json:"account"`
Config struct {
ClientID string `json:"client_id"`
Interface struct {
Addresses struct {
V4 string `json:"v4"`
V6 string `json:"v6"`
} `json:"addresses"`
} `json:"interface"`
Peers []struct {
PublicKey string `json:"public_key"`
Endpoint struct {
V4 string `json:"v4"`
V6 string `json:"v6"`
Host string `json:"host"`
Ports []int `json:"ports"`
} `json:"endpoint"`
} `json:"peers"`
Services struct {
HTTPProxy string `json:"http_proxy"`
} `json:"services"`
Metrics struct {
Ping int `json:"ping"`
Report int `json:"report"`
} `json:"metrics"`
} `json:"config"`
Token string `json:"token"`
WARPEnabled bool `json:"warp_enabled"`
WaitlistEnabled bool `json:"waitlist_enabled"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
Tos time.Time `json:"tos"`
Place int `json:"place"`
Locale string `json:"locale"`
Enabled bool `json:"enabled"`
InstallID string `json:"install_id"`
FcmToken string `json:"fcm_token"`
Policy struct {
TunnelProtocol string `json:"tunnel_protocol"`
} `json:"policy"`
}

View File

@@ -0,0 +1,19 @@
package cloudflare
import (
"crypto/rand"
"encoding/hex"
"time"
)
func GenerateRandomAndroidSerial() (string, error) {
serial := make([]byte, 8)
if _, err := rand.Read(serial); err != nil {
return "", err
}
return hex.EncodeToString(serial), nil
}
func TimeAsCfString(t time.Time) string {
return t.Format("2006-01-02T15:04:05.000-07:00")
}

View File

@@ -63,9 +63,7 @@ parseLine:
} }
continue continue
} }
if strings.HasSuffix(ruleLine, "|") { ruleLine = strings.TrimSuffix(ruleLine, "|")
ruleLine = ruleLine[:len(ruleLine)-1]
}
var ( var (
isExclude bool isExclude bool
isSuffix bool isSuffix bool
@@ -76,7 +74,7 @@ parseLine:
) )
if !strings.HasPrefix(ruleLine, "/") && strings.Contains(ruleLine, "$") { if !strings.HasPrefix(ruleLine, "/") && strings.Contains(ruleLine, "$") {
params := common.SubstringAfter(ruleLine, "$") params := common.SubstringAfter(ruleLine, "$")
for _, param := range strings.Split(params, ",") { for param := range strings.SplitSeq(params, ",") {
paramParts := strings.Split(param, "=") paramParts := strings.Split(param, "=")
var ignored bool var ignored bool
if len(paramParts) > 0 && len(paramParts) <= 2 { if len(paramParts) > 0 && len(paramParts) <= 2 {
@@ -106,9 +104,7 @@ parseLine:
ruleLine = ruleLine[2:] ruleLine = ruleLine[2:]
isExclude = true isExclude = true
} }
if strings.HasSuffix(ruleLine, "|") { ruleLine = strings.TrimSuffix(ruleLine, "|")
ruleLine = ruleLine[:len(ruleLine)-1]
}
if strings.HasPrefix(ruleLine, "||") { if strings.HasPrefix(ruleLine, "||") {
ruleLine = ruleLine[2:] ruleLine = ruleLine[2:]
isSuffix = true isSuffix = true
@@ -414,18 +410,18 @@ func ignoreIPCIDRRegexp(ruleLine string) bool {
} }
func parseAdGuardHostLine(ruleLine string) (string, error) { func parseAdGuardHostLine(ruleLine string) (string, error) {
idx := strings.Index(ruleLine, " ") before, after, ok := strings.Cut(ruleLine, " ")
if idx == -1 { if !ok {
return "", os.ErrInvalid return "", os.ErrInvalid
} }
address, err := netip.ParseAddr(ruleLine[:idx]) address, err := netip.ParseAddr(before)
if err != nil { if err != nil {
return "", err return "", err
} }
if !address.IsUnspecified() { if !address.IsUnspecified() {
return "", nil return "", nil
} }
domain := ruleLine[idx+1:] domain := after
if !M.IsDomainName(domain) { if !M.IsDomainName(domain) {
return "", E.New("invalid domain name: ", domain) return "", E.New("invalid domain name: ", domain)
} }

View File

@@ -149,7 +149,10 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
} else { } else {
dialer.Timeout = C.TCPConnectTimeout dialer.Timeout = C.TCPConnectTimeout
} }
if !options.DisableTCPKeepAlive { if options.DisableTCPKeepAlive {
dialer.KeepAlive = -1
dialer.KeepAliveConfig.Enable = false
} else {
keepIdle := time.Duration(options.TCPKeepAlive) keepIdle := time.Duration(options.TCPKeepAlive)
if keepIdle == 0 { if keepIdle == 0 {
keepIdle = C.TCPKeepAliveInitial keepIdle = C.TCPKeepAliveInitial
@@ -239,7 +242,7 @@ func setMarkWrapper(networkManager adapter.NetworkManager, mark uint32, isDefaul
func (d *DefaultDialer) DialContext(ctx context.Context, network string, address M.Socksaddr) (net.Conn, error) { func (d *DefaultDialer) DialContext(ctx context.Context, network string, address M.Socksaddr) (net.Conn, error) {
if !address.IsValid() { if !address.IsValid() {
return nil, E.New("invalid address") return nil, E.New("invalid address")
} else if address.IsFqdn() { } else if address.IsDomain() {
return nil, E.New("domain not resolved") return nil, E.New("domain not resolved")
} }
if d.networkStrategy == nil { if d.networkStrategy == nil {
@@ -329,9 +332,9 @@ func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksadd
func (d *DefaultDialer) DialerForICMPDestination(destination netip.Addr) net.Dialer { func (d *DefaultDialer) DialerForICMPDestination(destination netip.Addr) net.Dialer {
if !destination.Is6() { if !destination.Is6() {
return d.dialer6.Dialer
} else {
return d.dialer4.Dialer return d.dialer4.Dialer
} else {
return d.dialer6.Dialer
} }
} }

View File

@@ -136,18 +136,16 @@ func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, d
go startRacer(fallbackCtx, false, iif) go startRacer(fallbackCtx, false, iif)
} }
var errors []error var errors []error
for { for res := range results {
select { if res.error == nil {
case res := <-results: return res.Conn, res.primary, nil
if res.error == nil { }
return res.Conn, res.primary, nil errors = append(errors, res.error)
} if len(errors) == len(primaryInterfaces)+len(fallbackInterfaces) {
errors = append(errors, res.error) return nil, false, E.Errors(errors...)
if len(errors) == len(primaryInterfaces)+len(fallbackInterfaces) {
return nil, false, E.Errors(errors...)
}
} }
} }
return nil, false, E.Errors(errors...)
} }
func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listener net.ListenConfig, network string, addr string, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) { func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listener net.ListenConfig, network string, addr string, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
@@ -184,6 +182,12 @@ func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listene
func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType) (primaryInterfaces []adapter.NetworkInterface, fallbackInterfaces []adapter.NetworkInterface) { func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType) (primaryInterfaces []adapter.NetworkInterface, fallbackInterfaces []adapter.NetworkInterface) {
interfaces := networkManager.NetworkInterfaces() interfaces := networkManager.NetworkInterfaces()
myInterface := networkManager.InterfaceMonitor().MyInterface()
if myInterface != "" {
interfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
return it.Name != myInterface
})
}
switch strategy { switch strategy {
case C.NetworkStrategyDefault: case C.NetworkStrategyDefault:
if len(interfaceType) == 0 { if len(interfaceType) == 0 {

View File

@@ -96,7 +96,7 @@ func (d *resolveDialer) DialContext(ctx context.Context, network string, destina
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !destination.IsFqdn() { if !destination.IsDomain() {
return d.dialer.DialContext(ctx, network, destination) return d.dialer.DialContext(ctx, network, destination)
} }
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug) ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
@@ -116,7 +116,7 @@ func (d *resolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !destination.IsFqdn() { if !destination.IsDomain() {
return d.dialer.ListenPacket(ctx, destination) return d.dialer.ListenPacket(ctx, destination)
} }
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug) ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
@@ -144,7 +144,7 @@ func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !destination.IsFqdn() { if !destination.IsDomain() {
return d.dialer.DialContext(ctx, network, destination) return d.dialer.DialContext(ctx, network, destination)
} }
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug) ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
@@ -167,7 +167,7 @@ func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.C
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !destination.IsFqdn() { if !destination.IsDomain() {
return d.dialer.ListenPacket(ctx, destination) return d.dialer.ListenPacket(ctx, destination)
} }
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug) ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)

View File

@@ -19,11 +19,6 @@ func oldWriteString(writer varbin.Writer, value string) error {
return varbin.Write(writer, binary.BigEndian, value) return varbin.Write(writer, binary.BigEndian, value)
} }
func oldWriteItem(writer varbin.Writer, item Item) error {
//nolint:staticcheck
return varbin.Write(writer, binary.BigEndian, item)
}
func oldReadString(reader varbin.Reader) (string, error) { func oldReadString(reader varbin.Reader) (string, error) {
//nolint:staticcheck //nolint:staticcheck
return varbin.ReadValue[string](reader, binary.BigEndian) return varbin.ReadValue[string](reader, binary.BigEndian)
@@ -224,7 +219,7 @@ func TestGeositeWriteReadCompat(t *testing.T) {
func generateLargeItems(count int) map[string][]Item { func generateLargeItems(count int) map[string][]Item {
items := make([]Item, count) items := make([]Item, count)
for i := 0; i < count; i++ { for i := range count {
items[i] = Item{ items[i] = Item{
Type: ItemType(i % 4), Type: ItemType(i % 4),
Value: strings.Repeat("x", i%200) + ".com", Value: strings.Repeat("x", i%200) + ".com",

View File

@@ -48,12 +48,6 @@ func NewReader(readSeeker io.ReadSeeker) (*Reader, []string, error) {
return reader, codes, nil return reader, codes, nil
} }
type geositeMetadata struct {
Code string
Index uint64
Length uint64
}
func (r *Reader) readMetadata() error { func (r *Reader) readMetadata() error {
counter := &readCounter{Reader: r.reader} counter := &readCounter{Reader: r.reader}
reader := bufio.NewReader(counter) reader := bufio.NewReader(counter)
@@ -101,6 +95,9 @@ func (r *Reader) readMetadata() error {
} }
func (r *Reader) Read(code string) ([]Item, error) { func (r *Reader) Read(code string) ([]Item, error) {
r.access.Lock()
defer r.access.Unlock()
index, exists := r.domainIndex[code] index, exists := r.domainIndex[code]
if !exists { if !exists {
return nil, E.New("code ", code, " not exists!") return nil, E.New("code ", code, " not exists!")

View File

@@ -11,3 +11,13 @@ func ContextWithIsExternalConnection(ctx context.Context) context.Context {
func IsExternalConnectionFromContext(ctx context.Context) bool { func IsExternalConnectionFromContext(ctx context.Context) bool {
return ctx.Value(contextKeyIsExternalConnection{}) != nil return ctx.Value(contextKeyIsExternalConnection{}) != nil
} }
type contextKeyIsProviderConnection struct{}
func ContextWithIsProviderConnection(ctx context.Context) context.Context {
return context.WithValue(ctx, contextKeyIsProviderConnection{}, true)
}
func IsProviderConnectionFromContext(ctx context.Context) bool {
return ctx.Value(contextKeyIsProviderConnection{}) != nil
}

View File

@@ -17,30 +17,31 @@ type Group struct {
type groupConnItem struct { type groupConnItem struct {
conn io.Closer conn io.Closer
isExternal bool isExternal bool
isProvider bool
} }
func NewGroup() *Group { func NewGroup() *Group {
return &Group{} return &Group{}
} }
func (g *Group) NewConn(conn net.Conn, isExternal bool) net.Conn { func (g *Group) NewConn(conn net.Conn, isExternal bool, isProvider bool) net.Conn {
g.access.Lock() g.access.Lock()
defer g.access.Unlock() defer g.access.Unlock()
item := g.connections.PushBack(&groupConnItem{conn, isExternal}) item := g.connections.PushBack(&groupConnItem{conn, isExternal, isProvider})
return &Conn{Conn: conn, group: g, element: item} return &Conn{Conn: conn, group: g, element: item}
} }
func (g *Group) NewPacketConn(conn net.PacketConn, isExternal bool) net.PacketConn { func (g *Group) NewPacketConn(conn net.PacketConn, isExternal bool, isProvider bool) net.PacketConn {
g.access.Lock() g.access.Lock()
defer g.access.Unlock() defer g.access.Unlock()
item := g.connections.PushBack(&groupConnItem{conn, isExternal}) item := g.connections.PushBack(&groupConnItem{conn, isExternal, isProvider})
return &PacketConn{PacketConn: conn, group: g, element: item} return &PacketConn{PacketConn: conn, group: g, element: item}
} }
func (g *Group) NewSingPacketConn(conn N.PacketConn, isExternal bool) N.PacketConn { func (g *Group) NewSingPacketConn(conn N.PacketConn, isExternal bool, isProvider bool) N.PacketConn {
g.access.Lock() g.access.Lock()
defer g.access.Unlock() defer g.access.Unlock()
item := g.connections.PushBack(&groupConnItem{conn, isExternal}) item := g.connections.PushBack(&groupConnItem{conn, isExternal, isProvider})
return &SingPacketConn{PacketConn: conn, group: g, element: item} return &SingPacketConn{PacketConn: conn, group: g, element: item}
} }

View File

@@ -131,7 +131,7 @@ func (j *ClientHello) parseHandshake(hs []byte) error {
return &ParseError{LengthErr, 7} return &ParseError{LengthErr, 7}
} }
for i := 0; i < numCiphers; i++ { for i := range numCiphers {
cipherSuite := uint16(cs[2+i<<1])<<8 | uint16(cs[3+i<<1]) cipherSuite := uint16(cs[2+i<<1])<<8 | uint16(cs[3+i<<1])
cipherSuites = append(cipherSuites, cipherSuite) cipherSuites = append(cipherSuites, cipherSuite)
} }
@@ -234,7 +234,7 @@ func (j *ClientHello) parseExtensions(exs []byte) error {
return &ParseError{LengthErr, 16} return &ParseError{LengthErr, 16}
} }
for i := 0; i < numCurves; i++ { for i := range numCurves {
ecType := uint16(sex[i*2])<<8 | uint16(sex[1+i*2]) ecType := uint16(sex[i*2])<<8 | uint16(sex[1+i*2])
ellipticCurves = append(ellipticCurves, ecType) ellipticCurves = append(ellipticCurves, ecType)
} }
@@ -256,7 +256,7 @@ func (j *ClientHello) parseExtensions(exs []byte) error {
return &ParseError{LengthErr, 18} return &ParseError{LengthErr, 18}
} }
for i := 0; i < numPF; i++ { for i := range numPF {
ellipticCurvePF[i] = uint8(sex[i]) ellipticCurvePF[i] = uint8(sex[i])
} }
case versionExtensionType: case versionExtensionType:

View File

@@ -12,7 +12,6 @@ type klock struct {
ref uint64 ref uint64
} }
// Create new Kmutex
func New[T comparable]() *Kmutex[T] { func New[T comparable]() *Kmutex[T] {
l := sync.Mutex{} l := sync.Mutex{}
return &Kmutex[T]{ return &Kmutex[T]{
@@ -21,7 +20,6 @@ func New[T comparable]() *Kmutex[T] {
} }
} }
// Unlock Kmutex by unique ID
func (km *Kmutex[T]) Unlock(key T) { func (km *Kmutex[T]) Unlock(key T) {
km.l.Lock() km.l.Lock()
defer km.l.Unlock() defer km.l.Unlock()
@@ -37,7 +35,6 @@ func (km *Kmutex[T]) Unlock(key T) {
kl.cond.Signal() kl.cond.Signal()
} }
// Lock Kmutex by unique ID
func (km *Kmutex[T]) Lock(key T) { func (km *Kmutex[T]) Lock(key T) {
km.l.Lock() km.l.Lock()
defer km.l.Unlock() defer km.l.Unlock()

View File

@@ -6,48 +6,7 @@
package ktls package ktls
import ( import "golang.org/x/crypto/cryptobyte"
"fmt"
"golang.org/x/crypto/cryptobyte"
)
// The marshalingFunction type is an adapter to allow the use of ordinary
// functions as cryptobyte.MarshalingValue.
type marshalingFunction func(b *cryptobyte.Builder) error
func (f marshalingFunction) Marshal(b *cryptobyte.Builder) error {
return f(b)
}
// addBytesWithLength appends a sequence of bytes to the cryptobyte.Builder. If
// the length of the sequence is not the value specified, it produces an error.
func addBytesWithLength(b *cryptobyte.Builder, v []byte, n int) {
b.AddValue(marshalingFunction(func(b *cryptobyte.Builder) error {
if len(v) != n {
return fmt.Errorf("invalid value length: expected %d, got %d", n, len(v))
}
b.AddBytes(v)
return nil
}))
}
// addUint64 appends a big-endian, 64-bit value to the cryptobyte.Builder.
func addUint64(b *cryptobyte.Builder, v uint64) {
b.AddUint32(uint32(v >> 32))
b.AddUint32(uint32(v))
}
// readUint64 decodes a big-endian, 64-bit value into out and advances over it.
// It reports whether the read was successful.
func readUint64(s *cryptobyte.String, out *uint64) bool {
var hi, lo uint32
if !s.ReadUint32(&hi) || !s.ReadUint32(&lo) {
return false
}
*out = uint64(hi)<<32 | uint64(lo)
return true
}
// readUint8LengthPrefixed acts like s.ReadUint8LengthPrefixed, but targets a // readUint8LengthPrefixed acts like s.ReadUint8LengthPrefixed, but targets a
// []byte instead of a cryptobyte.String. // []byte instead of a cryptobyte.String.
@@ -61,12 +20,6 @@ func readUint16LengthPrefixed(s *cryptobyte.String, out *[]byte) bool {
return s.ReadUint16LengthPrefixed((*cryptobyte.String)(out)) return s.ReadUint16LengthPrefixed((*cryptobyte.String)(out))
} }
// readUint24LengthPrefixed acts like s.ReadUint24LengthPrefixed, but targets a
// []byte instead of a cryptobyte.String.
func readUint24LengthPrefixed(s *cryptobyte.String, out *[]byte) bool {
return s.ReadUint24LengthPrefixed((*cryptobyte.String)(out))
}
type keyUpdateMsg struct { type keyUpdateMsg struct {
updateRequested bool updateRequested bool
} }
@@ -125,11 +78,6 @@ const (
typeMessageHash uint8 = 254 // synthetic message typeMessageHash uint8 = 254 // synthetic message
) )
// TLS compression types.
const (
compressionNone uint8 = 0
)
// TLS extension numbers // TLS extension numbers
const ( const (
extensionServerName uint16 = 0 extensionServerName uint16 = 0

View File

@@ -12,6 +12,7 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"unsafe"
) )
func (c *Conn) Read(b []byte) (int, error) { func (c *Conn) Read(b []byte) (int, error) {
@@ -229,7 +230,7 @@ func (c *Conn) readRawRecord() (typ uint8, data []byte, err error) {
record := c.rawConn.RawInput.Next(recordHeaderLen + n) record := c.rawConn.RawInput.Next(recordHeaderLen + n)
data, typ, err = c.rawConn.In.Decrypt(record) data, typ, err = c.rawConn.In.Decrypt(record)
if err != nil { if err != nil {
err = c.rawConn.In.SetErrorLocked(c.sendAlert(uint8(err.(tls.AlertError)))) err = c.rawConn.In.SetErrorLocked(c.sendAlert(*(*uint8)((*[2]unsafe.Pointer)(unsafe.Pointer(&err))[1])))
return return
} }
return return

View File

@@ -77,78 +77,5 @@ func (c *Conn) writeRecordLocked(typ uint16, data []byte) (n int, err error) {
if !c.kernelTx { if !c.kernelTx {
return c.rawConn.WriteRecordLocked(typ, data) return c.rawConn.WriteRecordLocked(typ, data)
} }
/*for len(data) > 0 {
m := len(data)
if maxPayload := c.maxPayloadSizeForWrite(typ); m > maxPayload {
m = maxPayload
}
_, err = c.writeKernelRecord(typ, data[:m])
if err != nil {
return
}
n += m
data = data[m:]
}*/
return c.writeKernelRecord(typ, data) return c.writeKernelRecord(typ, data)
} }
const (
// tcpMSSEstimate is a conservative estimate of the TCP maximum segment
// size (MSS). A constant is used, rather than querying the kernel for
// the actual MSS, to avoid complexity. The value here is the IPv6
// minimum MTU (1280 bytes) minus the overhead of an IPv6 header (40
// bytes) and a TCP header with timestamps (32 bytes).
tcpMSSEstimate = 1208
// recordSizeBoostThreshold is the number of bytes of application data
// sent after which the TLS record size will be increased to the
// maximum.
recordSizeBoostThreshold = 128 * 1024
)
func (c *Conn) maxPayloadSizeForWrite(typ uint16) int {
if /*c.config.DynamicRecordSizingDisabled ||*/ typ != recordTypeApplicationData {
return maxPlaintext
}
if *c.rawConn.PacketsSent >= recordSizeBoostThreshold {
return maxPlaintext
}
// Subtract TLS overheads to get the maximum payload size.
payloadBytes := tcpMSSEstimate - recordHeaderLen - c.rawConn.Out.ExplicitNonceLen()
if rawCipher := *c.rawConn.Out.Cipher; rawCipher != nil {
switch ciph := rawCipher.(type) {
case cipher.Stream:
payloadBytes -= (*c.rawConn.Out.Mac).Size()
case cipher.AEAD:
payloadBytes -= ciph.Overhead()
/*case cbcMode:
blockSize := ciph.BlockSize()
// The payload must fit in a multiple of blockSize, with
// room for at least one padding byte.
payloadBytes = (payloadBytes & ^(blockSize - 1)) - 1
// The RawMac is appended before padding so affects the
// payload size directly.
payloadBytes -= c.out.mac.Size()*/
default:
panic("unknown cipher type")
}
}
if *c.rawConn.Vers == tls.VersionTLS13 {
payloadBytes-- // encrypted ContentType
}
// Allow packet growth in arithmetic progression up to max.
pkt := *c.rawConn.PacketsSent
*c.rawConn.PacketsSent++
if pkt > 1000 {
return maxPlaintext // avoid overflow in multiply below
}
n := payloadBytes * int(pkt+1)
if n > maxPlaintext {
n = maxPlaintext
}
return n
}

View File

@@ -151,6 +151,7 @@ func ListenNetworkNamespace[T any](nameOrPath string, block func() (T, error)) (
if err != nil { if err != nil {
return common.DefaultValue[T](), E.Cause(err, "get current netns") return common.DefaultValue[T](), E.Cause(err, "get current netns")
} }
defer currentNs.Close()
defer netns.Set(currentNs) defer netns.Set(currentNs)
var targetNs netns.NsHandle var targetNs netns.NsHandle
if strings.HasPrefix(nameOrPath, "/") { if strings.HasPrefix(nameOrPath, "/") {

View File

@@ -37,7 +37,10 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
if l.listenOptions.ReuseAddr { if l.listenOptions.ReuseAddr {
listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr()) listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr())
} }
if !l.listenOptions.DisableTCPKeepAlive { if l.listenOptions.DisableTCPKeepAlive {
listenConfig.KeepAlive = -1
listenConfig.KeepAliveConfig.Enable = false
} else {
keepIdle := time.Duration(l.listenOptions.TCPKeepAlive) keepIdle := time.Duration(l.listenOptions.TCPKeepAlive)
if keepIdle == 0 { if keepIdle == 0 {
keepIdle = C.TCPKeepAliveInitial keepIdle = C.TCPKeepAliveInitial

View File

@@ -14,6 +14,7 @@ import (
type Searcher interface { type Searcher interface {
FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error)
Close() error
} }
var ErrNotFound = E.New("process not found") var ErrNotFound = E.New("process not found")
@@ -28,7 +29,7 @@ func FindProcessInfo(searcher Searcher, ctx context.Context, network string, sou
if err != nil { if err != nil {
return nil, err return nil, err
} }
if info.UserId != -1 { if info.UserId != -1 && info.UserName == "" {
osUser, _ := user.LookupId(F.ToString(info.UserId)) osUser, _ := user.LookupId(F.ToString(info.UserId))
if osUser != nil { if osUser != nil {
info.UserName = osUser.Username info.UserName = osUser.Username

View File

@@ -6,6 +6,7 @@ import (
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-tun" "github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common"
) )
var _ Searcher = (*androidSearcher)(nil) var _ Searcher = (*androidSearcher)(nil)
@@ -18,22 +19,30 @@ func NewSearcher(config Config) (Searcher, error) {
return &androidSearcher{config.PackageManager}, nil return &androidSearcher{config.PackageManager}, nil
} }
func (s *androidSearcher) Close() error {
return nil
}
func (s *androidSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) { func (s *androidSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
_, uid, err := resolveSocketByNetlink(network, source, destination) family, protocol, err := socketDiagSettings(network, source)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if sharedPackage, loaded := s.packageManager.SharedPackageByID(uid % 100000); loaded { _, uid, err := querySocketDiagOnce(family, protocol, source)
return &adapter.ConnectionOwner{ if err != nil {
UserId: int32(uid), return nil, err
AndroidPackageName: sharedPackage,
}, nil
} }
if packageName, loaded := s.packageManager.PackageByID(uid % 100000); loaded { appID := uid % 100000
return &adapter.ConnectionOwner{ var packageNames []string
UserId: int32(uid), if sharedPackage, loaded := s.packageManager.SharedPackageByID(appID); loaded {
AndroidPackageName: packageName, packageNames = append(packageNames, sharedPackage)
}, nil
} }
return &adapter.ConnectionOwner{UserId: int32(uid)}, nil if packages, loaded := s.packageManager.PackagesByID(appID); loaded {
packageNames = append(packageNames, packages...)
}
packageNames = common.Uniq(packageNames)
return &adapter.ConnectionOwner{
UserId: int32(uid),
AndroidPackageNames: packageNames,
}, nil
} }

View File

@@ -1,19 +1,15 @@
//go:build darwin
package process package process
import ( import (
"context" "context"
"encoding/binary"
"net/netip" "net/netip"
"os"
"strconv" "strconv"
"strings" "strings"
"syscall" "syscall"
"unsafe"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
N "github.com/sagernet/sing/common/network"
"golang.org/x/sys/unix"
) )
var _ Searcher = (*darwinSearcher)(nil) var _ Searcher = (*darwinSearcher)(nil)
@@ -24,12 +20,12 @@ func NewSearcher(_ Config) (Searcher, error) {
return &darwinSearcher{}, nil return &darwinSearcher{}, nil
} }
func (d *darwinSearcher) Close() error {
return nil
}
func (d *darwinSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) { func (d *darwinSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
processName, err := findProcessName(network, source.Addr(), int(source.Port())) return FindDarwinConnectionOwner(network, source, destination)
if err != nil {
return nil, err
}
return &adapter.ConnectionOwner{ProcessPath: processName, UserId: -1}, nil
} }
var structSize = func() int { var structSize = func() int {
@@ -47,107 +43,3 @@ var structSize = func() int {
return 384 return 384
} }
}() }()
func findProcessName(network string, ip netip.Addr, port int) (string, error) {
var spath string
switch network {
case N.NetworkTCP:
spath = "net.inet.tcp.pcblist_n"
case N.NetworkUDP:
spath = "net.inet.udp.pcblist_n"
default:
return "", os.ErrInvalid
}
isIPv4 := ip.Is4()
value, err := unix.SysctlRaw(spath)
if err != nil {
return "", err
}
buf := value
// from darwin-xnu/bsd/netinet/in_pcblist.c:get_pcblist_n
// size/offset are round up (aligned) to 8 bytes in darwin
// rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) +
// 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n))
itemSize := structSize
if network == N.NetworkTCP {
// rup8(sizeof(xtcpcb_n))
itemSize += 208
}
var fallbackUDPProcess string
// skip the first xinpgen(24 bytes) block
for i := 24; i+itemSize <= len(buf); i += itemSize {
// offset of xinpcb_n and xsocket_n
inp, so := i, i+104
srcPort := binary.BigEndian.Uint16(buf[inp+18 : inp+20])
if uint16(port) != srcPort {
continue
}
// xinpcb_n.inp_vflag
flag := buf[inp+44]
var srcIP netip.Addr
srcIsIPv4 := false
switch {
case flag&0x1 > 0 && isIPv4:
// ipv4
srcIP = netip.AddrFrom4([4]byte(buf[inp+76 : inp+80]))
srcIsIPv4 = true
case flag&0x2 > 0 && !isIPv4:
// ipv6
srcIP = netip.AddrFrom16([16]byte(buf[inp+64 : inp+80]))
default:
continue
}
if ip == srcIP {
// xsocket_n.so_last_pid
pid := readNativeUint32(buf[so+68 : so+72])
return getExecPathFromPID(pid)
}
// udp packet connection may be not equal with srcIP
if network == N.NetworkUDP && srcIP.IsUnspecified() && isIPv4 == srcIsIPv4 {
pid := readNativeUint32(buf[so+68 : so+72])
fallbackUDPProcess, _ = getExecPathFromPID(pid)
}
}
if network == N.NetworkUDP && len(fallbackUDPProcess) > 0 {
return fallbackUDPProcess, nil
}
return "", ErrNotFound
}
func getExecPathFromPID(pid uint32) (string, error) {
const (
procpidpathinfo = 0xb
procpidpathinfosize = 1024
proccallnumpidinfo = 0x2
)
buf := make([]byte, procpidpathinfosize)
_, _, errno := syscall.Syscall6(
syscall.SYS_PROC_INFO,
proccallnumpidinfo,
uintptr(pid),
procpidpathinfo,
0,
uintptr(unsafe.Pointer(&buf[0])),
procpidpathinfosize)
if errno != 0 {
return "", errno
}
return unix.ByteSliceToString(buf), nil
}
func readNativeUint32(b []byte) uint32 {
return *(*uint32)(unsafe.Pointer(&b[0]))
}

View File

@@ -0,0 +1,270 @@
//go:build darwin
package process
import (
"encoding/binary"
"net/netip"
"os"
"sync"
"syscall"
"time"
"unsafe"
"github.com/sagernet/sing-box/adapter"
N "github.com/sagernet/sing/common/network"
"golang.org/x/sys/unix"
)
const (
darwinSnapshotTTL = 200 * time.Millisecond
darwinXinpgenSize = 24
darwinXsocketOffset = 104
darwinXinpcbForeignPort = 16
darwinXinpcbLocalPort = 18
darwinXinpcbVFlag = 44
darwinXinpcbForeignAddr = 48
darwinXinpcbLocalAddr = 64
darwinXinpcbIPv4Addr = 12
darwinXsocketUID = 64
darwinXsocketLastPID = 68
darwinTCPExtraStructSize = 208
)
type darwinConnectionEntry struct {
localAddr netip.Addr
remoteAddr netip.Addr
localPort uint16
remotePort uint16
pid uint32
uid int32
}
type darwinConnectionMatchKind uint8
const (
darwinConnectionMatchExact darwinConnectionMatchKind = iota
darwinConnectionMatchLocalFallback
darwinConnectionMatchWildcardFallback
)
type darwinSnapshot struct {
createdAt time.Time
entries []darwinConnectionEntry
}
type darwinConnectionFinder struct {
access sync.Mutex
ttl time.Duration
snapshots map[string]darwinSnapshot
builder func(string) (darwinSnapshot, error)
}
var sharedDarwinConnectionFinder = newDarwinConnectionFinder(darwinSnapshotTTL)
func newDarwinConnectionFinder(ttl time.Duration) *darwinConnectionFinder {
return &darwinConnectionFinder{
ttl: ttl,
snapshots: make(map[string]darwinSnapshot),
builder: buildDarwinSnapshot,
}
}
func FindDarwinConnectionOwner(network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
return sharedDarwinConnectionFinder.find(network, source, destination)
}
func (f *darwinConnectionFinder) find(network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
networkName := N.NetworkName(network)
source = normalizeDarwinAddrPort(source)
destination = normalizeDarwinAddrPort(destination)
var lastOwner *adapter.ConnectionOwner
for attempt := range 2 {
snapshot, fromCache, err := f.loadSnapshot(networkName, attempt > 0)
if err != nil {
return nil, err
}
entry, matchKind, err := matchDarwinConnectionEntry(snapshot.entries, networkName, source, destination)
if err != nil {
if err == ErrNotFound && fromCache {
continue
}
return nil, err
}
if fromCache && matchKind != darwinConnectionMatchExact {
continue
}
owner := &adapter.ConnectionOwner{
UserId: entry.uid,
}
lastOwner = owner
if entry.pid == 0 {
return owner, nil
}
processPath, err := getExecPathFromPID(entry.pid)
if err == nil {
owner.ProcessPath = processPath
return owner, nil
}
if fromCache {
continue
}
return owner, nil
}
if lastOwner != nil {
return lastOwner, nil
}
return nil, ErrNotFound
}
func (f *darwinConnectionFinder) loadSnapshot(network string, forceRefresh bool) (darwinSnapshot, bool, error) {
f.access.Lock()
defer f.access.Unlock()
if !forceRefresh {
if snapshot, loaded := f.snapshots[network]; loaded && time.Since(snapshot.createdAt) < f.ttl {
return snapshot, true, nil
}
}
snapshot, err := f.builder(network)
if err != nil {
return darwinSnapshot{}, false, err
}
f.snapshots[network] = snapshot
return snapshot, false, nil
}
func buildDarwinSnapshot(network string) (darwinSnapshot, error) {
spath, itemSize, err := darwinSnapshotSettings(network)
if err != nil {
return darwinSnapshot{}, err
}
value, err := unix.SysctlRaw(spath)
if err != nil {
return darwinSnapshot{}, err
}
return darwinSnapshot{
createdAt: time.Now(),
entries: parseDarwinSnapshot(value, itemSize),
}, nil
}
func darwinSnapshotSettings(network string) (string, int, error) {
itemSize := structSize
switch network {
case N.NetworkTCP:
return "net.inet.tcp.pcblist_n", itemSize + darwinTCPExtraStructSize, nil
case N.NetworkUDP:
return "net.inet.udp.pcblist_n", itemSize, nil
default:
return "", 0, os.ErrInvalid
}
}
func parseDarwinSnapshot(buf []byte, itemSize int) []darwinConnectionEntry {
entries := make([]darwinConnectionEntry, 0, (len(buf)-darwinXinpgenSize)/itemSize)
for i := darwinXinpgenSize; i+itemSize <= len(buf); i += itemSize {
inp := i
so := i + darwinXsocketOffset
entry, ok := parseDarwinConnectionEntry(buf[inp:so], buf[so:so+structSize-darwinXsocketOffset])
if ok {
entries = append(entries, entry)
}
}
return entries
}
func parseDarwinConnectionEntry(inp []byte, so []byte) (darwinConnectionEntry, bool) {
if len(inp) < darwinXsocketOffset || len(so) < structSize-darwinXsocketOffset {
return darwinConnectionEntry{}, false
}
entry := darwinConnectionEntry{
remotePort: binary.BigEndian.Uint16(inp[darwinXinpcbForeignPort : darwinXinpcbForeignPort+2]),
localPort: binary.BigEndian.Uint16(inp[darwinXinpcbLocalPort : darwinXinpcbLocalPort+2]),
pid: binary.NativeEndian.Uint32(so[darwinXsocketLastPID : darwinXsocketLastPID+4]),
uid: int32(binary.NativeEndian.Uint32(so[darwinXsocketUID : darwinXsocketUID+4])),
}
flag := inp[darwinXinpcbVFlag]
switch {
case flag&0x1 != 0:
entry.remoteAddr = netip.AddrFrom4([4]byte(inp[darwinXinpcbForeignAddr+darwinXinpcbIPv4Addr : darwinXinpcbForeignAddr+darwinXinpcbIPv4Addr+4]))
entry.localAddr = netip.AddrFrom4([4]byte(inp[darwinXinpcbLocalAddr+darwinXinpcbIPv4Addr : darwinXinpcbLocalAddr+darwinXinpcbIPv4Addr+4]))
return entry, true
case flag&0x2 != 0:
entry.remoteAddr = netip.AddrFrom16([16]byte(inp[darwinXinpcbForeignAddr : darwinXinpcbForeignAddr+16]))
entry.localAddr = netip.AddrFrom16([16]byte(inp[darwinXinpcbLocalAddr : darwinXinpcbLocalAddr+16]))
return entry, true
default:
return darwinConnectionEntry{}, false
}
}
func matchDarwinConnectionEntry(entries []darwinConnectionEntry, network string, source netip.AddrPort, destination netip.AddrPort) (darwinConnectionEntry, darwinConnectionMatchKind, error) {
sourceAddr := source.Addr()
if !sourceAddr.IsValid() {
return darwinConnectionEntry{}, darwinConnectionMatchExact, os.ErrInvalid
}
var localFallback darwinConnectionEntry
var hasLocalFallback bool
var wildcardFallback darwinConnectionEntry
var hasWildcardFallback bool
for _, entry := range entries {
if entry.localPort != source.Port() || sourceAddr.BitLen() != entry.localAddr.BitLen() {
continue
}
if entry.localAddr == sourceAddr && destination.IsValid() && entry.remotePort == destination.Port() && entry.remoteAddr == destination.Addr() {
return entry, darwinConnectionMatchExact, nil
}
if !destination.IsValid() && entry.localAddr == sourceAddr {
return entry, darwinConnectionMatchExact, nil
}
if network != N.NetworkUDP {
continue
}
if !hasLocalFallback && entry.localAddr == sourceAddr {
hasLocalFallback = true
localFallback = entry
}
if !hasWildcardFallback && entry.localAddr.IsUnspecified() {
hasWildcardFallback = true
wildcardFallback = entry
}
}
if hasLocalFallback {
return localFallback, darwinConnectionMatchLocalFallback, nil
}
if hasWildcardFallback {
return wildcardFallback, darwinConnectionMatchWildcardFallback, nil
}
return darwinConnectionEntry{}, darwinConnectionMatchExact, ErrNotFound
}
func normalizeDarwinAddrPort(addrPort netip.AddrPort) netip.AddrPort {
if !addrPort.IsValid() {
return addrPort
}
return netip.AddrPortFrom(addrPort.Addr().Unmap(), addrPort.Port())
}
func getExecPathFromPID(pid uint32) (string, error) {
const (
procpidpathinfo = 0xb
procpidpathinfosize = 1024
proccallnumpidinfo = 0x2
)
buf := make([]byte, procpidpathinfosize)
_, _, errno := syscall.Syscall6(
syscall.SYS_PROC_INFO,
proccallnumpidinfo,
uintptr(pid),
procpidpathinfo,
0,
uintptr(unsafe.Pointer(&buf[0])),
procpidpathinfosize,
)
if errno != 0 {
return "", errno
}
return unix.ByteSliceToString(buf), nil
}

View File

@@ -4,33 +4,82 @@ package process
import ( import (
"context" "context"
"errors"
"net/netip" "net/netip"
"syscall"
"time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
) )
var _ Searcher = (*linuxSearcher)(nil) var _ Searcher = (*linuxSearcher)(nil)
type linuxSearcher struct { type linuxSearcher struct {
logger log.ContextLogger logger log.ContextLogger
diagConns [4]*socketDiagConn
processPathCache *uidProcessPathCache
} }
func NewSearcher(config Config) (Searcher, error) { func NewSearcher(config Config) (Searcher, error) {
return &linuxSearcher{config.Logger}, nil searcher := &linuxSearcher{
logger: config.Logger,
processPathCache: newUIDProcessPathCache(time.Second),
}
for _, family := range []uint8{syscall.AF_INET, syscall.AF_INET6} {
for _, protocol := range []uint8{syscall.IPPROTO_TCP, syscall.IPPROTO_UDP} {
searcher.diagConns[socketDiagConnIndex(family, protocol)] = newSocketDiagConn(family, protocol)
}
}
return searcher, nil
}
func (s *linuxSearcher) Close() error {
var errs []error
for _, conn := range s.diagConns {
if conn == nil {
continue
}
errs = append(errs, conn.Close())
}
return E.Errors(errs...)
} }
func (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) { func (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
inode, uid, err := resolveSocketByNetlink(network, source, destination) inode, uid, err := s.resolveSocketByNetlink(network, source, destination)
if err != nil { if err != nil {
return nil, err return nil, err
} }
processPath, err := resolveProcessNameByProcSearch(inode, uid) processInfo := &adapter.ConnectionOwner{
UserId: int32(uid),
}
processPath, err := s.processPathCache.findProcessPath(inode, uid)
if err != nil { if err != nil {
s.logger.DebugContext(ctx, "find process path: ", err) s.logger.DebugContext(ctx, "find process path: ", err)
} else {
processInfo.ProcessPath = processPath
} }
return &adapter.ConnectionOwner{ return processInfo, nil
UserId: int32(uid), }
ProcessPath: processPath,
}, nil func (s *linuxSearcher) resolveSocketByNetlink(network string, source netip.AddrPort, destination netip.AddrPort) (inode, uid uint32, err error) {
family, protocol, err := socketDiagSettings(network, source)
if err != nil {
return 0, 0, err
}
conn := s.diagConns[socketDiagConnIndex(family, protocol)]
if conn == nil {
return 0, 0, E.New("missing socket diag connection for family=", family, " protocol=", protocol)
}
if destination.IsValid() && source.Addr().BitLen() == destination.Addr().BitLen() {
inode, uid, err = conn.query(source, destination)
if err == nil {
return inode, uid, nil
}
if !errors.Is(err, ErrNotFound) {
return 0, 0, err
}
}
return querySocketDiagOnce(family, protocol, source)
} }

View File

@@ -1,45 +1,70 @@
//go:build linux //go:build linux
//nolint:unused
package process package process
import ( import (
"bytes"
"encoding/binary" "encoding/binary"
"fmt" "errors"
"net"
"net/netip" "net/netip"
"os" "os"
"path" "path/filepath"
"strings" "strings"
"sync"
"syscall" "syscall"
"time"
"unicode" "unicode"
"unsafe"
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/contrab/freelru"
"github.com/sagernet/sing/contrab/maphash"
) )
// from https://github.com/vishvananda/netlink/blob/bca67dfc8220b44ef582c9da4e9172bf1c9ec973/nl/nl_linux.go#L52-L62
var nativeEndian = func() binary.ByteOrder {
var x uint32 = 0x01020304
if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
return binary.BigEndian
}
return binary.LittleEndian
}()
const ( const (
sizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + 8 + 48 sizeOfSocketDiagRequestData = 56
socketDiagByFamily = 20 sizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + sizeOfSocketDiagRequestData
pathProc = "/proc" socketDiagResponseMinSize = 72
socketDiagByFamily = 20
pathProc = "/proc"
) )
func resolveSocketByNetlink(network string, source netip.AddrPort, destination netip.AddrPort) (inode, uid uint32, err error) { type socketDiagConn struct {
var family uint8 access sync.Mutex
var protocol uint8 family uint8
protocol uint8
fd int
}
type uidProcessPathCache struct {
cache freelru.Cache[uint32, *uidProcessPaths]
}
type uidProcessPaths struct {
entries map[uint32]string
}
func newSocketDiagConn(family, protocol uint8) *socketDiagConn {
return &socketDiagConn{
family: family,
protocol: protocol,
fd: -1,
}
}
func socketDiagConnIndex(family, protocol uint8) int {
index := 0
if protocol == syscall.IPPROTO_UDP {
index += 2
}
if family == syscall.AF_INET6 {
index++
}
return index
}
func socketDiagSettings(network string, source netip.AddrPort) (family, protocol uint8, err error) {
switch network { switch network {
case N.NetworkTCP: case N.NetworkTCP:
protocol = syscall.IPPROTO_TCP protocol = syscall.IPPROTO_TCP
@@ -48,151 +73,308 @@ func resolveSocketByNetlink(network string, source netip.AddrPort, destination n
default: default:
return 0, 0, os.ErrInvalid return 0, 0, os.ErrInvalid
} }
switch {
if source.Addr().Is4() { case source.Addr().Is4():
family = syscall.AF_INET family = syscall.AF_INET
} else { case source.Addr().Is6():
family = syscall.AF_INET6 family = syscall.AF_INET6
default:
return 0, 0, os.ErrInvalid
} }
return family, protocol, nil
req := packSocketDiagRequest(family, protocol, source)
socket, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_INET_DIAG)
if err != nil {
return 0, 0, E.Cause(err, "dial netlink")
}
defer syscall.Close(socket)
syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, &syscall.Timeval{Usec: 100})
syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, &syscall.Timeval{Usec: 100})
err = syscall.Connect(socket, &syscall.SockaddrNetlink{
Family: syscall.AF_NETLINK,
Pad: 0,
Pid: 0,
Groups: 0,
})
if err != nil {
return
}
_, err = syscall.Write(socket, req)
if err != nil {
return 0, 0, E.Cause(err, "write netlink request")
}
buffer := buf.New()
defer buffer.Release()
n, err := syscall.Read(socket, buffer.FreeBytes())
if err != nil {
return 0, 0, E.Cause(err, "read netlink response")
}
buffer.Truncate(n)
messages, err := syscall.ParseNetlinkMessage(buffer.Bytes())
if err != nil {
return 0, 0, E.Cause(err, "parse netlink message")
} else if len(messages) == 0 {
return 0, 0, E.New("unexcepted netlink response")
}
message := messages[0]
if message.Header.Type&syscall.NLMSG_ERROR != 0 {
return 0, 0, E.New("netlink message: NLMSG_ERROR")
}
inode, uid = unpackSocketDiagResponse(&messages[0])
return
} }
func packSocketDiagRequest(family, protocol byte, source netip.AddrPort) []byte { func newUIDProcessPathCache(ttl time.Duration) *uidProcessPathCache {
s := make([]byte, 16) cache := common.Must1(freelru.NewSharded[uint32, *uidProcessPaths](64, maphash.NewHasher[uint32]().Hash32))
copy(s, source.Addr().AsSlice()) cache.SetLifetime(ttl)
return &uidProcessPathCache{cache: cache}
buf := make([]byte, sizeOfSocketDiagRequest)
nativeEndian.PutUint32(buf[0:4], sizeOfSocketDiagRequest)
nativeEndian.PutUint16(buf[4:6], socketDiagByFamily)
nativeEndian.PutUint16(buf[6:8], syscall.NLM_F_REQUEST|syscall.NLM_F_DUMP)
nativeEndian.PutUint32(buf[8:12], 0)
nativeEndian.PutUint32(buf[12:16], 0)
buf[16] = family
buf[17] = protocol
buf[18] = 0
buf[19] = 0
nativeEndian.PutUint32(buf[20:24], 0xFFFFFFFF)
binary.BigEndian.PutUint16(buf[24:26], source.Port())
binary.BigEndian.PutUint16(buf[26:28], 0)
copy(buf[28:44], s)
copy(buf[44:60], net.IPv6zero)
nativeEndian.PutUint32(buf[60:64], 0)
nativeEndian.PutUint64(buf[64:72], 0xFFFFFFFFFFFFFFFF)
return buf
} }
func unpackSocketDiagResponse(msg *syscall.NetlinkMessage) (inode, uid uint32) { func (c *uidProcessPathCache) findProcessPath(targetInode, uid uint32) (string, error) {
if len(msg.Data) < 72 { if cached, ok := c.cache.Get(uid); ok {
return 0, 0 if processPath, found := cached.entries[targetInode]; found {
return processPath, nil
}
} }
processPaths, err := buildProcessPathByUIDCache(uid)
data := msg.Data
uid = nativeEndian.Uint32(data[64:68])
inode = nativeEndian.Uint32(data[68:72])
return
}
func resolveProcessNameByProcSearch(inode, uid uint32) (string, error) {
files, err := os.ReadDir(pathProc)
if err != nil { if err != nil {
return "", err return "", err
} }
c.cache.Add(uid, &uidProcessPaths{entries: processPaths})
processPath, found := processPaths[targetInode]
if !found {
return "", E.New("process of uid(", uid, "), inode(", targetInode, ") not found")
}
return processPath, nil
}
func (c *socketDiagConn) Close() error {
c.access.Lock()
defer c.access.Unlock()
return c.closeLocked()
}
func (c *socketDiagConn) query(source netip.AddrPort, destination netip.AddrPort) (inode, uid uint32, err error) {
c.access.Lock()
defer c.access.Unlock()
request := packSocketDiagRequest(c.family, c.protocol, source, destination, false)
for range 2 {
err = c.ensureOpenLocked()
if err != nil {
return 0, 0, E.Cause(err, "dial netlink")
}
inode, uid, err = querySocketDiag(c.fd, request)
if err == nil || errors.Is(err, ErrNotFound) {
return inode, uid, err
}
if !shouldRetrySocketDiag(err) {
return 0, 0, err
}
_ = c.closeLocked()
}
return 0, 0, err
}
func querySocketDiagOnce(family, protocol uint8, source netip.AddrPort) (inode, uid uint32, err error) {
fd, err := openSocketDiag()
if err != nil {
return 0, 0, E.Cause(err, "dial netlink")
}
defer syscall.Close(fd)
return querySocketDiag(fd, packSocketDiagRequest(family, protocol, source, netip.AddrPort{}, true))
}
func (c *socketDiagConn) ensureOpenLocked() error {
if c.fd != -1 {
return nil
}
fd, err := openSocketDiag()
if err != nil {
return err
}
c.fd = fd
return nil
}
func openSocketDiag() (int, error) {
fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM|syscall.SOCK_CLOEXEC, syscall.NETLINK_INET_DIAG)
if err != nil {
return -1, err
}
timeout := &syscall.Timeval{Usec: 100}
if err = syscall.SetsockoptTimeval(fd, syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, timeout); err != nil {
syscall.Close(fd)
return -1, err
}
if err = syscall.SetsockoptTimeval(fd, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, timeout); err != nil {
syscall.Close(fd)
return -1, err
}
if err = syscall.Connect(fd, &syscall.SockaddrNetlink{
Family: syscall.AF_NETLINK,
Pid: 0,
Groups: 0,
}); err != nil {
syscall.Close(fd)
return -1, err
}
return fd, nil
}
func (c *socketDiagConn) closeLocked() error {
if c.fd == -1 {
return nil
}
err := syscall.Close(c.fd)
c.fd = -1
return err
}
func packSocketDiagRequest(family, protocol byte, source netip.AddrPort, destination netip.AddrPort, dump bool) []byte {
request := make([]byte, sizeOfSocketDiagRequest)
binary.NativeEndian.PutUint32(request[0:4], sizeOfSocketDiagRequest)
binary.NativeEndian.PutUint16(request[4:6], socketDiagByFamily)
flags := uint16(syscall.NLM_F_REQUEST)
if dump {
flags |= syscall.NLM_F_DUMP
}
binary.NativeEndian.PutUint16(request[6:8], flags)
binary.NativeEndian.PutUint32(request[8:12], 0)
binary.NativeEndian.PutUint32(request[12:16], 0)
request[16] = family
request[17] = protocol
request[18] = 0
request[19] = 0
if dump {
binary.NativeEndian.PutUint32(request[20:24], 0xFFFFFFFF)
}
requestSource := source
requestDestination := destination
if protocol == syscall.IPPROTO_UDP && !dump && destination.IsValid() {
// udp_dump_one expects the exact-match endpoints reversed for historical reasons.
requestSource, requestDestination = destination, source
}
binary.BigEndian.PutUint16(request[24:26], requestSource.Port())
binary.BigEndian.PutUint16(request[26:28], requestDestination.Port())
if family == syscall.AF_INET6 {
copy(request[28:44], requestSource.Addr().AsSlice())
if requestDestination.IsValid() {
copy(request[44:60], requestDestination.Addr().AsSlice())
}
} else {
copy(request[28:32], requestSource.Addr().AsSlice())
if requestDestination.IsValid() {
copy(request[44:48], requestDestination.Addr().AsSlice())
}
}
binary.NativeEndian.PutUint32(request[60:64], 0)
binary.NativeEndian.PutUint64(request[64:72], 0xFFFFFFFFFFFFFFFF)
return request
}
func querySocketDiag(fd int, request []byte) (inode, uid uint32, err error) {
_, err = syscall.Write(fd, request)
if err != nil {
return 0, 0, E.Cause(err, "write netlink request")
}
buffer := make([]byte, 64<<10)
n, err := syscall.Read(fd, buffer)
if err != nil {
return 0, 0, E.Cause(err, "read netlink response")
}
messages, err := syscall.ParseNetlinkMessage(buffer[:n])
if err != nil {
return 0, 0, E.Cause(err, "parse netlink message")
}
return unpackSocketDiagMessages(messages)
}
func unpackSocketDiagMessages(messages []syscall.NetlinkMessage) (inode, uid uint32, err error) {
for _, message := range messages {
switch message.Header.Type {
case syscall.NLMSG_DONE:
continue
case syscall.NLMSG_ERROR:
err = unpackSocketDiagError(&message)
if err != nil {
return 0, 0, err
}
case socketDiagByFamily:
inode, uid = unpackSocketDiagResponse(&message)
if inode != 0 || uid != 0 {
return inode, uid, nil
}
}
}
return 0, 0, ErrNotFound
}
func unpackSocketDiagResponse(msg *syscall.NetlinkMessage) (inode, uid uint32) {
if len(msg.Data) < socketDiagResponseMinSize {
return 0, 0
}
uid = binary.NativeEndian.Uint32(msg.Data[64:68])
inode = binary.NativeEndian.Uint32(msg.Data[68:72])
return inode, uid
}
func unpackSocketDiagError(msg *syscall.NetlinkMessage) error {
if len(msg.Data) < 4 {
return E.New("netlink message: NLMSG_ERROR")
}
errno := int32(binary.NativeEndian.Uint32(msg.Data[:4]))
if errno == 0 {
return nil
}
if errno < 0 {
errno = -errno
}
sysErr := syscall.Errno(errno)
switch sysErr {
case syscall.ENOENT, syscall.ESRCH:
return ErrNotFound
default:
return E.New("netlink message: ", sysErr)
}
}
func shouldRetrySocketDiag(err error) bool {
return err != nil && !errors.Is(err, ErrNotFound)
}
func buildProcessPathByUIDCache(uid uint32) (map[uint32]string, error) {
files, err := os.ReadDir(pathProc)
if err != nil {
return nil, err
}
buffer := make([]byte, syscall.PathMax) buffer := make([]byte, syscall.PathMax)
socket := []byte(fmt.Sprintf("socket:[%d]", inode)) processPaths := make(map[uint32]string)
for _, file := range files {
for _, f := range files { if !file.IsDir() || !isPid(file.Name()) {
if !f.IsDir() || !isPid(f.Name()) {
continue continue
} }
info, err := file.Info()
info, err := f.Info()
if err != nil { if err != nil {
return "", err if isIgnorableProcError(err) {
continue
}
return nil, err
} }
if info.Sys().(*syscall.Stat_t).Uid != uid { if info.Sys().(*syscall.Stat_t).Uid != uid {
continue continue
} }
processPath := filepath.Join(pathProc, file.Name())
processPath := path.Join(pathProc, f.Name()) fdPath := filepath.Join(processPath, "fd")
fdPath := path.Join(processPath, "fd") exePath, err := os.Readlink(filepath.Join(processPath, "exe"))
if err != nil {
if isIgnorableProcError(err) {
continue
}
return nil, err
}
fds, err := os.ReadDir(fdPath) fds, err := os.ReadDir(fdPath)
if err != nil { if err != nil {
continue continue
} }
for _, fd := range fds { for _, fd := range fds {
n, err := syscall.Readlink(path.Join(fdPath, fd.Name()), buffer) n, err := syscall.Readlink(filepath.Join(fdPath, fd.Name()), buffer)
if err != nil { if err != nil {
continue continue
} }
inode, ok := parseSocketInode(buffer[:n])
if bytes.Equal(buffer[:n], socket) { if !ok {
return os.Readlink(path.Join(processPath, "exe")) continue
}
if _, loaded := processPaths[inode]; !loaded {
processPaths[inode] = exePath
} }
} }
} }
return processPaths, nil
}
return "", fmt.Errorf("process of uid(%d),inode(%d) not found", uid, inode) func isIgnorableProcError(err error) bool {
return os.IsNotExist(err) || os.IsPermission(err)
}
func parseSocketInode(link []byte) (uint32, bool) {
const socketPrefix = "socket:["
if len(link) <= len(socketPrefix) || string(link[:len(socketPrefix)]) != socketPrefix || link[len(link)-1] != ']' {
return 0, false
}
var inode uint64
for _, char := range link[len(socketPrefix) : len(link)-1] {
if char < '0' || char > '9' {
return 0, false
}
inode = inode*10 + uint64(char-'0')
if inode > uint64(^uint32(0)) {
return 0, false
}
}
return uint32(inode), true
} }
func isPid(s string) bool { func isPid(s string) bool {

View File

@@ -0,0 +1,60 @@
//go:build linux
package process
import (
"net"
"net/netip"
"os"
"syscall"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestQuerySocketDiagUDPExact(t *testing.T) {
t.Parallel()
server, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})
require.NoError(t, err)
defer server.Close()
client, err := net.DialUDP("udp4", nil, server.LocalAddr().(*net.UDPAddr))
require.NoError(t, err)
defer client.Close()
err = client.SetDeadline(time.Now().Add(time.Second))
require.NoError(t, err)
_, err = client.Write([]byte{0})
require.NoError(t, err)
err = server.SetReadDeadline(time.Now().Add(time.Second))
require.NoError(t, err)
buffer := make([]byte, 1)
_, _, err = server.ReadFromUDP(buffer)
require.NoError(t, err)
source := addrPortFromUDPAddr(t, client.LocalAddr())
destination := addrPortFromUDPAddr(t, client.RemoteAddr())
fd, err := openSocketDiag()
require.NoError(t, err)
defer syscall.Close(fd)
inode, uid, err := querySocketDiag(fd, packSocketDiagRequest(syscall.AF_INET, syscall.IPPROTO_UDP, source, destination, false))
require.NoError(t, err)
require.NotZero(t, inode)
require.EqualValues(t, os.Getuid(), uid)
}
func addrPortFromUDPAddr(t *testing.T, addr net.Addr) netip.AddrPort {
t.Helper()
udpAddr, ok := addr.(*net.UDPAddr)
require.True(t, ok)
ip, ok := netip.AddrFromSlice(udpAddr.IP)
require.True(t, ok)
return netip.AddrPortFrom(ip.Unmap(), uint16(udpAddr.Port))
}

View File

@@ -28,6 +28,10 @@ func initWin32API() error {
return winiphlpapi.LoadExtendedTable() return winiphlpapi.LoadExtendedTable()
} }
func (s *windowsSearcher) Close() error {
return nil
}
func (s *windowsSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) { func (s *windowsSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
pid, err := winiphlpapi.FindPid(network, source) pid, err := winiphlpapi.FindPid(network, source)
if err != nil { if err != nil {

View File

@@ -109,7 +109,7 @@ func getInterfaceDisplayName(name string) (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
for _, deviceSpan := range strings.Split(string(content), "Ethernet Address") { for deviceSpan := range strings.SplitSeq(string(content), "Ethernet Address") {
if strings.Contains(deviceSpan, "Device: "+name) { if strings.Contains(deviceSpan, "Device: "+name) {
substr := "Hardware Port: " substr := "Hardware Port: "
deviceSpan = deviceSpan[strings.Index(deviceSpan, substr)+len(substr):] deviceSpan = deviceSpan[strings.Index(deviceSpan, substr)+len(substr):]

View File

@@ -40,14 +40,14 @@ func (m *connmanMonitor) ReadWIFIState() adapter.WIFIState {
defer cancel() defer cancel()
cmObj := m.conn.Object("net.connman", "/") cmObj := m.conn.Object("net.connman", "/")
var services []interface{} var services []any
err := cmObj.CallWithContext(ctx, "net.connman.Manager.GetServices", 0).Store(&services) err := cmObj.CallWithContext(ctx, "net.connman.Manager.GetServices", 0).Store(&services)
if err != nil { if err != nil {
return adapter.WIFIState{} return adapter.WIFIState{}
} }
for _, service := range services { for _, service := range services {
servicePair, ok := service.([]interface{}) servicePair, ok := service.([]any)
if !ok || len(servicePair) != 2 { if !ok || len(servicePair) != 2 {
continue continue
} }

View File

@@ -1,3 +1,4 @@
//nolint:unused
package settings package settings
import ( import (
@@ -73,13 +74,13 @@ func (m *wpaSupplicantMonitor) ReadWIFIState() adapter.WIFIState {
scanner := bufio.NewScanner(strings.NewReader(status)) scanner := bufio.NewScanner(strings.NewReader(status))
for scanner.Scan() { for scanner.Scan() {
line := scanner.Text() line := scanner.Text()
if strings.HasPrefix(line, "wpa_state=") { if after, ok := strings.CutPrefix(line, "wpa_state="); ok {
state := strings.TrimPrefix(line, "wpa_state=") state := after
connected = state == "COMPLETED" connected = state == "COMPLETED"
} else if strings.HasPrefix(line, "ssid=") { } else if after, ok := strings.CutPrefix(line, "ssid="); ok {
ssid = strings.TrimPrefix(line, "ssid=") ssid = after
} else if strings.HasPrefix(line, "bssid=") { } else if after, ok := strings.CutPrefix(line, "bssid="); ok {
bssid = strings.TrimPrefix(line, "bssid=") bssid = after
} }
} }

View File

@@ -1,5 +1,6 @@
//go:build !linux && !windows //go:build !linux && !windows
//nolint:unused
package settings package settings
import ( import (

View File

@@ -54,9 +54,8 @@ type xorNonceAEAD struct {
aead cipher.AEAD aead cipher.AEAD
} }
func (f *xorNonceAEAD) NonceSize() int { return 8 } // 64-bit sequence number func (f *xorNonceAEAD) NonceSize() int { return 8 } // 64-bit sequence number
func (f *xorNonceAEAD) Overhead() int { return f.aead.Overhead() } func (f *xorNonceAEAD) Overhead() int { return f.aead.Overhead() }
func (f *xorNonceAEAD) explicitNonceLen() int { return 0 }
func (f *xorNonceAEAD) Seal(out, nonce, plaintext, additionalData []byte) []byte { func (f *xorNonceAEAD) Seal(out, nonce, plaintext, additionalData []byte) []byte {
for i, b := range nonce { for i, b := range nonce {

View File

@@ -1,6 +1,8 @@
package sniff package sniff
import ( import (
"slices"
"github.com/sagernet/sing-box/common/ja3" "github.com/sagernet/sing-box/common/ja3"
) )
@@ -15,15 +17,8 @@ const (
// Note: uQUIC with Chromium mimicry cannot be reliably distinguished from real Chromium // Note: uQUIC with Chromium mimicry cannot be reliably distinguished from real Chromium
// since it uses the same TLS fingerprint, so it will be identified as Chromium. // since it uses the same TLS fingerprint, so it will be identified as Chromium.
func isQUICGo(fingerprint *ja3.ClientHello) bool { func isQUICGo(fingerprint *ja3.ClientHello) bool {
for _, curve := range fingerprint.EllipticCurves { if slices.Contains(fingerprint.EllipticCurves, x25519Kyber768Draft00) {
if curve == x25519Kyber768Draft00 { return true
return true
}
} }
for _, ext := range fingerprint.Extensions { return slices.Contains(fingerprint.Extensions, extensionRenegotiationInfo)
if ext == extensionRenegotiationInfo {
return true
}
}
return false
} }

View File

@@ -30,7 +30,7 @@ func TestSniffQUICQuicGoFingerprint(t *testing.T) {
go func() { go func() {
var packets [][]byte var packets [][]byte
udpConn.SetReadDeadline(time.Now().Add(3 * time.Second)) udpConn.SetReadDeadline(time.Now().Add(3 * time.Second))
for i := 0; i < 10; i++ { for range 10 {
buf := make([]byte, 2048) buf := make([]byte, 2048)
n, _, err := udpConn.ReadFromUDP(buf) n, _, err := udpConn.ReadFromUDP(buf)
if err != nil { if err != nil {
@@ -104,7 +104,7 @@ func TestSniffQUICInitialFromQuicGo(t *testing.T) {
go func() { go func() {
var packets [][]byte var packets [][]byte
udpConn.SetReadDeadline(time.Now().Add(3 * time.Second)) udpConn.SetReadDeadline(time.Now().Add(3 * time.Second))
for i := 0; i < 5; i++ { // Capture up to 5 packets for range 5 { // Capture up to 5 packets
buf := make([]byte, 2048) buf := make([]byte, 2048)
n, _, err := udpConn.ReadFromUDP(buf) n, _, err := udpConn.ReadFromUDP(buf)
if err != nil { if err != nil {

View File

@@ -78,7 +78,7 @@ func Read(reader io.Reader, recover bool) (ruleSetCompat option.PlainRuleSetComp
} }
ruleSetCompat.Version = version ruleSetCompat.Version = version
ruleSetCompat.Options.Rules = make([]option.HeadlessRule, length) ruleSetCompat.Options.Rules = make([]option.HeadlessRule, length)
for i := uint64(0); i < length; i++ { for i := range length {
ruleSetCompat.Options.Rules[i], err = readRule(bReader, recover) ruleSetCompat.Options.Rules[i], err = readRule(bReader, recover)
if err != nil { if err != nil {
err = E.Cause(err, "read rule[", i, "]") err = E.Cause(err, "read rule[", i, "]")
@@ -644,7 +644,7 @@ func readLogicalRule(reader varbin.Reader, recovery bool) (logicalRule option.Lo
return return
} }
logicalRule.Rules = make([]option.HeadlessRule, length) logicalRule.Rules = make([]option.HeadlessRule, length)
for i := uint64(0); i < length; i++ { for i := range length {
logicalRule.Rules[i], err = readRule(reader, recovery) logicalRule.Rules[i], err = readRule(reader, recovery)
if err != nil { if err != nil {
err = E.Cause(err, "read logical rule [", i, "]") err = E.Cause(err, "read logical rule [", i, "]")

View File

@@ -450,7 +450,7 @@ func buildIPSet(cidrs ...string) *netipx.IPSet {
func buildLargeIPSet(count int) *netipx.IPSet { func buildLargeIPSet(count int) *netipx.IPSet {
var builder netipx.IPSetBuilder var builder netipx.IPSetBuilder
for i := 0; i < count; i++ { for i := range count {
prefix := netip.PrefixFrom(netip.AddrFrom4([4]byte{10, byte(i / 256), byte(i % 256), 0}), 24) prefix := netip.PrefixFrom(netip.AddrFrom4([4]byte{10, byte(i / 256), byte(i % 256), 0}), 24)
builder.AddPrefix(prefix) builder.AddPrefix(prefix)
} }

View File

@@ -0,0 +1,74 @@
package tls
import (
"context"
"crypto/ecdsa"
"crypto/tls"
"crypto/x509"
"time"
"github.com/sagernet/quic-go/http3"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
)
func NewMASQUEClient(ctx context.Context, logger logger.ContextLogger, serverName string, cert [][]byte, privateKey *ecdsa.PrivateKey, peerPublicKey *ecdsa.PublicKey, options option.MASQUEOutboundTLSOptions) (Config, error) {
var tlsConfig tls.Config
tlsConfig.ServerName = serverName
tlsConfig.InsecureSkipVerify = true
tlsConfig.NextProtos = []string{http3.NextProtoH3}
tlsConfig.Certificates = []tls.Certificate{
{
Certificate: cert,
PrivateKey: privateKey,
},
}
if options.CipherSuites != nil {
find:
for _, cipherSuite := range options.CipherSuites {
for _, tlsCipherSuite := range tls.CipherSuites() {
if cipherSuite == tlsCipherSuite.Name {
tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, tlsCipherSuite.ID)
continue find
}
}
return nil, E.New("unknown cipher_suite: ", cipherSuite)
}
}
for _, curve := range options.CurvePreferences {
tlsConfig.CurvePreferences = append(tlsConfig.CurvePreferences, tls.CurveID(curve))
}
if !options.Insecure {
tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
if len(rawCerts) == 0 {
return nil
}
cert, err := x509.ParseCertificate(rawCerts[0])
if err != nil {
return err
}
if _, ok := cert.PublicKey.(*ecdsa.PublicKey); !ok {
return x509.ErrUnsupportedAlgorithm
}
if !cert.PublicKey.(*ecdsa.PublicKey).Equal(peerPublicKey) {
return x509.CertificateInvalidError{Cert: cert, Reason: 10, Detail: "remote endpoint has a different public key than what we trust in config.json"}
}
return nil
}
}
var config Config = &STDClientConfig{ctx, &tlsConfig, options.Fragment, time.Duration(options.FragmentFallbackDelay), options.RecordFragment}
if options.KernelRx || options.KernelTx {
if !C.IsLinux {
return nil, E.New("kTLS is only supported on Linux")
}
config = &KTLSClientConfig{
Config: config,
logger: logger,
kernelTx: options.KernelTx,
kernelRx: options.KernelRx,
}
}
return config, nil
}

View File

@@ -267,8 +267,8 @@ type realityVerifier struct {
} }
func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
p, _ := reflect.TypeOf(c.Conn).Elem().FieldByName("peerCertificates") p, _ := reflect.TypeFor[utls.Conn]().FieldByName("peerCertificates")
certs := *(*([]*x509.Certificate))(unsafe.Pointer(uintptr(unsafe.Pointer(c.Conn)) + p.Offset)) certs := *(*([]*x509.Certificate))(unsafe.Add(unsafe.Pointer(c.Conn), p.Offset))
if pub, ok := certs[0].PublicKey.(ed25519.PublicKey); ok { if pub, ok := certs[0].PublicKey.(ed25519.PublicKey); ok {
h := hmac.New(sha512.New, c.authKey) h := hmac.New(sha512.New, c.authKey)
h.Write(pub) h.Write(pub)

View File

@@ -141,13 +141,14 @@ func (c *STDServerConfig) startWatcher() error {
func (c *STDServerConfig) certificateUpdated(path string) error { func (c *STDServerConfig) certificateUpdated(path string) error {
if path == c.certificatePath || path == c.keyPath { if path == c.certificatePath || path == c.keyPath {
if path == c.certificatePath { switch path {
case c.certificatePath:
certificate, err := os.ReadFile(c.certificatePath) certificate, err := os.ReadFile(c.certificatePath)
if err != nil { if err != nil {
return E.Cause(err, "reload certificate from ", c.certificatePath) return E.Cause(err, "reload certificate from ", c.certificatePath)
} }
c.certificate = certificate c.certificate = certificate
} else if path == c.keyPath { case c.keyPath:
key, err := os.ReadFile(c.keyPath) key, err := os.ReadFile(c.keyPath)
if err != nil { if err != nil {
return E.Cause(err, "reload key from ", c.keyPath) return E.Cause(err, "reload key from ", c.keyPath)
@@ -338,9 +339,10 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.
} }
tlsConfig.ClientCAs = clientCertificateCA tlsConfig.ClientCAs = clientCertificateCA
} else if len(options.ClientCertificatePublicKeySHA256) > 0 { } else if len(options.ClientCertificatePublicKeySHA256) > 0 {
if tlsConfig.ClientAuth == tls.RequireAndVerifyClientCert { switch tlsConfig.ClientAuth {
case tls.RequireAndVerifyClientCert:
tlsConfig.ClientAuth = tls.RequireAnyClientCert tlsConfig.ClientAuth = tls.RequireAnyClientCert
} else if tlsConfig.ClientAuth == tls.VerifyClientCertIfGiven { case tls.VerifyClientCertIfGiven:
tlsConfig.ClientAuth = tls.RequestClientCert tlsConfig.ClientAuth = tls.RequestClientCert
} }
tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {

79
common/utils.go Normal file
View File

@@ -0,0 +1,79 @@
package common
import (
"encoding/base64"
"encoding/json"
"reflect"
"regexp"
"strconv"
"strings"
"time"
Xbadoption "github.com/sagernet/sing-box/common/xray/json/badoption"
"github.com/sagernet/sing/common/json/badoption"
)
func StringToType[T any](str string) T {
var value T
v := reflect.ValueOf(&value).Elem()
switch any(value).(type) {
case badoption.Duration:
d, err := time.ParseDuration(str)
if err != nil {
v.SetInt(StringToType[int64](str))
} else {
v.Set(reflect.ValueOf(d))
}
return value
case badoption.HTTPHeader:
headers := badoption.HTTPHeader{}
reg := regexp.MustCompile(`^[ \t]*?(\S+?):[ \t]*?(\S+?)[ \t]*?$`)
for _, header := range strings.Split(str, "\n") {
result := reg.FindStringSubmatch(header)
if result != nil {
key := result[1]
headers[key] = strings.Split(result[2], ",")
}
}
v.Set(reflect.ValueOf(headers))
return value
}
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
i, _ := strconv.ParseInt(str, 10, 64)
v.SetInt(i)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
i, _ := strconv.ParseUint(str, 10, 64)
v.SetUint(i)
case reflect.Float32, reflect.Float64:
f, _ := strconv.ParseFloat(str, 64)
v.SetFloat(f)
case reflect.Bool:
b, _ := strconv.ParseBool(str)
v.SetBool(b)
default:
panic("unsupported type")
}
return value
}
func DecodeBase64URLSafe(content string) (string, error) {
s := strings.ReplaceAll(content, " ", "-")
s = strings.ReplaceAll(s, "/", "_")
s = strings.ReplaceAll(s, "+", "-")
s = strings.ReplaceAll(s, "=", "")
result, err := base64.RawURLEncoding.DecodeString(s)
if err != nil {
return content, nil
}
return string(result), nil
}
func ParseXHTTPRange(value string) (Xbadoption.Range, error) {
result := Xbadoption.Range{}
encoded, err := json.Marshal(value)
if err != nil {
return result, err
}
return result, result.UnmarshalJSON(encoded)
}

View File

@@ -38,8 +38,8 @@ func MergeMulti(dest MultiBuffer, src MultiBuffer) (MultiBuffer, MultiBuffer) {
// MergeBytes merges the given bytes into MultiBuffer and return the new address of the merged MultiBuffer. // MergeBytes merges the given bytes into MultiBuffer and return the new address of the merged MultiBuffer.
func MergeBytes(dest MultiBuffer, src []byte) MultiBuffer { func MergeBytes(dest MultiBuffer, src []byte) MultiBuffer {
n := len(dest) n := len(dest)
if n > 0 && !(dest)[n-1].IsFull() { if n > 0 && !dest[n-1].IsFull() {
nBytes, _ := (dest)[n-1].Write(src) nBytes, _ := dest[n-1].Write(src)
src = src[nBytes:] src = src[nBytes:]
} }

View File

@@ -42,7 +42,7 @@ func New(opts ...Option) (*Reader, *Writer) {
} }
for _, opt := range opts { for _, opt := range opts {
opt(&(p.option)) opt(&p.option)
} }
return &Reader{ return &Reader{

View File

@@ -1,28 +1,290 @@
package utils package utils
import ( import (
"hash/fnv"
"math"
"math/rand" "math/rand"
"net/http"
"strconv" "strconv"
"strings"
"time" "time"
"github.com/klauspost/cpuid/v2" "github.com/klauspost/cpuid/v2"
) )
func ChromeVersion() int { func GetRandomizer() *rand.Rand {
// Use only CPU info as seed for PRNG // Seed the PRNG with the hash of CPU info, increasing the overall probable space.
seed := int64(cpuid.CPU.Family + cpuid.CPU.Model + cpuid.CPU.PhysicalCores + cpuid.CPU.LogicalCores + cpuid.CPU.CacheLine) fnvHash := fnv.New64()
rng := rand.New(rand.NewSource(seed)) fnvHash.Write([]byte(strconv.Itoa(cpuid.CPU.Family) + strconv.Itoa(cpuid.CPU.Model) + strconv.Itoa(cpuid.CPU.PhysicalCores) + strconv.Itoa(cpuid.CPU.LogicalCores) + strconv.Itoa(cpuid.CPU.CacheLine) + strconv.Itoa(cpuid.CPU.ThreadsPerCore)))
// Start from Chrome 144 released on 2026.1.13 return rand.New(rand.NewSource(int64(fnvHash.Sum64())))
releaseDate := time.Date(2026, 1, 13, 0, 0, 0, 0, time.UTC)
version := 144
now := time.Now()
// Each version has random 25-45 day interval
for releaseDate.Before(now) {
releaseDate = releaseDate.AddDate(0, 0, rng.Intn(21)+25)
version++
}
return version - 1
} }
// ChromeUA provides default browser User-Agent based on CPU-seeded PRNG. var globalRng *rand.Rand = GetRandomizer()
var ChromeUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + strconv.Itoa(ChromeVersion()) + ".0.0.0 Safari/537.36"
// The Chrome version generator will suffer from deviation of a normal distribution.
func ChromeVersion() int {
// Start from Chrome 144, released on 2026.1.13.
var startVersion int = 144
var timeStart int64 = time.Date(2026, 1, 13, 0, 0, 0, 0, time.UTC).Unix() / 86400
var timeCurrent int64 = time.Now().Unix() / 86400
var timeDiff int = int((timeCurrent - timeStart - 35)) - int(math.Floor(math.Pow(globalRng.Float64(), 2)*105))
return startVersion + (timeDiff / 35) // It's 31.15 currently.
}
var safariMinorMap [25]int = [25]int{
0, 0, 0, 1, 1,
1, 2, 2, 2, 2, 3, 3, 3, 4, 4,
4, 5, 5, 5, 5, 5, 6, 6, 6, 6,
}
// The following version generators use deterministic generators, but with the distribution scaled by a curve.
func CurlVersion() string {
// curl 8.0.0 was released on 20/03/2023.
var timeCurrent int64 = time.Now().Unix() / 86400
var timeStart int64 = time.Date(2023, 3, 20, 0, 0, 0, 0, time.UTC).Unix() / 86400
var timeDiff int = int((timeCurrent - timeStart - 60)) - int(math.Floor(math.Pow(globalRng.Float64(), 2)*165))
var minorValue int = int(timeDiff / 57) // The release cadence is actually 56.67 days.
return "8." + strconv.Itoa(minorValue) + ".0"
}
func FirefoxVersion() int {
// Firefox 128 ESR was released on 09/07/2023.
var timeCurrent int64 = time.Now().Unix() / 86400
var timeStart int64 = time.Date(2024, 7, 29, 0, 0, 0, 0, time.UTC).Unix() / 86400
timeDiff := timeCurrent - timeStart - 25 - int64(math.Floor(math.Pow(globalRng.Float64(), 2)*50))
return int(timeDiff/30) + 128
}
func SafariVersion() string {
var anchoredTime time.Time = time.Now()
var releaseYear int = anchoredTime.Year()
var splitPoint time.Time = time.Date(releaseYear, 9, 23, 0, 0, 0, 0, time.UTC)
delayedDays := int(math.Floor(math.Pow(globalRng.Float64(), 3) * 75))
splitPoint = splitPoint.AddDate(0, 0, delayedDays)
if anchoredTime.Compare(splitPoint) < 0 {
releaseYear--
splitPoint = time.Date(releaseYear, 9, 23, 0, 0, 0, 0, time.UTC)
splitPoint = splitPoint.AddDate(0, 0, delayedDays)
}
minorVersion := safariMinorMap[(anchoredTime.Unix()-splitPoint.Unix())/1296000]
return strconv.Itoa(releaseYear-1999) + "." + strconv.Itoa(minorVersion)
}
// The full Chromium brand GREASE implementation
var (
clientHintGreaseNA = []string{" ", "(", ":", "-", ".", "/", ")", ";", "=", "?", "_"}
clientHintVersionNA = []string{"8", "99", "24"}
clientHintShuffle3 = [][3]int{{0, 1, 2}, {0, 2, 1}, {1, 0, 2}, {1, 2, 0}, {2, 0, 1}, {2, 1, 0}}
clientHintShuffle4 = [][4]int{
{0, 1, 2, 3},
{0, 1, 3, 2},
{0, 2, 1, 3},
{0, 2, 3, 1},
{0, 3, 1, 2},
{0, 3, 2, 1},
{1, 0, 2, 3},
{1, 0, 3, 2},
{1, 2, 0, 3},
{1, 2, 3, 0},
{1, 3, 0, 2},
{1, 3, 2, 0},
{2, 0, 1, 3},
{2, 0, 3, 1},
{2, 1, 0, 3},
{2, 1, 3, 0},
{2, 3, 0, 1},
{2, 3, 1, 0},
{3, 0, 1, 2},
{3, 0, 2, 1},
{3, 1, 0, 2},
{3, 1, 2, 0},
{3, 2, 0, 1},
{3, 2, 1, 0},
}
)
func getGreasedChInvalidBrand(seed int) string {
return "\"Not" + clientHintGreaseNA[seed%len(clientHintGreaseNA)] + "A" + clientHintGreaseNA[(seed+1)%len(clientHintGreaseNA)] + "Brand\";v=\"" + clientHintVersionNA[seed%len(clientHintVersionNA)] + "\""
}
func getGreasedChOrder(brandLength int, seed int) []int {
switch brandLength {
case 1:
return []int{0}
case 2:
return []int{seed % brandLength, (seed + 1) % brandLength}
case 3:
return clientHintShuffle3[seed%len(clientHintShuffle3)][:]
default:
return clientHintShuffle4[seed%len(clientHintShuffle4)][:]
}
//return []int{}
}
func getUngreasedChUa(majorVersion int, forkName string) []string {
// Set the capacity to 4, the maximum allowed brand size, so Go will never allocate memory twice
baseChUa := make([]string, 0, 4)
baseChUa = append(baseChUa, getGreasedChInvalidBrand(majorVersion),
"\"Chromium\";v=\""+strconv.Itoa(majorVersion)+"\"")
switch forkName {
case "chrome":
baseChUa = append(baseChUa, "\"Google Chrome\";v=\""+strconv.Itoa(majorVersion)+"\"")
case "edge":
baseChUa = append(baseChUa, "\"Microsoft Edge\";v=\""+strconv.Itoa(majorVersion)+"\"")
}
return baseChUa
}
func getGreasedChUa(majorVersion int, forkName string) string {
ungreasedCh := getUngreasedChUa(majorVersion, forkName)
shuffleMap := getGreasedChOrder(len(ungreasedCh), majorVersion)
shuffledCh := make([]string, len(ungreasedCh))
for i, e := range shuffleMap {
shuffledCh[e] = ungreasedCh[i]
}
return strings.Join(shuffledCh, ", ")
}
// The code below provides a coherent default browser user agent string based on a CPU-seeded PRNG.
var (
CurlUA = "curl/" + CurlVersion()
AnchoredFirefoxVersion = strconv.Itoa(FirefoxVersion())
FirefoxUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:" + AnchoredFirefoxVersion + ".0) Gecko/20100101 Firefox/" + AnchoredFirefoxVersion + ".0"
SafariUA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/" + SafariVersion() + " Safari/605.1.15"
)
// Chromium browsers.
var (
AnchoredChromeVersion = ChromeVersion()
ChromeUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0 Safari/537.36"
ChromeUACH = getGreasedChUa(AnchoredChromeVersion, "chrome")
MSEdgeUA = ChromeUA + "Edg/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0"
MSEdgeUACH = getGreasedChUa(AnchoredChromeVersion, "edge")
)
func applyMasqueradedHeaders(header http.Header, browser string, variant string) {
// Browser-specific.
switch browser {
case "chrome":
header["Sec-CH-UA"] = []string{ChromeUACH}
header["Sec-CH-UA-Mobile"] = []string{"?0"}
header["Sec-CH-UA-Platform"] = []string{"\"Windows\""}
header["DNT"] = []string{"1"}
header.Set("User-Agent", ChromeUA)
header.Set("Accept-Language", "en-US,en;q=0.9")
case "edge":
header["Sec-CH-UA"] = []string{MSEdgeUACH}
header["Sec-CH-UA-Mobile"] = []string{"?0"}
header["Sec-CH-UA-Platform"] = []string{"\"Windows\""}
header["DNT"] = []string{"1"}
header.Set("User-Agent", MSEdgeUA)
header.Set("Accept-Language", "en-US,en;q=0.9")
case "firefox":
header.Set("User-Agent", FirefoxUA)
header["DNT"] = []string{"1"}
header.Set("Accept-Language", "en-US,en;q=0.5")
case "safari":
header.Set("User-Agent", SafariUA)
header.Set("Accept-Language", "en-US,en;q=0.9")
case "golang":
// Expose the default net/http header.
header.Del("User-Agent")
return
case "curl":
header.Set("User-Agent", CurlUA)
return
}
// Context-specific.
switch variant {
case "nav":
if header.Get("Cache-Control") == "" {
switch browser {
case "chrome", "edge":
header.Set("Cache-Control", "max-age=0")
}
}
header.Set("Upgrade-Insecure-Requests", "1")
if header.Get("Accept") == "" {
switch browser {
case "chrome", "edge":
header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")
case "firefox", "safari":
header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
}
}
header.Set("Sec-Fetch-Site", "none")
header.Set("Sec-Fetch-Mode", "navigate")
switch browser {
case "safari":
default:
header.Set("Sec-Fetch-User", "?1")
}
header.Set("Sec-Fetch-Dest", "document")
header.Set("Priority", "u=0, i")
case "ws":
header.Set("Sec-Fetch-Mode", "websocket")
switch browser {
case "safari":
// Safari is NOT web-compliant here!
header.Set("Sec-Fetch-Dest", "websocket")
default:
header.Set("Sec-Fetch-Dest", "empty")
}
header.Set("Sec-Fetch-Site", "same-origin")
if header.Get("Cache-Control") == "" {
header.Set("Cache-Control", "no-cache")
}
if header.Get("Pragma") == "" {
header.Set("Pragma", "no-cache")
}
if header.Get("Accept") == "" {
header.Set("Accept", "*/*")
}
case "fetch":
header.Set("Sec-Fetch-Mode", "cors")
header.Set("Sec-Fetch-Dest", "empty")
header.Set("Sec-Fetch-Site", "same-origin")
if header.Get("Priority") == "" {
switch browser {
case "chrome", "edge":
header.Set("Priority", "u=1, i")
case "firefox":
header.Set("Priority", "u=4")
case "safari":
header.Set("Priority", "u=3, i")
}
}
if header.Get("Cache-Control") == "" {
header.Set("Cache-Control", "no-cache")
}
if header.Get("Pragma") == "" {
header.Set("Pragma", "no-cache")
}
if header.Get("Accept") == "" {
header.Set("Accept", "*/*")
}
}
}
func TryDefaultHeadersWith(header http.Header, variant string) {
// The global UA special value handler for transports. Used to be called HandleTransportUASettings.
// Just a FYI to whoever needing to fix this piece of code after some spontaneous event, I tried to make the two methods separate to let the code be cleaner and more organized.
if len(header.Values("User-Agent")) < 1 {
applyMasqueradedHeaders(header, "chrome", variant)
} else {
switch header.Get("User-Agent") {
case "chrome":
applyMasqueradedHeaders(header, "chrome", variant)
case "firefox":
applyMasqueradedHeaders(header, "firefox", variant)
case "safari":
applyMasqueradedHeaders(header, "safari", variant)
case "edge":
applyMasqueradedHeaders(header, "edge", variant)
case "curl":
applyMasqueradedHeaders(header, "curl", variant)
case "golang":
applyMasqueradedHeaders(header, "golang", variant)
}
}
}

View File

@@ -29,6 +29,7 @@ const (
DNSTypeDHCP = "dhcp" DNSTypeDHCP = "dhcp"
DNSTypeTailscale = "tailscale" DNSTypeTailscale = "tailscale"
DNSTypeSDNS = "sdns" DNSTypeSDNS = "sdns"
DNSTypeFallback = "fallback"
) )
const ( const (

9
constant/manager_api.go Normal file
View File

@@ -0,0 +1,9 @@
package constant
const (
ManagerAPIServer = "server"
ManagerAPIClient = "client"
ManagerAPIProtocolHTTP = "http"
ManagerAPIProtocolGrpc = "grpc"
)

View File

@@ -0,0 +1,6 @@
package constant
const (
NodeManagerAPIServer = "server"
NodeManagerAPIClient = "client"
)

20
constant/provider.go Normal file
View File

@@ -0,0 +1,20 @@
package constant
const (
ProviderTypeInline = "inline"
ProviderTypeLocal = "local"
ProviderTypeRemote = "remote"
)
func ProviderDisplayName(providerType string) string {
switch providerType {
case ProviderTypeInline:
return "Inline"
case ProviderTypeLocal:
return "Local"
case ProviderTypeRemote:
return "Remote"
default:
return "Unknown"
}
}

View File

@@ -16,6 +16,9 @@ const (
TypeNaive = "naive" TypeNaive = "naive"
TypeWireGuard = "wireguard" TypeWireGuard = "wireguard"
TypeWARP = "warp" TypeWARP = "warp"
TypeMASQUE = "masque"
TypeMTProxy = "mtproxy"
TypeParser = "parser"
TypeHysteria = "hysteria" TypeHysteria = "hysteria"
TypeTor = "tor" TypeTor = "tor"
TypeSSH = "ssh" TypeSSH = "ssh"
@@ -27,15 +30,17 @@ const (
TypeTUIC = "tuic" TypeTUIC = "tuic"
TypeHysteria2 = "hysteria2" TypeHysteria2 = "hysteria2"
TypeBond = "bond" TypeBond = "bond"
TypeTunnelServer = "tunnel-server" TypeFailover = "failover"
TypeTunnelClient = "tunnel-client" TypeVPNServer = "vpn-server"
TypeVPNClient = "vpn-client"
TypeTailscale = "tailscale" TypeTailscale = "tailscale"
TypeConnectionLimiter = "connection-limiter" TypeConnectionLimiter = "connection-limiter"
TypeBandwidthLimiter = "bandwidth-limiter" TypeBandwidthLimiter = "bandwidth-limiter"
TypeTrafficLimiter = "traffic-limiter" TypeTrafficLimiter = "traffic-limiter"
TypeRateLimiter = "rate-limiter"
TypeAdminPanel = "admin-panel" TypeAdminPanel = "admin-panel"
TypeNodeManagerServer = "node-manager-server" TypeManagerAPI = "manager-api"
TypeNodeManagerClient = "node-manager-client" TypeNodeManagerAPI = "node-manager-api"
TypeDERP = "derp" TypeDERP = "derp"
TypeManager = "manager" TypeManager = "manager"
TypeNode = "node" TypeNode = "node"
@@ -44,10 +49,11 @@ const (
TypeCCM = "ccm" TypeCCM = "ccm"
TypeOCM = "ocm" TypeOCM = "ocm"
TypeOOMKiller = "oom-killer" TypeOOMKiller = "oom-killer"
TypeProfiler = "profiler"
) )
const ( const (
TypeFailover = "failover" TypeFallback = "fallback"
TypeSelector = "selector" TypeSelector = "selector"
TypeURLTest = "urltest" TypeURLTest = "urltest"
) )
@@ -84,6 +90,12 @@ func ProxyDisplayName(proxyType string) string {
return "WireGuard" return "WireGuard"
case TypeWARP: case TypeWARP:
return "WARP" return "WARP"
case TypeMASQUE:
return "MASQUE"
case TypeMTProxy:
return "MTProxy"
case TypeParser:
return "Parser"
case TypeHysteria: case TypeHysteria:
return "Hysteria" return "Hysteria"
case TypeTor: case TypeTor:
@@ -102,22 +114,34 @@ func ProxyDisplayName(proxyType string) string {
return "Hysteria2" return "Hysteria2"
case TypeBond: case TypeBond:
return "Bond" return "Bond"
case TypeFailover:
return "Failover"
case TypeMieru: case TypeMieru:
return "Mieru" return "Mieru"
case TypeAnyTLS: case TypeAnyTLS:
return "AnyTLS" return "AnyTLS"
case TypeFailover: case TypeFallback:
return "Failover" return "Fallback"
case TypeTailscale: case TypeTailscale:
return "Tailscale" return "Tailscale"
case TypeSelector: case TypeSelector:
return "Selector" return "Selector"
case TypeURLTest: case TypeURLTest:
return "URLTest" return "URLTest"
case TypeTunnelClient: case TypeConnectionLimiter:
return "Tunnel client" return "Connection Limiter"
case TypeTunnelServer: case TypeBandwidthLimiter:
return "Tunnel server" return "Bandwidth Limiter"
case TypeTrafficLimiter:
return "Traffic Limiter"
case TypeRateLimiter:
return "Rate Limiter"
case TypeVPNClient:
return "VPN Client"
case TypeVPNServer:
return "VPN Server"
case TypeProfiler:
return "Profiler"
default: default:
return "Unknown" return "Unknown"
} }

View File

@@ -1,20 +0,0 @@
package constant
type WARPConfig struct {
PrivateKey string `json:"private_key"`
Interface struct {
Addresses struct {
V4 string `json:"v4"`
V6 string `json:"v6"`
} `json:"addresses"`
} `json:"interface"`
Peers []struct {
PublicKey string `json:"public_key"`
Endpoint struct {
V4 string `json:"v4"`
V6 string `json:"v6"`
Host string `json:"host"`
Ports []int `json:"ports"`
} `json:"endpoint"`
} `json:"peers"`
}

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