Compare commits

...

154 commits

Author SHA1 Message Date
Toby
88890dde2d
Merge pull request #1370 from apernet/wip-bump-quicgo
feat: quic-go v0.52.0
2025-06-07 12:18:29 -07:00
Toby
b5ddcb5bc4 feat: quic-go v0.52.0 2025-06-07 11:59:30 -07:00
Toby
483fde51b1
Merge pull request #1362 from apernet/wip-noprintshare
fix(client): remove share uri in log
2025-06-01 11:01:35 -07:00
Haruue
3a9e952af0
fix(client): remove share uri in log
close: #1355

Since we already have the "share" subcommand, this feature is
unnecessary for the "client" subcommand.

This commit disables printing the share URI after the client starts, but
keeps this behavior for users who specified the `--qr` flag (who may
still rely on it) and shows a deprecation warning.
2025-05-27 18:27:54 +09:00
Toby
2adeec2900
Merge pull request #1348 from apernet/ci-update
chore: update go version to 1.24 in workflows
2025-04-20 14:33:58 -07:00
Toby
aa5f68a6f7 chore: update go version to 1.24 in workflows 2025-04-20 14:33:40 -07:00
Toby
b2567df63c
Merge pull request #1347 from apernet/bump-quic
feat: quic-go v0.51.0
2025-04-20 14:29:31 -07:00
Toby
16c7ebeeb6
Merge pull request #1346 from dropbigfish/master
chore: fix function name in comment
2025-04-20 14:29:10 -07:00
Toby
c2c4a9545e fix: broken cc after connection migration 2025-04-20 14:27:45 -07:00
Toby
29cd04fdef feat: quic-go v0.51.0 (wip) 2025-04-20 14:04:53 -07:00
dropbigfish
5239a23aee chore: fix function name in comment
Signed-off-by: dropbigfish <fillfish@foxmail.com>
2025-04-20 12:06:13 +08:00
Toby
245c6e9bd1
Merge pull request #1332 from apernet/add-license
chore: add LICENSE to packages
2025-03-18 20:45:32 -07:00
Toby
ffab01730a chore: add LICENSE to packages 2025-03-18 20:44:59 -07:00
Toby
401ed5245d
Merge pull request #1306 from apernet/wip-userpass-ignore-case
Make username of userpass case insensitive
2025-02-03 18:05:27 -08:00
Toby
9466bc4e2f
Merge pull request #1307 from apernet/bump-quic
feat: quic-go v0.49.0
2025-02-03 18:04:56 -08:00
Toby
e11ad2b93b feat: quic-go v0.49.0 2025-02-03 18:04:17 -08:00
Haruue
7652ddcd99
chore: unexport UserPassAuthenticator.Users 2025-02-03 12:39:52 +09:00
Haruue
e1df8aa4e2
chore: make username of userpass case insensitive
close: #1297

Just a workaround for "uppercase usernames do not work".

Usernames in different cases (like "Gawr" and "gawR") will now conflict.
2025-02-03 12:34:01 +09:00
Toby
8c05217590
Merge pull request #1293 from zyppe/master
Add support for loongarch64
2025-01-12 19:57:53 -08:00
Haruue
d86aa0b4e2
chore(scripts): detect arch for loong64 2025-01-08 14:57:02 +09:00
Haruue
537e8144ea
ci: add linux/loong64 to platforms.txt 2025-01-08 14:56:15 +09:00
zyppe
817d6c9a2d
Add support for loongarch64
Test on Loongson 3A5000
2025-01-04 22:45:11 +08:00
Toby
5520bcc405
Merge pull request #1287 from apernet/fix-tun-ipv6-disable
fix: tun failed on linux when ipv6.disable=1
2025-01-03 20:50:10 -08:00
Toby
9e90d7d155
Merge pull request #1288 from apernet/wip-masq-insecure-upstream
feat: allow skip cert verify in masquerade.proxy
2024-12-29 11:25:38 -08:00
Toby
8aa80c233e fix: rename insecureSkipVerify to insecure for consistency 2024-12-29 11:25:08 -08:00
Haruue
2bdaf7b46a
feat: allow skip cert verify in masquerade.proxy
close: #1278

masquerade.proxy.insecureSkipVerify
2024-12-29 13:58:12 +09:00
Haruue
53a4ce2598
fix: tun failed on linux when ipv6.disable=1
close: #1285
2024-12-29 13:33:32 +09:00
Toby
cd396eea60
Merge pull request #1272 from apernet/wip-rename-libversion
chore(version): rename LibVersion to Libraries
2024-12-12 18:40:49 -08:00
Haruue
400fed3bd6
chore(version): rename LibVersion to Libraries
close: #1271

A key that also contains "Version" broke the version parsing of some
third-party clients.
2024-12-11 18:08:54 +09:00
Toby
6655d2a78d
Merge pull request #1258 from apernet/wip-server-fastopen
feat(server): tcp fast open on direct outbounds
2024-12-10 23:03:52 -08:00
Toby
5e11ea18fb chore: update core/go.mod 2024-12-10 22:42:25 -08:00
Haruue
d8c61c59d7
chore: disable fallback mode of tfo dialer
tfo-go caches the "unsupported" status when fallback mode is enabled.
In other words, if the hysteria server is started with
net.ipv4.tcp_fastopen=0 and it fails once, the tfo will not be enabled
until it is restarted, even if the user later sets sysctl
net.ipv4.tcp_fastopen=3.
2024-11-23 22:31:14 +09:00
Haruue
16c964b3e1
feat(server): tcp fast open on direct outbounds 2024-11-23 21:37:18 +09:00
Toby
15e31d48a0
Merge pull request #1247 from apernet/wip-dumpstream
feat: add /dump/streams as a traffic stats API
2024-11-08 17:31:35 -08:00
Toby
3e8c20518d chore: minor code tweaks 2024-11-08 14:29:50 -08:00
Toby
9cb8cb4f53 Merge branch 'master' into wip-dumpstream 2024-11-08 14:15:11 -08:00
Haruue
7ac8d87dda
test: fix integration_tests for trafficlogger 2024-11-08 16:03:48 +09:00
Haruue
0681638568
feat(trafficlogger): dump streams stats 2024-11-08 15:28:50 +09:00
Toby
c34f23755a
Merge pull request #1244 from apernet/better-version
feat: add toolchain & quic-go to version info
2024-11-04 21:28:53 -08:00
Toby
a52b02ba2b
Merge pull request #1243 from apernet/bump-quic
feat: quic-go v0.48.1
2024-11-04 21:27:32 -08:00
Haruue
d4a1c2b580
fix(scripts): extra line in installed version
Checking for installed version ... v2.5.2
v0.47.1
Checking for latest version ... v2.5.2
2024-11-05 10:06:43 +09:00
Toby
685cd3663b feat: add toolchain & quic-go to version info 2024-11-04 12:01:00 -08:00
Toby
04cf6f2e1a feat: quic-go v0.48.1 2024-11-04 11:32:58 -08:00
Toby
a2c7b8fd19
Merge pull request #1242 from apernet/fix-portunion-65535
fix: infinite loop in PortUnion.Ports() when port range contains 65535
2024-11-04 10:38:08 -08:00
Haruue
9a21e2e8c6
chore: a better fix to portunion 2024-11-05 01:30:44 +09:00
Haruue
a9422e63be
test: add ut for PortUnion.Ports() 2024-11-05 00:51:14 +09:00
Haruue
d65997c02b
fix: inf loop in PortUnion.Ports() when end=65535
fix: #1240

Any uint16 value is less than or equal to 65535.
2024-11-05 00:47:02 +09:00
Toby
78598bfd1b
Merge pull request #1229 from apernet/wip-share
feat: share subcommand
2024-10-19 11:43:55 -07:00
Toby
4567713ed8
Merge pull request #1228 from apernet/wip-masq-proxy-url-scheme
fix: check the URL scheme of `masquerade.proxy.url` in server config
2024-10-19 09:49:32 -07:00
Haruue Icymoon (usamimi-wsl)
99e959f8c9 feat: share subcommand
Useful for third-party scripts/clients that just want to generate the
sharing URI without starting the client.
2024-10-19 17:24:52 +08:00
Haruue Icymoon (usamimi-wsl)
af2d75d1d0 fix: check masq url scheme in server cfg parsing
Check the url scheme of masquerade.proxy.url when parsing server config
and fail fast if it is not "http" or "https".

ref: #1227

The user assigned the URL with a naked hostname and got errors until the
request was handled.
2024-10-19 16:27:16 +08:00
Toby
b960beabbd
Merge pull request #1216 from apernet/bump-quic
feat: quic-go v0.47.0
2024-10-04 22:33:56 -07:00
Toby
ecc95fb973
Merge pull request #1206 from apernet/fix-quic-sniff
fix: quic sniff not work if udp msg fragmentated
2024-10-04 19:33:00 -07:00
Haruue
1001b2b1ad
chore: fix comments 2024-10-05 10:23:43 +08:00
Toby
ef6da94927 feat: quic-go v0.47.0 2024-10-04 11:21:30 -07:00
Toby
b3116c6268 feat: update TestUDPSessionManager to cover the fragmented msg hook 2024-10-04 10:47:41 -07:00
Toby
947701897b fix: TestClientServerHookUDP 2024-10-04 10:29:25 -07:00
Haruue
4e2f138008
chore: fix comments 2024-10-04 16:57:32 +08:00
Haruue
dc023ae13a
fix: udpSessionManager.mutex reentrant by cleanup 2024-10-04 16:33:41 +08:00
Haruue
931fc2fdb2
chore: replace guard routine with CloseWithErr() 2024-10-04 11:27:36 +08:00
Haruue
4ecbd57294
fix: quic sniff not work if udp msg fragmentated 2024-09-22 22:53:47 +08:00
Toby
21ea2a024a
Merge pull request #1191 from apernet/wip-sni-guard
feat: local cert loader & sni guard
2024-08-25 10:30:17 -07:00
Haruue
d4b9c5a822
test: add requirements.txt for ut scripts 2024-08-25 13:36:45 +08:00
Toby
4ed3f21d72 fix: crash when the tls option is not used & change from python3 to python 2024-08-24 17:07:45 -07:00
Haruue
667b08ec3e
test: add tests for certloader 2024-08-24 17:31:52 +08:00
Haruue
bcf830c29a
chore: only init cert.Leaf when not populated
since Go 1.23, cert.Leaf will be populated after loaded.

see doc of tls.LoadX509KeyPair for details
2024-08-24 13:46:25 +08:00
Haruue
45893b5d1e
test: update server_test for sniGuard 2024-08-24 13:40:42 +08:00
Haruue
57a48a674b
chore: replace rwlock with atomic pointer 2024-08-24 10:37:08 +08:00
Haruue
fd2d20a46a
feat: local cert loader & sni guard 2024-08-24 00:27:57 +08:00
Haruue
903666f18e
Merge pull request #1188 from apernet/fix-scripts-selinux-detect
fix(scripts): detect selinux with getenforce
2024-08-21 18:27:25 +08:00
Haruue
00df1cab0f
fix(scripts): detect selinux with getenforce
chcon is widely available in coreutils, even if the system doesn't
support selinux.
2024-08-21 18:18:41 +08:00
Toby
4c04660684
Merge pull request #1184 from apernet/bump-quic
feat: quic-go v0.46.0
2024-08-16 21:06:49 -07:00
Toby
f2712aac93
Merge pull request #1183 from apernet/fix-http-sniff
fix: sniffing handled HTTP host header incorrectly
2024-08-16 20:52:08 -07:00
Toby
55c3a064cc fix: never overwrite the port 2024-08-16 20:48:14 -07:00
Toby
7e70547dbd feat: quic-go v0.46.0 2024-08-16 16:16:05 -07:00
Toby
f014c00546 fix: add a test case 2024-08-16 15:51:42 -07:00
Toby
48bf9b964a fix: sniffing handled HTTP host header incorrectly 2024-08-16 15:46:30 -07:00
Toby
442ee3898c
Merge pull request #1176 from apernet/fix-test-reqhook
fix(test): signature mismatch of udpIO.Hook
2024-08-04 15:05:05 -07:00
Toby
d527ff13b5
Merge pull request #1175 from apernet/bump-quic
feat: quic-go v0.45.2
2024-08-04 10:10:05 -07:00
Haruue
604132f8d0
fix(test): signature mismatch of udpIO.Hook
ref: #1125
2024-08-04 14:38:36 +08:00
Toby
c62c8c5513 feat: quic-go v0.45.2 2024-08-03 13:14:34 -07:00
Toby
b563f3981f
Merge pull request #1144 from yiguous/patch-1
fix escaped auth
2024-07-10 13:16:43 -07:00
yiguous
a7ecd08046
fix escaped auth 2024-07-05 18:34:54 +08:00
Toby
458ee1386c
Merge pull request #1141 from apernet/bump-quic
feat: quic-go v0.45.1
2024-07-02 15:25:52 -07:00
Toby
8d9c7fa04c feat: quic-go v0.45.1 2024-07-02 15:24:48 -07:00
Toby
0ce3df4396
Merge pull request #1134 from apernet/wip-sniff
feat: server-side sniffing for HTTP/TLS/QUIC
2024-06-30 21:16:23 -07:00
Toby
5315b60610
Merge pull request #1136 from apernet/wip-speedtest-grace
feat: graceful speed test shutdown
2024-06-30 20:50:06 -07:00
Toby
6a90fe18ee feat: graceful speed test shutdown 2024-06-30 20:16:55 -07:00
Toby
deeeafd8d7 feat: allow specifying port ranges for sniffing 2024-06-30 12:04:59 -07:00
Toby
b481b49a28 chore: import format fix 2024-06-29 17:46:04 -07:00
Toby
7b4def4c35 chore: add sniff test cases 2024-06-29 17:42:30 -07:00
Toby
3412368d20 feat: app sniff options 2024-06-29 16:27:57 -07:00
Toby
16bfdc7720 feat: QUIC sniffing 2024-06-29 15:52:56 -07:00
Toby
8aab735029 feat: experimental HTTP/TLS sniffing implementation (no QUIC yet) 2024-06-29 13:40:52 -07:00
Toby
988b86ae55
Merge pull request #1125 from apernet/wip-reqhook
feat(core): server RequestHook support
2024-06-18 22:00:06 -07:00
Toby
c78dbb38a1 feat: add a Check method to let the implementation decide whether to hook a request 2024-06-18 21:46:25 -07:00
Toby
2c62a1a1b4 fix: do not require client-side fast open 2024-06-16 13:26:02 -07:00
Toby
506d8e01b8 Merge branch 'master' into wip-reqhook 2024-06-16 13:10:23 -07:00
Toby
c5e7aa3f02
Merge pull request #1126 from apernet/wip-fix-formatspeed
fix: incorrect speed conversion base
2024-06-15 19:36:58 -07:00
Toby
a852febc1f fix: incorrect speed conversion base 2024-06-15 15:42:39 -07:00
Toby
feacb1f85e feat(core): server RequestHook support 2024-06-15 14:15:56 -07:00
Toby
4c2a905892
Merge pull request #1102 from mritd/master
feat(acme): add acme dns-01 challenge support
2024-06-11 13:07:23 -07:00
Toby
d318903783 chore: go mod tidy 2024-06-10 16:30:22 -07:00
Toby
18d075cc07 feat: rework acme config format 2024-06-10 16:28:21 -07:00
Toby
bc0e18980b Merge branch 'master' into acme2 2024-06-10 15:20:57 -07:00
Toby
52c8f82c2b
Merge pull request #1110 from apernet/fix-http-connect-header
fix(client/http): ffmpeg not works with proxy
2024-05-30 12:04:01 -07:00
Haruue
23b79688fb
chore(client/http): rm "Connection: close" header
Magic of undocumented features.
2024-05-30 23:15:13 +08:00
Haruue
e1ac7c88ab
fix(client/http): ffmpeg not works with proxy
Go's resp.Write() adds a "Content-Length: 0" header and it seems that
ffmpeg doesn't like this and immediately closes the proxy connection.

close: #1109
2024-05-30 22:55:39 +08:00
Toby
492145c124
Merge pull request #1105 from apernet/wip-rbq
fix: BBR stuck in STARTUP phase due to incorrect app-limited logic
2024-05-28 21:54:06 -07:00
Haruue
8fca92a319
Merge pull request #1106 from apernet/fix-hyperbole-cmdpkg
fix(hyperbole): missing v2 in cmdpkg
2024-05-29 11:31:24 +08:00
Haruue
10234e5daf
fix(hyperbole): missing v2 in cmdpkg 2024-05-29 11:28:33 +08:00
kovacs
3c22e5967f
fix(acme): fix config name
fix config name

Signed-off-by: kovacs <mritd@linux.com>
2024-05-27 12:45:50 +08:00
kovacs
3024fc079c
feat(acme): add dns provider
add dns provider

Signed-off-by: kovacs <mritd@linux.com>
2024-05-27 11:43:31 +08:00
Toby
146d077124
Merge pull request #1100 from apernet/fix-systemd-wd-centos7
fix(scripts): WorkingDirectory on CentOS 7
2024-05-25 11:11:05 -07:00
Haruue
9e9b4dbc7d
feat(scripts): change HYSTERIA_USER w/o --force 2024-05-25 17:13:22 +08:00
Haruue
788d04cfdd
fix(scripts): WorkingDirectory on CentOS 7
WorkingDirectory=~ requires systemd v227 or later, which is released on
Oct 2015, only CentOS 7 use an earlier version actually.

ref: systemd/systemd@5f5d8eab1f
2024-05-25 14:27:58 +08:00
Toby
12d4fbae80 Merge branch 'master' into wip-rbq 2024-05-23 18:51:57 -07:00
Toby
44f4ddacfe
Merge pull request #1093 from apernet/wip-bump-quic
feat: quic-go v0.44.0
2024-05-22 18:47:10 -07:00
Toby
adee547c21 feat: quic-go v0.44.0 2024-05-20 15:20:31 -07:00
Toby
09b08fa494 fix: try to fix maybeAppLimited 2 2024-05-20 14:19:04 -07:00
Toby
cd512ce1c6 chore: various tweaks 2024-05-19 11:46:52 -07:00
Toby
5b0ab76d44 Merge branch 'master' into wip-rbq 2024-05-18 15:59:56 -07:00
Toby
396dd0a68c
Merge pull request #1089 from apernet/fix-mod-name
fix: mod name major version suffix v2
2024-05-18 15:59:10 -07:00
Toby
e0e75c4630 wip: BBR experimental changes 2024-05-18 15:01:16 -07:00
Haruue
1742f83b8e
ci: create release tags for core/ and extras/ 2024-05-18 17:14:15 +08:00
Haruue
0c198abd2e
fix: mod name major version suffix v2
ref: https://go.dev/ref/mod#major-version-suffixes
2024-05-18 11:28:47 +08:00
Toby
15e58468a7
Merge pull request #1088 from apernet/wip-shutdown
feat: graceful client shutdown
2024-05-17 19:50:14 -07:00
Toby
b216c4f128 feat: graceful client shutdown 2024-05-17 18:02:58 -07:00
Toby
4c0bd74094
Merge pull request #1087 from apernet/fix-memleak
fix: quic-go memory leak
2024-05-17 17:12:19 -07:00
Toby
2701a6e23f fix: quic-go memory leak 2024-05-17 17:10:59 -07:00
Toby
a3c4cfa4b5
Merge pull request #1075 from HynoR/feat/online
feat: Add getOnline feature
2024-05-11 14:16:32 -07:00
Toby
9d4b3e608a chore: small changes to TrafficLogger function names & update all mocks to mockery v2.43.0 2024-05-11 13:55:55 -07:00
Haruue
6a34a9e7a0
test: fix unit tests
mock files regenerated with mockery v2.43.0
2024-05-11 15:08:44 +08:00
Haruue
ba9b3cdebb
refactor(online): track count instead of raddr 2024-05-11 11:23:20 +08:00
HynoR
88eef7617f refactor getOnline feature 2024-05-09 14:05:45 +08:00
HynoR
2366882bd6 add getOnline feature 2024-05-09 10:10:20 +08:00
HynoR
415ef42b5a add getOnline feature 2024-05-09 09:42:14 +08:00
Toby
c831b987cd
Merge pull request #1067 from apernet/fix-udp
fix: update in quic-go (http3) broke UDP functionality
2024-04-28 20:32:37 -07:00
Toby
b79c43171a fix: update in quic-go (http3) broke UDP functionality 2024-04-28 20:22:11 -07:00
Toby
d2805577ff
Merge pull request #1066 from apernet/ci-go122
ci: update to go 1.22
2024-04-27 21:00:43 -07:00
Toby
8412ec3ab3 ci: update to go 1.22 2024-04-27 21:00:21 -07:00
Toby
59f16d0792
Merge pull request #1064 from apernet/bump-quic-go
feat: quic-go v0.43.0
2024-04-27 20:50:35 -07:00
Toby
00813c4622 feat: quic-go v0.43.0 2024-04-27 13:04:51 -07:00
Haruue
b8b8122ecf
Merge pull request #1062 from apernet/fix-scripts-selinux
fix(scripts): chcon error on CentOS 7
2024-04-26 15:01:02 +08:00
Haruue
e7d7dbbf8f
fix(scripts): chcon error on CentOS 7 2024-04-26 14:52:34 +08:00
Toby
f586d513bc
Merge pull request #1050 from apernet/bump-xnet
chore(deps): bump golang.org/x/net from 0.21.0 to 0.24.0
2024-04-19 14:42:38 -07:00
Toby
c392b0338b chore(deps): bump golang.org/x/net from 0.21.0 to 0.24.0 2024-04-19 14:42:06 -07:00
Toby
3409904294
Merge pull request #1042 from apernet/sync-readme
chore: sync README
2024-04-15 15:06:55 -07:00
Toby
1b78b2ec90 chore: sync README 2024-04-15 15:06:16 -07:00
Toby
bf1cc0847e
Merge pull request #1041 from apernet/fix-cert-check
fix: check if cert-key is loadable on server start
2024-04-15 14:58:32 -07:00
Toby
dc1f58414a chore: improve comments 2024-04-15 14:58:09 -07:00
Toby
2fcbde08d8
Merge pull request #1038 from apernet/wip-pacer
feat: pacer code improvements
2024-04-15 14:47:53 -07:00
Haruue
9752347073
fix: check if cert-key is loadable on server start
close: #1040
2024-04-15 19:31:23 +08:00
Toby
2408301c98 feat: pacer code improvements 2024-04-14 15:07:43 -07:00
135 changed files with 5090 additions and 785 deletions

104
.github/workflows/autotag.yaml vendored Normal file
View file

@ -0,0 +1,104 @@
name: "Create release tags for nested modules"
on:
push:
tags:
- app/v*.*.*
permissions:
contents: write
jobs:
tag:
name: "Create tags"
runs-on: ubuntu-latest
steps:
- name: "Extract tagbase"
id: extract_tagbase
uses: actions/github-script@v7
with:
script: |
const ref = context.ref;
core.info(`context.ref: ${ref}`);
const refPrefix = 'refs/tags/app/';
if (!ref.startsWith(refPrefix)) {
core.setFailed(`context.ref does not start with ${refPrefix}: ${ref}`);
return;
}
const tagbase = ref.slice(refPrefix.length);
core.info(`tagbase: ${tagbase}`);
core.setOutput('tagbase', tagbase);
- name: "Tagging core/*"
uses: actions/github-script@v7
env:
INPUT_TAGPREFIX: "core/"
INPUT_TAGBASE: ${{ steps.extract_tagbase.outputs.tagbase }}
with:
script: |
const tagbase = core.getInput('tagbase', { required: true });
const tagprefix = core.getInput('tagprefix', { required: true });
const refname = `tags/${tagprefix}${tagbase}`;
core.info(`creating ref ${refname}`);
try {
await github.rest.git.createRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `refs/${refname}`,
sha: context.sha
});
core.info(`created ref ${refname}`);
return;
} catch (error) {
core.info(`failed to create ref ${refname}: ${error}`);
}
core.info(`updating ref ${refname}`)
try {
await github.rest.git.updateRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: refname,
sha: context.sha
});
core.info(`updated ref ${refname}`);
return;
} catch (error) {
core.setFailed(`failed to update ref ${refname}: ${error}`);
}
- name: "Tagging extras/*"
uses: actions/github-script@v7
env:
INPUT_TAGPREFIX: "extras/"
INPUT_TAGBASE: ${{ steps.extract_tagbase.outputs.tagbase }}
with:
script: |
const tagbase = core.getInput('tagbase', { required: true });
const tagprefix = core.getInput('tagprefix', { required: true });
const refname = `tags/${tagprefix}${tagbase}`;
core.info(`creating ref ${refname}`);
try {
await github.rest.git.createRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `refs/${refname}`,
sha: context.sha
});
core.info(`created ref ${refname}`);
return;
} catch (error) {
core.info(`failed to create ref ${refname}: ${error}`);
}
core.info(`updating ref ${refname}`)
try {
await github.rest.git.updateRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: refname,
sha: context.sha
});
core.info(`updated ref ${refname}`);
return;
} catch (error) {
core.setFailed(`failed to update ref ${refname}: ${error}`);
}

View file

@ -19,7 +19,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: "1.21"
go-version: "1.24"
- name: Setup Python # This is for the build script
uses: actions/setup-python@v5

View file

@ -23,7 +23,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: "1.21"
go-version: "1.24"
- name: Setup Python # This is for the build script
uses: actions/setup-python@v5

View file

@ -48,8 +48,8 @@
</div>
<div>
<h3>🤗 Cards on the table</h3>
<p>We have well-documented specifications and code for developers to contribute and build their own apps. And a helpful community, too.</p>
<h3>🤗 Chill and supportive</h3>
<p>We have well-documented specifications and code for developers to contribute and/or build their own apps. And a helpful community, too.</p>
</div>
</div>

7
app/LICENSE.md Normal file
View file

@ -0,0 +1,7 @@
Copyright 2023 Toby
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -9,30 +9,32 @@ import (
"net"
"net/netip"
"os"
"os/signal"
"runtime"
"slices"
"strconv"
"strings"
"syscall"
"time"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"go.uber.org/zap"
"github.com/apernet/hysteria/app/internal/forwarding"
"github.com/apernet/hysteria/app/internal/http"
"github.com/apernet/hysteria/app/internal/proxymux"
"github.com/apernet/hysteria/app/internal/redirect"
"github.com/apernet/hysteria/app/internal/sockopts"
"github.com/apernet/hysteria/app/internal/socks5"
"github.com/apernet/hysteria/app/internal/tproxy"
"github.com/apernet/hysteria/app/internal/tun"
"github.com/apernet/hysteria/app/internal/url"
"github.com/apernet/hysteria/app/internal/utils"
"github.com/apernet/hysteria/core/client"
"github.com/apernet/hysteria/extras/correctnet"
"github.com/apernet/hysteria/extras/obfs"
"github.com/apernet/hysteria/extras/transport/udphop"
"github.com/apernet/hysteria/app/v2/internal/forwarding"
"github.com/apernet/hysteria/app/v2/internal/http"
"github.com/apernet/hysteria/app/v2/internal/proxymux"
"github.com/apernet/hysteria/app/v2/internal/redirect"
"github.com/apernet/hysteria/app/v2/internal/sockopts"
"github.com/apernet/hysteria/app/v2/internal/socks5"
"github.com/apernet/hysteria/app/v2/internal/tproxy"
"github.com/apernet/hysteria/app/v2/internal/tun"
"github.com/apernet/hysteria/app/v2/internal/url"
"github.com/apernet/hysteria/app/v2/internal/utils"
"github.com/apernet/hysteria/core/v2/client"
"github.com/apernet/hysteria/extras/v2/correctnet"
"github.com/apernet/hysteria/extras/v2/obfs"
"github.com/apernet/hysteria/extras/v2/transport/udphop"
)
// Client flags
@ -391,7 +393,11 @@ func (c *clientConfig) parseURI() bool {
return false
}
if u.User != nil {
c.Auth = u.User.String()
auth, err := url.QueryUnescape(u.User.String())
if err != nil {
return false
}
c.Auth = auth
}
c.Server = u.Host
q := u.Query()
@ -464,8 +470,10 @@ func runClient(cmd *cobra.Command, args []string) {
defer c.Close()
uri := config.URI()
logger.Info("use this URI to share your server", zap.String("uri", uri))
if showQR {
logger.Warn("--qr flag is deprecated and will be removed in future release, " +
"please use `share` subcommand to generate share URI and QR code")
logger.Info("use this URI to share your server", zap.String("uri", uri))
utils.PrintQR(uri)
}
@ -512,13 +520,42 @@ func runClient(cmd *cobra.Command, args []string) {
})
}
runner.Run()
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
defer signal.Stop(signalChan)
runnerChan := make(chan clientModeRunnerResult, 1)
go func() {
runnerChan <- runner.Run()
}()
select {
case <-signalChan:
logger.Info("received signal, shutting down gracefully")
case r := <-runnerChan:
if r.OK {
logger.Info(r.Msg)
} else {
_ = c.Close() // Close the client here as Fatal will exit the program without running defer
if r.Err != nil {
logger.Fatal(r.Msg, zap.Error(r.Err))
} else {
logger.Fatal(r.Msg)
}
}
}
}
type clientModeRunner struct {
ModeMap map[string]func() error
}
type clientModeRunnerResult struct {
OK bool
Msg string
Err error
}
func (r *clientModeRunner) Add(name string, f func() error) {
if r.ModeMap == nil {
r.ModeMap = make(map[string]func() error)
@ -526,9 +563,9 @@ func (r *clientModeRunner) Add(name string, f func() error) {
r.ModeMap[name] = f
}
func (r *clientModeRunner) Run() {
func (r *clientModeRunner) Run() clientModeRunnerResult {
if len(r.ModeMap) == 0 {
logger.Fatal("no mode specified")
return clientModeRunnerResult{OK: false, Msg: "no mode specified"}
}
type modeError struct {
@ -546,9 +583,13 @@ func (r *clientModeRunner) Run() {
for i := 0; i < len(r.ModeMap); i++ {
e := <-errChan
if e.Err != nil {
logger.Fatal("failed to run "+e.Name, zap.Error(e.Err))
return clientModeRunnerResult{OK: false, Msg: "failed to run " + e.Name, Err: e.Err}
}
}
// We don't really have any such cases, as currently none of our modes would stop on themselves without error.
// But we leave the possibility here for future expansion.
return clientModeRunnerResult{OK: true, Msg: "finished without error"}
}
func clientSOCKS5(config socks5Config, c client.Client) error {

View file

@ -7,7 +7,7 @@ import (
"github.com/spf13/viper"
"go.uber.org/zap"
"github.com/apernet/hysteria/core/client"
"github.com/apernet/hysteria/core/v2/client"
)
// pingCmd represents the ping command

View file

@ -32,17 +32,21 @@ var (
appVersion = "Unknown"
appDate = "Unknown"
appType = "Unknown" // aka channel
appToolchain = "Unknown"
appCommit = "Unknown"
appPlatform = "Unknown"
appArch = "Unknown"
libVersion = "Unknown"
appVersionLong = fmt.Sprintf("Version:\t%s\n"+
"BuildDate:\t%s\n"+
"BuildType:\t%s\n"+
"Toolchain:\t%s\n"+
"CommitHash:\t%s\n"+
"Platform:\t%s\n"+
"Architecture:\t%s",
appVersion, appDate, appType, appCommit, appPlatform, appArch)
"Architecture:\t%s\n"+
"Libraries:\tquic-go=%s",
appVersion, appDate, appType, appToolchain, appCommit, appPlatform, appArch, libVersion)
appAboutLong = fmt.Sprintf("%s\n%s\n%s\n\n%s", appLogo, appDesc, appAuthors, appVersionLong)
)

View file

@ -10,24 +10,33 @@ import (
"net/http"
"net/http/httputil"
"net/url"
"os"
"strconv"
"strings"
"time"
"github.com/caddyserver/certmagic"
"github.com/libdns/cloudflare"
"github.com/libdns/duckdns"
"github.com/libdns/gandi"
"github.com/libdns/godaddy"
"github.com/libdns/namedotcom"
"github.com/libdns/vultr"
"github.com/mholt/acmez/acme"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"go.uber.org/zap"
"github.com/apernet/hysteria/app/internal/utils"
"github.com/apernet/hysteria/core/server"
"github.com/apernet/hysteria/extras/auth"
"github.com/apernet/hysteria/extras/correctnet"
"github.com/apernet/hysteria/extras/masq"
"github.com/apernet/hysteria/extras/obfs"
"github.com/apernet/hysteria/extras/outbounds"
"github.com/apernet/hysteria/extras/trafficlogger"
"github.com/apernet/hysteria/app/v2/internal/utils"
"github.com/apernet/hysteria/core/v2/server"
"github.com/apernet/hysteria/extras/v2/auth"
"github.com/apernet/hysteria/extras/v2/correctnet"
"github.com/apernet/hysteria/extras/v2/masq"
"github.com/apernet/hysteria/extras/v2/obfs"
"github.com/apernet/hysteria/extras/v2/outbounds"
"github.com/apernet/hysteria/extras/v2/sniff"
"github.com/apernet/hysteria/extras/v2/trafficlogger"
eUtils "github.com/apernet/hysteria/extras/v2/utils"
)
const (
@ -57,6 +66,7 @@ type serverConfig struct {
UDPIdleTimeout time.Duration `mapstructure:"udpIdleTimeout"`
Auth serverConfigAuth `mapstructure:"auth"`
Resolver serverConfigResolver `mapstructure:"resolver"`
Sniff serverConfigSniff `mapstructure:"sniff"`
ACL serverConfigACL `mapstructure:"acl"`
Outbounds []serverConfigOutboundEntry `mapstructure:"outbounds"`
TrafficStats serverConfigTrafficStats `mapstructure:"trafficStats"`
@ -75,18 +85,42 @@ type serverConfigObfs struct {
type serverConfigTLS struct {
Cert string `mapstructure:"cert"`
Key string `mapstructure:"key"`
SNIGuard string `mapstructure:"sniGuard"` // "disable", "dns-san", "strict"
}
type serverConfigACME struct {
// Common fields
Domains []string `mapstructure:"domains"`
Email string `mapstructure:"email"`
CA string `mapstructure:"ca"`
ListenHost string `mapstructure:"listenHost"`
Dir string `mapstructure:"dir"`
// Type selection
Type string `mapstructure:"type"`
HTTP serverConfigACMEHTTP `mapstructure:"http"`
TLS serverConfigACMETLS `mapstructure:"tls"`
DNS serverConfigACMEDNS `mapstructure:"dns"`
// Legacy fields for backwards compatibility
// Only applicable when Type is empty
DisableHTTP bool `mapstructure:"disableHTTP"`
DisableTLSALPN bool `mapstructure:"disableTLSALPN"`
ListenHost string `mapstructure:"listenHost"`
AltHTTPPort int `mapstructure:"altHTTPPort"`
AltTLSALPNPort int `mapstructure:"altTLSALPNPort"`
Dir string `mapstructure:"dir"`
}
type serverConfigACMEHTTP struct {
AltPort int `mapstructure:"altPort"`
}
type serverConfigACMETLS struct {
AltPort int `mapstructure:"altPort"`
}
type serverConfigACMEDNS struct {
Name string `mapstructure:"name"`
Config map[string]string `mapstructure:"config"`
}
type serverConfigQUIC struct {
@ -149,6 +183,14 @@ type serverConfigResolver struct {
HTTPS serverConfigResolverHTTPS `mapstructure:"https"`
}
type serverConfigSniff struct {
Enable bool `mapstructure:"enable"`
Timeout time.Duration `mapstructure:"timeout"`
RewriteDomain bool `mapstructure:"rewriteDomain"`
TCPPorts string `mapstructure:"tcpPorts"`
UDPPorts string `mapstructure:"udpPorts"`
}
type serverConfigACL struct {
File string `mapstructure:"file"`
Inline []string `mapstructure:"inline"`
@ -162,6 +204,7 @@ type serverConfigOutboundDirect struct {
BindIPv4 string `mapstructure:"bindIPv4"`
BindIPv6 string `mapstructure:"bindIPv6"`
BindDevice string `mapstructure:"bindDevice"`
FastOpen bool `mapstructure:"fastOpen"`
}
type serverConfigOutboundSOCKS5 struct {
@ -195,6 +238,7 @@ type serverConfigMasqueradeFile struct {
type serverConfigMasqueradeProxy struct {
URL string `mapstructure:"url"`
RewriteHost bool `mapstructure:"rewriteHost"`
Insecure bool `mapstructure:"insecure"`
}
type serverConfigMasqueradeString struct {
@ -250,16 +294,45 @@ func (c *serverConfig) fillTLSConfig(hyConfig *server.Config) error {
return configError{Field: "tls", Err: errors.New("cannot set both tls and acme")}
}
if c.TLS != nil {
// SNI guard
var sniGuard utils.SNIGuardFunc
switch strings.ToLower(c.TLS.SNIGuard) {
case "", "dns-san":
sniGuard = utils.SNIGuardDNSSAN
case "strict":
sniGuard = utils.SNIGuardStrict
case "disable":
sniGuard = nil
default:
return configError{Field: "tls.sniGuard", Err: errors.New("unsupported SNI guard")}
}
// Local TLS cert
if c.TLS.Cert == "" || c.TLS.Key == "" {
return configError{Field: "tls", Err: errors.New("empty cert or key path")}
}
certLoader := &utils.LocalCertificateLoader{
CertFile: c.TLS.Cert,
KeyFile: c.TLS.Key,
SNIGuard: sniGuard,
}
// Try loading the cert-key pair here to catch errors early
// (e.g. invalid files or insufficient permissions)
err := certLoader.InitializeCache()
if err != nil {
var pathErr *os.PathError
if errors.As(err, &pathErr) {
if pathErr.Path == c.TLS.Cert {
return configError{Field: "tls.cert", Err: pathErr}
}
if pathErr.Path == c.TLS.Key {
return configError{Field: "tls.key", Err: pathErr}
}
}
return configError{Field: "tls", Err: err}
}
// Use GetCertificate instead of Certificates so that
// users can update the cert without restarting the server.
hyConfig.TLSConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
cert, err := tls.LoadX509KeyPair(c.TLS.Cert, c.TLS.Key)
return &cert, err
}
hyConfig.TLSConfig.GetCertificate = certLoader.GetCertificate
} else {
// ACME
dataDir := c.ACME.Dir
@ -279,11 +352,7 @@ func (c *serverConfig) fillTLSConfig(hyConfig *server.Config) error {
cmIssuer := certmagic.NewACMEIssuer(cmCfg, certmagic.ACMEIssuer{
Email: c.ACME.Email,
Agreed: true,
DisableHTTPChallenge: c.ACME.DisableHTTP,
DisableTLSALPNChallenge: c.ACME.DisableTLSALPN,
ListenHost: c.ACME.ListenHost,
AltHTTPPort: c.ACME.AltHTTPPort,
AltTLSALPNPort: c.ACME.AltTLSALPNPort,
Logger: logger,
})
switch strings.ToLower(c.ACME.CA) {
@ -298,8 +367,82 @@ func (c *serverConfig) fillTLSConfig(hyConfig *server.Config) error {
}
cmIssuer.ExternalAccount = eab
default:
return configError{Field: "acme.ca", Err: errors.New("unknown CA")}
return configError{Field: "acme.ca", Err: errors.New("unsupported CA")}
}
switch strings.ToLower(c.ACME.Type) {
case "http":
cmIssuer.DisableHTTPChallenge = false
cmIssuer.DisableTLSALPNChallenge = true
cmIssuer.DNS01Solver = nil
cmIssuer.AltHTTPPort = c.ACME.HTTP.AltPort
case "tls":
cmIssuer.DisableHTTPChallenge = true
cmIssuer.DisableTLSALPNChallenge = false
cmIssuer.DNS01Solver = nil
cmIssuer.AltTLSALPNPort = c.ACME.TLS.AltPort
case "dns":
cmIssuer.DisableHTTPChallenge = true
cmIssuer.DisableTLSALPNChallenge = true
if c.ACME.DNS.Name == "" {
return configError{Field: "acme.dns.name", Err: errors.New("empty DNS provider name")}
}
if c.ACME.DNS.Config == nil {
return configError{Field: "acme.dns.config", Err: errors.New("empty DNS provider config")}
}
switch strings.ToLower(c.ACME.DNS.Name) {
case "cloudflare":
cmIssuer.DNS01Solver = &certmagic.DNS01Solver{
DNSProvider: &cloudflare.Provider{
APIToken: c.ACME.DNS.Config["cloudflare_api_token"],
},
}
case "duckdns":
cmIssuer.DNS01Solver = &certmagic.DNS01Solver{
DNSProvider: &duckdns.Provider{
APIToken: c.ACME.DNS.Config["duckdns_api_token"],
OverrideDomain: c.ACME.DNS.Config["duckdns_override_domain"],
},
}
case "gandi":
cmIssuer.DNS01Solver = &certmagic.DNS01Solver{
DNSProvider: &gandi.Provider{
BearerToken: c.ACME.DNS.Config["gandi_api_token"],
},
}
case "godaddy":
cmIssuer.DNS01Solver = &certmagic.DNS01Solver{
DNSProvider: &godaddy.Provider{
APIToken: c.ACME.DNS.Config["godaddy_api_token"],
},
}
case "namedotcom":
cmIssuer.DNS01Solver = &certmagic.DNS01Solver{
DNSProvider: &namedotcom.Provider{
Token: c.ACME.DNS.Config["namedotcom_token"],
User: c.ACME.DNS.Config["namedotcom_user"],
Server: c.ACME.DNS.Config["namedotcom_server"],
},
}
case "vultr":
cmIssuer.DNS01Solver = &certmagic.DNS01Solver{
DNSProvider: &vultr.Provider{
APIToken: c.ACME.DNS.Config["vultr_api_token"],
},
}
default:
return configError{Field: "acme.dns.name", Err: errors.New("unsupported DNS provider")}
}
case "":
// Legacy compatibility mode
cmIssuer.DisableHTTPChallenge = c.ACME.DisableHTTP
cmIssuer.DisableTLSALPNChallenge = c.ACME.DisableTLSALPN
cmIssuer.AltHTTPPort = c.ACME.AltHTTPPort
cmIssuer.AltTLSALPNPort = c.ACME.AltTLSALPNPort
default:
return configError{Field: "acme.type", Err: errors.New("unsupported ACME type")}
}
cmCfg.Issuers = []certmagic.Issuer{cmIssuer}
cmCache := certmagic.NewCache(certmagic.CacheOptions{
GetConfigForCert: func(cert certmagic.Certificate) (*certmagic.Config, error) {
@ -377,18 +520,18 @@ func (c *serverConfig) fillQUICConfig(hyConfig *server.Config) error {
}
func serverConfigOutboundDirectToOutbound(c serverConfigOutboundDirect) (outbounds.PluggableOutbound, error) {
var mode outbounds.DirectOutboundMode
opts := outbounds.DirectOutboundOptions{}
switch strings.ToLower(c.Mode) {
case "", "auto":
mode = outbounds.DirectOutboundModeAuto
opts.Mode = outbounds.DirectOutboundModeAuto
case "64":
mode = outbounds.DirectOutboundMode64
opts.Mode = outbounds.DirectOutboundMode64
case "46":
mode = outbounds.DirectOutboundMode46
opts.Mode = outbounds.DirectOutboundMode46
case "6":
mode = outbounds.DirectOutboundMode6
opts.Mode = outbounds.DirectOutboundMode6
case "4":
mode = outbounds.DirectOutboundMode4
opts.Mode = outbounds.DirectOutboundMode4
default:
return nil, configError{Field: "outbounds.direct.mode", Err: errors.New("unsupported mode")}
}
@ -405,12 +548,14 @@ func serverConfigOutboundDirectToOutbound(c serverConfigOutboundDirect) (outboun
if len(c.BindIPv6) > 0 && ip6 == nil {
return nil, configError{Field: "outbounds.direct.bindIPv6", Err: errors.New("invalid IPv6 address")}
}
return outbounds.NewDirectOutboundBindToIPs(mode, ip4, ip6)
opts.BindIP4 = ip4
opts.BindIP6 = ip6
}
if bindDevice {
return outbounds.NewDirectOutboundBindToDevice(mode, c.BindDevice)
opts.DeviceName = c.BindDevice
}
return outbounds.NewDirectOutboundSimple(mode), nil
opts.FastOpen = c.FastOpen
return outbounds.NewDirectOutboundWithOptions(opts)
}
func serverConfigOutboundSOCKS5ToOutbound(c serverConfigOutboundSOCKS5) (outbounds.PluggableOutbound, error) {
@ -427,6 +572,29 @@ func serverConfigOutboundHTTPToOutbound(c serverConfigOutboundHTTP) (outbounds.P
return outbounds.NewHTTPOutbound(c.URL, c.Insecure)
}
func (c *serverConfig) fillRequestHook(hyConfig *server.Config) error {
if c.Sniff.Enable {
s := &sniff.Sniffer{
Timeout: c.Sniff.Timeout,
RewriteDomain: c.Sniff.RewriteDomain,
}
if c.Sniff.TCPPorts != "" {
s.TCPPorts = eUtils.ParsePortUnion(c.Sniff.TCPPorts)
if s.TCPPorts == nil {
return configError{Field: "sniff.tcpPorts", Err: errors.New("invalid port union")}
}
}
if c.Sniff.UDPPorts != "" {
s.UDPPorts = eUtils.ParsePortUnion(c.Sniff.UDPPorts)
if s.UDPPorts == nil {
return configError{Field: "sniff.udpPorts", Err: errors.New("invalid port union")}
}
}
hyConfig.RequestHook = s
}
return nil
}
func (c *serverConfig) fillOutboundConfig(hyConfig *server.Config) error {
// Resolver, ACL, actual outbound are all implemented through the Outbound interface.
// Depending on the config, we build a chain like this:
@ -587,7 +755,7 @@ func (c *serverConfig) fillAuthenticator(hyConfig *server.Config) error {
if len(c.Auth.UserPass) == 0 {
return configError{Field: "auth.userpass", Err: errors.New("empty auth userpass")}
}
hyConfig.Authenticator = &auth.UserPassAuthenticator{Users: c.Auth.UserPass}
hyConfig.Authenticator = auth.NewUserPassAuthenticator(c.Auth.UserPass)
return nil
case "http", "https":
if c.Auth.HTTP.URL == "" {
@ -640,6 +808,28 @@ func (c *serverConfig) fillMasqHandler(hyConfig *server.Config) error {
if err != nil {
return configError{Field: "masquerade.proxy.url", Err: err}
}
if u.Scheme != "http" && u.Scheme != "https" {
return configError{Field: "masquerade.proxy.url", Err: fmt.Errorf("unsupported protocol scheme \"%s\"", u.Scheme)}
}
transport := http.DefaultTransport
if c.Masquerade.Proxy.Insecure {
transport = &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
// use default configs from http.DefaultTransport
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
}
handler = &httputil.ReverseProxy{
Rewrite: func(r *httputil.ProxyRequest) {
r.SetURL(u)
@ -649,6 +839,7 @@ func (c *serverConfig) fillMasqHandler(hyConfig *server.Config) error {
r.Out.Host = r.In.Host
}
},
Transport: transport,
ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {
logger.Error("HTTP reverse proxy error", zap.Error(err))
w.WriteHeader(http.StatusBadGateway)
@ -707,6 +898,7 @@ func (c *serverConfig) Config() (*server.Config, error) {
c.fillConn,
c.fillTLSConfig,
c.fillQUICConfig,
c.fillRequestHook,
c.fillOutboundConfig,
c.fillBandwidthConfig,
c.fillIgnoreClientBandwidth,

View file

@ -28,6 +28,7 @@ func TestServerConfig(t *testing.T) {
TLS: &serverConfigTLS{
Cert: "some.crt",
Key: "some.key",
SNIGuard: "strict",
},
ACME: &serverConfigACME{
Domains: []string{
@ -36,11 +37,26 @@ func TestServerConfig(t *testing.T) {
},
Email: "haha@cringe.net",
CA: "zero",
ListenHost: "127.0.0.9",
Dir: "random_dir",
Type: "dns",
HTTP: serverConfigACMEHTTP{
AltPort: 8888,
},
TLS: serverConfigACMETLS{
AltPort: 44333,
},
DNS: serverConfigACMEDNS{
Name: "gomommy",
Config: map[string]string{
"key1": "value1",
"key2": "value2",
},
},
DisableHTTP: true,
DisableTLSALPN: true,
AltHTTPPort: 9980,
AltTLSALPNPort: 9443,
Dir: "random_dir",
AltHTTPPort: 8080,
AltTLSALPNPort: 4433,
},
QUIC: serverConfigQUIC{
InitStreamReceiveWindow: 77881,
@ -96,6 +112,13 @@ func TestServerConfig(t *testing.T) {
Insecure: true,
},
},
Sniff: serverConfigSniff{
Enable: true,
Timeout: 1 * time.Second,
RewriteDomain: true,
TCPPorts: "80,443,1000-2000",
UDPPorts: "443",
},
ACL: serverConfigACL{
File: "chnroute.txt",
Inline: []string{
@ -115,6 +138,7 @@ func TestServerConfig(t *testing.T) {
BindIPv4: "2.4.6.8",
BindIPv6: "0:0:0:0:0:ffff:0204:0608",
BindDevice: "eth233",
FastOpen: true,
},
},
{
@ -147,6 +171,7 @@ func TestServerConfig(t *testing.T) {
Proxy: serverConfigMasqueradeProxy{
URL: "https://some.site.net",
RewriteHost: true,
Insecure: true,
},
String: serverConfigMasqueradeString{
Content: "aint nothin here",

View file

@ -8,6 +8,7 @@ obfs:
tls:
cert: some.crt
key: some.key
sniGuard: strict
acme:
domains:
@ -15,11 +16,22 @@ acme:
- sub2.example.com
email: haha@cringe.net
ca: zero
listenHost: 127.0.0.9
dir: random_dir
type: dns
http:
altPort: 8888
tls:
altPort: 44333
dns:
name: gomommy
config:
key1: value1
key2: value2
disableHTTP: true
disableTLSALPN: true
altHTTPPort: 9980
altTLSALPNPort: 9443
dir: random_dir
altHTTPPort: 8080
altTLSALPNPort: 4433
quic:
initStreamReceiveWindow: 77881
@ -72,6 +84,13 @@ resolver:
sni: real.stuff.net
insecure: true
sniff:
enable: true
timeout: 1s
rewriteDomain: true
tcpPorts: 80,443,1000-2000
udpPorts: 443
acl:
file: chnroute.txt
inline:
@ -89,6 +108,7 @@ outbounds:
bindIPv4: 2.4.6.8
bindIPv6: 0:0:0:0:0:ffff:0204:0608
bindDevice: eth233
fastOpen: true
- name: badstuff
type: socks5
socks5:
@ -112,6 +132,7 @@ masquerade:
proxy:
url: https://some.site.net
rewriteHost: true
insecure: true
string:
content: aint nothin here
headers:

55
app/cmd/share.go Normal file
View file

@ -0,0 +1,55 @@
package cmd
import (
"fmt"
"github.com/apernet/hysteria/app/v2/internal/utils"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"go.uber.org/zap"
)
var (
noText bool
withQR bool
)
// shareCmd represents the share command
var shareCmd = &cobra.Command{
Use: "share",
Short: "Generate share URI",
Long: "Generate a hysteria2:// URI from a client config for sharing",
Run: runShare,
}
func init() {
initShareFlags()
rootCmd.AddCommand(shareCmd)
}
func initShareFlags() {
shareCmd.Flags().BoolVar(&noText, "notext", false, "do not show URI as text")
shareCmd.Flags().BoolVar(&withQR, "qr", false, "show URI as QR code")
}
func runShare(cmd *cobra.Command, args []string) {
if err := viper.ReadInConfig(); err != nil {
logger.Fatal("failed to read client config", zap.Error(err))
}
var config clientConfig
if err := viper.Unmarshal(&config); err != nil {
logger.Fatal("failed to parse client config", zap.Error(err))
}
if _, err := config.Config(); err != nil {
logger.Fatal("failed to load client config", zap.Error(err))
}
u := config.URI()
if !noText {
fmt.Println(u)
}
if withQR {
utils.PrintQR(u)
}
}

View file

@ -3,16 +3,19 @@ package cmd
import (
"errors"
"fmt"
"os"
"os/signal"
"syscall"
"time"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"go.uber.org/zap"
"github.com/apernet/hysteria/core/client"
hyErrors "github.com/apernet/hysteria/core/errors"
"github.com/apernet/hysteria/extras/outbounds"
"github.com/apernet/hysteria/extras/outbounds/speedtest"
"github.com/apernet/hysteria/core/v2/client"
hyErrors "github.com/apernet/hysteria/core/v2/errors"
"github.com/apernet/hysteria/extras/v2/outbounds"
"github.com/apernet/hysteria/extras/v2/outbounds/speedtest"
)
var (
@ -68,12 +71,27 @@ func runSpeedtest(cmd *cobra.Command, args []string) {
zap.Bool("udpEnabled", info.UDPEnabled),
zap.Uint64("tx", info.Tx))
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
defer signal.Stop(signalChan)
runChan := make(chan struct{}, 1)
go func() {
if !skipDownload {
runDownloadTest(c)
}
if !skipUpload {
runUploadTest(c)
}
runChan <- struct{}{}
}()
select {
case <-signalChan:
logger.Info("received signal, shutting down gracefully")
case <-runChan:
logger.Info("speed test complete")
}
}
func runDownloadTest(c client.Client) {
@ -152,8 +170,8 @@ func formatSpeed(bytes uint32, duration time.Duration, useBytes bool) string {
speed *= 8
}
unitIndex := 0
for speed > 1024 && unitIndex < len(units)-1 {
speed /= 1024
for speed > 1000 && unitIndex < len(units)-1 {
speed /= 1000
unitIndex++
}
return fmt.Sprintf("%.2f %s", speed, units[unitIndex])

View file

@ -6,8 +6,8 @@ import (
"github.com/spf13/cobra"
"go.uber.org/zap"
"github.com/apernet/hysteria/app/internal/utils"
"github.com/apernet/hysteria/core/client"
"github.com/apernet/hysteria/app/v2/internal/utils"
"github.com/apernet/hysteria/core/v2/client"
)
const (

View file

@ -1,71 +1,92 @@
module github.com/apernet/hysteria/app
module github.com/apernet/hysteria/app/v2
go 1.21
go 1.23
toolchain go1.24.2
require (
github.com/apernet/go-tproxy v0.0.0-20230809025308-8f4723fd742f
github.com/apernet/hysteria/core v0.0.0-00010101000000-000000000000
github.com/apernet/hysteria/extras v0.0.0-00010101000000-000000000000
github.com/apernet/hysteria/core/v2 v2.0.0-00010101000000-000000000000
github.com/apernet/hysteria/extras/v2 v2.0.0-00010101000000-000000000000
github.com/apernet/sing-tun v0.2.6-0.20240323130332-b9f6511036ad
github.com/caddyserver/certmagic v0.17.2
github.com/libdns/cloudflare v0.1.1
github.com/libdns/duckdns v0.2.0
github.com/libdns/gandi v1.0.3
github.com/libdns/godaddy v1.0.3
github.com/libdns/namedotcom v0.3.3
github.com/libdns/vultr v1.0.0
github.com/mdp/qrterminal/v3 v3.1.1
github.com/mholt/acmez v1.0.4
github.com/sagernet/sing v0.3.2
github.com/spf13/cobra v1.7.0
github.com/spf13/viper v1.15.0
github.com/stretchr/testify v1.8.4
github.com/stretchr/testify v1.9.0
github.com/txthinking/socks5 v0.0.0-20230325130024-4230056ae301
go.uber.org/zap v1.24.0
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db
golang.org/x/sys v0.17.0
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
golang.org/x/sys v0.25.0
)
require (
github.com/apernet/quic-go v0.42.1-0.20240323215309-32a339817822 // indirect
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/apernet/quic-go v0.52.1-0.20250607183305-9320c9d14431 // indirect
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 // indirect
github.com/cloudflare/circl v1.3.9 // indirect
github.com/database64128/netx-go v0.0.0-20240905055117-62795b8b054a // indirect
github.com/database64128/tfo-go/v2 v2.2.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.6 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/klauspost/cpuid/v2 v2.1.1 // indirect
github.com/libdns/libdns v0.2.1 // indirect
github.com/libdns/libdns v0.2.2 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/miekg/dns v1.1.55 // indirect
github.com/miekg/dns v1.1.59 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/refraction-networking/utls v1.6.6 // indirect
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect
github.com/spf13/afero v1.9.3 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
github.com/vultr/govultr/v3 v3.6.4 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/mock v0.4.0 // indirect
go.uber.org/mock v0.5.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.11.1 // indirect
google.golang.org/protobuf v1.33.0 // indirect
golang.org/x/crypto v0.26.0 // indirect
golang.org/x/mod v0.18.0 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/oauth2 v0.20.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/tools v0.22.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
rsc.io/qr v0.2.0 // indirect
)
replace github.com/apernet/hysteria/core => ../core
replace github.com/apernet/hysteria/core/v2 => ../core
replace github.com/apernet/hysteria/extras => ../extras
replace github.com/apernet/hysteria/extras/v2 => ../extras

View file

@ -38,10 +38,12 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/apernet/go-tproxy v0.0.0-20230809025308-8f4723fd742f h1:uVh0qpEslrWjgzx9vOcyCqsOY3c9kofDZ1n+qaw35ZY=
github.com/apernet/go-tproxy v0.0.0-20230809025308-8f4723fd742f/go.mod h1:xkkq9D4ygcldQQhKS/w9CadiCKwCngU7K9E3DaKahpM=
github.com/apernet/quic-go v0.42.1-0.20240323215309-32a339817822 h1:+ZSzRxSMg1+fLTQKcIUvD2cCCgS+1rtyRhs+NL5oBgA=
github.com/apernet/quic-go v0.42.1-0.20240323215309-32a339817822/go.mod h1:j3QaAM7sVJqptDQyIQRWA6mASCfuxoHJszn67JQh1GE=
github.com/apernet/quic-go v0.52.1-0.20250607183305-9320c9d14431 h1:9/jM7e+kVALd7Jfu1c27dcEpT/Fd/Gzq2OsQjKjakKI=
github.com/apernet/quic-go v0.52.1-0.20250607183305-9320c9d14431/go.mod h1:I/47OIGG5H/IfAm+nz2c6hm6b/NkEhpvptAoiPcY7jQ=
github.com/apernet/sing-tun v0.2.6-0.20240323130332-b9f6511036ad h1:QzQ2sKpc9o42HNRR8ukM5uMC/RzR2HgZd/Nvaqol2C0=
github.com/apernet/sing-tun v0.2.6-0.20240323130332-b9f6511036ad/go.mod h1:S5IydyLSN/QAfvY+r2GoomPJ6hidtXWm/Ad18sJVssk=
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 h1:4NNbNM2Iq/k57qEu7WfL67UrbPq1uFWxW4qODCohi+0=
@ -55,10 +57,16 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.3.9 h1:QFrlgFYf2Qpi8bSpVPK1HBvWpx16v/1TZivyo7pGuBE=
github.com/cloudflare/circl v1.3.9/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/database64128/netx-go v0.0.0-20240905055117-62795b8b054a h1:t4SDi0pmNkryzKdM4QF3o5vqSP4GRjeZD/6j3nyxNP0=
github.com/database64128/netx-go v0.0.0-20240905055117-62795b8b054a/go.mod h1:7K2NQKbabB5mBl41vF6YayYl5g7YpDwc4dQ5iMpP3Lg=
github.com/database64128/tfo-go/v2 v2.2.2 h1:BxynF4qGF5ct3DpPLEG62uyJZ3LQhqaf0Ken+kyy7PM=
github.com/database64128/tfo-go/v2 v2.2.2/go.mod h1:2IW8jppdBwdVMjA08uEyMNnqiAHKUlqAA+J8NrsfktY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -68,6 +76,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
@ -106,8 +116,8 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@ -119,8 +129,10 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@ -141,6 +153,12 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-retryablehttp v0.7.6 h1:TwRYfx2z2C4cLbXmT8I5PgP/xmuqASDyiVuGYfs9GZM=
github.com/hashicorp/go-retryablehttp v0.7.6/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4=
@ -154,31 +172,49 @@ github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.1.1 h1:t0wUqjowdm8ezddV5k0tLWVklVuvLJpoHeb4WBdydm0=
github.com/klauspost/cpuid/v2 v2.1.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis=
github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
github.com/libdns/cloudflare v0.1.1 h1:FVPfWwP8zZCqj268LZjmkDleXlHPlFU9KC4OJ3yn054=
github.com/libdns/cloudflare v0.1.1/go.mod h1:9VK91idpOjg6v7/WbjkEW49bSCxj00ALesIFDhJ8PBU=
github.com/libdns/duckdns v0.2.0 h1:vd3pE09G2qTx1Zh1o3LmrivWSByD3Z5FbL7csX5vDgE=
github.com/libdns/duckdns v0.2.0/go.mod h1:jCQ/7+qvhLK39+28qXvKEYGBBvmHBCmIwNqdJTCUmVs=
github.com/libdns/gandi v1.0.3 h1:FIvipWOg/O4zi75fPRmtcolRKqI6MgrbpFy2p5KYdUk=
github.com/libdns/gandi v1.0.3/go.mod h1:G6dw58Xnji2xX+lb+uZxGbtmfxKllm1CGHE2bOPG3WA=
github.com/libdns/godaddy v1.0.3 h1:PX1FOYDQ1HGQzz8mVOmtwm3aa6Sv5MwCkNzivUUTA44=
github.com/libdns/godaddy v1.0.3/go.mod h1:vuKWUXnvblDvcaiRwutOoLl7DuB21x8tI06owsF/JTM=
github.com/libdns/libdns v0.2.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
github.com/libdns/namedotcom v0.3.3 h1:R10C7+IqQGVeC4opHHMiFNBxdNBg1bi65ZwqLESl+jE=
github.com/libdns/namedotcom v0.3.3/go.mod h1:GbYzsAF2yRUpI0WgIK5fs5UX+kDVUPaYCFLpTnKQm0s=
github.com/libdns/vultr v1.0.0 h1:W8B4+k2bm9ro3bZLSZV9hMOQI+uO6Svu+GmD+Olz7ZI=
github.com/libdns/vultr v1.0.0/go.mod h1:8K1HJExcbeHS4YPkFHRZpqpXZzZ+DZAA0m0VikJgEqk=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mdp/qrterminal/v3 v3.1.1 h1:cIPwg3QU0OIm9+ce/lRfWXhPwEjOSKwk3HBwL3HBTyc=
github.com/mdp/qrterminal/v3 v3.1.1/go.mod h1:5lJlXe7Jdr8wlPDdcsJttv1/knsRgzXASyr4dcGZqNU=
github.com/mholt/acmez v1.0.4 h1:N3cE4Pek+dSolbsofIkAYz6H1d3pE+2G0os7QHslf80=
github.com/mholt/acmez v1.0.4/go.mod h1:qFGLZ4u+ehWINeJZjzPlsnjJBCPAADWTcIqE/7DAYQY=
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/miekg/dns v1.1.51/go.mod h1:2Z9d3CP1LQWihRZUf29mQ19yDThaI4DAYzte2CaQW5c=
github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
@ -194,11 +230,13 @@ github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qR
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/refraction-networking/utls v1.6.6 h1:igFsYBUJPYM8Rno9xUuDoM5GQrVEqY4llzEXOkL43Ig=
github.com/refraction-networking/utls v1.6.6/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
@ -220,8 +258,9 @@ github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU=
github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@ -231,8 +270,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf h1:7PflaKRtU4np/epFxRXlFhlzLXZzKFrH5/I4so5Ove0=
@ -241,6 +280,8 @@ github.com/txthinking/socks5 v0.0.0-20230325130024-4230056ae301 h1:d/Wr/Vl/wiJHc
github.com/txthinking/socks5 v0.0.0-20230325130024-4230056ae301/go.mod h1:ntmMHL/xPq1WLeKiw8p/eRATaae6PiVRNipHFJxI8PM=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vultr/govultr/v3 v3.6.4 h1:unvY9eXlBw667ECQZDbBDOIaWB8wkk6Bx+yB0IMKXJ4=
github.com/vultr/govultr/v3 v3.6.4/go.mod h1:rt9v2x114jZmmLAE/h5N5jnxTmsK9ewwS2oQZ0UBQzM=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@ -259,8 +300,8 @@ go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
@ -277,8 +318,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -289,8 +330,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -317,8 +358,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -331,6 +372,7 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -354,8 +396,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -365,6 +407,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -378,8 +422,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -389,6 +433,7 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -422,8 +467,8 @@ golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
@ -435,13 +480,11 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@ -462,6 +505,7 @@ golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
@ -492,8 +536,8 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
golang.org/x/tools v0.11.1 h1:ojD5zOW8+7dOGzdnNgersm8aPfcDjhMp12UfG93NIMc=
golang.org/x/tools v0.11.1/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -586,12 +630,12 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=

View file

@ -4,7 +4,7 @@ import (
"io"
"net"
"github.com/apernet/hysteria/core/client"
"github.com/apernet/hysteria/core/v2/client"
)
type TCPTunnel struct {

View file

@ -7,7 +7,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/apernet/hysteria/app/internal/utils_test"
"github.com/apernet/hysteria/app/v2/internal/utils_test"
)
func TestTCPTunnel(t *testing.T) {

View file

@ -6,7 +6,7 @@ import (
"sync/atomic"
"time"
"github.com/apernet/hysteria/core/client"
"github.com/apernet/hysteria/core/v2/client"
)
const (

View file

@ -7,7 +7,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/apernet/hysteria/app/internal/utils_test"
"github.com/apernet/hysteria/app/v2/internal/utils_test"
)
func TestUDPTunnel(t *testing.T) {

View file

@ -12,7 +12,7 @@ import (
"strings"
"time"
"github.com/apernet/hysteria/core/client"
"github.com/apernet/hysteria/core/v2/client"
)
const (
@ -278,6 +278,11 @@ func sendSimpleResponse(conn net.Conn, req *http.Request, statusCode int) error
ProtoMinor: req.ProtoMinor,
Header: http.Header{},
}
// Remove the "Content-Length: 0" header, some clients (e.g. ffmpeg) may not like it.
resp.ContentLength = -1
// Also, prevent the "Connection: close" header.
resp.Close = false
resp.Uncompressed = true
return resp.Write(conn)
}

View file

@ -10,7 +10,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/apernet/hysteria/core/client"
"github.com/apernet/hysteria/core/v2/client"
)
const (

View file

@ -1,4 +1,4 @@
// Code generated by mockery v2.42.2. DO NOT EDIT.
// Code generated by mockery v2.43.0. DO NOT EDIT.
package mocks

View file

@ -1,4 +1,4 @@
// Code generated by mockery v2.42.2. DO NOT EDIT.
// Code generated by mockery v2.43.0. DO NOT EDIT.
package mocks

View file

@ -4,7 +4,7 @@ import (
"net"
"sync"
"github.com/apernet/hysteria/extras/correctnet"
"github.com/apernet/hysteria/extras/v2/correctnet"
)
type muxManager struct {

View file

@ -7,7 +7,7 @@ import (
"testing"
"time"
"github.com/apernet/hysteria/app/internal/proxymux/internal/mocks"
"github.com/apernet/hysteria/app/v2/internal/proxymux/internal/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"

View file

@ -8,7 +8,7 @@ import (
"syscall"
"unsafe"
"github.com/apernet/hysteria/core/client"
"github.com/apernet/hysteria/core/v2/client"
)
const (

View file

@ -6,7 +6,7 @@ import (
"errors"
"net"
"github.com/apernet/hysteria/core/client"
"github.com/apernet/hysteria/core/v2/client"
)
type TCPRedirect struct {

View file

@ -7,7 +7,7 @@ import (
"github.com/txthinking/socks5"
"github.com/apernet/hysteria/core/client"
"github.com/apernet/hysteria/core/v2/client"
)
const udpBufferSize = 4096

View file

@ -8,7 +8,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/apernet/hysteria/app/internal/utils_test"
"github.com/apernet/hysteria/app/v2/internal/utils_test"
)
func TestServer(t *testing.T) {

View file

@ -5,7 +5,7 @@ import (
"net"
"github.com/apernet/go-tproxy"
"github.com/apernet/hysteria/core/client"
"github.com/apernet/hysteria/core/v2/client"
)
type TCPTProxy struct {

View file

@ -6,7 +6,7 @@ import (
"errors"
"net"
"github.com/apernet/hysteria/core/client"
"github.com/apernet/hysteria/core/v2/client"
)
type TCPTProxy struct {

View file

@ -6,7 +6,7 @@ import (
"time"
"github.com/apernet/go-tproxy"
"github.com/apernet/hysteria/core/client"
"github.com/apernet/hysteria/core/v2/client"
)
const (

View file

@ -7,7 +7,7 @@ import (
"net"
"time"
"github.com/apernet/hysteria/core/client"
"github.com/apernet/hysteria/core/v2/client"
)
type UDPTProxy struct {

View file

@ -0,0 +1,14 @@
//go:build !unix && !windows
package tun
import "net"
func isIPv6Supported() bool {
lis, err := net.ListenPacket("udp6", "[::1]:0")
if err != nil {
return false
}
_ = lis.Close()
return true
}

View file

@ -0,0 +1,16 @@
//go:build unix
package tun
import (
"golang.org/x/sys/unix"
)
func isIPv6Supported() bool {
sock, err := unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_UDP)
if err != nil {
return false
}
_ = unix.Close(sock)
return true
}

View file

@ -0,0 +1,24 @@
//go:build windows
package tun
import (
"golang.org/x/sys/windows"
)
func isIPv6Supported() bool {
var wsaData windows.WSAData
err := windows.WSAStartup(uint32(0x202), &wsaData)
if err != nil {
// Failing silently: it is not our duty to report such errors
return true
}
defer windows.WSACleanup()
sock, err := windows.Socket(windows.AF_INET6, windows.SOCK_DGRAM, windows.IPPROTO_UDP)
if err != nil {
return false
}
_ = windows.Closesocket(sock)
return true
}

View file

@ -14,7 +14,7 @@ import (
"github.com/sagernet/sing/common/network"
"go.uber.org/zap"
"github.com/apernet/hysteria/core/client"
"github.com/apernet/hysteria/core/v2/client"
)
type Server struct {
@ -49,6 +49,10 @@ type EventLogger interface {
}
func (s *Server) Serve() error {
if !isIPv6Supported() {
s.Logger.Warn("tun-pre-check", zap.String("msg", "IPv6 is not supported or enabled on this system, TUN device is created without IPv6 support."))
s.Inet6Address = nil
}
tunOpts := tun.Options{
Name: s.IfName,
Inet4Address: s.Inet4Address,

View file

@ -0,0 +1,198 @@
package utils
import (
"crypto/tls"
"crypto/x509"
"fmt"
"net"
"os"
"strings"
"sync"
"sync/atomic"
"time"
)
type LocalCertificateLoader struct {
CertFile string
KeyFile string
SNIGuard SNIGuardFunc
lock sync.Mutex
cache atomic.Pointer[localCertificateCache]
}
type SNIGuardFunc func(info *tls.ClientHelloInfo, cert *tls.Certificate) error
// localCertificateCache holds the certificate and its mod times.
// this struct is designed to be read-only.
//
// to update the cache, use LocalCertificateLoader.makeCache and
// update the LocalCertificateLoader.cache field.
type localCertificateCache struct {
certificate *tls.Certificate
certModTime time.Time
keyModTime time.Time
}
func (l *LocalCertificateLoader) InitializeCache() error {
l.lock.Lock()
defer l.lock.Unlock()
cache, err := l.makeCache()
if err != nil {
return err
}
l.cache.Store(cache)
return nil
}
func (l *LocalCertificateLoader) GetCertificate(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
cert, err := l.getCertificateWithCache()
if err != nil {
return nil, err
}
if l.SNIGuard == nil {
return cert, nil
}
err = l.SNIGuard(info, cert)
if err != nil {
return nil, err
}
return cert, nil
}
func (l *LocalCertificateLoader) checkModTime() (certModTime, keyModTime time.Time, err error) {
fi, err := os.Stat(l.CertFile)
if err != nil {
err = fmt.Errorf("failed to stat certificate file: %w", err)
return
}
certModTime = fi.ModTime()
fi, err = os.Stat(l.KeyFile)
if err != nil {
err = fmt.Errorf("failed to stat key file: %w", err)
return
}
keyModTime = fi.ModTime()
return
}
func (l *LocalCertificateLoader) makeCache() (cache *localCertificateCache, err error) {
c := &localCertificateCache{}
c.certModTime, c.keyModTime, err = l.checkModTime()
if err != nil {
return
}
cert, err := tls.LoadX509KeyPair(l.CertFile, l.KeyFile)
if err != nil {
return
}
c.certificate = &cert
if c.certificate.Leaf == nil {
// certificate.Leaf was left nil by tls.LoadX509KeyPair before Go 1.23
c.certificate.Leaf, err = x509.ParseCertificate(cert.Certificate[0])
if err != nil {
return
}
}
cache = c
return
}
func (l *LocalCertificateLoader) getCertificateWithCache() (*tls.Certificate, error) {
cache := l.cache.Load()
certModTime, keyModTime, terr := l.checkModTime()
if terr != nil {
if cache != nil {
// use cache when file is temporarily unavailable
return cache.certificate, nil
}
return nil, terr
}
if cache != nil && cache.certModTime.Equal(certModTime) && cache.keyModTime.Equal(keyModTime) {
// cache is up-to-date
return cache.certificate, nil
}
if cache != nil {
if !l.lock.TryLock() {
// another goroutine is updating the cache
return cache.certificate, nil
}
} else {
l.lock.Lock()
}
defer l.lock.Unlock()
if l.cache.Load() != cache {
// another goroutine updated the cache
return l.cache.Load().certificate, nil
}
newCache, err := l.makeCache()
if err != nil {
if cache != nil {
// use cache when loading failed
return cache.certificate, nil
}
return nil, err
}
l.cache.Store(newCache)
return newCache.certificate, nil
}
// getNameFromClientHello returns a normalized form of hello.ServerName.
// If hello.ServerName is empty (i.e. client did not use SNI), then the
// associated connection's local address is used to extract an IP address.
//
// ref: https://github.com/caddyserver/certmagic/blob/3bad5b6bb595b09c14bd86ff0b365d302faaf5e2/handshake.go#L838
func getNameFromClientHello(hello *tls.ClientHelloInfo) string {
normalizedName := func(serverName string) string {
return strings.ToLower(strings.TrimSpace(serverName))
}
localIPFromConn := func(c net.Conn) string {
if c == nil {
return ""
}
localAddr := c.LocalAddr().String()
ip, _, err := net.SplitHostPort(localAddr)
if err != nil {
ip = localAddr
}
if scopeIDStart := strings.Index(ip, "%"); scopeIDStart > -1 {
ip = ip[:scopeIDStart]
}
return ip
}
if name := normalizedName(hello.ServerName); name != "" {
return name
}
return localIPFromConn(hello.Conn)
}
func SNIGuardDNSSAN(info *tls.ClientHelloInfo, cert *tls.Certificate) error {
if len(cert.Leaf.DNSNames) == 0 {
return nil
}
return SNIGuardStrict(info, cert)
}
func SNIGuardStrict(info *tls.ClientHelloInfo, cert *tls.Certificate) error {
hostname := getNameFromClientHello(info)
err := cert.Leaf.VerifyHostname(hostname)
if err != nil {
return fmt.Errorf("sni guard: %w", err)
}
return nil
}

View file

@ -0,0 +1,139 @@
package utils
import (
"crypto/tls"
"log"
"net/http"
"os"
"os/exec"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
const (
testListen = "127.82.39.147:12947"
testCAFile = "./testcerts/ca"
testCertFile = "./testcerts/cert"
testKeyFile = "./testcerts/key"
)
func TestCertificateLoaderPathError(t *testing.T) {
assert.NoError(t, os.RemoveAll(testCertFile))
assert.NoError(t, os.RemoveAll(testKeyFile))
loader := LocalCertificateLoader{
CertFile: testCertFile,
KeyFile: testKeyFile,
SNIGuard: SNIGuardStrict,
}
err := loader.InitializeCache()
var pathErr *os.PathError
assert.ErrorAs(t, err, &pathErr)
}
func TestCertificateLoaderFullChain(t *testing.T) {
assert.NoError(t, generateTestCertificate([]string{"example.com"}, "fullchain"))
loader := LocalCertificateLoader{
CertFile: testCertFile,
KeyFile: testKeyFile,
SNIGuard: SNIGuardStrict,
}
assert.NoError(t, loader.InitializeCache())
lis, err := tls.Listen("tcp", testListen, &tls.Config{
GetCertificate: loader.GetCertificate,
})
assert.NoError(t, err)
defer lis.Close()
go http.Serve(lis, nil)
assert.Error(t, runTestTLSClient("unmatched-sni.example.com"))
assert.Error(t, runTestTLSClient(""))
assert.NoError(t, runTestTLSClient("example.com"))
}
func TestCertificateLoaderNoSAN(t *testing.T) {
assert.NoError(t, generateTestCertificate(nil, "selfsign"))
loader := LocalCertificateLoader{
CertFile: testCertFile,
KeyFile: testKeyFile,
SNIGuard: SNIGuardDNSSAN,
}
assert.NoError(t, loader.InitializeCache())
lis, err := tls.Listen("tcp", testListen, &tls.Config{
GetCertificate: loader.GetCertificate,
})
assert.NoError(t, err)
defer lis.Close()
go http.Serve(lis, nil)
assert.NoError(t, runTestTLSClient(""))
}
func TestCertificateLoaderReplaceCertificate(t *testing.T) {
assert.NoError(t, generateTestCertificate([]string{"example.com"}, "fullchain"))
loader := LocalCertificateLoader{
CertFile: testCertFile,
KeyFile: testKeyFile,
SNIGuard: SNIGuardStrict,
}
assert.NoError(t, loader.InitializeCache())
lis, err := tls.Listen("tcp", testListen, &tls.Config{
GetCertificate: loader.GetCertificate,
})
assert.NoError(t, err)
defer lis.Close()
go http.Serve(lis, nil)
assert.NoError(t, runTestTLSClient("example.com"))
assert.Error(t, runTestTLSClient("2.example.com"))
assert.NoError(t, generateTestCertificate([]string{"2.example.com"}, "fullchain"))
assert.Error(t, runTestTLSClient("example.com"))
assert.NoError(t, runTestTLSClient("2.example.com"))
}
func generateTestCertificate(dnssan []string, certType string) error {
args := []string{
"certloader_test_gencert.py",
"--ca", testCAFile,
"--cert", testCertFile,
"--key", testKeyFile,
"--type", certType,
}
if len(dnssan) > 0 {
args = append(args, "--dnssan", strings.Join(dnssan, ","))
}
cmd := exec.Command("python", args...)
out, err := cmd.CombinedOutput()
if err != nil {
log.Printf("Failed to generate test certificate: %s", out)
return err
}
return nil
}
func runTestTLSClient(sni string) error {
args := []string{
"certloader_test_tlsclient.py",
"--server", testListen,
"--ca", testCAFile,
}
if sni != "" {
args = append(args, "--sni", sni)
}
cmd := exec.Command("python", args...)
out, err := cmd.CombinedOutput()
if err != nil {
log.Printf("Failed to run test TLS client: %s", out)
return err
}
return nil
}

View file

@ -0,0 +1,134 @@
import argparse
import datetime
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption
def create_key():
return ec.generate_private_key(ec.SECP256R1())
def create_certificate(cert_type, subject, issuer, private_key, public_key, dns_san=None):
serial_number = x509.random_serial_number()
not_valid_before = datetime.datetime.now(datetime.UTC)
not_valid_after = not_valid_before + datetime.timedelta(days=365)
subject_name = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, subject.get('C', 'ZZ')),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, subject.get('O', 'No Organization')),
x509.NameAttribute(NameOID.COMMON_NAME, subject.get('CN', 'No CommonName')),
])
issuer_name = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, issuer.get('C', 'ZZ')),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, issuer.get('O', 'No Organization')),
x509.NameAttribute(NameOID.COMMON_NAME, issuer.get('CN', 'No CommonName')),
])
builder = x509.CertificateBuilder()
builder = builder.subject_name(subject_name)
builder = builder.issuer_name(issuer_name)
builder = builder.public_key(public_key)
builder = builder.serial_number(serial_number)
builder = builder.not_valid_before(not_valid_before)
builder = builder.not_valid_after(not_valid_after)
if cert_type == 'root':
builder = builder.add_extension(
x509.BasicConstraints(ca=True, path_length=None), critical=True
)
elif cert_type == 'intermediate':
builder = builder.add_extension(
x509.BasicConstraints(ca=True, path_length=0), critical=True
)
elif cert_type == 'leaf':
builder = builder.add_extension(
x509.BasicConstraints(ca=False, path_length=None), critical=True
)
else:
raise ValueError(f'Invalid cert_type: {cert_type}')
if dns_san:
builder = builder.add_extension(
x509.SubjectAlternativeName([x509.DNSName(d) for d in dns_san.split(',')]),
critical=False
)
return builder.sign(private_key=private_key, algorithm=hashes.SHA256())
def main():
parser = argparse.ArgumentParser(description='Generate HTTPS server certificate.')
parser.add_argument('--ca', required=True,
help='Path to write the X509 CA certificate in PEM format')
parser.add_argument('--cert', required=True,
help='Path to write the X509 certificate in PEM format')
parser.add_argument('--key', required=True,
help='Path to write the private key in PEM format')
parser.add_argument('--dnssan', required=False, default=None,
help='Comma-separated list of DNS SANs')
parser.add_argument('--type', required=True, choices=['selfsign', 'fullchain'],
help='Type of certificate to generate')
args = parser.parse_args()
key = create_key()
public_key = key.public_key()
if args.type == 'selfsign':
subject = {"C": "ZZ", "O": "Certificate", "CN": "Certificate"}
cert = create_certificate(
cert_type='root',
subject=subject,
issuer=subject,
private_key=key,
public_key=public_key,
dns_san=args.dnssan)
with open(args.ca, 'wb') as f:
f.write(cert.public_bytes(Encoding.PEM))
with open(args.cert, 'wb') as f:
f.write(cert.public_bytes(Encoding.PEM))
with open(args.key, 'wb') as f:
f.write(
key.private_bytes(Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption()))
elif args.type == 'fullchain':
ca_key = create_key()
ca_public_key = ca_key.public_key()
ca_subject = {"C": "ZZ", "O": "Root CA", "CN": "Root CA"}
ca_cert = create_certificate(
cert_type='root',
subject=ca_subject,
issuer=ca_subject,
private_key=ca_key,
public_key=ca_public_key)
intermediate_key = create_key()
intermediate_public_key = intermediate_key.public_key()
intermediate_subject = {"C": "ZZ", "O": "Intermediate CA", "CN": "Intermediate CA"}
intermediate_cert = create_certificate(
cert_type='intermediate',
subject=intermediate_subject,
issuer=ca_subject,
private_key=ca_key,
public_key=intermediate_public_key)
leaf_subject = {"C": "ZZ", "O": "Leaf Certificate", "CN": "Leaf Certificate"}
cert = create_certificate(
cert_type='leaf',
subject=leaf_subject,
issuer=intermediate_subject,
private_key=intermediate_key,
public_key=public_key,
dns_san=args.dnssan)
with open(args.ca, 'wb') as f:
f.write(ca_cert.public_bytes(Encoding.PEM))
with open(args.cert, 'wb') as f:
f.write(cert.public_bytes(Encoding.PEM))
f.write(intermediate_cert.public_bytes(Encoding.PEM))
with open(args.key, 'wb') as f:
f.write(
key.private_bytes(Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption()))
if __name__ == "__main__":
main()

View file

@ -0,0 +1,60 @@
import argparse
import ssl
import socket
import sys
def check_tls(server, ca_cert, sni, alpn):
try:
host, port = server.split(":")
port = int(port)
if ca_cert:
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile=ca_cert)
context.check_hostname = sni is not None
context.verify_mode = ssl.CERT_REQUIRED
else:
context = ssl.create_default_context()
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
if alpn:
context.set_alpn_protocols([p for p in alpn.split(",")])
with socket.create_connection((host, port)) as sock:
with context.wrap_socket(sock, server_hostname=sni) as ssock:
# Verify handshake and certificate
print(f'Connected to {ssock.version()} using {ssock.cipher()}')
print(f'Server certificate validated and details: {ssock.getpeercert()}')
print("OK")
return 0
except Exception as e:
print(f"Error: {e}")
return 1
def main():
parser = argparse.ArgumentParser(description="Test TLS Server")
parser.add_argument("--server", required=True,
help="Server address to test (e.g., 127.1.2.3:8443)")
parser.add_argument("--ca", required=False, default=None,
help="CA certificate file used to validate the server certificate"
"Omit to use insecure connection")
parser.add_argument("--sni", required=False, default=None,
help="SNI to send in ClientHello")
parser.add_argument("--alpn", required=False, default='h2',
help="ALPN to send in ClientHello")
args = parser.parse_args()
exit_status = check_tls(
server=args.server,
ca_cert=args.ca,
sni=args.sni,
alpn=args.alpn)
sys.exit(exit_status)
if __name__ == "__main__":
main()

View file

@ -7,8 +7,8 @@ import (
"os"
"time"
"github.com/apernet/hysteria/extras/outbounds/acl"
"github.com/apernet/hysteria/extras/outbounds/acl/v2geo"
"github.com/apernet/hysteria/extras/v2/outbounds/acl"
"github.com/apernet/hysteria/extras/v2/outbounds/acl/v2geo"
)
const (

View file

@ -0,0 +1,3 @@
# This directory is used for certificate generation in certloader_test.go
/*
!/.gitignore

View file

@ -8,7 +8,7 @@ import (
"net/http"
"time"
"github.com/apernet/hysteria/core/client"
"github.com/apernet/hysteria/core/v2/client"
)
const (

View file

@ -5,7 +5,7 @@ import (
"net"
"time"
"github.com/apernet/hysteria/core/client"
"github.com/apernet/hysteria/core/v2/client"
)
type MockEchoHyClient struct{}

View file

@ -1,6 +1,6 @@
package main
import "github.com/apernet/hysteria/app/cmd"
import "github.com/apernet/hysteria/app/v2/cmd"
func main() {
cmd.Execute()

7
core/LICENSE.md Normal file
View file

@ -0,0 +1,7 @@
Copyright 2023 Toby
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -2,7 +2,7 @@ with-expecter: true
inpackage: true
dir: .
packages:
github.com/apernet/hysteria/core/client:
github.com/apernet/hysteria/core/v2/client:
interfaces:
udpIO:
config:

View file

@ -3,15 +3,16 @@ package client
import (
"context"
"crypto/tls"
"errors"
"net"
"net/http"
"net/url"
"time"
coreErrs "github.com/apernet/hysteria/core/errors"
"github.com/apernet/hysteria/core/internal/congestion"
"github.com/apernet/hysteria/core/internal/protocol"
"github.com/apernet/hysteria/core/internal/utils"
coreErrs "github.com/apernet/hysteria/core/v2/errors"
"github.com/apernet/hysteria/core/v2/internal/congestion"
"github.com/apernet/hysteria/core/v2/internal/protocol"
"github.com/apernet/hysteria/core/v2/internal/utils"
"github.com/apernet/quic-go"
"github.com/apernet/quic-go/http3"
@ -83,13 +84,13 @@ func (c *clientImpl) connect() (*HandshakeInfo, error) {
KeepAlivePeriod: c.config.QUICConfig.KeepAlivePeriod,
DisablePathMTUDiscovery: c.config.QUICConfig.DisablePathMTUDiscovery,
EnableDatagrams: true,
DisablePathManager: true,
}
// Prepare RoundTripper
var conn quic.EarlyConnection
rt := &http3.RoundTripper{
EnableDatagrams: true,
TLSClientConfig: tlsConfig,
QuicConfig: quicConfig,
QUICConfig: quicConfig,
Dial: func(ctx context.Context, _ string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
qc, err := quic.DialEarly(ctx, pktConn, c.config.ServerAddr, tlsCfg, cfg)
if err != nil {
@ -222,19 +223,22 @@ func (c *clientImpl) Close() error {
return nil
}
var nonPermanentErrors = []error{
quic.StreamLimitReachedError{},
}
// wrapIfConnectionClosed checks if the error returned by quic-go
// indicates that the QUIC connection has been permanently closed,
// and if so, wraps the error with coreErrs.ClosedError.
// PITFALL: sometimes quic-go has "internal errors" that are not net.Error,
// but we still need to treat them as ClosedError.
// is recoverable (listed in nonPermanentErrors) or permanent.
// Recoverable errors are returned as-is,
// permanent ones are wrapped as ClosedError.
func wrapIfConnectionClosed(err error) error {
netErr, ok := err.(net.Error)
if !ok || !netErr.Temporary() {
return coreErrs.ClosedError{Err: err}
} else {
for _, e := range nonPermanentErrors {
if errors.Is(err, e) {
return err
}
}
return coreErrs.ClosedError{Err: err}
}
type tcpConn struct {
Orig quic.Stream

View file

@ -5,8 +5,8 @@ import (
"net"
"time"
"github.com/apernet/hysteria/core/errors"
"github.com/apernet/hysteria/core/internal/pmtud"
"github.com/apernet/hysteria/core/v2/errors"
"github.com/apernet/hysteria/core/v2/internal/pmtud"
)
const (

View file

@ -1,9 +1,9 @@
// Code generated by mockery v2.32.0. DO NOT EDIT.
// Code generated by mockery v2.43.0. DO NOT EDIT.
package client
import (
protocol "github.com/apernet/hysteria/core/internal/protocol"
protocol "github.com/apernet/hysteria/core/v2/internal/protocol"
mock "github.com/stretchr/testify/mock"
)
@ -24,6 +24,10 @@ func (_m *mockUDPIO) EXPECT() *mockUDPIO_Expecter {
func (_m *mockUDPIO) ReceiveMessage() (*protocol.UDPMessage, error) {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for ReceiveMessage")
}
var r0 *protocol.UDPMessage
var r1 error
if rf, ok := ret.Get(0).(func() (*protocol.UDPMessage, error)); ok {
@ -77,6 +81,10 @@ func (_c *mockUDPIO_ReceiveMessage_Call) RunAndReturn(run func() (*protocol.UDPM
func (_m *mockUDPIO) SendMessage(_a0 []byte, _a1 *protocol.UDPMessage) error {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for SendMessage")
}
var r0 error
if rf, ok := ret.Get(0).(func([]byte, *protocol.UDPMessage) error); ok {
r0 = rf(_a0, _a1)

View file

@ -4,7 +4,7 @@ import (
"net"
"sync"
coreErrs "github.com/apernet/hysteria/core/errors"
coreErrs "github.com/apernet/hysteria/core/v2/errors"
)
// reconnectableClientImpl is a wrapper of Client, which can reconnect when the connection is closed,

View file

@ -8,9 +8,9 @@ import (
"github.com/apernet/quic-go"
coreErrs "github.com/apernet/hysteria/core/errors"
"github.com/apernet/hysteria/core/internal/frag"
"github.com/apernet/hysteria/core/internal/protocol"
coreErrs "github.com/apernet/hysteria/core/v2/errors"
"github.com/apernet/hysteria/core/v2/internal/frag"
"github.com/apernet/hysteria/core/v2/internal/protocol"
)
const (

View file

@ -10,8 +10,8 @@ import (
"github.com/stretchr/testify/mock"
"go.uber.org/goleak"
coreErrs "github.com/apernet/hysteria/core/errors"
"github.com/apernet/hysteria/core/internal/protocol"
coreErrs "github.com/apernet/hysteria/core/v2/errors"
"github.com/apernet/hysteria/core/v2/internal/protocol"
)
func TestUDPSessionManager(t *testing.T) {

View file

@ -1,33 +1,37 @@
module github.com/apernet/hysteria/core
module github.com/apernet/hysteria/core/v2
go 1.21
go 1.23
toolchain go1.24.2
require (
github.com/apernet/quic-go v0.42.1-0.20240323215309-32a339817822
github.com/stretchr/testify v1.8.4
github.com/apernet/quic-go v0.52.1-0.20250607183305-9320c9d14431
github.com/stretchr/testify v1.9.0
go.uber.org/goleak v1.2.1
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
golang.org/x/time v0.5.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/stretchr/objx v0.5.0 // indirect
go.uber.org/mock v0.4.0 // indirect
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.11.1 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
go.uber.org/mock v0.5.0 // indirect
golang.org/x/crypto v0.26.0 // indirect
golang.org/x/mod v0.18.0 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/tools v0.22.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View file

@ -1,5 +1,5 @@
github.com/apernet/quic-go v0.42.1-0.20240323215309-32a339817822 h1:+ZSzRxSMg1+fLTQKcIUvD2cCCgS+1rtyRhs+NL5oBgA=
github.com/apernet/quic-go v0.42.1-0.20240323215309-32a339817822/go.mod h1:j3QaAM7sVJqptDQyIQRWA6mASCfuxoHJszn67JQh1GE=
github.com/apernet/quic-go v0.52.1-0.20250607183305-9320c9d14431 h1:9/jM7e+kVALd7Jfu1c27dcEpT/Fd/Gzq2OsQjKjakKI=
github.com/apernet/quic-go v0.52.1-0.20250607183305-9320c9d14431/go.mod h1:I/47OIGG5H/IfAm+nz2c6hm6b/NkEhpvptAoiPcY7jQ=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@ -11,62 +11,66 @@ github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.11.1 h1:ojD5zOW8+7dOGzdnNgersm8aPfcDjhMp12UfG93NIMc=
golang.org/x/tools v0.11.1/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -4,11 +4,13 @@ import (
"fmt"
"math/rand"
"net"
"os"
"strconv"
"time"
"github.com/apernet/quic-go/congestion"
"github.com/apernet/hysteria/core/internal/congestion/common"
"github.com/apernet/hysteria/core/v2/internal/congestion/common"
)
// BbrSender implements BBR congestion control algorithm. BBR aims to estimate
@ -37,6 +39,8 @@ const (
derivedHighGain = 2.773
// The newly derived CWND gain for STARTUP, 2.
derivedHighCWNDGain = 2.0
debugEnv = "HYSTERIA_BBR_DEBUG"
)
// The cycle of gains used during the PROBE_BW stage.
@ -61,7 +65,7 @@ const (
// Flag.
defaultStartupFullLossCount = 8
quicBbr2DefaultLossThreshold = 0.02
maxBbrBurstPackets = 3
maxBbrBurstPackets = 10
)
type bbrMode int
@ -237,6 +241,8 @@ type bbrSender struct {
maxDatagramSize congestion.ByteCount
// Recorded on packet sent. equivalent |unacked_packets_->bytes_in_flight()|
bytesInFlight congestion.ByteCount
debug bool
}
var _ congestion.CongestionControl = &bbrSender{}
@ -259,6 +265,7 @@ func newBbrSender(
initialCongestionWindow,
initialMaxCongestionWindow congestion.ByteCount,
) *bbrSender {
debug, _ := strconv.ParseBool(os.Getenv(debugEnv))
b := &bbrSender{
clock: clock,
mode: bbrModeStartup,
@ -284,6 +291,7 @@ func newBbrSender(
cwndToCalculateMinPacingRate: initialCongestionWindow,
maxCongestionWindowWithNetworkParametersAdjusted: initialMaxCongestionWindow,
maxDatagramSize: initialMaxDatagramSize,
debug: debug,
}
b.pacer = common.NewPacer(b.bandwidthForPacer)
@ -411,7 +419,7 @@ func (b *bbrSender) OnCongestionEventEx(priorInFlight congestion.ByteCount, even
// packet in lost_packets.
var lastPacketSendState sendTimeState
b.maybeApplimited(priorInFlight)
b.maybeAppLimited(priorInFlight)
// Update bytesInFlight
b.bytesInFlight = priorInFlight
@ -539,7 +547,7 @@ func (b *bbrSender) setDrainGain(drainGain float64) {
b.drainGain = drainGain
}
// What's the current estimated bandwidth in bytes per second.
// Get the current bandwidth estimate. Note that Bandwidth is in bits per second.
func (b *bbrSender) bandwidthEstimate() Bandwidth {
return b.maxBandwidth.GetBest()
}
@ -607,6 +615,10 @@ func (b *bbrSender) enterStartupMode(now time.Time) {
// b.maybeTraceStateChange(logging.CongestionStateStartup)
b.pacingGain = b.highGain
b.congestionWindowGain = b.highCwndGain
if b.debug {
b.debugPrint("Phase: STARTUP")
}
}
// Enters the PROBE_BW mode.
@ -625,6 +637,10 @@ func (b *bbrSender) enterProbeBandwidthMode(now time.Time) {
b.lastCycleStart = now
b.pacingGain = pacingGain[b.cycleCurrentOffset]
if b.debug {
b.debugPrint("Phase: PROBE_BW")
}
}
// Updates the round-trip counter if a round-trip has passed. Returns true if
@ -698,14 +714,8 @@ func (b *bbrSender) checkIfFullBandwidthReached(lastPacketSendState *sendTimeSta
}
}
func (b *bbrSender) maybeApplimited(bytesInFlight congestion.ByteCount) {
congestionWindow := b.GetCongestionWindow()
if bytesInFlight >= congestionWindow {
return
}
availableBytes := congestionWindow - bytesInFlight
drainLimited := b.mode == bbrModeDrain && bytesInFlight > congestionWindow/2
if !drainLimited || availableBytes > maxBbrBurstPackets*b.maxDatagramSize {
func (b *bbrSender) maybeAppLimited(bytesInFlight congestion.ByteCount) {
if bytesInFlight < b.getTargetCongestionWindow(1) {
b.sampler.OnAppLimited()
}
}
@ -718,6 +728,10 @@ func (b *bbrSender) maybeExitStartupOrDrain(now time.Time) {
// b.maybeTraceStateChange(logging.CongestionStateDrain)
b.pacingGain = b.drainGain
b.congestionWindowGain = b.highCwndGain
if b.debug {
b.debugPrint("Phase: DRAIN")
}
}
if b.mode == bbrModeDrain && b.bytesInFlight <= b.getTargetCongestionWindow(1) {
b.enterProbeBandwidthMode(now)
@ -733,6 +747,12 @@ func (b *bbrSender) maybeEnterOrExitProbeRtt(now time.Time, isRoundStart, minRtt
// Do not decide on the time to exit PROBE_RTT until the |bytes_in_flight|
// is at the target small value.
b.exitProbeRttAt = time.Time{}
if b.debug {
b.debugPrint("BandwidthEstimate: %s, CongestionWindowGain: %.2f, PacingGain: %.2f, PacingRate: %s",
formatSpeed(b.bandwidthEstimate()), b.congestionWindowGain, b.pacingGain, formatSpeed(b.PacingRate()))
b.debugPrint("Phase: PROBE_RTT")
}
}
if b.mode == bbrModeProbeRtt {
@ -754,6 +774,9 @@ func (b *bbrSender) maybeEnterOrExitProbeRtt(now time.Time, isRoundStart, minRtt
}
if now.Sub(b.exitProbeRttAt) >= 0 && b.probeRttRoundPassed {
b.minRttTimestamp = now
if b.debug {
b.debugPrint("MinRTT: %s", b.getMinRtt())
}
if !b.isAtFullBandwidth {
b.enterStartupMode(now)
} else {
@ -925,6 +948,12 @@ func (b *bbrSender) shouldExitStartupDueToLoss(lastPacketSendState *sendTimeStat
return false
}
func (b *bbrSender) debugPrint(format string, a ...any) {
fmt.Printf("[BBRSender] [%s] %s\n",
time.Now().Format("15:04:05"),
fmt.Sprintf(format, a...))
}
func bdpFromRttAndBandwidth(rtt time.Duration, bandwidth Bandwidth) congestion.ByteCount {
return congestion.ByteCount(rtt) * congestion.ByteCount(bandwidth) / congestion.ByteCount(BytesPerSecond) / congestion.ByteCount(time.Second)
}
@ -942,3 +971,14 @@ func GetInitialPacketSize(addr net.Addr) congestion.ByteCount {
return congestion.MinInitialPacketSize
}
}
func formatSpeed(bw Bandwidth) string {
bwf := float64(bw)
units := []string{"bps", "Kbps", "Mbps", "Gbps"}
unitIndex := 0
for bwf > 1000 && unitIndex < len(units)-1 {
bwf /= 1000
unitIndex++
}
return fmt.Sprintf("%.2f %s", bwf, units[unitIndex])
}

View file

@ -152,7 +152,7 @@ func (p *packetNumberIndexedQueue[T]) EntrySlotsUsed() int {
return p.entries.Len()
}
// LastPacket returns packet number of the first entry in the queue.
// FirstPacket returns packet number of the first entry in the queue.
func (p *packetNumberIndexedQueue[T]) FirstPacket() (packetNumber congestion.PacketNumber) {
return p.firstPacket
}

View file

@ -6,7 +6,7 @@ import (
"strconv"
"time"
"github.com/apernet/hysteria/core/internal/congestion/common"
"github.com/apernet/hysteria/core/v2/internal/congestion/common"
"github.com/apernet/quic-go/congestion"
)

View file

@ -1,7 +1,6 @@
package common
import (
"math"
"time"
"github.com/apernet/quic-go/congestion"
@ -9,6 +8,7 @@ import (
const (
maxBurstPackets = 10
maxBurstPacingDelayMultiplier = 4
)
// Pacer implements a token bucket pacing algorithm.
@ -46,12 +46,12 @@ func (p *Pacer) Budget(now time.Time) congestion.ByteCount {
if budget < 0 { // protect against overflows
budget = congestion.ByteCount(1<<62 - 1)
}
return minByteCount(p.maxBurstSize(), budget)
return min(p.maxBurstSize(), budget)
}
func (p *Pacer) maxBurstSize() congestion.ByteCount {
return maxByteCount(
congestion.ByteCount((congestion.MinPacingDelay+time.Millisecond).Nanoseconds())*p.getBandwidth()/1e9,
return max(
congestion.ByteCount((maxBurstPacingDelayMultiplier*congestion.MinPacingDelay).Nanoseconds())*p.getBandwidth()/1e9,
maxBurstPackets*p.maxDatagramSize,
)
}
@ -62,34 +62,18 @@ func (p *Pacer) TimeUntilSend() time.Time {
if p.budgetAtLastSent >= p.maxDatagramSize {
return time.Time{}
}
return p.lastSentTime.Add(maxDuration(
congestion.MinPacingDelay,
time.Duration(math.Ceil(float64(p.maxDatagramSize-p.budgetAtLastSent)*1e9/
float64(p.getBandwidth())))*time.Nanosecond,
))
diff := 1e9 * uint64(p.maxDatagramSize-p.budgetAtLastSent)
bw := uint64(p.getBandwidth())
// We might need to round up this value.
// Otherwise, we might have a budget (slightly) smaller than the datagram size when the timer expires.
d := diff / bw
// this is effectively a math.Ceil, but using only integer math
if diff%bw > 0 {
d++
}
return p.lastSentTime.Add(max(congestion.MinPacingDelay, time.Duration(d)*time.Nanosecond))
}
func (p *Pacer) SetMaxDatagramSize(s congestion.ByteCount) {
p.maxDatagramSize = s
}
func maxByteCount(a, b congestion.ByteCount) congestion.ByteCount {
if a < b {
return b
}
return a
}
func minByteCount(a, b congestion.ByteCount) congestion.ByteCount {
if a < b {
return a
}
return b
}
func maxDuration(a, b time.Duration) time.Duration {
if a > b {
return a
}
return b
}

View file

@ -1,8 +1,8 @@
package congestion
import (
"github.com/apernet/hysteria/core/internal/congestion/bbr"
"github.com/apernet/hysteria/core/internal/congestion/brutal"
"github.com/apernet/hysteria/core/v2/internal/congestion/bbr"
"github.com/apernet/hysteria/core/v2/internal/congestion/brutal"
"github.com/apernet/quic-go"
)

View file

@ -1,7 +1,7 @@
package frag
import (
"github.com/apernet/hysteria/core/internal/protocol"
"github.com/apernet/hysteria/core/v2/internal/protocol"
)
func FragUDPMessage(m *protocol.UDPMessage, maxSize int) []protocol.UDPMessage {

View file

@ -4,7 +4,7 @@ import (
"reflect"
"testing"
"github.com/apernet/hysteria/core/internal/protocol"
"github.com/apernet/hysteria/core/v2/internal/protocol"
)
func TestFragUDPMessage(t *testing.T) {

View file

@ -7,7 +7,7 @@ packages:
Conn:
config:
mockname: MockConn
github.com/apernet/hysteria/core/server:
github.com/apernet/hysteria/core/v2/server:
interfaces:
Outbound:
config:
@ -24,3 +24,6 @@ packages:
TrafficLogger:
config:
mockname: MockTrafficLogger
RequestHook:
config:
mockname: MockRequestHook

View file

@ -9,10 +9,10 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/apernet/hysteria/core/client"
"github.com/apernet/hysteria/core/errors"
"github.com/apernet/hysteria/core/internal/integration_tests/mocks"
"github.com/apernet/hysteria/core/server"
"github.com/apernet/hysteria/core/v2/client"
"github.com/apernet/hysteria/core/v2/errors"
"github.com/apernet/hysteria/core/v2/internal/integration_tests/mocks"
"github.com/apernet/hysteria/core/v2/server"
)
// TestClientServerTCPClose tests whether the client/server propagates the close of a connection correctly.

View file

@ -0,0 +1,147 @@
package integration_tests
import (
"io"
"net"
"testing"
"github.com/apernet/hysteria/core/v2/client"
"github.com/apernet/hysteria/core/v2/internal/integration_tests/mocks"
"github.com/apernet/hysteria/core/v2/server"
"github.com/apernet/quic-go"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
func TestClientServerHookTCP(t *testing.T) {
fakeEchoAddr := "hahanope:6666"
realEchoAddr := "127.0.0.1:22333"
// Create server
udpConn, udpAddr, err := serverConn()
assert.NoError(t, err)
auth := mocks.NewMockAuthenticator(t)
auth.EXPECT().Authenticate(mock.Anything, mock.Anything, mock.Anything).Return(true, "nobody")
hook := mocks.NewMockRequestHook(t)
hook.EXPECT().Check(false, fakeEchoAddr).Return(true).Once()
hook.EXPECT().TCP(mock.Anything, mock.Anything).RunAndReturn(func(stream quic.Stream, s *string) ([]byte, error) {
assert.Equal(t, fakeEchoAddr, *s)
// Change the address
*s = realEchoAddr
// Read the first 5 bytes and replace them with "byeee"
data := make([]byte, 5)
_, err := io.ReadFull(stream, data)
if err != nil {
return nil, err
}
assert.Equal(t, []byte("hello"), data)
return []byte("byeee"), nil
}).Once()
s, err := server.NewServer(&server.Config{
TLSConfig: serverTLSConfig(),
Conn: udpConn,
RequestHook: hook,
Authenticator: auth,
})
assert.NoError(t, err)
defer s.Close()
go s.Serve()
// Create TCP echo server
echoListener, err := net.Listen("tcp", realEchoAddr)
assert.NoError(t, err)
echoServer := &tcpEchoServer{Listener: echoListener}
defer echoServer.Close()
go echoServer.Serve()
// Create client
c, _, err := client.NewClient(&client.Config{
ServerAddr: udpAddr,
TLSConfig: client.TLSConfig{InsecureSkipVerify: true},
})
assert.NoError(t, err)
defer c.Close()
// Dial TCP
conn, err := c.TCP(fakeEchoAddr)
assert.NoError(t, err)
defer conn.Close()
// Send and receive data
sData := []byte("hello world")
_, err = conn.Write(sData)
assert.NoError(t, err)
rData := make([]byte, len(sData))
_, err = io.ReadFull(conn, rData)
assert.NoError(t, err)
assert.Equal(t, []byte("byeee world"), rData)
}
func TestClientServerHookUDP(t *testing.T) {
fakeEchoAddr := "hahanope:6666"
realEchoAddr := "127.0.0.1:22333"
// Create server
udpConn, udpAddr, err := serverConn()
assert.NoError(t, err)
auth := mocks.NewMockAuthenticator(t)
auth.EXPECT().Authenticate(mock.Anything, mock.Anything, mock.Anything).Return(true, "nobody")
hook := mocks.NewMockRequestHook(t)
hook.EXPECT().Check(true, fakeEchoAddr).Return(true).Once()
hook.EXPECT().UDP(mock.Anything, mock.Anything).RunAndReturn(func(bytes []byte, s *string) error {
assert.Equal(t, fakeEchoAddr, *s)
assert.Equal(t, []byte("hello world"), bytes)
// Change the address
*s = realEchoAddr
return nil
}).Once()
s, err := server.NewServer(&server.Config{
TLSConfig: serverTLSConfig(),
Conn: udpConn,
RequestHook: hook,
Authenticator: auth,
})
assert.NoError(t, err)
defer s.Close()
go s.Serve()
// Create UDP echo server
echoConn, err := net.ListenPacket("udp", realEchoAddr)
assert.NoError(t, err)
echoServer := &udpEchoServer{Conn: echoConn}
defer echoServer.Close()
go echoServer.Serve()
// Create client
c, _, err := client.NewClient(&client.Config{
ServerAddr: udpAddr,
TLSConfig: client.TLSConfig{InsecureSkipVerify: true},
})
assert.NoError(t, err)
defer c.Close()
// Listen UDP
conn, err := c.UDP()
assert.NoError(t, err)
defer conn.Close()
// Send and receive data
sData := []byte("hello world")
err = conn.Send(sData, fakeEchoAddr)
assert.NoError(t, err)
rData, rAddr, err := conn.Receive()
assert.NoError(t, err)
assert.Equal(t, sData, rData)
// Hook address change is transparent,
// the client should still see the fake echo address it sent packets to
assert.Equal(t, fakeEchoAddr, rAddr)
// Subsequent packets should also be sent to the real echo server
sData = []byte("never stop fighting")
err = conn.Send(sData, fakeEchoAddr)
assert.NoError(t, err)
rData, rAddr, err = conn.Receive()
assert.NoError(t, err)
assert.Equal(t, sData, rData)
assert.Equal(t, fakeEchoAddr, rAddr)
}

View file

@ -12,9 +12,9 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/apernet/hysteria/core/internal/integration_tests/mocks"
"github.com/apernet/hysteria/core/internal/protocol"
"github.com/apernet/hysteria/core/server"
"github.com/apernet/hysteria/core/v2/internal/integration_tests/mocks"
"github.com/apernet/hysteria/core/v2/internal/protocol"
"github.com/apernet/hysteria/core/v2/server"
"github.com/apernet/quic-go"
"github.com/apernet/quic-go/http3"
@ -41,7 +41,6 @@ func TestServerMasquerade(t *testing.T) {
// QUIC connection & RoundTripper
var conn quic.EarlyConnection
rt := &http3.RoundTripper{
EnableDatagrams: true,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},

View file

@ -1,4 +1,4 @@
// Code generated by mockery v2.32.0. DO NOT EDIT.
// Code generated by mockery v2.43.0. DO NOT EDIT.
package mocks
@ -25,6 +25,10 @@ func (_m *MockAuthenticator) EXPECT() *MockAuthenticator_Expecter {
func (_m *MockAuthenticator) Authenticate(addr net.Addr, auth string, tx uint64) (bool, string) {
ret := _m.Called(addr, auth, tx)
if len(ret) == 0 {
panic("no return value specified for Authenticate")
}
var r0 bool
var r1 string
if rf, ok := ret.Get(0).(func(net.Addr, string, uint64) (bool, string)); ok {

View file

@ -1,4 +1,4 @@
// Code generated by mockery v2.32.0. DO NOT EDIT.
// Code generated by mockery v2.43.0. DO NOT EDIT.
package mocks
@ -27,6 +27,10 @@ func (_m *MockConn) EXPECT() *MockConn_Expecter {
func (_m *MockConn) Close() error {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Close")
}
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
@ -68,6 +72,10 @@ func (_c *MockConn_Close_Call) RunAndReturn(run func() error) *MockConn_Close_Ca
func (_m *MockConn) LocalAddr() net.Addr {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for LocalAddr")
}
var r0 net.Addr
if rf, ok := ret.Get(0).(func() net.Addr); ok {
r0 = rf()
@ -111,6 +119,10 @@ func (_c *MockConn_LocalAddr_Call) RunAndReturn(run func() net.Addr) *MockConn_L
func (_m *MockConn) Read(b []byte) (int, error) {
ret := _m.Called(b)
if len(ret) == 0 {
panic("no return value specified for Read")
}
var r0 int
var r1 error
if rf, ok := ret.Get(0).(func([]byte) (int, error)); ok {
@ -163,6 +175,10 @@ func (_c *MockConn_Read_Call) RunAndReturn(run func([]byte) (int, error)) *MockC
func (_m *MockConn) RemoteAddr() net.Addr {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for RemoteAddr")
}
var r0 net.Addr
if rf, ok := ret.Get(0).(func() net.Addr); ok {
r0 = rf()
@ -206,6 +222,10 @@ func (_c *MockConn_RemoteAddr_Call) RunAndReturn(run func() net.Addr) *MockConn_
func (_m *MockConn) SetDeadline(t time.Time) error {
ret := _m.Called(t)
if len(ret) == 0 {
panic("no return value specified for SetDeadline")
}
var r0 error
if rf, ok := ret.Get(0).(func(time.Time) error); ok {
r0 = rf(t)
@ -248,6 +268,10 @@ func (_c *MockConn_SetDeadline_Call) RunAndReturn(run func(time.Time) error) *Mo
func (_m *MockConn) SetReadDeadline(t time.Time) error {
ret := _m.Called(t)
if len(ret) == 0 {
panic("no return value specified for SetReadDeadline")
}
var r0 error
if rf, ok := ret.Get(0).(func(time.Time) error); ok {
r0 = rf(t)
@ -290,6 +314,10 @@ func (_c *MockConn_SetReadDeadline_Call) RunAndReturn(run func(time.Time) error)
func (_m *MockConn) SetWriteDeadline(t time.Time) error {
ret := _m.Called(t)
if len(ret) == 0 {
panic("no return value specified for SetWriteDeadline")
}
var r0 error
if rf, ok := ret.Get(0).(func(time.Time) error); ok {
r0 = rf(t)
@ -332,6 +360,10 @@ func (_c *MockConn_SetWriteDeadline_Call) RunAndReturn(run func(time.Time) error
func (_m *MockConn) Write(b []byte) (int, error) {
ret := _m.Called(b)
if len(ret) == 0 {
panic("no return value specified for Write")
}
var r0 int
var r1 error
if rf, ok := ret.Get(0).(func([]byte) (int, error)); ok {

View file

@ -1,4 +1,4 @@
// Code generated by mockery v2.32.0. DO NOT EDIT.
// Code generated by mockery v2.43.0. DO NOT EDIT.
package mocks

View file

@ -1,4 +1,4 @@
// Code generated by mockery v2.32.0. DO NOT EDIT.
// Code generated by mockery v2.43.0. DO NOT EDIT.
package mocks
@ -7,7 +7,7 @@ import (
mock "github.com/stretchr/testify/mock"
server "github.com/apernet/hysteria/core/server"
server "github.com/apernet/hysteria/core/v2/server"
)
// MockOutbound is an autogenerated mock type for the Outbound type
@ -27,6 +27,10 @@ func (_m *MockOutbound) EXPECT() *MockOutbound_Expecter {
func (_m *MockOutbound) TCP(reqAddr string) (net.Conn, error) {
ret := _m.Called(reqAddr)
if len(ret) == 0 {
panic("no return value specified for TCP")
}
var r0 net.Conn
var r1 error
if rf, ok := ret.Get(0).(func(string) (net.Conn, error)); ok {
@ -81,6 +85,10 @@ func (_c *MockOutbound_TCP_Call) RunAndReturn(run func(string) (net.Conn, error)
func (_m *MockOutbound) UDP(reqAddr string) (server.UDPConn, error) {
ret := _m.Called(reqAddr)
if len(ret) == 0 {
panic("no return value specified for UDP")
}
var r0 server.UDPConn
var r1 error
if rf, ok := ret.Get(0).(func(string) (server.UDPConn, error)); ok {

View file

@ -0,0 +1,188 @@
// Code generated by mockery v2.43.0. DO NOT EDIT.
package mocks
import (
quic "github.com/apernet/quic-go"
mock "github.com/stretchr/testify/mock"
)
// MockRequestHook is an autogenerated mock type for the RequestHook type
type MockRequestHook struct {
mock.Mock
}
type MockRequestHook_Expecter struct {
mock *mock.Mock
}
func (_m *MockRequestHook) EXPECT() *MockRequestHook_Expecter {
return &MockRequestHook_Expecter{mock: &_m.Mock}
}
// Check provides a mock function with given fields: isUDP, reqAddr
func (_m *MockRequestHook) Check(isUDP bool, reqAddr string) bool {
ret := _m.Called(isUDP, reqAddr)
if len(ret) == 0 {
panic("no return value specified for Check")
}
var r0 bool
if rf, ok := ret.Get(0).(func(bool, string) bool); ok {
r0 = rf(isUDP, reqAddr)
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// MockRequestHook_Check_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Check'
type MockRequestHook_Check_Call struct {
*mock.Call
}
// Check is a helper method to define mock.On call
// - isUDP bool
// - reqAddr string
func (_e *MockRequestHook_Expecter) Check(isUDP interface{}, reqAddr interface{}) *MockRequestHook_Check_Call {
return &MockRequestHook_Check_Call{Call: _e.mock.On("Check", isUDP, reqAddr)}
}
func (_c *MockRequestHook_Check_Call) Run(run func(isUDP bool, reqAddr string)) *MockRequestHook_Check_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(bool), args[1].(string))
})
return _c
}
func (_c *MockRequestHook_Check_Call) Return(_a0 bool) *MockRequestHook_Check_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockRequestHook_Check_Call) RunAndReturn(run func(bool, string) bool) *MockRequestHook_Check_Call {
_c.Call.Return(run)
return _c
}
// TCP provides a mock function with given fields: stream, reqAddr
func (_m *MockRequestHook) TCP(stream quic.Stream, reqAddr *string) ([]byte, error) {
ret := _m.Called(stream, reqAddr)
if len(ret) == 0 {
panic("no return value specified for TCP")
}
var r0 []byte
var r1 error
if rf, ok := ret.Get(0).(func(quic.Stream, *string) ([]byte, error)); ok {
return rf(stream, reqAddr)
}
if rf, ok := ret.Get(0).(func(quic.Stream, *string) []byte); ok {
r0 = rf(stream, reqAddr)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]byte)
}
}
if rf, ok := ret.Get(1).(func(quic.Stream, *string) error); ok {
r1 = rf(stream, reqAddr)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockRequestHook_TCP_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'TCP'
type MockRequestHook_TCP_Call struct {
*mock.Call
}
// TCP is a helper method to define mock.On call
// - stream quic.Stream
// - reqAddr *string
func (_e *MockRequestHook_Expecter) TCP(stream interface{}, reqAddr interface{}) *MockRequestHook_TCP_Call {
return &MockRequestHook_TCP_Call{Call: _e.mock.On("TCP", stream, reqAddr)}
}
func (_c *MockRequestHook_TCP_Call) Run(run func(stream quic.Stream, reqAddr *string)) *MockRequestHook_TCP_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(quic.Stream), args[1].(*string))
})
return _c
}
func (_c *MockRequestHook_TCP_Call) Return(_a0 []byte, _a1 error) *MockRequestHook_TCP_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockRequestHook_TCP_Call) RunAndReturn(run func(quic.Stream, *string) ([]byte, error)) *MockRequestHook_TCP_Call {
_c.Call.Return(run)
return _c
}
// UDP provides a mock function with given fields: data, reqAddr
func (_m *MockRequestHook) UDP(data []byte, reqAddr *string) error {
ret := _m.Called(data, reqAddr)
if len(ret) == 0 {
panic("no return value specified for UDP")
}
var r0 error
if rf, ok := ret.Get(0).(func([]byte, *string) error); ok {
r0 = rf(data, reqAddr)
} else {
r0 = ret.Error(0)
}
return r0
}
// MockRequestHook_UDP_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UDP'
type MockRequestHook_UDP_Call struct {
*mock.Call
}
// UDP is a helper method to define mock.On call
// - data []byte
// - reqAddr *string
func (_e *MockRequestHook_Expecter) UDP(data interface{}, reqAddr interface{}) *MockRequestHook_UDP_Call {
return &MockRequestHook_UDP_Call{Call: _e.mock.On("UDP", data, reqAddr)}
}
func (_c *MockRequestHook_UDP_Call) Run(run func(data []byte, reqAddr *string)) *MockRequestHook_UDP_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].([]byte), args[1].(*string))
})
return _c
}
func (_c *MockRequestHook_UDP_Call) Return(_a0 error) *MockRequestHook_UDP_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockRequestHook_UDP_Call) RunAndReturn(run func([]byte, *string) error) *MockRequestHook_UDP_Call {
_c.Call.Return(run)
return _c
}
// NewMockRequestHook creates a new instance of MockRequestHook. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewMockRequestHook(t interface {
mock.TestingT
Cleanup(func())
}) *MockRequestHook {
mock := &MockRequestHook{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View file

@ -1,8 +1,13 @@
// Code generated by mockery v2.32.0. DO NOT EDIT.
// Code generated by mockery v2.43.0. DO NOT EDIT.
package mocks
import mock "github.com/stretchr/testify/mock"
import (
quic "github.com/apernet/quic-go"
mock "github.com/stretchr/testify/mock"
server "github.com/apernet/hysteria/core/v2/server"
)
// MockTrafficLogger is an autogenerated mock type for the TrafficLogger type
type MockTrafficLogger struct {
@ -17,10 +22,48 @@ func (_m *MockTrafficLogger) EXPECT() *MockTrafficLogger_Expecter {
return &MockTrafficLogger_Expecter{mock: &_m.Mock}
}
// Log provides a mock function with given fields: id, tx, rx
func (_m *MockTrafficLogger) Log(id string, tx uint64, rx uint64) bool {
// LogOnlineState provides a mock function with given fields: id, online
func (_m *MockTrafficLogger) LogOnlineState(id string, online bool) {
_m.Called(id, online)
}
// MockTrafficLogger_LogOnlineState_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LogOnlineState'
type MockTrafficLogger_LogOnlineState_Call struct {
*mock.Call
}
// LogOnlineState is a helper method to define mock.On call
// - id string
// - online bool
func (_e *MockTrafficLogger_Expecter) LogOnlineState(id interface{}, online interface{}) *MockTrafficLogger_LogOnlineState_Call {
return &MockTrafficLogger_LogOnlineState_Call{Call: _e.mock.On("LogOnlineState", id, online)}
}
func (_c *MockTrafficLogger_LogOnlineState_Call) Run(run func(id string, online bool)) *MockTrafficLogger_LogOnlineState_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(string), args[1].(bool))
})
return _c
}
func (_c *MockTrafficLogger_LogOnlineState_Call) Return() *MockTrafficLogger_LogOnlineState_Call {
_c.Call.Return()
return _c
}
func (_c *MockTrafficLogger_LogOnlineState_Call) RunAndReturn(run func(string, bool)) *MockTrafficLogger_LogOnlineState_Call {
_c.Call.Return(run)
return _c
}
// LogTraffic provides a mock function with given fields: id, tx, rx
func (_m *MockTrafficLogger) LogTraffic(id string, tx uint64, rx uint64) bool {
ret := _m.Called(id, tx, rx)
if len(ret) == 0 {
panic("no return value specified for LogTraffic")
}
var r0 bool
if rf, ok := ret.Get(0).(func(string, uint64, uint64) bool); ok {
r0 = rf(id, tx, rx)
@ -31,32 +74,99 @@ func (_m *MockTrafficLogger) Log(id string, tx uint64, rx uint64) bool {
return r0
}
// MockTrafficLogger_Log_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Log'
type MockTrafficLogger_Log_Call struct {
// MockTrafficLogger_LogTraffic_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LogTraffic'
type MockTrafficLogger_LogTraffic_Call struct {
*mock.Call
}
// Log is a helper method to define mock.On call
// LogTraffic is a helper method to define mock.On call
// - id string
// - tx uint64
// - rx uint64
func (_e *MockTrafficLogger_Expecter) Log(id interface{}, tx interface{}, rx interface{}) *MockTrafficLogger_Log_Call {
return &MockTrafficLogger_Log_Call{Call: _e.mock.On("Log", id, tx, rx)}
func (_e *MockTrafficLogger_Expecter) LogTraffic(id interface{}, tx interface{}, rx interface{}) *MockTrafficLogger_LogTraffic_Call {
return &MockTrafficLogger_LogTraffic_Call{Call: _e.mock.On("LogTraffic", id, tx, rx)}
}
func (_c *MockTrafficLogger_Log_Call) Run(run func(id string, tx uint64, rx uint64)) *MockTrafficLogger_Log_Call {
func (_c *MockTrafficLogger_LogTraffic_Call) Run(run func(id string, tx uint64, rx uint64)) *MockTrafficLogger_LogTraffic_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(string), args[1].(uint64), args[2].(uint64))
})
return _c
}
func (_c *MockTrafficLogger_Log_Call) Return(ok bool) *MockTrafficLogger_Log_Call {
func (_c *MockTrafficLogger_LogTraffic_Call) Return(ok bool) *MockTrafficLogger_LogTraffic_Call {
_c.Call.Return(ok)
return _c
}
func (_c *MockTrafficLogger_Log_Call) RunAndReturn(run func(string, uint64, uint64) bool) *MockTrafficLogger_Log_Call {
func (_c *MockTrafficLogger_LogTraffic_Call) RunAndReturn(run func(string, uint64, uint64) bool) *MockTrafficLogger_LogTraffic_Call {
_c.Call.Return(run)
return _c
}
// TraceStream provides a mock function with given fields: stream, stats
func (_m *MockTrafficLogger) TraceStream(stream quic.Stream, stats *server.StreamStats) {
_m.Called(stream, stats)
}
// MockTrafficLogger_TraceStream_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'TraceStream'
type MockTrafficLogger_TraceStream_Call struct {
*mock.Call
}
// TraceStream is a helper method to define mock.On call
// - stream quic.Stream
// - stats *server.StreamStats
func (_e *MockTrafficLogger_Expecter) TraceStream(stream interface{}, stats interface{}) *MockTrafficLogger_TraceStream_Call {
return &MockTrafficLogger_TraceStream_Call{Call: _e.mock.On("TraceStream", stream, stats)}
}
func (_c *MockTrafficLogger_TraceStream_Call) Run(run func(stream quic.Stream, stats *server.StreamStats)) *MockTrafficLogger_TraceStream_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(quic.Stream), args[1].(*server.StreamStats))
})
return _c
}
func (_c *MockTrafficLogger_TraceStream_Call) Return() *MockTrafficLogger_TraceStream_Call {
_c.Call.Return()
return _c
}
func (_c *MockTrafficLogger_TraceStream_Call) RunAndReturn(run func(quic.Stream, *server.StreamStats)) *MockTrafficLogger_TraceStream_Call {
_c.Call.Return(run)
return _c
}
// UntraceStream provides a mock function with given fields: stream
func (_m *MockTrafficLogger) UntraceStream(stream quic.Stream) {
_m.Called(stream)
}
// MockTrafficLogger_UntraceStream_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UntraceStream'
type MockTrafficLogger_UntraceStream_Call struct {
*mock.Call
}
// UntraceStream is a helper method to define mock.On call
// - stream quic.Stream
func (_e *MockTrafficLogger_Expecter) UntraceStream(stream interface{}) *MockTrafficLogger_UntraceStream_Call {
return &MockTrafficLogger_UntraceStream_Call{Call: _e.mock.On("UntraceStream", stream)}
}
func (_c *MockTrafficLogger_UntraceStream_Call) Run(run func(stream quic.Stream)) *MockTrafficLogger_UntraceStream_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(quic.Stream))
})
return _c
}
func (_c *MockTrafficLogger_UntraceStream_Call) Return() *MockTrafficLogger_UntraceStream_Call {
_c.Call.Return()
return _c
}
func (_c *MockTrafficLogger_UntraceStream_Call) RunAndReturn(run func(quic.Stream)) *MockTrafficLogger_UntraceStream_Call {
_c.Call.Return(run)
return _c
}

View file

@ -1,4 +1,4 @@
// Code generated by mockery v2.32.0. DO NOT EDIT.
// Code generated by mockery v2.43.0. DO NOT EDIT.
package mocks
@ -21,6 +21,10 @@ func (_m *MockUDPConn) EXPECT() *MockUDPConn_Expecter {
func (_m *MockUDPConn) Close() error {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Close")
}
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
@ -62,6 +66,10 @@ func (_c *MockUDPConn_Close_Call) RunAndReturn(run func() error) *MockUDPConn_Cl
func (_m *MockUDPConn) ReadFrom(b []byte) (int, string, error) {
ret := _m.Called(b)
if len(ret) == 0 {
panic("no return value specified for ReadFrom")
}
var r0 int
var r1 string
var r2 error
@ -121,6 +129,10 @@ func (_c *MockUDPConn_ReadFrom_Call) RunAndReturn(run func([]byte) (int, string,
func (_m *MockUDPConn) WriteTo(b []byte, addr string) (int, error) {
ret := _m.Called(b, addr)
if len(ret) == 0 {
panic("no return value specified for WriteTo")
}
var r0 int
var r1 error
if rf, ok := ret.Get(0).(func([]byte, string) (int, error)); ok {

View file

@ -8,10 +8,10 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/apernet/hysteria/core/client"
coreErrs "github.com/apernet/hysteria/core/errors"
"github.com/apernet/hysteria/core/internal/integration_tests/mocks"
"github.com/apernet/hysteria/core/server"
"github.com/apernet/hysteria/core/v2/client"
coreErrs "github.com/apernet/hysteria/core/v2/errors"
"github.com/apernet/hysteria/core/v2/internal/integration_tests/mocks"
"github.com/apernet/hysteria/core/v2/server"
)
// Smoke tests that act as a sanity check for client & server to ensure they can talk to each other correctly.

View file

@ -13,9 +13,9 @@ import (
"github.com/stretchr/testify/mock"
"golang.org/x/time/rate"
"github.com/apernet/hysteria/core/client"
"github.com/apernet/hysteria/core/internal/integration_tests/mocks"
"github.com/apernet/hysteria/core/server"
"github.com/apernet/hysteria/core/v2/client"
"github.com/apernet/hysteria/core/v2/internal/integration_tests/mocks"
"github.com/apernet/hysteria/core/v2/server"
)
type tcpStressor struct {

View file

@ -9,9 +9,9 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/apernet/hysteria/core/client"
"github.com/apernet/hysteria/core/internal/integration_tests/mocks"
"github.com/apernet/hysteria/core/server"
"github.com/apernet/hysteria/core/v2/client"
"github.com/apernet/hysteria/core/v2/internal/integration_tests/mocks"
"github.com/apernet/hysteria/core/v2/server"
)
// TestClientServerTrafficLoggerTCP tests that the traffic logger is correctly called for TCP connections,
@ -36,6 +36,7 @@ func TestClientServerTrafficLoggerTCP(t *testing.T) {
go s.Serve()
// Create client
trafficLogger.EXPECT().LogOnlineState("nobody", true).Return().Once()
c, _, err := client.NewClient(&client.Config{
ServerAddr: udpAddr,
TLSConfig: client.TLSConfig{InsecureSkipVerify: true},
@ -61,12 +62,13 @@ func TestClientServerTrafficLoggerTCP(t *testing.T) {
return nil
})
serverOb.EXPECT().TCP(addr).Return(sobConn, nil).Once()
trafficLogger.EXPECT().TraceStream(mock.Anything, mock.Anything).Return().Once()
conn, err := c.TCP(addr)
assert.NoError(t, err)
// Client reads from server
trafficLogger.EXPECT().Log("nobody", uint64(0), uint64(11)).Return(true).Once()
trafficLogger.EXPECT().LogTraffic("nobody", uint64(0), uint64(11)).Return(true).Once()
sobConnCh <- []byte("knock knock")
buf := make([]byte, 100)
n, err := conn.Read(buf)
@ -75,7 +77,7 @@ func TestClientServerTrafficLoggerTCP(t *testing.T) {
assert.Equal(t, "knock knock", string(buf[:n]))
// Client writes to server
trafficLogger.EXPECT().Log("nobody", uint64(12), uint64(0)).Return(true).Once()
trafficLogger.EXPECT().LogTraffic("nobody", uint64(12), uint64(0)).Return(true).Once()
sobConn.EXPECT().Write([]byte("who is there")).Return(12, nil).Once()
n, err = conn.Write([]byte("who is there"))
assert.NoError(t, err)
@ -83,7 +85,9 @@ func TestClientServerTrafficLoggerTCP(t *testing.T) {
time.Sleep(1 * time.Second) // Need some time for the server to receive the data
// Client reads from server again but blocked
trafficLogger.EXPECT().Log("nobody", uint64(0), uint64(4)).Return(false).Once()
trafficLogger.EXPECT().UntraceStream(mock.Anything).Return().Once()
trafficLogger.EXPECT().LogTraffic("nobody", uint64(0), uint64(4)).Return(false).Once()
trafficLogger.EXPECT().LogOnlineState("nobody", false).Return().Once()
sobConnCh <- []byte("nope")
n, err = conn.Read(buf)
assert.Zero(t, n)
@ -116,6 +120,7 @@ func TestClientServerTrafficLoggerUDP(t *testing.T) {
go s.Serve()
// Create client
trafficLogger.EXPECT().LogOnlineState("nobody", true).Return().Once()
c, _, err := client.NewClient(&client.Config{
ServerAddr: udpAddr,
TLSConfig: client.TLSConfig{InsecureSkipVerify: true},
@ -146,14 +151,14 @@ func TestClientServerTrafficLoggerUDP(t *testing.T) {
assert.NoError(t, err)
// Client writes to server
trafficLogger.EXPECT().Log("nobody", uint64(9), uint64(0)).Return(true).Once()
trafficLogger.EXPECT().LogTraffic("nobody", uint64(9), uint64(0)).Return(true).Once()
sobConn.EXPECT().WriteTo([]byte("small sad"), addr).Return(9, nil).Once()
err = conn.Send([]byte("small sad"), addr)
assert.NoError(t, err)
time.Sleep(1 * time.Second) // Need some time for the server to receive the data
// Client reads from server
trafficLogger.EXPECT().Log("nobody", uint64(0), uint64(7)).Return(true).Once()
trafficLogger.EXPECT().LogTraffic("nobody", uint64(0), uint64(7)).Return(true).Once()
sobConnCh <- []byte("big mad")
bs, rAddr, err := conn.Receive()
assert.NoError(t, err)
@ -161,7 +166,8 @@ func TestClientServerTrafficLoggerUDP(t *testing.T) {
assert.Equal(t, "big mad", string(bs))
// Client reads from server again but blocked
trafficLogger.EXPECT().Log("nobody", uint64(0), uint64(4)).Return(false).Once()
trafficLogger.EXPECT().LogTraffic("nobody", uint64(0), uint64(4)).Return(false).Once()
trafficLogger.EXPECT().LogOnlineState("nobody", false).Return().Once()
sobConnCh <- []byte("nope")
bs, rAddr, err = conn.Receive()
assert.Equal(t, err, io.EOF)

View file

@ -5,7 +5,7 @@ import (
"io"
"net"
"github.com/apernet/hysteria/core/server"
"github.com/apernet/hysteria/core/v2/server"
)
// This file provides utilities for the integration tests.

View file

@ -6,7 +6,7 @@ import (
"fmt"
"io"
"github.com/apernet/hysteria/core/errors"
"github.com/apernet/hysteria/core/v2/errors"
"github.com/apernet/quic-go/quicvarint"
)

View file

@ -22,3 +22,33 @@ func (t *AtomicTime) Set(new time.Time) {
func (t *AtomicTime) Get() time.Time {
return t.v.Load().(time.Time)
}
type Atomic[T any] struct {
v atomic.Value
}
func (a *Atomic[T]) Load() T {
value := a.v.Load()
if value == nil {
var zero T
return zero
}
return value.(T)
}
func (a *Atomic[T]) Store(value T) {
a.v.Store(value)
}
func (a *Atomic[T]) Swap(new T) T {
old := a.v.Swap(new)
if old == nil {
var zero T
return zero
}
return old.(T)
}
func (a *Atomic[T]) CompareAndSwap(old, new T) bool {
return a.v.CompareAndSwap(old, new)
}

View file

@ -2,7 +2,7 @@ with-expecter: true
inpackage: true
dir: .
packages:
github.com/apernet/hysteria/core/server:
github.com/apernet/hysteria/core/v2/server:
interfaces:
udpIO:
config:

View file

@ -4,10 +4,13 @@ import (
"crypto/tls"
"net"
"net/http"
"sync/atomic"
"time"
"github.com/apernet/hysteria/core/errors"
"github.com/apernet/hysteria/core/internal/pmtud"
"github.com/apernet/hysteria/core/v2/errors"
"github.com/apernet/hysteria/core/v2/internal/pmtud"
"github.com/apernet/hysteria/core/v2/internal/utils"
"github.com/apernet/quic-go"
)
const (
@ -22,6 +25,7 @@ type Config struct {
TLSConfig TLSConfig
QUICConfig QUICConfig
Conn net.PacketConn
RequestHook RequestHook
Outbound Outbound
BandwidthConfig BandwidthConfig
IgnoreClientBandwidth bool
@ -110,6 +114,19 @@ type QUICConfig struct {
DisablePathMTUDiscovery bool // The server may still override this to true on unsupported platforms.
}
// RequestHook allows filtering and modifying requests before the server connects to the remote.
// A request will only be hooked if Check returns true.
// The returned byte slice, if not empty, will be sent to the remote before proxying - this is
// mainly for "putting back" the content read from the client for sniffing, etc.
// Return a non-nil error to abort the connection.
// Note that due to the current architectural limitations, it can only inspect the first packet
// of a UDP connection. It also cannot put back any data as the first packet is always sent as-is.
type RequestHook interface {
Check(isUDP bool, reqAddr string) bool
TCP(stream quic.Stream, reqAddr *string) ([]byte, error)
UDP(data []byte, reqAddr *string) error
}
// Outbound provides the implementation of how the server should connect to remote servers.
// Although UDP includes a reqAddr, the implementation does not necessarily have to use it
// to make a "connected" UDP connection that does not accept packets from other addresses.
@ -195,5 +212,68 @@ type EventLogger interface {
// bandwidth limits or post-connection authentication, for example.
// The implementation of this interface must be thread-safe.
type TrafficLogger interface {
Log(id string, tx, rx uint64) (ok bool)
LogTraffic(id string, tx, rx uint64) (ok bool)
LogOnlineState(id string, online bool)
TraceStream(stream quic.Stream, stats *StreamStats)
UntraceStream(stream quic.Stream)
}
type StreamState int
const (
// StreamStateInitial indicates the initial state of a stream.
// Client has opened the stream, but we have not received the proxy request yet.
StreamStateInitial StreamState = iota
// StreamStateHooking indicates that the hook (usually sniff) is processing.
// Client has sent the proxy request, but sniff requires more data to complete.
StreamStateHooking
// StreamStateConnecting indicates that we are connecting to the proxy target.
StreamStateConnecting
// StreamStateEstablished indicates the proxy is established.
StreamStateEstablished
// StreamStateClosed indicates the stream is closed.
StreamStateClosed
)
func (s StreamState) String() string {
switch s {
case StreamStateInitial:
return "init"
case StreamStateHooking:
return "hook"
case StreamStateConnecting:
return "connect"
case StreamStateEstablished:
return "estab"
case StreamStateClosed:
return "closed"
default:
return "unknown"
}
}
type StreamStats struct {
State utils.Atomic[StreamState]
AuthID string
ConnID uint32
InitialTime time.Time
ReqAddr utils.Atomic[string]
HookedReqAddr utils.Atomic[string]
Tx atomic.Uint64
Rx atomic.Uint64
LastActiveTime utils.Atomic[time.Time]
}
func (s *StreamStats) setHookedReqAddr(addr string) {
if addr != s.ReqAddr.Load() {
s.HookedReqAddr.Store(addr)
}
}

View file

@ -3,6 +3,7 @@ package server
import (
"errors"
"io"
"time"
)
var errDisconnect = errors.New("traffic logger requested disconnect")
@ -31,23 +32,27 @@ func copyBufferLog(dst io.Writer, src io.Reader, log func(n uint64) bool) error
}
}
func copyTwoWayWithLogger(id string, serverRw, remoteRw io.ReadWriter, l TrafficLogger) error {
func copyTwoWayEx(id string, serverRw, remoteRw io.ReadWriter, l TrafficLogger, stats *StreamStats) error {
errChan := make(chan error, 2)
go func() {
errChan <- copyBufferLog(serverRw, remoteRw, func(n uint64) bool {
return l.Log(id, 0, n)
stats.LastActiveTime.Store(time.Now())
stats.Rx.Add(n)
return l.LogTraffic(id, 0, n)
})
}()
go func() {
errChan <- copyBufferLog(remoteRw, serverRw, func(n uint64) bool {
return l.Log(id, n, 0)
stats.LastActiveTime.Store(time.Now())
stats.Tx.Add(n)
return l.LogTraffic(id, n, 0)
})
}()
// Block until one of the two goroutines returns
return <-errChan
}
// copyTwoWay is the "fast-path" version of copyTwoWayWithLogger that does not log traffic.
// copyTwoWay is the "fast-path" version of copyTwoWayEx that does not log traffic or update stream stats.
// It uses the built-in io.Copy instead of our own copyBufferLog.
func copyTwoWay(serverRw, remoteRw io.ReadWriter) error {
errChan := make(chan error, 2)

View file

@ -1,4 +1,4 @@
// Code generated by mockery v2.32.0. DO NOT EDIT.
// Code generated by mockery v2.43.0. DO NOT EDIT.
package server
@ -21,6 +21,10 @@ func (_m *mockUDPConn) EXPECT() *mockUDPConn_Expecter {
func (_m *mockUDPConn) Close() error {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Close")
}
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
@ -62,6 +66,10 @@ func (_c *mockUDPConn_Close_Call) RunAndReturn(run func() error) *mockUDPConn_Cl
func (_m *mockUDPConn) ReadFrom(b []byte) (int, string, error) {
ret := _m.Called(b)
if len(ret) == 0 {
panic("no return value specified for ReadFrom")
}
var r0 int
var r1 string
var r2 error
@ -121,6 +129,10 @@ func (_c *mockUDPConn_ReadFrom_Call) RunAndReturn(run func([]byte) (int, string,
func (_m *mockUDPConn) WriteTo(b []byte, addr string) (int, error) {
ret := _m.Called(b, addr)
if len(ret) == 0 {
panic("no return value specified for WriteTo")
}
var r0 int
var r1 error
if rf, ok := ret.Get(0).(func([]byte, string) (int, error)); ok {

View file

@ -1,4 +1,4 @@
// Code generated by mockery v2.32.0. DO NOT EDIT.
// Code generated by mockery v2.43.0. DO NOT EDIT.
package server

View file

@ -1,9 +1,9 @@
// Code generated by mockery v2.32.0. DO NOT EDIT.
// Code generated by mockery v2.43.0. DO NOT EDIT.
package server
import (
protocol "github.com/apernet/hysteria/core/internal/protocol"
protocol "github.com/apernet/hysteria/core/v2/internal/protocol"
mock "github.com/stretchr/testify/mock"
)
@ -20,10 +20,61 @@ func (_m *mockUDPIO) EXPECT() *mockUDPIO_Expecter {
return &mockUDPIO_Expecter{mock: &_m.Mock}
}
// Hook provides a mock function with given fields: data, reqAddr
func (_m *mockUDPIO) Hook(data []byte, reqAddr *string) error {
ret := _m.Called(data, reqAddr)
if len(ret) == 0 {
panic("no return value specified for Hook")
}
var r0 error
if rf, ok := ret.Get(0).(func([]byte, *string) error); ok {
r0 = rf(data, reqAddr)
} else {
r0 = ret.Error(0)
}
return r0
}
// mockUDPIO_Hook_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Hook'
type mockUDPIO_Hook_Call struct {
*mock.Call
}
// Hook is a helper method to define mock.On call
// - data []byte
// - reqAddr *string
func (_e *mockUDPIO_Expecter) Hook(data interface{}, reqAddr interface{}) *mockUDPIO_Hook_Call {
return &mockUDPIO_Hook_Call{Call: _e.mock.On("Hook", data, reqAddr)}
}
func (_c *mockUDPIO_Hook_Call) Run(run func(data []byte, reqAddr *string)) *mockUDPIO_Hook_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].([]byte), args[1].(*string))
})
return _c
}
func (_c *mockUDPIO_Hook_Call) Return(_a0 error) *mockUDPIO_Hook_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *mockUDPIO_Hook_Call) RunAndReturn(run func([]byte, *string) error) *mockUDPIO_Hook_Call {
_c.Call.Return(run)
return _c
}
// ReceiveMessage provides a mock function with given fields:
func (_m *mockUDPIO) ReceiveMessage() (*protocol.UDPMessage, error) {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for ReceiveMessage")
}
var r0 *protocol.UDPMessage
var r1 error
if rf, ok := ret.Get(0).(func() (*protocol.UDPMessage, error)); ok {
@ -77,6 +128,10 @@ func (_c *mockUDPIO_ReceiveMessage_Call) RunAndReturn(run func() (*protocol.UDPM
func (_m *mockUDPIO) SendMessage(_a0 []byte, _a1 *protocol.UDPMessage) error {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for SendMessage")
}
var r0 error
if rf, ok := ret.Get(0).(func([]byte, *protocol.UDPMessage) error); ok {
r0 = rf(_a0, _a1)
@ -120,6 +175,10 @@ func (_c *mockUDPIO_SendMessage_Call) RunAndReturn(run func([]byte, *protocol.UD
func (_m *mockUDPIO) UDP(reqAddr string) (UDPConn, error) {
ret := _m.Called(reqAddr)
if len(ret) == 0 {
panic("no return value specified for UDP")
}
var r0 UDPConn
var r1 error
if rf, ok := ret.Get(0).(func(string) (UDPConn, error)); ok {

View file

@ -3,15 +3,17 @@ package server
import (
"context"
"crypto/tls"
"math/rand"
"net/http"
"sync"
"time"
"github.com/apernet/quic-go"
"github.com/apernet/quic-go/http3"
"github.com/apernet/hysteria/core/internal/congestion"
"github.com/apernet/hysteria/core/internal/protocol"
"github.com/apernet/hysteria/core/internal/utils"
"github.com/apernet/hysteria/core/v2/internal/congestion"
"github.com/apernet/hysteria/core/v2/internal/protocol"
"github.com/apernet/hysteria/core/v2/internal/utils"
)
const (
@ -41,6 +43,7 @@ func NewServer(config *Config) (Server, error) {
MaxIncomingStreams: config.QUICConfig.MaxIncomingStreams,
DisablePathMTUDiscovery: config.QUICConfig.DisablePathMTUDiscovery,
EnableDatagrams: true,
DisablePathManager: true,
}
listener, err := quic.Listen(config.Conn, tlsConfig, quicConfig)
if err != nil {
@ -77,14 +80,18 @@ func (s *serverImpl) Close() error {
func (s *serverImpl) handleClient(conn quic.Connection) {
handler := newH3sHandler(s.config, conn)
h3s := http3.Server{
EnableDatagrams: true,
Handler: handler,
StreamHijacker: handler.ProxyStreamHijacker,
}
err := h3s.ServeQUICConn(conn)
// If the client is authenticated, we need to log the disconnect event
if handler.authenticated && s.config.EventLogger != nil {
s.config.EventLogger.Disconnect(conn.RemoteAddr(), handler.authID, err)
if handler.authenticated {
if tl := s.config.TrafficLogger; tl != nil {
tl.LogOnlineState(handler.authID, false)
}
if el := s.config.EventLogger; el != nil {
el.Disconnect(conn.RemoteAddr(), handler.authID, err)
}
}
_ = conn.CloseWithError(closeErrCodeOK, "")
}
@ -96,6 +103,7 @@ type h3sHandler struct {
authenticated bool
authMutex sync.Mutex
authID string
connID uint32 // a random id for dump streams
udpSM *udpSessionManager // Only set after authentication
}
@ -104,6 +112,7 @@ func newH3sHandler(config *Config, conn quic.Connection) *h3sHandler {
return &h3sHandler{
config: config,
conn: conn,
connID: rand.Uint32(),
}
}
@ -154,8 +163,11 @@ func (h *h3sHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
})
w.WriteHeader(protocol.StatusAuthOK)
// Call event logger
if h.config.EventLogger != nil {
h.config.EventLogger.Connect(h.conn.RemoteAddr(), id, actualTx)
if tl := h.config.TrafficLogger; tl != nil {
tl.LogOnlineState(id, true)
}
if el := h.config.EventLogger; el != nil {
el.Connect(h.conn.RemoteAddr(), id, actualTx)
}
// Initialize UDP session manager (if UDP is enabled)
// We use sync.Once to make sure that only one goroutine is started,
@ -163,7 +175,7 @@ func (h *h3sHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if !h.config.DisableUDP {
go func() {
sm := newUDPSessionManager(
&udpIOImpl{h.conn, id, h.config.TrafficLogger, h.config.Outbound},
&udpIOImpl{h.conn, id, h.config.TrafficLogger, h.config.RequestHook, h.config.Outbound},
&udpEventLoggerImpl{h.conn, id, h.config.EventLogger},
h.config.UDPIdleTimeout)
h.udpSM = sm
@ -180,7 +192,7 @@ func (h *h3sHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}
func (h *h3sHandler) ProxyStreamHijacker(ft http3.FrameType, conn quic.Connection, stream quic.Stream, err error) (bool, error) {
func (h *h3sHandler) ProxyStreamHijacker(ft http3.FrameType, id quic.ConnectionTracingID, stream quic.Stream, err error) (bool, error) {
if err != nil || !h.authenticated {
return false, nil
}
@ -198,20 +210,59 @@ func (h *h3sHandler) ProxyStreamHijacker(ft http3.FrameType, conn quic.Connectio
}
func (h *h3sHandler) handleTCPRequest(stream quic.Stream) {
trafficLogger := h.config.TrafficLogger
streamStats := &StreamStats{
AuthID: h.authID,
ConnID: h.connID,
InitialTime: time.Now(),
}
streamStats.State.Store(StreamStateInitial)
streamStats.LastActiveTime.Store(time.Now())
defer func() {
streamStats.State.Store(StreamStateClosed)
}()
if trafficLogger != nil {
trafficLogger.TraceStream(stream, streamStats)
defer trafficLogger.UntraceStream(stream)
}
// Read request
reqAddr, err := protocol.ReadTCPRequest(stream)
if err != nil {
_ = stream.Close()
return
}
streamStats.ReqAddr.Store(reqAddr)
// Call the hook if set
var putback []byte
var hooked bool
if h.config.RequestHook != nil {
hooked = h.config.RequestHook.Check(false, reqAddr)
// When the hook is enabled, the server should always accept a connection
// so that the client will send whatever request the hook wants to see.
// This is essentially a server-side fast-open.
if hooked {
streamStats.State.Store(StreamStateHooking)
_ = protocol.WriteTCPResponse(stream, true, "RequestHook enabled")
putback, err = h.config.RequestHook.TCP(stream, &reqAddr)
if err != nil {
_ = stream.Close()
return
}
streamStats.setHookedReqAddr(reqAddr)
}
}
// Log the event
if h.config.EventLogger != nil {
h.config.EventLogger.TCPRequest(h.conn.RemoteAddr(), h.authID, reqAddr)
}
// Dial target
streamStats.State.Store(StreamStateConnecting)
tConn, err := h.config.Outbound.TCP(reqAddr)
if err != nil {
if !hooked {
_ = protocol.WriteTCPResponse(stream, false, err.Error())
}
_ = stream.Close()
// Log the error
if h.config.EventLogger != nil {
@ -219,10 +270,18 @@ func (h *h3sHandler) handleTCPRequest(stream quic.Stream) {
}
return
}
_ = protocol.WriteTCPResponse(stream, true, "")
if !hooked {
_ = protocol.WriteTCPResponse(stream, true, "Connected")
}
streamStats.State.Store(StreamStateEstablished)
// Put back the data if the hook requested
if len(putback) > 0 {
n, _ := tConn.Write(putback)
streamStats.Tx.Add(uint64(n))
}
// Start proxying
if h.config.TrafficLogger != nil {
err = copyTwoWayWithLogger(h.authID, stream, tConn, h.config.TrafficLogger)
if trafficLogger != nil {
err = copyTwoWayEx(h.authID, stream, tConn, trafficLogger, streamStats)
} else {
// Use the fast path if no traffic logger is set
err = copyTwoWay(stream, tConn)
@ -253,6 +312,7 @@ type udpIOImpl struct {
Conn quic.Connection
AuthID string
TrafficLogger TrafficLogger
RequestHook RequestHook
Outbound Outbound
}
@ -269,7 +329,7 @@ func (io *udpIOImpl) ReceiveMessage() (*protocol.UDPMessage, error) {
continue
}
if io.TrafficLogger != nil {
ok := io.TrafficLogger.Log(io.AuthID, uint64(len(udpMsg.Data)), 0)
ok := io.TrafficLogger.LogTraffic(io.AuthID, uint64(len(udpMsg.Data)), 0)
if !ok {
// TrafficLogger requested to disconnect the client
_ = io.Conn.CloseWithError(closeErrCodeTrafficLimitReached, "")
@ -282,7 +342,7 @@ func (io *udpIOImpl) ReceiveMessage() (*protocol.UDPMessage, error) {
func (io *udpIOImpl) SendMessage(buf []byte, msg *protocol.UDPMessage) error {
if io.TrafficLogger != nil {
ok := io.TrafficLogger.Log(io.AuthID, 0, uint64(len(msg.Data)))
ok := io.TrafficLogger.LogTraffic(io.AuthID, 0, uint64(len(msg.Data)))
if !ok {
// TrafficLogger requested to disconnect the client
_ = io.Conn.CloseWithError(closeErrCodeTrafficLimitReached, "")
@ -297,6 +357,14 @@ func (io *udpIOImpl) SendMessage(buf []byte, msg *protocol.UDPMessage) error {
return io.Conn.SendDatagram(buf[:msgN])
}
func (io *udpIOImpl) Hook(data []byte, reqAddr *string) error {
if io.RequestHook != nil && io.RequestHook.Check(true, *reqAddr) {
return io.RequestHook.UDP(data, reqAddr)
} else {
return nil
}
}
func (io *udpIOImpl) UDP(reqAddr string) (UDPConn, error) {
return io.Outbound.UDP(reqAddr)
}

View file

@ -8,9 +8,9 @@ import (
"github.com/apernet/quic-go"
"github.com/apernet/hysteria/core/internal/frag"
"github.com/apernet/hysteria/core/internal/protocol"
"github.com/apernet/hysteria/core/internal/utils"
"github.com/apernet/hysteria/core/v2/internal/frag"
"github.com/apernet/hysteria/core/v2/internal/protocol"
"github.com/apernet/hysteria/core/v2/internal/utils"
)
const (
@ -20,6 +20,7 @@ const (
type udpIO interface {
ReceiveMessage() (*protocol.UDPMessage, error)
SendMessage([]byte, *protocol.UDPMessage) error
Hook(data []byte, reqAddr *string) error
UDP(reqAddr string) (UDPConn, error)
}
@ -30,10 +31,57 @@ type udpEventLogger interface {
type udpSessionEntry struct {
ID uint32
Conn UDPConn
OverrideAddr string // Ignore the address in the UDP message, always use this if not empty
OriginalAddr string // The original address in the UDP message
D *frag.Defragger
Last *utils.AtomicTime
Timeout bool // true if the session is closed due to timeout
IO udpIO
DialFunc func(addr string, firstMsgData []byte) (conn UDPConn, actualAddr string, err error)
ExitFunc func(err error)
conn UDPConn
connLock sync.Mutex
closed bool
}
func newUDPSessionEntry(
id uint32, io udpIO,
dialFunc func(string, []byte) (UDPConn, string, error),
exitFunc func(error),
) (e *udpSessionEntry) {
e = &udpSessionEntry{
ID: id,
D: &frag.Defragger{},
Last: utils.NewAtomicTime(time.Now()),
IO: io,
DialFunc: dialFunc,
ExitFunc: exitFunc,
}
return
}
// CloseWithErr closes the session and calls ExitFunc with the given error.
// A nil error indicates the session is cleaned up due to timeout.
func (e *udpSessionEntry) CloseWithErr(err error) {
// We need this lock to ensure not to create conn after session exit
e.connLock.Lock()
if e.closed {
// Already closed
e.connLock.Unlock()
return
}
e.closed = true
if e.conn != nil {
_ = e.conn.Close()
}
e.connLock.Unlock()
e.ExitFunc(err)
}
// Feed feeds a UDP message to the session.
@ -47,23 +95,78 @@ func (e *udpSessionEntry) Feed(msg *protocol.UDPMessage) (int, error) {
if dfMsg == nil {
return 0, nil
}
return e.Conn.WriteTo(dfMsg.Data, dfMsg.Addr)
if e.conn == nil {
err := e.initConn(dfMsg)
if err != nil {
return 0, err
}
}
// ReceiveLoop receives incoming UDP packets, packs them into UDP messages,
// and sends using the provided io.
// Exit and returns error when either the underlying UDP connection returns
// error (e.g. closed), or the provided io returns error when sending.
func (e *udpSessionEntry) ReceiveLoop(io udpIO) error {
addr := dfMsg.Addr
if e.OverrideAddr != "" {
addr = e.OverrideAddr
}
return e.conn.WriteTo(dfMsg.Data, addr)
}
// initConn initializes the UDP connection of the session.
// If no error is returned, the e.conn is set to the new connection.
func (e *udpSessionEntry) initConn(firstMsg *protocol.UDPMessage) error {
// We need this lock to ensure not to create conn after session exit
e.connLock.Lock()
if e.closed {
e.connLock.Unlock()
return errors.New("session is closed")
}
conn, actualAddr, err := e.DialFunc(firstMsg.Addr, firstMsg.Data)
if err != nil {
// Fail fast if DialFunc failed
// (usually indicates the connection has been rejected by the ACL)
e.connLock.Unlock()
// CloseWithErr acquires the connLock again
e.CloseWithErr(err)
return err
}
e.conn = conn
if firstMsg.Addr != actualAddr {
// Hook changed the address, enable address override
e.OverrideAddr = actualAddr
e.OriginalAddr = firstMsg.Addr
}
go e.receiveLoop()
e.connLock.Unlock()
return nil
}
// receiveLoop receives incoming UDP packets, packs them into UDP messages,
// and sends using the IO.
// Exit when either the underlying UDP connection returns error (e.g. closed),
// or the IO returns error when sending.
func (e *udpSessionEntry) receiveLoop() {
udpBuf := make([]byte, protocol.MaxUDPSize)
msgBuf := make([]byte, protocol.MaxUDPSize)
for {
udpN, rAddr, err := e.Conn.ReadFrom(udpBuf)
udpN, rAddr, err := e.conn.ReadFrom(udpBuf)
if err != nil {
return err
e.CloseWithErr(err)
return
}
e.Last.Set(time.Now())
if e.OriginalAddr != "" {
// Use the original address in the opposite direction,
// otherwise the QUIC clients or NAT on the client side
// may not treat it as the same UDP session.
rAddr = e.OriginalAddr
}
msg := &protocol.UDPMessage{
SessionID: e.ID,
PacketID: 0,
@ -72,9 +175,10 @@ func (e *udpSessionEntry) ReceiveLoop(io udpIO) error {
Addr: rAddr,
Data: udpBuf[:udpN],
}
err = sendMessageAutoFrag(io, msgBuf, msg)
err = sendMessageAutoFrag(e.IO, msgBuf, msg)
if err != nil {
return err
e.CloseWithErr(err)
return
}
}
}
@ -155,19 +259,23 @@ func (m *udpSessionManager) idleCleanupLoop(stopCh <-chan struct{}) {
}
func (m *udpSessionManager) cleanup(idleOnly bool) {
timeoutEntry := make([]*udpSessionEntry, 0, len(m.m))
// We use RLock here as we are only scanning the map, not deleting from it.
m.mutex.RLock()
defer m.mutex.RUnlock()
now := time.Now()
for _, entry := range m.m {
if !idleOnly || now.Sub(entry.Last.Get()) > m.idleTimeout {
entry.Timeout = true
_ = entry.Conn.Close()
// Closing the connection here will cause the ReceiveLoop to exit,
// and the session will be removed from the map there.
timeoutEntry = append(timeoutEntry, entry)
}
}
m.mutex.RUnlock()
for _, entry := range timeoutEntry {
// This eventually calls entry.ExitFunc,
// where the m.mutex will be locked again to remove the entry from the map.
entry.CloseWithErr(nil)
}
}
func (m *udpSessionManager) feed(msg *protocol.UDPMessage) {
@ -177,35 +285,31 @@ func (m *udpSessionManager) feed(msg *protocol.UDPMessage) {
// Create a new session if not exists
if entry == nil {
m.eventLogger.New(msg.SessionID, msg.Addr)
conn, err := m.io.UDP(msg.Addr)
dialFunc := func(addr string, firstMsgData []byte) (conn UDPConn, actualAddr string, err error) {
// Call the hook
err = m.io.Hook(firstMsgData, &addr)
if err != nil {
m.eventLogger.Close(msg.SessionID, err)
return
}
entry = &udpSessionEntry{
ID: msg.SessionID,
Conn: conn,
D: &frag.Defragger{},
Last: utils.NewAtomicTime(time.Now()),
actualAddr = addr
// Log the event
m.eventLogger.New(msg.SessionID, addr)
// Dial target
conn, err = m.io.UDP(addr)
return
}
// Start the receive loop for this session
go func() {
err := entry.ReceiveLoop(m.io)
if !entry.Timeout {
_ = entry.Conn.Close()
exitFunc := func(err error) {
// Log the event
m.eventLogger.Close(entry.ID, err)
} else {
// Connection already closed by timeout cleanup,
// no need to close again here.
// Use nil error to indicate timeout.
m.eventLogger.Close(entry.ID, nil)
}
// Remove the session from the map
m.mutex.Lock()
delete(m.m, entry.ID)
m.mutex.Unlock()
}()
}
entry = newUDPSessionEntry(msg.SessionID, m.io, dialFunc, exitFunc)
// Insert the session into the map
m.mutex.Lock()
m.m[msg.SessionID] = entry

View file

@ -9,7 +9,7 @@ import (
"github.com/stretchr/testify/mock"
"go.uber.org/goleak"
"github.com/apernet/hysteria/core/internal/protocol"
"github.com/apernet/hysteria/core/v2/internal/protocol"
)
func TestUDPSessionManager(t *testing.T) {
@ -49,6 +49,7 @@ func TestUDPSessionManager(t *testing.T) {
eventLogger.EXPECT().New(msg1.SessionID, msg1.Addr).Return().Once()
udpConn1 := newMockUDPConn(t)
udpConn1Ch := make(chan []byte, 1)
io.EXPECT().Hook(msg1.Data, &msg1.Addr).Return(nil).Once()
io.EXPECT().UDP(msg1.Addr).Return(udpConn1, nil).Once()
udpConn1.EXPECT().WriteTo(msg1.Data, msg1.Addr).Return(5, nil).Once()
udpConn1.EXPECT().ReadFrom(mock.Anything).RunAndReturn(func(b []byte) (int, string, error) {
@ -65,31 +66,44 @@ func TestUDPSessionManager(t *testing.T) {
msgCh <- msg1
udpConn1Ch <- []byte("hi back")
msg2 := &protocol.UDPMessage{
msg2data := []byte("how are you doing?")
msg2_1 := &protocol.UDPMessage{
SessionID: 5678,
PacketID: 0,
FragID: 0,
FragCount: 1,
FragCount: 2,
Addr: "address2.net:12450",
Data: []byte("how are you"),
Data: msg2data[:6],
}
eventLogger.EXPECT().New(msg2.SessionID, msg2.Addr).Return().Once()
msg2_2 := &protocol.UDPMessage{
SessionID: 5678,
PacketID: 0,
FragID: 1,
FragCount: 2,
Addr: "address2.net:12450",
Data: msg2data[6:],
}
eventLogger.EXPECT().New(msg2_1.SessionID, msg2_1.Addr).Return().Once()
udpConn2 := newMockUDPConn(t)
udpConn2Ch := make(chan []byte, 1)
io.EXPECT().UDP(msg2.Addr).Return(udpConn2, nil).Once()
udpConn2.EXPECT().WriteTo(msg2.Data, msg2.Addr).Return(11, nil).Once()
// On fragmentation, make sure hook gets the whole message
io.EXPECT().Hook(msg2data, &msg2_1.Addr).Return(nil).Once()
io.EXPECT().UDP(msg2_1.Addr).Return(udpConn2, nil).Once()
udpConn2.EXPECT().WriteTo(msg2data, msg2_1.Addr).Return(11, nil).Once()
udpConn2.EXPECT().ReadFrom(mock.Anything).RunAndReturn(func(b []byte) (int, string, error) {
return udpReadFunc(msg2.Addr, udpConn2Ch, b)
return udpReadFunc(msg2_1.Addr, udpConn2Ch, b)
})
io.EXPECT().SendMessage(mock.Anything, &protocol.UDPMessage{
SessionID: msg2.SessionID,
SessionID: msg2_1.SessionID,
PacketID: 0,
FragID: 0,
FragCount: 1,
Addr: msg2.Addr,
Addr: msg2_1.Addr,
Data: []byte("im fine"),
}).Return(nil).Once()
msgCh <- msg2
msgCh <- msg2_1
msgCh <- msg2_2
udpConn2Ch <- []byte("im fine")
msg3 := &protocol.UDPMessage{
@ -122,7 +136,7 @@ func TestUDPSessionManager(t *testing.T) {
return nil
}).Once()
eventLogger.EXPECT().Close(msg1.SessionID, nil).Once()
eventLogger.EXPECT().Close(msg2.SessionID, nil).Once()
eventLogger.EXPECT().Close(msg2_1.SessionID, nil).Once()
time.Sleep(3 * time.Second) // Wait for timeout
mock.AssertExpectationsForObjects(t, io, eventLogger, udpConn1, udpConn2)
@ -139,6 +153,7 @@ func TestUDPSessionManager(t *testing.T) {
}
eventLogger.EXPECT().New(msg4.SessionID, msg4.Addr).Return().Once()
udpConn4 := newMockUDPConn(t)
io.EXPECT().Hook(msg4.Data, &msg4.Addr).Return(nil).Once()
io.EXPECT().UDP(msg4.Addr).Return(udpConn4, nil).Once()
udpConn4.EXPECT().WriteTo(msg4.Data, msg4.Addr).Return(12, nil).Once()
udpConn4.EXPECT().ReadFrom(mock.Anything).Return(0, "", errUDPClosed).Once()
@ -160,6 +175,7 @@ func TestUDPSessionManager(t *testing.T) {
Data: []byte("babe i miss you"),
}
eventLogger.EXPECT().New(msg5.SessionID, msg5.Addr).Return().Once()
io.EXPECT().Hook(msg5.Data, &msg5.Addr).Return(nil).Once()
io.EXPECT().UDP(msg5.Addr).Return(nil, errUDPIO).Once()
eventLogger.EXPECT().Close(msg5.SessionID, errUDPIO).Once()
msgCh <- msg5

7
extras/LICENSE.md Normal file
View file

@ -0,0 +1,7 @@
Copyright 2023 Toby
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -6,7 +6,7 @@ import (
"strconv"
"strings"
"github.com/apernet/hysteria/core/server"
"github.com/apernet/hysteria/core/v2/server"
)
var _ server.Authenticator = &CommandAuthenticator{}

View file

@ -10,7 +10,7 @@ import (
"net/http"
"time"
"github.com/apernet/hysteria/core/server"
"github.com/apernet/hysteria/core/v2/server"
)
const (

View file

@ -3,7 +3,7 @@ package auth
import (
"net"
"github.com/apernet/hysteria/core/server"
"github.com/apernet/hysteria/core/v2/server"
)
var _ server.Authenticator = &PasswordAuthenticator{}

View file

@ -4,7 +4,7 @@ import (
"net"
"strings"
"github.com/apernet/hysteria/core/server"
"github.com/apernet/hysteria/core/v2/server"
)
const (
@ -16,7 +16,17 @@ var _ server.Authenticator = &UserPassAuthenticator{}
// UserPassAuthenticator checks the provided auth string against a map of username/password pairs.
// The format of the auth string must be "username:password".
type UserPassAuthenticator struct {
Users map[string]string
users map[string]string
}
func NewUserPassAuthenticator(users map[string]string) *UserPassAuthenticator {
// Usernames are case-insensitive, as they are already lowercased by viper.
// Lowercase it again on our own to make it explicit.
lcUsers := make(map[string]string, len(users))
for user, pass := range users {
lcUsers[strings.ToLower(user)] = pass
}
return &UserPassAuthenticator{users: lcUsers}
}
func (a *UserPassAuthenticator) Authenticate(addr net.Addr, auth string, tx uint64) (ok bool, id string) {
@ -24,7 +34,7 @@ func (a *UserPassAuthenticator) Authenticate(addr net.Addr, auth string, tx uint
if !ok {
return false, ""
}
rp, ok := a.Users[u]
rp, ok := a.users[u]
if !ok || rp != p {
return false, ""
}
@ -36,5 +46,6 @@ func splitUserPass(auth string) (user, pass string, ok bool) {
if len(rs) != 2 {
return "", "", false
}
return rs[0], rs[1], true
// Usernames are case-insensitive
return strings.ToLower(rs[0]), rs[1], true
}

View file

@ -85,12 +85,26 @@ func TestUserPassAuthenticator(t *testing.T) {
wantOk: false,
wantId: "",
},
{
name: "case insensitive username",
fields: fields{
Users: map[string]string{
"gawR": "gura",
"fubuki": "shirakami",
},
},
args: args{
addr: nil,
auth: "Gawr:gura",
tx: 0,
},
wantOk: true,
wantId: "gawr",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := &UserPassAuthenticator{
Users: tt.fields.Users,
}
a := NewUserPassAuthenticator(tt.fields.Users)
gotOk, gotId := a.Authenticate(tt.args.addr, tt.args.auth, tt.args.tx)
if gotOk != tt.wantOk {
t.Errorf("Authenticate() gotOk = %v, want %v", gotOk, tt.wantOk)

View file

@ -1,37 +1,47 @@
module github.com/apernet/hysteria/extras
module github.com/apernet/hysteria/extras/v2
go 1.21
go 1.23
toolchain go1.24.2
require (
github.com/apernet/hysteria/core v0.0.0-00010101000000-000000000000
github.com/apernet/hysteria/core/v2 v2.0.0-00010101000000-000000000000
github.com/apernet/quic-go v0.52.1-0.20250607183305-9320c9d14431
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6
github.com/database64128/tfo-go/v2 v2.2.2
github.com/hashicorp/golang-lru/v2 v2.0.5
github.com/miekg/dns v1.1.55
github.com/stretchr/testify v1.8.4
github.com/miekg/dns v1.1.59
github.com/refraction-networking/utls v1.6.6
github.com/stretchr/testify v1.9.0
github.com/txthinking/socks5 v0.0.0-20230325130024-4230056ae301
golang.org/x/crypto v0.19.0
golang.org/x/net v0.21.0
google.golang.org/protobuf v1.33.0
golang.org/x/crypto v0.26.0
golang.org/x/net v0.28.0
google.golang.org/protobuf v1.34.1
)
require (
github.com/apernet/quic-go v0.42.1-0.20240323215309-32a339817822 // indirect
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/cloudflare/circl v1.3.9 // indirect
github.com/database64128/netx-go v0.0.0-20240905055117-62795b8b054a // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf // indirect
go.uber.org/mock v0.4.0 // indirect
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.11.1 // indirect
go.uber.org/mock v0.5.0 // indirect
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
golang.org/x/mod v0.18.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/tools v0.22.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace github.com/apernet/hysteria/core => ../core
replace github.com/apernet/hysteria/core/v2 => ../core

View file

@ -1,10 +1,19 @@
github.com/apernet/quic-go v0.42.1-0.20240323215309-32a339817822 h1:+ZSzRxSMg1+fLTQKcIUvD2cCCgS+1rtyRhs+NL5oBgA=
github.com/apernet/quic-go v0.42.1-0.20240323215309-32a339817822/go.mod h1:j3QaAM7sVJqptDQyIQRWA6mASCfuxoHJszn67JQh1GE=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/apernet/quic-go v0.52.1-0.20250607183305-9320c9d14431 h1:9/jM7e+kVALd7Jfu1c27dcEpT/Fd/Gzq2OsQjKjakKI=
github.com/apernet/quic-go v0.52.1-0.20250607183305-9320c9d14431/go.mod h1:I/47OIGG5H/IfAm+nz2c6hm6b/NkEhpvptAoiPcY7jQ=
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 h1:4NNbNM2Iq/k57qEu7WfL67UrbPq1uFWxW4qODCohi+0=
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6/go.mod h1:J29hk+f9lJrblVIfiJOtTFk+OblBawmib4uz/VdKzlg=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cloudflare/circl v1.3.9 h1:QFrlgFYf2Qpi8bSpVPK1HBvWpx16v/1TZivyo7pGuBE=
github.com/cloudflare/circl v1.3.9/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/database64128/netx-go v0.0.0-20240905055117-62795b8b054a h1:t4SDi0pmNkryzKdM4QF3o5vqSP4GRjeZD/6j3nyxNP0=
github.com/database64128/netx-go v0.0.0-20240905055117-62795b8b054a/go.mod h1:7K2NQKbabB5mBl41vF6YayYl5g7YpDwc4dQ5iMpP3Lg=
github.com/database64128/tfo-go/v2 v2.2.2 h1:BxynF4qGF5ct3DpPLEG62uyJZ3LQhqaf0Ken+kyy7PM=
github.com/database64128/tfo-go/v2 v2.2.2/go.mod h1:2IW8jppdBwdVMjA08uEyMNnqiAHKUlqAA+J8NrsfktY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -12,22 +21,24 @@ github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4=
github.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/miekg/dns v1.1.51/go.mod h1:2Z9d3CP1LQWihRZUf29mQ19yDThaI4DAYzte2CaQW5c=
github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
@ -36,17 +47,18 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/refraction-networking/utls v1.6.6 h1:igFsYBUJPYM8Rno9xUuDoM5GQrVEqY4llzEXOkL43Ig=
github.com/refraction-networking/utls v1.6.6/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf h1:7PflaKRtU4np/epFxRXlFhlzLXZzKFrH5/I4so5Ove0=
github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf/go.mod h1:CLUSJbazqETbaR+i0YAhXBICV9TrKH93pziccMhmhpM=
github.com/txthinking/socks5 v0.0.0-20230325130024-4230056ae301 h1:d/Wr/Vl/wiJHc3AHYbYs5I3PucJvRuw3SvbmlIRf+oM=
@ -54,29 +66,29 @@ github.com/txthinking/socks5 v0.0.0-20230325130024-4230056ae301/go.mod h1:ntmMHL
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -84,8 +96,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
@ -93,22 +105,20 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
golang.org/x/tools v0.11.1 h1:ojD5zOW8+7dOGzdnNgersm8aPfcDjhMp12UfG93NIMc=
golang.org/x/tools v0.11.1/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -7,7 +7,7 @@ import (
"net"
"net/http"
"github.com/apernet/hysteria/extras/correctnet"
"github.com/apernet/hysteria/extras/v2/correctnet"
)
// MasqTCPServer covers the TCP parts of a standard web server (TCP based HTTP/HTTPS).

View file

@ -2,7 +2,7 @@ with-expecter: true
inpackage: true
dir: .
packages:
github.com/apernet/hysteria/extras/outbounds:
github.com/apernet/hysteria/extras/v2/outbounds:
interfaces:
PluggableOutbound:
config:

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