Compare commits

..

No commits in common. "master" and "extras/v2.5.2" have entirely different histories.

43 changed files with 164 additions and 1099 deletions

View file

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

View file

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

View file

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

View file

@ -470,10 +470,8 @@ 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)
}

View file

@ -29,24 +29,20 @@ const (
var (
// These values will be injected by the build system
appVersion = "Unknown"
appDate = "Unknown"
appType = "Unknown" // aka channel
appToolchain = "Unknown"
appCommit = "Unknown"
appPlatform = "Unknown"
appArch = "Unknown"
libVersion = "Unknown"
appVersion = "Unknown"
appDate = "Unknown"
appType = "Unknown" // aka channel
appCommit = "Unknown"
appPlatform = "Unknown"
appArch = "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\n"+
"Libraries:\tquic-go=%s",
appVersion, appDate, appType, appToolchain, appCommit, appPlatform, appArch, libVersion)
"Architecture:\t%s",
appVersion, appDate, appType, appCommit, appPlatform, appArch)
appAboutLong = fmt.Sprintf("%s\n%s\n%s\n\n%s", appLogo, appDesc, appAuthors, appVersionLong)
)

View file

@ -204,7 +204,6 @@ type serverConfigOutboundDirect struct {
BindIPv4 string `mapstructure:"bindIPv4"`
BindIPv6 string `mapstructure:"bindIPv6"`
BindDevice string `mapstructure:"bindDevice"`
FastOpen bool `mapstructure:"fastOpen"`
}
type serverConfigOutboundSOCKS5 struct {
@ -238,7 +237,6 @@ type serverConfigMasqueradeFile struct {
type serverConfigMasqueradeProxy struct {
URL string `mapstructure:"url"`
RewriteHost bool `mapstructure:"rewriteHost"`
Insecure bool `mapstructure:"insecure"`
}
type serverConfigMasqueradeString struct {
@ -520,18 +518,18 @@ func (c *serverConfig) fillQUICConfig(hyConfig *server.Config) error {
}
func serverConfigOutboundDirectToOutbound(c serverConfigOutboundDirect) (outbounds.PluggableOutbound, error) {
opts := outbounds.DirectOutboundOptions{}
var mode outbounds.DirectOutboundMode
switch strings.ToLower(c.Mode) {
case "", "auto":
opts.Mode = outbounds.DirectOutboundModeAuto
mode = outbounds.DirectOutboundModeAuto
case "64":
opts.Mode = outbounds.DirectOutboundMode64
mode = outbounds.DirectOutboundMode64
case "46":
opts.Mode = outbounds.DirectOutboundMode46
mode = outbounds.DirectOutboundMode46
case "6":
opts.Mode = outbounds.DirectOutboundMode6
mode = outbounds.DirectOutboundMode6
case "4":
opts.Mode = outbounds.DirectOutboundMode4
mode = outbounds.DirectOutboundMode4
default:
return nil, configError{Field: "outbounds.direct.mode", Err: errors.New("unsupported mode")}
}
@ -548,14 +546,12 @@ 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")}
}
opts.BindIP4 = ip4
opts.BindIP6 = ip6
return outbounds.NewDirectOutboundBindToIPs(mode, ip4, ip6)
}
if bindDevice {
opts.DeviceName = c.BindDevice
return outbounds.NewDirectOutboundBindToDevice(mode, c.BindDevice)
}
opts.FastOpen = c.FastOpen
return outbounds.NewDirectOutboundWithOptions(opts)
return outbounds.NewDirectOutboundSimple(mode), nil
}
func serverConfigOutboundSOCKS5ToOutbound(c serverConfigOutboundSOCKS5) (outbounds.PluggableOutbound, error) {
@ -755,7 +751,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.NewUserPassAuthenticator(c.Auth.UserPass)
hyConfig.Authenticator = &auth.UserPassAuthenticator{Users: c.Auth.UserPass}
return nil
case "http", "https":
if c.Auth.HTTP.URL == "" {
@ -808,28 +804,6 @@ 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)
@ -839,7 +813,6 @@ 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)

View file

@ -138,7 +138,6 @@ func TestServerConfig(t *testing.T) {
BindIPv4: "2.4.6.8",
BindIPv6: "0:0:0:0:0:ffff:0204:0608",
BindDevice: "eth233",
FastOpen: true,
},
},
{
@ -171,7 +170,6 @@ func TestServerConfig(t *testing.T) {
Proxy: serverConfigMasqueradeProxy{
URL: "https://some.site.net",
RewriteHost: true,
Insecure: true,
},
String: serverConfigMasqueradeString{
Content: "aint nothin here",

View file

@ -108,7 +108,6 @@ outbounds:
bindIPv4: 2.4.6.8
bindIPv6: 0:0:0:0:0:ffff:0204:0608
bindDevice: eth233
fastOpen: true
- name: badstuff
type: socks5
socks5:
@ -132,7 +131,6 @@ masquerade:
proxy:
url: https://some.site.net
rewriteHost: true
insecure: true
string:
content: aint nothin here
headers:

View file

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

View file

@ -1,8 +1,8 @@
module github.com/apernet/hysteria/app/v2
go 1.23
go 1.22
toolchain go1.24.2
toolchain go1.23.2
require (
github.com/apernet/go-tproxy v0.0.0-20230809025308-8f4723fd742f
@ -25,16 +25,14 @@ require (
github.com/txthinking/socks5 v0.0.0-20230325130024-4230056ae301
go.uber.org/zap v1.24.0
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
golang.org/x/sys v0.25.0
golang.org/x/sys v0.23.0
)
require (
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/apernet/quic-go v0.52.1-0.20250607183305-9320c9d14431 // indirect
github.com/apernet/quic-go v0.47.1-0.20241004180137-a80d14e2080d // 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
@ -71,16 +69,16 @@ require (
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.5.0 // indirect
go.uber.org/mock v0.4.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/crypto v0.26.0 // indirect
golang.org/x/mod v0.18.0 // indirect
golang.org/x/mod v0.17.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
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // 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

View file

@ -42,8 +42,8 @@ github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1
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.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/quic-go v0.47.1-0.20241004180137-a80d14e2080d h1:KWRCWISqJOgY9/0hhH8Bevjw/k4tCQ7oJlXLyFv8u9s=
github.com/apernet/quic-go v0.47.1-0.20241004180137-a80d14e2080d/go.mod h1:x0paLlmCzNOUDDQIgmgFWmnpWQIEuH1GNfA6NdgSTuM=
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=
@ -63,10 +63,6 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX
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=
@ -300,8 +296,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.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
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=
@ -358,8 +354,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.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.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=
@ -467,8 +463,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.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
golang.org/x/sys v0.23.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=
@ -485,6 +481,8 @@ 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=
@ -536,8 +534,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.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
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=

View file

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

View file

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

View file

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

View file

@ -49,10 +49,6 @@ type EventLogger interface {
}
func (s *Server) Serve() error {
if !isIPv6Supported() {
s.Logger.Warn("tun-pre-check", zap.String("msg", "IPv6 is not supported or enabled on this system, TUN device is created without IPv6 support."))
s.Inet6Address = nil
}
tunOpts := tun.Options{
Name: s.IfName,
Inet4Address: s.Inet4Address,

View file

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

View file

@ -3,7 +3,6 @@ package client
import (
"context"
"crypto/tls"
"errors"
"net"
"net/http"
"net/url"
@ -84,7 +83,6 @@ 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
@ -223,21 +221,18 @@ func (c *clientImpl) Close() error {
return nil
}
var nonPermanentErrors = []error{
quic.StreamLimitReachedError{},
}
// wrapIfConnectionClosed checks if the error returned by quic-go
// is recoverable (listed in nonPermanentErrors) or permanent.
// Recoverable errors are returned as-is,
// permanent ones are wrapped as ClosedError.
// 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.
func wrapIfConnectionClosed(err error) error {
for _, e := range nonPermanentErrors {
if errors.Is(err, e) {
return err
}
netErr, ok := err.(net.Error)
if !ok || !netErr.Temporary() {
return coreErrs.ClosedError{Err: err}
} else {
return err
}
return coreErrs.ClosedError{Err: err}
}
type tcpConn struct {

View file

@ -1,11 +1,11 @@
module github.com/apernet/hysteria/core/v2
go 1.23
go 1.22
toolchain go1.24.2
toolchain go1.23.2
require (
github.com/apernet/quic-go v0.52.1-0.20250607183305-9320c9d14431
github.com/apernet/quic-go v0.47.1-0.20241004180137-a80d14e2080d
github.com/stretchr/testify v1.9.0
go.uber.org/goleak v1.2.1
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
@ -23,14 +23,13 @@ require (
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
go.uber.org/mock v0.4.0 // indirect
golang.org/x/crypto v0.26.0 // indirect
golang.org/x/mod v0.18.0 // indirect
golang.org/x/mod v0.17.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/sys v0.23.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/tools v0.22.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

View file

@ -1,5 +1,5 @@
github.com/apernet/quic-go v0.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/quic-go v0.47.1-0.20241004180137-a80d14e2080d h1:KWRCWISqJOgY9/0hhH8Bevjw/k4tCQ7oJlXLyFv8u9s=
github.com/apernet/quic-go v0.47.1-0.20241004180137-a80d14e2080d/go.mod h1:x0paLlmCzNOUDDQIgmgFWmnpWQIEuH1GNfA6NdgSTuM=
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=
@ -45,27 +45,27 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
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.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
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.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/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.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.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
golang.org/x/sys v0.23.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.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
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=

View file

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

View file

@ -2,12 +2,7 @@
package mocks
import (
quic "github.com/apernet/quic-go"
mock "github.com/stretchr/testify/mock"
server "github.com/apernet/hysteria/core/v2/server"
)
import mock "github.com/stretchr/testify/mock"
// MockTrafficLogger is an autogenerated mock type for the TrafficLogger type
type MockTrafficLogger struct {
@ -104,73 +99,6 @@ func (_c *MockTrafficLogger_LogTraffic_Call) RunAndReturn(run func(string, uint6
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
}
// NewMockTrafficLogger creates a new instance of MockTrafficLogger. 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 NewMockTrafficLogger(t interface {

View file

@ -62,7 +62,6 @@ 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)
@ -85,7 +84,6 @@ 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().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")

View file

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

View file

@ -4,12 +4,10 @@ import (
"crypto/tls"
"net"
"net/http"
"sync/atomic"
"time"
"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"
)
@ -214,66 +212,4 @@ type EventLogger interface {
type TrafficLogger interface {
LogTraffic(id string, tx, rx uint64) (ok bool)
LogOnlineState(id string, online bool)
TraceStream(stream quic.Stream, stats *StreamStats)
UntraceStream(stream quic.Stream)
}
type StreamState int
const (
// StreamStateInitial indicates the initial state of a stream.
// Client has opened the stream, but we have not received the proxy request yet.
StreamStateInitial StreamState = iota
// StreamStateHooking indicates that the hook (usually sniff) is processing.
// Client has sent the proxy request, but sniff requires more data to complete.
StreamStateHooking
// StreamStateConnecting indicates that we are connecting to the proxy target.
StreamStateConnecting
// StreamStateEstablished indicates the proxy is established.
StreamStateEstablished
// StreamStateClosed indicates the stream is closed.
StreamStateClosed
)
func (s StreamState) String() string {
switch s {
case StreamStateInitial:
return "init"
case StreamStateHooking:
return "hook"
case StreamStateConnecting:
return "connect"
case StreamStateEstablished:
return "estab"
case StreamStateClosed:
return "closed"
default:
return "unknown"
}
}
type StreamStats struct {
State utils.Atomic[StreamState]
AuthID string
ConnID uint32
InitialTime time.Time
ReqAddr utils.Atomic[string]
HookedReqAddr utils.Atomic[string]
Tx atomic.Uint64
Rx atomic.Uint64
LastActiveTime utils.Atomic[time.Time]
}
func (s *StreamStats) setHookedReqAddr(addr string) {
if addr != s.ReqAddr.Load() {
s.HookedReqAddr.Store(addr)
}
}

View file

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

View file

@ -3,10 +3,8 @@ package server
import (
"context"
"crypto/tls"
"math/rand"
"net/http"
"sync"
"time"
"github.com/apernet/quic-go"
"github.com/apernet/quic-go/http3"
@ -43,7 +41,6 @@ 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 {
@ -103,7 +100,6 @@ type h3sHandler struct {
authenticated bool
authMutex sync.Mutex
authID string
connID uint32 // a random id for dump streams
udpSM *udpSessionManager // Only set after authentication
}
@ -112,7 +108,6 @@ func newH3sHandler(config *Config, conn quic.Connection) *h3sHandler {
return &h3sHandler{
config: config,
conn: conn,
connID: rand.Uint32(),
}
}
@ -210,29 +205,12 @@ func (h *h3sHandler) ProxyStreamHijacker(ft http3.FrameType, id quic.ConnectionT
}
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
@ -242,14 +220,12 @@ func (h *h3sHandler) handleTCPRequest(stream quic.Stream) {
// 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
@ -257,7 +233,6 @@ func (h *h3sHandler) handleTCPRequest(stream quic.Stream) {
h.config.EventLogger.TCPRequest(h.conn.RemoteAddr(), h.authID, reqAddr)
}
// Dial target
streamStats.State.Store(StreamStateConnecting)
tConn, err := h.config.Outbound.TCP(reqAddr)
if err != nil {
if !hooked {
@ -273,15 +248,13 @@ func (h *h3sHandler) handleTCPRequest(stream quic.Stream) {
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))
_, _ = tConn.Write(putback)
}
// Start proxying
if trafficLogger != nil {
err = copyTwoWayEx(h.authID, stream, tConn, trafficLogger, streamStats)
if h.config.TrafficLogger != nil {
err = copyTwoWayWithLogger(h.authID, stream, tConn, h.config.TrafficLogger)
} else {
// Use the fast path if no traffic logger is set
err = copyTwoWay(stream, tConn)

View file

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

View file

@ -16,17 +16,7 @@ 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
}
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}
Users map[string]string
}
func (a *UserPassAuthenticator) Authenticate(addr net.Addr, auth string, tx uint64) (ok bool, id string) {
@ -34,7 +24,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, ""
}
@ -46,6 +36,5 @@ func splitUserPass(auth string) (user, pass string, ok bool) {
if len(rs) != 2 {
return "", "", false
}
// Usernames are case-insensitive
return strings.ToLower(rs[0]), rs[1], true
return rs[0], rs[1], true
}

View file

@ -85,26 +85,12 @@ 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 := NewUserPassAuthenticator(tt.fields.Users)
a := &UserPassAuthenticator{
Users: tt.fields.Users,
}
gotOk, gotId := a.Authenticate(tt.args.addr, tt.args.auth, tt.args.tx)
if gotOk != tt.wantOk {
t.Errorf("Authenticate() gotOk = %v, want %v", gotOk, tt.wantOk)

View file

@ -1,14 +1,13 @@
module github.com/apernet/hysteria/extras/v2
go 1.23
go 1.22
toolchain go1.24.2
toolchain go1.23.2
require (
github.com/apernet/hysteria/core/v2 v2.0.0-00010101000000-000000000000
github.com/apernet/quic-go v0.52.1-0.20250607183305-9320c9d14431
github.com/apernet/quic-go v0.47.1-0.20241004180137-a80d14e2080d
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.59
github.com/refraction-networking/utls v1.6.6
@ -22,7 +21,6 @@ require (
require (
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
@ -34,13 +32,13 @@ require (
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.5.0 // indirect
go.uber.org/mock v0.4.0 // indirect
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
golang.org/x/mod v0.18.0 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/sys v0.23.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/tools v0.22.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View file

@ -1,7 +1,7 @@
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/apernet/quic-go v0.47.1-0.20241004180137-a80d14e2080d h1:KWRCWISqJOgY9/0hhH8Bevjw/k4tCQ7oJlXLyFv8u9s=
github.com/apernet/quic-go v0.47.1-0.20241004180137-a80d14e2080d/go.mod h1:x0paLlmCzNOUDDQIgmgFWmnpWQIEuH1GNfA6NdgSTuM=
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=
@ -10,10 +10,6 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
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=
@ -66,8 +62,8 @@ 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.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
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.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.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
@ -76,8 +72,8 @@ golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJ
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.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.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=
@ -96,8 +92,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.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
golang.org/x/sys v0.23.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=
@ -107,12 +103,14 @@ 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.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.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.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=

View file

@ -1,229 +0,0 @@
package outbounds
import (
"net"
"sync"
"time"
"github.com/database64128/tfo-go/v2"
)
type fastOpenDialer struct {
dialer *tfo.Dialer
}
func newFastOpenDialer(netDialer *net.Dialer) *fastOpenDialer {
return &fastOpenDialer{
dialer: &tfo.Dialer{
Dialer: *netDialer,
},
}
}
// Dial returns immediately without actually establishing a connection.
// The connection will be established by the first Write() call.
func (d *fastOpenDialer) Dial(network, address string) (net.Conn, error) {
return &fastOpenConn{
dialer: d.dialer,
network: network,
address: address,
readyChan: make(chan struct{}),
}, nil
}
type fastOpenConn struct {
dialer *tfo.Dialer
network string
address string
conn net.Conn
connLock sync.RWMutex
readyChan chan struct{}
// States before connection ready
deadline *time.Time
readDeadline *time.Time
writeDeadline *time.Time
}
func (c *fastOpenConn) Read(b []byte) (n int, err error) {
c.connLock.RLock()
conn := c.conn
c.connLock.RUnlock()
if conn != nil {
return conn.Read(b)
}
// Wait until the connection is ready or closed
<-c.readyChan
if c.conn == nil {
// This is equivalent to isClosedBeforeReady() == true
return 0, net.ErrClosed
}
return c.conn.Read(b)
}
func (c *fastOpenConn) Write(b []byte) (n int, err error) {
c.connLock.RLock()
conn := c.conn
c.connLock.RUnlock()
if conn != nil {
return conn.Write(b)
}
c.connLock.RLock()
closed := c.isClosedBeforeReady()
c.connLock.RUnlock()
if closed {
return 0, net.ErrClosed
}
c.connLock.Lock()
defer c.connLock.Unlock()
if c.isClosedBeforeReady() {
// Closed by other goroutine
return 0, net.ErrClosed
}
conn = c.conn
if conn != nil {
// Established by other goroutine
return conn.Write(b)
}
conn, err = c.dialer.Dial(c.network, c.address, b)
if err != nil {
close(c.readyChan)
return 0, err
}
// Apply pre-set states
if c.deadline != nil {
_ = conn.SetDeadline(*c.deadline)
}
if c.readDeadline != nil {
_ = conn.SetReadDeadline(*c.readDeadline)
}
if c.writeDeadline != nil {
_ = conn.SetWriteDeadline(*c.writeDeadline)
}
c.conn = conn
close(c.readyChan)
return len(b), nil
}
func (c *fastOpenConn) Close() error {
c.connLock.RLock()
defer c.connLock.RUnlock()
if c.isClosedBeforeReady() {
return net.ErrClosed
}
if c.conn != nil {
return c.conn.Close()
}
close(c.readyChan)
return nil
}
// isClosedBeforeReady returns true if the connection is closed before the real connection is established.
// This function should be called with connLock.RLock().
func (c *fastOpenConn) isClosedBeforeReady() bool {
select {
case <-c.readyChan:
if c.conn == nil {
return true
}
default:
}
return false
}
func (c *fastOpenConn) LocalAddr() net.Addr {
c.connLock.RLock()
defer c.connLock.RUnlock()
if c.conn != nil {
return c.conn.LocalAddr()
}
return nil
}
func (c *fastOpenConn) RemoteAddr() net.Addr {
c.connLock.RLock()
conn := c.conn
c.connLock.RUnlock()
if conn != nil {
return conn.RemoteAddr()
}
addr, err := net.ResolveTCPAddr(c.network, c.address)
if err != nil {
return nil
}
return addr
}
func (c *fastOpenConn) SetDeadline(t time.Time) error {
c.connLock.RLock()
defer c.connLock.RUnlock()
c.deadline = &t
if c.conn != nil {
return c.conn.SetDeadline(t)
}
if c.isClosedBeforeReady() {
return net.ErrClosed
}
return nil
}
func (c *fastOpenConn) SetReadDeadline(t time.Time) error {
c.connLock.RLock()
defer c.connLock.RUnlock()
c.readDeadline = &t
if c.conn != nil {
return c.conn.SetReadDeadline(t)
}
if c.isClosedBeforeReady() {
return net.ErrClosed
}
return nil
}
func (c *fastOpenConn) SetWriteDeadline(t time.Time) error {
c.connLock.RLock()
defer c.connLock.RUnlock()
c.writeDeadline = &t
if c.conn != nil {
return c.conn.SetWriteDeadline(t)
}
if c.isClosedBeforeReady() {
return net.ErrClosed
}
return nil
}
var _ net.Conn = (*fastOpenConn)(nil)

View file

@ -35,8 +35,8 @@ type directOutbound struct {
Mode DirectOutboundMode
// Dialer4 and Dialer6 are used for IPv4 and IPv6 TCP connections respectively.
DialFunc4 func(network, address string) (net.Conn, error)
DialFunc6 func(network, address string) (net.Conn, error)
Dialer4 *net.Dialer
Dialer6 *net.Dialer
// DeviceName & BindIPs are for UDP connections. They don't use dialers, so we
// need to bind them when creating the connection.
@ -45,16 +45,6 @@ type directOutbound struct {
BindIP6 net.IP
}
type DirectOutboundOptions struct {
Mode DirectOutboundMode
DeviceName string
BindIP4 net.IP
BindIP6 net.IP
FastOpen bool
}
type noAddressError struct {
IPv4 bool
IPv6 bool
@ -94,57 +84,6 @@ func (e resolveError) Unwrap() error {
return e.Err
}
func NewDirectOutboundWithOptions(opts DirectOutboundOptions) (PluggableOutbound, error) {
dialer4 := &net.Dialer{
Timeout: defaultDialerTimeout,
}
if opts.BindIP4 != nil {
if opts.BindIP4.To4() == nil {
return nil, errors.New("BindIP4 must be an IPv4 address")
}
dialer4.LocalAddr = &net.TCPAddr{
IP: opts.BindIP4,
}
}
dialer6 := &net.Dialer{
Timeout: defaultDialerTimeout,
}
if opts.BindIP6 != nil {
if opts.BindIP6.To4() != nil {
return nil, errors.New("BindIP6 must be an IPv6 address")
}
dialer6.LocalAddr = &net.TCPAddr{
IP: opts.BindIP6,
}
}
if opts.DeviceName != "" {
err := dialerBindToDevice(dialer4, opts.DeviceName)
if err != nil {
return nil, err
}
err = dialerBindToDevice(dialer6, opts.DeviceName)
if err != nil {
return nil, err
}
}
dialFunc4 := dialer4.Dial
dialFunc6 := dialer6.Dial
if opts.FastOpen {
dialFunc4 = newFastOpenDialer(dialer4).Dial
dialFunc6 = newFastOpenDialer(dialer6).Dial
}
return &directOutbound{
Mode: opts.Mode,
DialFunc4: dialFunc4,
DialFunc6: dialFunc6,
DeviceName: opts.DeviceName,
BindIP4: opts.BindIP4,
BindIP6: opts.BindIP6,
}, nil
}
// NewDirectOutboundSimple creates a new directOutbound with the given mode,
// without binding to a specific device. Works on all platforms.
func NewDirectOutboundSimple(mode DirectOutboundMode) PluggableOutbound {
@ -152,9 +91,9 @@ func NewDirectOutboundSimple(mode DirectOutboundMode) PluggableOutbound {
Timeout: defaultDialerTimeout,
}
return &directOutbound{
Mode: mode,
DialFunc4: d.Dial,
DialFunc6: d.Dial,
Mode: mode,
Dialer4: d,
Dialer6: d,
}
}
@ -163,20 +102,34 @@ func NewDirectOutboundSimple(mode DirectOutboundMode) PluggableOutbound {
// can be nil, in which case the directOutbound will not bind to a specific address
// for that family.
func NewDirectOutboundBindToIPs(mode DirectOutboundMode, bindIP4, bindIP6 net.IP) (PluggableOutbound, error) {
return NewDirectOutboundWithOptions(DirectOutboundOptions{
Mode: mode,
if bindIP4 != nil && bindIP4.To4() == nil {
return nil, errors.New("bindIP4 must be an IPv4 address")
}
if bindIP6 != nil && bindIP6.To4() != nil {
return nil, errors.New("bindIP6 must be an IPv6 address")
}
ob := &directOutbound{
Mode: mode,
Dialer4: &net.Dialer{
Timeout: defaultDialerTimeout,
},
Dialer6: &net.Dialer{
Timeout: defaultDialerTimeout,
},
BindIP4: bindIP4,
BindIP6: bindIP6,
})
}
// NewDirectOutboundBindToDevice creates a new directOutbound with the given mode,
// and binds to the given device. Only works on Linux.
func NewDirectOutboundBindToDevice(mode DirectOutboundMode, deviceName string) (PluggableOutbound, error) {
return NewDirectOutboundWithOptions(DirectOutboundOptions{
Mode: mode,
DeviceName: deviceName,
})
}
if bindIP4 != nil {
ob.Dialer4.LocalAddr = &net.TCPAddr{
IP: bindIP4,
}
}
if bindIP6 != nil {
ob.Dialer6.LocalAddr = &net.TCPAddr{
IP: bindIP6,
}
}
return ob, nil
}
// resolve is our built-in DNS resolver for handling the case when
@ -248,9 +201,9 @@ func (d *directOutbound) TCP(reqAddr *AddrEx) (net.Conn, error) {
func (d *directOutbound) dialTCP(ip net.IP, port uint16) (net.Conn, error) {
if ip.To4() != nil {
return d.DialFunc4("tcp4", net.JoinHostPort(ip.String(), strconv.Itoa(int(port))))
return d.Dialer4.Dial("tcp4", net.JoinHostPort(ip.String(), strconv.Itoa(int(port))))
} else {
return d.DialFunc6("tcp6", net.JoinHostPort(ip.String(), strconv.Itoa(int(port))))
return d.Dialer6.Dial("tcp6", net.JoinHostPort(ip.String(), strconv.Itoa(int(port))))
}
}

View file

@ -6,31 +6,31 @@ import (
"syscall"
)
func dialerBindToDevice(dialer *net.Dialer, deviceName string) error {
// NewDirectOutboundBindToDevice creates a new directOutbound with the given mode,
// and binds to the given device. Only works on Linux.
func NewDirectOutboundBindToDevice(mode DirectOutboundMode, deviceName string) (PluggableOutbound, error) {
if err := verifyDeviceName(deviceName); err != nil {
return err
return nil, err
}
originControl := dialer.Control
dialer.Control = func(network, address string, c syscall.RawConn) error {
if originControl != nil {
// Chaining other control function
err := originControl(network, address, c)
d := &net.Dialer{
Timeout: defaultDialerTimeout,
Control: func(network, address string, c syscall.RawConn) error {
var errBind error
err := c.Control(func(fd uintptr) {
errBind = syscall.BindToDevice(int(fd), deviceName)
})
if err != nil {
return err
}
}
var errBind error
err := c.Control(func(fd uintptr) {
errBind = syscall.BindToDevice(int(fd), deviceName)
})
if err != nil {
return err
}
return errBind
return errBind
},
}
return nil
return &directOutbound{
Mode: mode,
Dialer4: d,
Dialer6: d,
DeviceName: deviceName,
}, nil
}
func verifyDeviceName(deviceName string) error {

View file

@ -7,8 +7,11 @@ import (
"net"
)
func dialerBindToDevice(dialer *net.Dialer, deviceName string) error {
return errors.New("binding to device is not supported on this platform")
// NewDirectOutboundBindToDevice creates a new directOutbound with the given mode,
// and binds to the given device. This doesn't work on non-Linux platforms, so this
// is just a stub function that always returns an error.
func NewDirectOutboundBindToDevice(mode DirectOutboundMode, deviceName string) (PluggableOutbound, error) {
return nil, errors.New("binding to device is not supported on this platform")
}
func udpConnBindToDevice(conn *net.UDPConn, deviceName string) error {

View file

@ -1,18 +1,12 @@
package trafficlogger
import (
"cmp"
"encoding/json"
"fmt"
"net/http"
"slices"
"strconv"
"strings"
"sync"
"time"
"github.com/apernet/hysteria/core/v2/server"
"github.com/apernet/quic-go"
)
const (
@ -31,7 +25,6 @@ func NewTrafficStatsServer(secret string) TrafficStatsServer {
StatsMap: make(map[string]*trafficStatsEntry),
KickMap: make(map[string]struct{}),
OnlineMap: make(map[string]int),
StreamMap: make(map[quic.Stream]*server.StreamStats),
Secret: secret,
}
}
@ -40,7 +33,6 @@ type trafficStatsServerImpl struct {
Mutex sync.RWMutex
StatsMap map[string]*trafficStatsEntry
OnlineMap map[string]int
StreamMap map[quic.Stream]*server.StreamStats
KickMap map[string]struct{}
Secret string
}
@ -71,7 +63,7 @@ func (s *trafficStatsServerImpl) LogTraffic(id string, tx, rx uint64) (ok bool)
return true
}
// LogOnlineState updates the online state to the online map.
// LogOnlineStateChanged updates the online state to the online map.
func (s *trafficStatsServerImpl) LogOnlineState(id string, online bool) {
s.Mutex.Lock()
defer s.Mutex.Unlock()
@ -86,20 +78,6 @@ func (s *trafficStatsServerImpl) LogOnlineState(id string, online bool) {
}
}
func (s *trafficStatsServerImpl) TraceStream(stream quic.Stream, stats *server.StreamStats) {
s.Mutex.Lock()
defer s.Mutex.Unlock()
s.StreamMap[stream] = stats
}
func (s *trafficStatsServerImpl) UntraceStream(stream quic.Stream) {
s.Mutex.Lock()
defer s.Mutex.Unlock()
delete(s.StreamMap, stream)
}
func (s *trafficStatsServerImpl) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if s.Secret != "" && r.Header.Get("Authorization") != s.Secret {
http.Error(w, "unauthorized", http.StatusUnauthorized)
@ -121,10 +99,6 @@ func (s *trafficStatsServerImpl) ServeHTTP(w http.ResponseWriter, r *http.Reques
s.getOnline(w, r)
return
}
if r.Method == http.MethodGet && r.URL.Path == "/dump/streams" {
s.getDumpStreams(w, r)
return
}
http.NotFound(w, r)
}
@ -163,126 +137,6 @@ func (s *trafficStatsServerImpl) getOnline(w http.ResponseWriter, r *http.Reques
_, _ = w.Write(jb)
}
type dumpStreamEntry struct {
State string `json:"state"`
Auth string `json:"auth"`
Connection uint32 `json:"connection"`
Stream uint64 `json:"stream"`
ReqAddr string `json:"req_addr"`
HookedReqAddr string `json:"hooked_req_addr"`
Tx uint64 `json:"tx"`
Rx uint64 `json:"rx"`
InitialAt string `json:"initial_at"`
LastActiveAt string `json:"last_active_at"`
// for text/plain output
initialTime time.Time
lastActiveTime time.Time
}
func (e *dumpStreamEntry) fromStreamStats(stream quic.Stream, s *server.StreamStats) {
e.State = s.State.Load().String()
e.Auth = s.AuthID
e.Connection = s.ConnID
e.Stream = uint64(stream.StreamID())
e.ReqAddr = s.ReqAddr.Load()
e.HookedReqAddr = s.HookedReqAddr.Load()
e.Tx = s.Tx.Load()
e.Rx = s.Rx.Load()
e.initialTime = s.InitialTime
e.lastActiveTime = s.LastActiveTime.Load()
e.InitialAt = e.initialTime.Format(time.RFC3339Nano)
e.LastActiveAt = e.lastActiveTime.Format(time.RFC3339Nano)
}
func formatDumpStreamLine(state, auth, connection, stream, reqAddr, hookedReqAddr, tx, rx, lifetime, lastActive string) string {
return fmt.Sprintf("%-8s %-12s %12s %8s %12s %12s %12s %12s %-16s %s", state, auth, connection, stream, tx, rx, lifetime, lastActive, reqAddr, hookedReqAddr)
}
func (e *dumpStreamEntry) String() string {
stateText := strings.ToUpper(e.State)
connectionText := fmt.Sprintf("%08X", e.Connection)
streamText := strconv.FormatUint(e.Stream, 10)
reqAddrText := e.ReqAddr
if reqAddrText == "" {
reqAddrText = "-"
}
hookedReqAddrText := e.HookedReqAddr
if hookedReqAddrText == "" {
hookedReqAddrText = "-"
}
txText := strconv.FormatUint(e.Tx, 10)
rxText := strconv.FormatUint(e.Rx, 10)
lifetime := time.Now().Sub(e.initialTime)
if lifetime < 10*time.Minute {
lifetime = lifetime.Round(time.Millisecond)
} else {
lifetime = lifetime.Round(time.Second)
}
lastActive := time.Now().Sub(e.lastActiveTime)
if lastActive < 10*time.Minute {
lastActive = lastActive.Round(time.Millisecond)
} else {
lastActive = lastActive.Round(time.Second)
}
return formatDumpStreamLine(stateText, e.Auth, connectionText, streamText, reqAddrText, hookedReqAddrText, txText, rxText, lifetime.String(), lastActive.String())
}
func (s *trafficStatsServerImpl) getDumpStreams(w http.ResponseWriter, r *http.Request) {
var entries []dumpStreamEntry
s.Mutex.RLock()
entries = make([]dumpStreamEntry, len(s.StreamMap))
index := 0
for stream, stats := range s.StreamMap {
entries[index].fromStreamStats(stream, stats)
index++
}
s.Mutex.RUnlock()
slices.SortFunc(entries, func(lhs, rhs dumpStreamEntry) int {
if ret := cmp.Compare(lhs.Auth, rhs.Auth); ret != 0 {
return ret
}
if ret := cmp.Compare(lhs.Connection, rhs.Connection); ret != 0 {
return ret
}
if ret := cmp.Compare(lhs.Stream, rhs.Stream); ret != 0 {
return ret
}
return 0
})
accept := r.Header.Get("Accept")
if strings.Contains(accept, "text/plain") {
// Generate netstat-like output for humans
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
// Print table header
_, _ = fmt.Fprintln(w, formatDumpStreamLine("State", "Auth", "Connection", "Stream", "Req-Addr", "Hooked-Req-Addr", "TX-Bytes", "RX-Bytes", "Lifetime", "Last-Active"))
for _, entry := range entries {
_, _ = fmt.Fprintln(w, entry.String())
}
return
}
// Response with json by default
wrapper := struct {
Streams []dumpStreamEntry `json:"streams"`
}{entries}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
err := json.NewEncoder(w).Encode(&wrapper)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func (s *trafficStatsServerImpl) kick(w http.ResponseWriter, r *http.Request) {
var ids []string
err := json.NewDecoder(r.Body).Decode(&ids)

View file

@ -74,7 +74,7 @@ func (u PortUnion) Normalize() PortUnion {
normalized := PortUnion{u[0]}
for _, current := range u[1:] {
last := &normalized[len(normalized)-1]
if uint32(current.Start) <= uint32(last.End)+1 {
if current.Start <= last.End+1 {
if current.End > last.End {
last.End = current.End
}
@ -89,8 +89,8 @@ func (u PortUnion) Normalize() PortUnion {
func (u PortUnion) Ports() []uint16 {
var ports []uint16
for _, r := range u {
for i := uint32(r.Start); i <= uint32(r.End); i++ {
ports = append(ports, uint16(i))
for i := r.Start; i <= r.End; i++ {
ports = append(ports, i)
}
}
return ports

View file

@ -2,7 +2,6 @@ package utils
import (
"reflect"
"slices"
"testing"
)
@ -52,16 +51,6 @@ func TestParsePortUnion(t *testing.T) {
s: "5678,1200-1236,9100-9012,1234-1240",
want: PortUnion{{1200, 1240}, {5678, 5678}, {9012, 9100}},
},
{
name: "multiple ports and ranges with 65535 (reversed, unsorted, overlapping)",
s: "5678,1200-1236,65531-65535,65532-65534,9100-9012,1234-1240",
want: PortUnion{{1200, 1240}, {5678, 5678}, {9012, 9100}, {65531, 65535}},
},
{
name: "multiple ports and ranges with 65535 (reversed, unsorted, overlapping) 2",
s: "5678,1200-1236,65532-65535,65531-65534,9100-9012,1234-1240",
want: PortUnion{{1200, 1240}, {5678, 5678}, {9012, 9100}, {65531, 65535}},
},
{
name: "invalid 1",
s: "1234-",
@ -101,50 +90,3 @@ func TestParsePortUnion(t *testing.T) {
})
}
}
func TestPortUnion_Ports(t *testing.T) {
tests := []struct {
name string
pu PortUnion
want []uint16
}{
{
name: "single port",
pu: PortUnion{{1234, 1234}},
want: []uint16{1234},
},
{
name: "multiple ports",
pu: PortUnion{{1234, 1236}},
want: []uint16{1234, 1235, 1236},
},
{
name: "multiple ports and ranges",
pu: PortUnion{{1234, 1236}, {5678, 5680}, {9000, 9002}},
want: []uint16{1234, 1235, 1236, 5678, 5679, 5680, 9000, 9001, 9002},
},
{
name: "single port 65535",
pu: PortUnion{{65535, 65535}},
want: []uint16{65535},
},
{
name: "port range with 65535",
pu: PortUnion{{65530, 65535}},
want: []uint16{65530, 65531, 65532, 65533, 65534, 65535},
},
{
name: "multiple ports and ranges with 65535",
pu: PortUnion{{65530, 65535}, {1234, 1236}},
want: []uint16{65530, 65531, 65532, 65533, 65534, 65535, 1234, 1235, 1236},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.pu.Ports(); !slices.Equal(got, tt.want) {
t.Errorf("PortUnion.Ports() = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -1,6 +1,6 @@
go 1.23
go 1.22
toolchain go1.24.2
toolchain go1.23.2
use (
./app

View file

@ -290,7 +290,6 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqiQ8Oz0pxTef2B4Vca2lvfk=
@ -299,7 +298,6 @@ golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852 h1:xYq6+9AtI+xP3M4r0N1hCkHr
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -312,8 +310,6 @@ golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2 h1:IRJeR9r1pYWsHKTRe/IInb7lYvbBVIqOgsX/u0mbOWY=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk=
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=

View file

@ -74,9 +74,6 @@ ARCH_ALIASES = {
"GOARCH": "amd64",
"GOAMD64": "v3",
},
"loong64": {
"GOARCH": "loong64",
},
}
@ -148,33 +145,12 @@ def get_app_commit():
return app_commit
def get_toolchain():
try:
output = subprocess.check_output(["go", "version"]).decode().strip()
if output.startswith("go version "):
output = output[11:]
return output
except Exception:
return "Unknown"
def get_current_os_arch():
d_os = subprocess.check_output(["go", "env", "GOOS"]).decode().strip()
d_arch = subprocess.check_output(["go", "env", "GOARCH"]).decode().strip()
return (d_os, d_arch)
def get_lib_version():
try:
with open(CORE_SRC_DIR + "/go.mod") as f:
for line in f:
line = line.strip()
if line.startswith("github.com/apernet/quic-go"):
return line.split(" ")[1].strip()
except Exception:
return "Unknown"
def get_app_platforms():
platforms = os.environ.get("HY_APP_PLATFORMS")
if not platforms:
@ -200,12 +176,8 @@ def cmd_build(pprof=False, release=False, race=False):
os.makedirs(BUILD_DIR, exist_ok=True)
app_version = get_app_version()
app_date = datetime.datetime.now(datetime.timezone.utc).strftime(
"%Y-%m-%dT%H:%M:%SZ"
)
app_toolchain = get_toolchain()
app_date = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
app_commit = get_app_commit()
lib_version = get_lib_version()
ldflags = [
"-X",
@ -218,11 +190,7 @@ def cmd_build(pprof=False, release=False, race=False):
+ ("release" if release else "dev")
+ ("-pprof" if pprof else ""),
"-X",
'"' + APP_SRC_CMD_PKG + ".appToolchain=" + app_toolchain + '"',
"-X",
APP_SRC_CMD_PKG + ".appCommit=" + app_commit,
"-X",
APP_SRC_CMD_PKG + ".libVersion=" + lib_version,
]
if release:
ldflags.append("-s")
@ -299,12 +267,8 @@ def cmd_run(args, pprof=False, race=False):
return
app_version = get_app_version()
app_date = datetime.datetime.now(datetime.timezone.utc).strftime(
"%Y-%m-%dT%H:%M:%SZ"
)
app_toolchain = get_toolchain()
app_date = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
app_commit = get_app_commit()
lib_version = get_lib_version()
current_os, current_arch = get_current_os_arch()
@ -316,15 +280,11 @@ def cmd_run(args, pprof=False, race=False):
"-X",
APP_SRC_CMD_PKG + ".appType=dev-run",
"-X",
'"' + APP_SRC_CMD_PKG + ".appToolchain=" + app_toolchain + '"',
"-X",
APP_SRC_CMD_PKG + ".appCommit=" + app_commit,
"-X",
APP_SRC_CMD_PKG + ".appPlatform=" + current_os,
"-X",
APP_SRC_CMD_PKG + ".appArch=" + current_arch,
"-X",
APP_SRC_CMD_PKG + ".libVersion=" + lib_version,
]
cmd = ["go", "run", "-ldflags", " ".join(ldflags)]

View file

@ -22,7 +22,6 @@ linux/s390x
linux/mipsle
linux/mipsle-sf
linux/riscv64
linux/loong64
# Android
android/386

View file

@ -436,9 +436,6 @@ check_environment_architecture() {
's390x')
ARCHITECTURE='s390x'
;;
'loongarch64')
ARCHITECTURE='loong64'
;;
*)
error "The architecture '$(uname -a)' is not supported."
note "Specify ARCHITECTURE=<architecture> to bypass this check and force this script to run on this $(uname -m)."
@ -875,7 +872,7 @@ is_hysteria1_version() {
get_installed_version() {
if is_hysteria_installed; then
if "$EXECUTABLE_INSTALL_PATH" version > /dev/null 2>&1; then
"$EXECUTABLE_INSTALL_PATH" version | grep '^Version' | grep -o 'v[.0-9]*'
"$EXECUTABLE_INSTALL_PATH" version | grep Version | grep -o 'v[.0-9]*'
elif "$EXECUTABLE_INSTALL_PATH" -v > /dev/null 2>&1; then
# hysteria 1
"$EXECUTABLE_INSTALL_PATH" -v | cut -d ' ' -f 3