mirror of
https://github.com/apernet/hysteria.git
synced 2025-07-01 23:59:51 +00:00
Compare commits
154 commits
app/v2.4.1
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
88890dde2d | ||
|
b5ddcb5bc4 | ||
|
483fde51b1 | ||
|
3a9e952af0 | ||
|
2adeec2900 | ||
|
aa5f68a6f7 | ||
|
b2567df63c | ||
|
16c7ebeeb6 | ||
|
c2c4a9545e | ||
|
29cd04fdef | ||
|
5239a23aee | ||
|
245c6e9bd1 | ||
|
ffab01730a | ||
|
401ed5245d | ||
|
9466bc4e2f | ||
|
e11ad2b93b | ||
|
7652ddcd99 | ||
|
e1df8aa4e2 | ||
|
8c05217590 | ||
|
d86aa0b4e2 | ||
|
537e8144ea | ||
|
817d6c9a2d | ||
|
5520bcc405 | ||
|
9e90d7d155 | ||
|
8aa80c233e | ||
|
2bdaf7b46a | ||
|
53a4ce2598 | ||
|
cd396eea60 | ||
|
400fed3bd6 | ||
|
6655d2a78d | ||
|
5e11ea18fb | ||
|
d8c61c59d7 | ||
|
16c964b3e1 | ||
|
15e31d48a0 | ||
|
3e8c20518d | ||
|
9cb8cb4f53 | ||
|
7ac8d87dda | ||
|
0681638568 | ||
|
c34f23755a | ||
|
a52b02ba2b | ||
|
d4a1c2b580 | ||
|
685cd3663b | ||
|
04cf6f2e1a | ||
|
a2c7b8fd19 | ||
|
9a21e2e8c6 | ||
|
a9422e63be | ||
|
d65997c02b | ||
|
78598bfd1b | ||
|
4567713ed8 | ||
|
99e959f8c9 | ||
|
af2d75d1d0 | ||
|
b960beabbd | ||
|
ecc95fb973 | ||
|
1001b2b1ad | ||
|
ef6da94927 | ||
|
b3116c6268 | ||
|
947701897b | ||
|
4e2f138008 | ||
|
dc023ae13a | ||
|
931fc2fdb2 | ||
|
4ecbd57294 | ||
|
21ea2a024a | ||
|
d4b9c5a822 | ||
|
4ed3f21d72 | ||
|
667b08ec3e | ||
|
bcf830c29a | ||
|
45893b5d1e | ||
|
57a48a674b | ||
|
fd2d20a46a | ||
|
903666f18e | ||
|
00df1cab0f | ||
|
4c04660684 | ||
|
f2712aac93 | ||
|
55c3a064cc | ||
|
7e70547dbd | ||
|
f014c00546 | ||
|
48bf9b964a | ||
|
442ee3898c | ||
|
d527ff13b5 | ||
|
604132f8d0 | ||
|
c62c8c5513 | ||
|
b563f3981f | ||
|
a7ecd08046 | ||
|
458ee1386c | ||
|
8d9c7fa04c | ||
|
0ce3df4396 | ||
|
5315b60610 | ||
|
6a90fe18ee | ||
|
deeeafd8d7 | ||
|
b481b49a28 | ||
|
7b4def4c35 | ||
|
3412368d20 | ||
|
16bfdc7720 | ||
|
8aab735029 | ||
|
988b86ae55 | ||
|
c78dbb38a1 | ||
|
2c62a1a1b4 | ||
|
506d8e01b8 | ||
|
c5e7aa3f02 | ||
|
a852febc1f | ||
|
feacb1f85e | ||
|
4c2a905892 | ||
|
d318903783 | ||
|
18d075cc07 | ||
|
bc0e18980b | ||
|
52c8f82c2b | ||
|
23b79688fb | ||
|
e1ac7c88ab | ||
|
492145c124 | ||
|
8fca92a319 | ||
|
10234e5daf | ||
|
3c22e5967f | ||
|
3024fc079c | ||
|
146d077124 | ||
|
9e9b4dbc7d | ||
|
788d04cfdd | ||
|
12d4fbae80 | ||
|
44f4ddacfe | ||
|
adee547c21 | ||
|
09b08fa494 | ||
|
cd512ce1c6 | ||
|
5b0ab76d44 | ||
|
396dd0a68c | ||
|
e0e75c4630 | ||
|
1742f83b8e | ||
|
0c198abd2e | ||
|
15e58468a7 | ||
|
b216c4f128 | ||
|
4c0bd74094 | ||
|
2701a6e23f | ||
|
a3c4cfa4b5 | ||
|
9d4b3e608a | ||
|
6a34a9e7a0 | ||
|
ba9b3cdebb | ||
|
88eef7617f | ||
|
2366882bd6 | ||
|
415ef42b5a | ||
|
c831b987cd | ||
|
b79c43171a | ||
|
d2805577ff | ||
|
8412ec3ab3 | ||
|
59f16d0792 | ||
|
00813c4622 | ||
|
b8b8122ecf | ||
|
e7d7dbbf8f | ||
|
f586d513bc | ||
|
c392b0338b | ||
|
3409904294 | ||
|
1b78b2ec90 | ||
|
bf1cc0847e | ||
|
dc1f58414a | ||
|
2fcbde08d8 | ||
|
9752347073 | ||
|
2408301c98 |
135 changed files with 5090 additions and 785 deletions
104
.github/workflows/autotag.yaml
vendored
Normal file
104
.github/workflows/autotag.yaml
vendored
Normal 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}`);
|
||||
}
|
2
.github/workflows/master.yml
vendored
2
.github/workflows/master.yml
vendored
|
@ -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
|
||||
|
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
@ -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
|
||||
|
|
|
@ -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
7
app/LICENSE.md
Normal 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.
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -29,20 +29,24 @@ const (
|
|||
|
||||
var (
|
||||
// These values will be injected by the build system
|
||||
appVersion = "Unknown"
|
||||
appDate = "Unknown"
|
||||
appType = "Unknown" // aka channel
|
||||
appCommit = "Unknown"
|
||||
appPlatform = "Unknown"
|
||||
appArch = "Unknown"
|
||||
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)
|
||||
)
|
||||
|
|
|
@ -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"`
|
||||
|
@ -73,20 +83,44 @@ type serverConfigObfs struct {
|
|||
}
|
||||
|
||||
type serverConfigTLS struct {
|
||||
Cert string `mapstructure:"cert"`
|
||||
Key string `mapstructure:"key"`
|
||||
Cert string `mapstructure:"cert"`
|
||||
Key string `mapstructure:"key"`
|
||||
SNIGuard string `mapstructure:"sniGuard"` // "disable", "dns-san", "strict"
|
||||
}
|
||||
|
||||
type serverConfigACME struct {
|
||||
Domains []string `mapstructure:"domains"`
|
||||
Email string `mapstructure:"email"`
|
||||
CA string `mapstructure:"ca"`
|
||||
DisableHTTP bool `mapstructure:"disableHTTP"`
|
||||
DisableTLSALPN bool `mapstructure:"disableTLSALPN"`
|
||||
ListenHost string `mapstructure:"listenHost"`
|
||||
AltHTTPPort int `mapstructure:"altHTTPPort"`
|
||||
AltTLSALPNPort int `mapstructure:"altTLSALPNPort"`
|
||||
Dir string `mapstructure:"dir"`
|
||||
// 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"`
|
||||
AltHTTPPort int `mapstructure:"altHTTPPort"`
|
||||
AltTLSALPNPort int `mapstructure:"altTLSALPNPort"`
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -277,14 +350,10 @@ func (c *serverConfig) fillTLSConfig(hyConfig *server.Config) error {
|
|||
Logger: logger,
|
||||
}
|
||||
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,
|
||||
Email: c.ACME.Email,
|
||||
Agreed: true,
|
||||
ListenHost: c.ACME.ListenHost,
|
||||
Logger: logger,
|
||||
})
|
||||
switch strings.ToLower(c.ACME.CA) {
|
||||
case "letsencrypt", "le", "":
|
||||
|
@ -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,
|
||||
|
|
|
@ -26,21 +26,37 @@ func TestServerConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
TLS: &serverConfigTLS{
|
||||
Cert: "some.crt",
|
||||
Key: "some.key",
|
||||
Cert: "some.crt",
|
||||
Key: "some.key",
|
||||
SNIGuard: "strict",
|
||||
},
|
||||
ACME: &serverConfigACME{
|
||||
Domains: []string{
|
||||
"sub1.example.com",
|
||||
"sub2.example.com",
|
||||
},
|
||||
Email: "haha@cringe.net",
|
||||
CA: "zero",
|
||||
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",
|
||||
|
|
|
@ -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
55
app/cmd/share.go
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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,11 +71,26 @@ func runSpeedtest(cmd *cobra.Command, args []string) {
|
|||
zap.Bool("udpEnabled", info.UDPEnabled),
|
||||
zap.Uint64("tx", info.Tx))
|
||||
|
||||
if !skipDownload {
|
||||
runDownloadTest(c)
|
||||
}
|
||||
if !skipUpload {
|
||||
runUploadTest(c)
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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])
|
||||
|
|
|
@ -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 (
|
||||
|
|
63
app/go.mod
63
app/go.mod
|
@ -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
|
||||
|
|
134
app/go.sum
134
app/go.sum
|
@ -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=
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/apernet/hysteria/core/client"
|
||||
"github.com/apernet/hysteria/core/v2/client"
|
||||
)
|
||||
|
||||
type TCPTunnel struct {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/hysteria/core/client"
|
||||
"github.com/apernet/hysteria/core/v2/client"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/apernet/hysteria/core/client"
|
||||
"github.com/apernet/hysteria/core/v2/client"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/apernet/hysteria/extras/correctnet"
|
||||
"github.com/apernet/hysteria/extras/v2/correctnet"
|
||||
)
|
||||
|
||||
type muxManager struct {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/apernet/hysteria/core/client"
|
||||
"github.com/apernet/hysteria/core/v2/client"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/apernet/hysteria/core/client"
|
||||
"github.com/apernet/hysteria/core/v2/client"
|
||||
)
|
||||
|
||||
type TCPRedirect struct {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/apernet/hysteria/core/client"
|
||||
"github.com/apernet/hysteria/core/v2/client"
|
||||
)
|
||||
|
||||
type TCPTProxy struct {
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/hysteria/core/client"
|
||||
"github.com/apernet/hysteria/core/v2/client"
|
||||
)
|
||||
|
||||
type UDPTProxy struct {
|
||||
|
|
14
app/internal/tun/check_ipv6_others.go
Normal file
14
app/internal/tun/check_ipv6_others.go
Normal 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
|
||||
}
|
16
app/internal/tun/check_ipv6_unix.go
Normal file
16
app/internal/tun/check_ipv6_unix.go
Normal 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
|
||||
}
|
24
app/internal/tun/check_ipv6_windows.go
Normal file
24
app/internal/tun/check_ipv6_windows.go
Normal 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
|
||||
}
|
|
@ -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,
|
||||
|
|
198
app/internal/utils/certloader.go
Normal file
198
app/internal/utils/certloader.go
Normal 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
|
||||
}
|
139
app/internal/utils/certloader_test.go
Normal file
139
app/internal/utils/certloader_test.go
Normal 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
|
||||
}
|
134
app/internal/utils/certloader_test_gencert.py
Normal file
134
app/internal/utils/certloader_test_gencert.py
Normal 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()
|
60
app/internal/utils/certloader_test_tlsclient.py
Normal file
60
app/internal/utils/certloader_test_tlsclient.py
Normal 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()
|
|
@ -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 (
|
||||
|
|
3
app/internal/utils/testcerts/.gitignore
vendored
Normal file
3
app/internal/utils/testcerts/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
# This directory is used for certificate generation in certloader_test.go
|
||||
/*
|
||||
!/.gitignore
|
|
@ -8,7 +8,7 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/hysteria/core/client"
|
||||
"github.com/apernet/hysteria/core/v2/client"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/hysteria/core/client"
|
||||
"github.com/apernet/hysteria/core/v2/client"
|
||||
)
|
||||
|
||||
type MockEchoHyClient struct{}
|
||||
|
|
|
@ -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
7
core/LICENSE.md
Normal 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.
|
|
@ -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:
|
||||
|
|
|
@ -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,18 +223,21 @@ 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 {
|
||||
return err
|
||||
for _, e := range nonPermanentErrors {
|
||||
if errors.Is(err, e) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return coreErrs.ClosedError{Err: err}
|
||||
}
|
||||
|
||||
type tcpConn struct {
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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) {
|
||||
|
|
40
core/go.mod
40
core/go.mod
|
@ -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
|
||||
)
|
||||
|
|
78
core/go.sum
78
core/go.sum
|
@ -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=
|
||||
|
|
|
@ -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])
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/quic-go/congestion"
|
||||
)
|
||||
|
||||
const (
|
||||
maxBurstPackets = 10
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
|
@ -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.
|
||||
|
|
147
core/internal/integration_tests/hook_test.go
Normal file
147
core/internal/integration_tests/hook_test.go
Normal 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)
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
188
core/internal/integration_tests/mocks/mock_RequestHook.go
Normal file
188
core/internal/integration_tests/mocks/mock_RequestHook.go
Normal 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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
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 {
|
||||
_ = protocol.WriteTCPResponse(stream, false, err.Error())
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -29,11 +30,58 @@ type udpEventLogger interface {
|
|||
}
|
||||
|
||||
type udpSessionEntry struct {
|
||||
ID uint32
|
||||
Conn UDPConn
|
||||
D *frag.Defragger
|
||||
Last *utils.AtomicTime
|
||||
Timeout bool // true if the session is closed due to timeout
|
||||
ID uint32
|
||||
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
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
addr := dfMsg.Addr
|
||||
if e.OverrideAddr != "" {
|
||||
addr = e.OverrideAddr
|
||||
}
|
||||
|
||||
return e.conn.WriteTo(dfMsg.Data, addr)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// 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)
|
||||
if err != nil {
|
||||
m.eventLogger.Close(msg.SessionID, err)
|
||||
dialFunc := func(addr string, firstMsgData []byte) (conn UDPConn, actualAddr string, err error) {
|
||||
// Call the hook
|
||||
err = m.io.Hook(firstMsgData, &addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
actualAddr = addr
|
||||
// Log the event
|
||||
m.eventLogger.New(msg.SessionID, addr)
|
||||
// Dial target
|
||||
conn, err = m.io.UDP(addr)
|
||||
return
|
||||
}
|
||||
entry = &udpSessionEntry{
|
||||
ID: msg.SessionID,
|
||||
Conn: conn,
|
||||
D: &frag.Defragger{},
|
||||
Last: utils.NewAtomicTime(time.Now()),
|
||||
}
|
||||
// Start the receive loop for this session
|
||||
go func() {
|
||||
err := entry.ReceiveLoop(m.io)
|
||||
if !entry.Timeout {
|
||||
_ = entry.Conn.Close()
|
||||
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)
|
||||
}
|
||||
exitFunc := func(err error) {
|
||||
// Log the event
|
||||
m.eventLogger.Close(entry.ID, err)
|
||||
|
||||
// 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
|
||||
|
|
|
@ -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
7
extras/LICENSE.md
Normal 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.
|
|
@ -6,7 +6,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/apernet/hysteria/core/server"
|
||||
"github.com/apernet/hysteria/core/v2/server"
|
||||
)
|
||||
|
||||
var _ server.Authenticator = &CommandAuthenticator{}
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/hysteria/core/server"
|
||||
"github.com/apernet/hysteria/core/v2/server"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -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{}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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=
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue