From eb854d02b27899deb288c4519c53ae429f6e7fcb Mon Sep 17 00:00:00 2001 From: fatedier Date: Sun, 29 Mar 2026 23:22:40 +0800 Subject: [PATCH] update deps, modernize code and fix proxy auth --- .github/workflows/ci.yml | 6 +- .github/workflows/golangci-lint.yml | 27 +- .golangci.yml | 218 ++++++++-------- go.mod | 12 +- go.sum | 18 +- log/global.go | 24 +- log/log.go | 28 +-- msg/json/msg.go | 4 +- msg/json/pack_test.go | 2 +- msg/json/process.go | 2 +- net/dial.go | 9 +- net/dial_option.go | 24 +- net/dial_test.go | 369 +++++++++++++++++++++++----- net/mux/mux.go | 4 +- net/util_test.go | 23 +- pool/buf.go | 4 +- 16 files changed, 506 insertions(+), 268 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 272c82c..afe3f69 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,13 +16,13 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: [ '1.21', '1.22' ] + go: [ '1.25', '1.26' ] name: Go ${{ matrix.go }} Test steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: go-version: ${{ matrix.go }} diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 1ff39e1..6be86cc 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -13,29 +13,12 @@ jobs: name: lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 + - uses: actions/checkout@v6 + - uses: actions/setup-go@v6 with: - go-version: '1.22' + go-version: '1.25' cache: false - name: golangci-lint - uses: golangci/golangci-lint-action@v4 + uses: golangci/golangci-lint-action@v9 with: - # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version - version: v1.57 - - # Optional: golangci-lint command line arguments. - # args: --issues-exit-code=0 - - # Optional: show only new issues if it's a pull request. The default value is `false`. - # only-new-issues: true - - # Optional: if set to true then the all caching functionality will be complete disabled, - # takes precedence over all other caching options. - # skip-cache: true - - # Optional: if set to true then the action don't cache or restore ~/go/pkg. - # skip-pkg-cache: true - - # Optional: if set to true then the action don't cache or restore ~/.cache/go-build. - # skip-build-cache: true + version: v2.10 diff --git a/.golangci.yml b/.golangci.yml index fc26e9b..a6bf65c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,136 +1,130 @@ -service: - golangci-lint-version: 1.57.x # use the fixed version to not introduce new linters unexpectedly - +version: "2" run: concurrency: 4 - # timeout for analysis, e.g. 30s, 5m, default is 1m - deadline: 20m + timeout: 20m build-tags: - integ - integfuzz - linters: - disable-all: true + default: none enable: - - unused + - asciicheck + - copyloopvar - errcheck - - exportloopref + - unused - gocritic - - gofumpt - - goimports - revive - - gosimple - govet - ineffassign - lll - misspell - staticcheck - - stylecheck - - typecheck - unconvert - unparam - - gci - gosec - - asciicheck - prealloc - predeclared - makezero - fast: false - -linters-settings: - errcheck: - # report about not checking of errors in type assetions: `a := b.(MyStruct)`; - # default is false: such cases aren't reported by default. - check-type-assertions: false - - # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`; - # default is false: such cases aren't reported by default. - check-blank: false - govet: - # report about shadowed variables - check-shadowing: false - maligned: - # print struct with more effective memory layout or not, false by default - suggest-new: true - misspell: - # Correct spellings using locale preferences for US or UK. - # Default is to use a neutral variety of English. - # Setting locale to US will correct the British spelling of 'colour' to 'color'. - locale: US - ignore-words: - - cancelled - - marshalled - lll: - # max line length, lines longer will be reported. Default is 120. - # '\t' is counted as 1 character by default, and can be changed with the tab-width option - line-length: 160 - # tab width in spaces. Default to 1. - tab-width: 1 - gocritic: - disabled-checks: - - exitAfterDefer - unused: - check-exported: false - unparam: - # Inspect exported functions, default is false. Set to true if no external program/library imports your code. - # XXX: if you enable this setting, unparam will report a lot of false-positives in text editors: - # if it's called for subdir of a project it can't find external interfaces. All text editor integrations - # with golangci-lint call it on a directory with the changed file. - check-exported: false - gci: - sections: - - standard - - default - - prefix(github.com/fatedier/golib/) - gosec: - severity: "low" - confidence: "low" - excludes: - - G505 + - modernize + settings: + errcheck: + check-type-assertions: false + check-blank: false + gocritic: + disabled-checks: + - exitAfterDefer + gosec: + excludes: ["G115", "G117", "G402", "G505"] + severity: low + confidence: low + govet: + disable: + - shadow + lll: + line-length: 160 + tab-width: 1 + misspell: + locale: US + ignore-rules: + - cancelled + - marshalled + modernize: + disable: + - omitzero + unparam: + check-exported: false + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + rules: + - linters: + - errcheck + - maligned + path: _test\.go$|^tests/|^samples/ + - linters: + - revive + - staticcheck + text: "use underscores in Go names" + - linters: + - staticcheck + path: ^crypto/ + text: "cipher.NewCFB" + - linters: + - staticcheck + text: "SA6002" + - linters: + - staticcheck + text: "snappy.NewWriter is deprecated" + - linters: + - revive + text: "unused-parameter" + - linters: + - revive + text: "avoid meaningless package names" + - linters: + - revive + text: "Go standard library package names" + - linters: + - unparam + text: "is always false" + paths: + - .*\.pb\.go + - .*\.gen\.go + - genfiles$ + - vendor$ + - bin$ + - third_party$ + - builtin$ + - examples$ + - node_modules +formatters: + enable: + - gci + - gofumpt + - goimports + settings: + gci: + sections: + - standard + - default + - prefix(github.com/fatedier/golib/) + exclusions: + generated: lax + paths: + - .*\.pb\.go + - .*\.gen\.go + - genfiles$ + - vendor$ + - bin$ + - third_party$ + - builtin$ + - examples$ + - node_modules issues: - # List of regexps of issue texts to exclude, empty list by default. - # But independently from this option we use default exclude patterns, - # it can be disabled by `exclude-use-default: false`. To list all - # excluded by default patterns execute `golangci-lint run --help` - # exclude: - # - composite literal uses unkeyed fields - - exclude-rules: - # Exclude some linters from running on test files. - - path: _test\.go$|^tests/|^samples/ - linters: - - errcheck - - maligned - - linters: - - revive - text: "unused-parameter" - - linters: - - unparam - text: "is always" - - linters: - - staticcheck - text: "SA6002" - - linters: - - staticcheck - text: "snappy.NewWriter is deprecated" - - exclude-dirs: - - genfiles$ - - vendor$ - - bin$ - exclude-files: - - ".*\\.pb\\.go" - - ".*\\.gen\\.go" - - # Independently from option `exclude` we use default exclude patterns, - # it can be disabled by this option. To list all - # excluded by default patterns execute `golangci-lint run --help`. - # Default value for this option is true. - exclude-use-default: true - - # Maximum issues count per one linter. Set to 0 to disable. Default is 50. - max-per-linter: 0 - - # Maximum count of issues with the same text. Set to 0 to disable. Default is 3. + max-issues-per-linter: 0 max-same-issues: 0 diff --git a/go.mod b/go.mod index 613f441..362f2e2 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,14 @@ module github.com/fatedier/golib -go 1.21 +go 1.25.0 require ( - github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 + github.com/Azure/go-ntlmssp v0.1.0 github.com/golang/snappy v0.0.4 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.11.1 github.com/xtaci/kcp-go/v5 v5.6.8 - golang.org/x/crypto v0.22.0 - golang.org/x/net v0.24.0 + golang.org/x/crypto v0.49.0 + golang.org/x/net v0.52.0 ) require ( @@ -23,7 +23,7 @@ require ( github.com/templexxx/xorsimd v0.4.2 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 // indirect - golang.org/x/sys v0.19.0 // indirect + golang.org/x/sys v0.42.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 1ede24d..a3c35d7 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/Azure/go-ntlmssp v0.1.0 h1:DjFo6YtWzNqNvQdrwEyr/e4nhU3vRiwenz5QX7sFz+A= +github.com/Azure/go-ntlmssp v0.1.0/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -48,8 +50,8 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1: github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -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/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/templexxx/cpu v0.1.0 h1:wVM+WIJP2nYaxVxqgHPD4wGA2aJ9rvrQRV8CvFzNb40= github.com/templexxx/cpu v0.1.0/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk= github.com/templexxx/xorsimd v0.4.2 h1:ocZZ+Nvu65LGHmCLZ7OoCtg8Fx8jnHKK37SjvngUoVI= @@ -63,8 +65,8 @@ github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37/go.mod h1:HpMP7DB2 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= +golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -75,8 +77,8 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 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= @@ -86,8 +88,8 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= 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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/log/global.go b/log/global.go index 8ba2841..4937baf 100644 --- a/log/global.go +++ b/log/global.go @@ -2,50 +2,50 @@ package log var DefaultLogger = New() -func Trace(args ...interface{}) { +func Trace(args ...any) { DefaultLogger.log(TraceLevel, 0, "", args...) } -func Debug(args ...interface{}) { +func Debug(args ...any) { DefaultLogger.log(DebugLevel, 0, "", args...) } -func Info(args ...interface{}) { +func Info(args ...any) { DefaultLogger.log(InfoLevel, 0, "", args...) } -func Warn(args ...interface{}) { +func Warn(args ...any) { DefaultLogger.log(WarnLevel, 0, "", args...) } -func Error(args ...interface{}) { +func Error(args ...any) { DefaultLogger.log(ErrorLevel, 0, "", args...) } -func Log(level Level, offset int, args ...interface{}) { +func Log(level Level, offset int, args ...any) { DefaultLogger.log(level, offset, "", args...) } -func Tracef(msg string, args ...interface{}) { +func Tracef(msg string, args ...any) { DefaultLogger.log(TraceLevel, 0, msg, args...) } -func Debugf(msg string, args ...interface{}) { +func Debugf(msg string, args ...any) { DefaultLogger.log(DebugLevel, 0, msg, args...) } -func Infof(msg string, args ...interface{}) { +func Infof(msg string, args ...any) { DefaultLogger.log(InfoLevel, 0, msg, args...) } -func Warnf(msg string, args ...interface{}) { +func Warnf(msg string, args ...any) { DefaultLogger.log(WarnLevel, 0, msg, args...) } -func Errorf(msg string, args ...interface{}) { +func Errorf(msg string, args ...any) { DefaultLogger.log(ErrorLevel, 0, msg, args...) } -func Logf(level Level, offset int, msg string, args ...interface{}) { +func Logf(level Level, offset int, msg string, args ...any) { DefaultLogger.log(level, offset, msg, args...) } diff --git a/log/log.go b/log/log.go index 5cc04fb..4a12f08 100644 --- a/log/log.go +++ b/log/log.go @@ -67,55 +67,55 @@ func (l *Logger) clone() *Logger { return clone } -func (l *Logger) Trace(args ...interface{}) { +func (l *Logger) Trace(args ...any) { l.log(TraceLevel, 0, "", args...) } -func (l *Logger) Debug(args ...interface{}) { +func (l *Logger) Debug(args ...any) { l.log(DebugLevel, 0, "", args...) } -func (l *Logger) Info(args ...interface{}) { +func (l *Logger) Info(args ...any) { l.log(InfoLevel, 0, "", args...) } -func (l *Logger) Warn(args ...interface{}) { +func (l *Logger) Warn(args ...any) { l.log(WarnLevel, 0, "", args...) } -func (l *Logger) Error(args ...interface{}) { +func (l *Logger) Error(args ...any) { l.log(ErrorLevel, 0, "", args...) } -func (l *Logger) Log(level Level, offset int, args ...interface{}) { +func (l *Logger) Log(level Level, offset int, args ...any) { l.log(level, offset, "", args...) } -func (l *Logger) Tracef(msg string, args ...interface{}) { +func (l *Logger) Tracef(msg string, args ...any) { l.log(TraceLevel, 0, msg, args...) } -func (l *Logger) Debugf(msg string, args ...interface{}) { +func (l *Logger) Debugf(msg string, args ...any) { l.log(DebugLevel, 0, msg, args...) } -func (l *Logger) Infof(msg string, args ...interface{}) { +func (l *Logger) Infof(msg string, args ...any) { l.log(InfoLevel, 0, msg, args...) } -func (l *Logger) Warnf(msg string, args ...interface{}) { +func (l *Logger) Warnf(msg string, args ...any) { l.log(WarnLevel, 0, msg, args...) } -func (l *Logger) Errorf(msg string, args ...interface{}) { +func (l *Logger) Errorf(msg string, args ...any) { l.log(ErrorLevel, 0, msg, args...) } -func (l *Logger) Logf(level Level, offset int, msg string, args ...interface{}) { +func (l *Logger) Logf(level Level, offset int, msg string, args ...any) { l.log(level, offset, msg, args...) } -func (l *Logger) log(level Level, offset int, msg string, args ...interface{}) { +func (l *Logger) log(level Level, offset int, msg string, args ...any) { if !l.level.Enabled(level) { return } @@ -155,7 +155,7 @@ func (l *Logger) log(level Level, offset int, msg string, args ...interface{}) { _, _ = l.out.Write(buffer.Bytes()) } -func getMessage(template string, fmtArgs []interface{}) string { +func getMessage(template string, fmtArgs []any) string { if len(fmtArgs) == 0 { return template } diff --git a/msg/json/msg.go b/msg/json/msg.go index d16d4f4..277c121 100644 --- a/msg/json/msg.go +++ b/msg/json/msg.go @@ -21,7 +21,7 @@ import ( var defaultMaxMsgLength int64 = 10240 // Message wraps socket packages for communicating between frpc and frps. -type Message interface{} +type Message any type MsgCtl struct { typeMap map[byte]reflect.Type @@ -38,7 +38,7 @@ func NewMsgCtl() *MsgCtl { } } -func (msgCtl *MsgCtl) RegisterMsg(typeByte byte, msg interface{}) { +func (msgCtl *MsgCtl) RegisterMsg(typeByte byte, msg any) { msgCtl.typeMap[typeByte] = reflect.TypeOf(msg) msgCtl.typeByteMap[reflect.TypeOf(msg)] = typeByte } diff --git a/msg/json/pack_test.go b/msg/json/pack_test.go index 5abc95a..e1b0ac3 100644 --- a/msg/json/pack_test.go +++ b/msg/json/pack_test.go @@ -78,7 +78,7 @@ func TestUnPack(t *testing.T) { // correct msg, err := msgCtl.UnPack(TypePong, []byte("{}")) assert.NoError(err) - assert.Equal(reflect.TypeOf(msg).Elem(), reflect.TypeOf(Pong{})) + assert.Equal(reflect.TypeOf(msg).Elem(), reflect.TypeFor[Pong]()) } func TestUnPackInto(t *testing.T) { diff --git a/msg/json/process.go b/msg/json/process.go index 5c66c18..febc16e 100644 --- a/msg/json/process.go +++ b/msg/json/process.go @@ -80,7 +80,7 @@ func (msgCtl *MsgCtl) ReadMsgInto(c io.Reader, msg Message) (err error) { return msgCtl.UnPackInto(buffer, msg) } -func (msgCtl *MsgCtl) WriteMsg(c io.Writer, msg interface{}) (err error) { +func (msgCtl *MsgCtl) WriteMsg(c io.Writer, msg any) (err error) { buffer, err := msgCtl.Pack(msg) if err != nil { return diff --git a/net/dial.go b/net/dial.go index ba6e02e..80b1efe 100644 --- a/net/dial.go +++ b/net/dial.go @@ -18,6 +18,7 @@ import ( "context" "fmt" "net" + "slices" "sort" kcp "github.com/xtaci/kcp-go/v5" @@ -43,13 +44,7 @@ func DialContext(ctx context.Context, addr string, opts ...DialOption) (c net.Co } if op.proxyAddr != "" { - support := false - for _, v := range supportedDialProxyTypes { - if op.proxyType == v { - support = true - break - } - } + support := slices.Contains(supportedDialProxyTypes, op.proxyType) if !support { return nil, fmt.Errorf("ProxyType must be http, https, socks5 or ntlm, not [%s]", op.proxyType) } diff --git a/net/dial_option.go b/net/dial_option.go index 6dea12c..9b52ea5 100644 --- a/net/dial_option.go +++ b/net/dial_option.go @@ -37,9 +37,9 @@ type ProxyAuth struct { Passwd string } -type DialMetas map[interface{}]interface{} +type DialMetas map[any]any -func (m DialMetas) Value(key interface{}) interface{} { +func (m DialMetas) Value(key any) any { return m[key] } @@ -200,8 +200,7 @@ func WithTLSConfigAndPriority(priority uint64, tlsConfig *tls.Config) DialOption do.afterHooks = append(do.afterHooks, AfterHook{ Priority: priority, Hook: func(ctx context.Context, c net.Conn, addr string) (context.Context, net.Conn, error) { - conn, err := tlsAfterHook(c, tlsConfig) - return ctx, conn, err + return ctx, tlsAfterHook(c, tlsConfig), nil }, }) }) @@ -309,8 +308,8 @@ func httpsProxyAfterHook(ctx context.Context, conn net.Conn, addr string, proxyA if proxyTLSConfig == nil { proxyTLSConfig = &tls.Config{} } - // Auto-set ServerName from proxy address if not explicitly configured. - if proxyTLSConfig.ServerName == "" && !proxyTLSConfig.InsecureSkipVerify { + // Auto-set ServerName from proxy address whenever the caller leaves it unset. + if proxyTLSConfig.ServerName == "" { host, _, err := net.SplitHostPort(proxyAddr) if err != nil { host = proxyAddr @@ -339,9 +338,7 @@ func ntlmHTTPProxyAfterHook(ctx context.Context, conn net.Conn, addr string) (ne return nil, err } if proxyAuth != nil { - domain := "" - _, domain, _ = ntlmssp.GetDomain(proxyAuth.Username) - negotiateMessage, err := ntlmssp.NewNegotiateMessage(domain, "") + negotiateMessage, err := ntlmssp.NewNegotiateMessage("", "") if err != nil { return nil, err } @@ -357,14 +354,13 @@ func ntlmHTTPProxyAfterHook(ctx context.Context, conn net.Conn, addr string) (ne if proxyAuth != nil && resp.StatusCode == 407 { challenge := resp.Header.Get("Proxy-Authenticate") - username, _, domainNeeded := ntlmssp.GetDomain(proxyAuth.Username) if strings.HasPrefix(challenge, "Negotiate ") { challengeMessage, err := base64.StdEncoding.DecodeString(challenge[len("Negotiate "):]) if err != nil { return nil, err } - authenticateMessage, err := ntlmssp.ProcessChallenge(challengeMessage, username, proxyAuth.Passwd, domainNeeded) + authenticateMessage, err := ntlmssp.NewAuthenticateMessage(challengeMessage, proxyAuth.Username, proxyAuth.Passwd, nil) if err != nil { return nil, err } @@ -389,9 +385,9 @@ func ntlmHTTPProxyAfterHook(ctx context.Context, conn net.Conn, addr string) (ne return conn, nil } -func tlsAfterHook(conn net.Conn, tlsConfig *tls.Config) (net.Conn, error) { +func tlsAfterHook(conn net.Conn, tlsConfig *tls.Config) net.Conn { if tlsConfig == nil { - return conn, nil + return conn } - return tls.Client(conn, tlsConfig), nil + return tls.Client(conn, tlsConfig) } diff --git a/net/dial_test.go b/net/dial_test.go index 5057154..12d1117 100644 --- a/net/dial_test.go +++ b/net/dial_test.go @@ -2,12 +2,23 @@ package net import ( "bufio" + "bytes" "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" "crypto/tls" "crypto/x509" + "crypto/x509/pkix" + "encoding/base64" + "encoding/binary" + "encoding/pem" "fmt" + "io" + "math/big" "net" "net/http" + "strings" "testing" "time" @@ -43,8 +54,7 @@ func TestHTTPSProxyAutoServerName(t *testing.T) { require := require.New(t) // Start a TLS server to verify the auto-derived ServerName works - cert, err := tls.X509KeyPair(localhostCert, localhostKey) - require.NoError(err) + cert, certPool := newLocalhostTestCertificate(t) tlsConfig := &tls.Config{Certificates: []tls.Certificate{cert}} l, err := tls.Listen("tcp", "127.0.0.1:0", tlsConfig) @@ -65,8 +75,6 @@ func TestHTTPSProxyAutoServerName(t *testing.T) { }() // Build TLS config with NO ServerName -- it should be auto-derived from proxyAddr - certPool := x509.NewCertPool() - certPool.AppendCertsFromPEM(localhostCert) proxyTLSCfg := &tls.Config{ RootCAs: certPool, // ServerName intentionally left empty @@ -87,11 +95,57 @@ func TestHTTPSProxyAutoServerName(t *testing.T) { require.NotNil(conn) } -func TestHTTPSProxyTLSHandshakeFailure(t *testing.T) { +func TestHTTPSProxyAutoServerNameWithInsecureSkipVerify(t *testing.T) { require := require.New(t) - cert, err := tls.X509KeyPair(localhostCert, localhostKey) + cert, _ := newLocalhostTestCertificate(t) + + tlsConfig := &tls.Config{ + GetConfigForClient: func(hello *tls.ClientHelloInfo) (*tls.Config, error) { + if hello.ServerName != "localhost" { + return nil, fmt.Errorf("unexpected SNI %q", hello.ServerName) + } + return &tls.Config{Certificates: []tls.Certificate{cert}}, nil + }, + } + l, err := tls.Listen("tcp", "127.0.0.1:0", tlsConfig) + require.NoError(err) + defer l.Close() + + go func() { + conn, err := l.Accept() + if err != nil { + return + } + defer conn.Close() + req, _ := http.ReadRequest(bufio.NewReader(conn)) + if req != nil && req.Method == "CONNECT" { + fmt.Fprintf(conn, "HTTP/1.1 200 Connection Established\r\n\r\n") + } + }() + + proxyTLSCfg := &tls.Config{ + InsecureSkipVerify: true, + } + + ctx := context.Background() + dialMetas := make(DialMetas) + ctx = context.WithValue(ctx, dialCtxKey, dialMetas) + dialMetas[proxyTLSConfigKey] = proxyTLSCfg + + rawConn, err := net.Dial("tcp", l.Addr().String()) + require.NoError(err) + defer rawConn.Close() + + conn, err := httpsProxyAfterHook(ctx, rawConn, "10.0.0.1:7000", "localhost:"+fmt.Sprint(l.Addr().(*net.TCPAddr).Port)) require.NoError(err) + require.NotNil(conn) +} + +func TestHTTPSProxyTLSHandshakeFailure(t *testing.T) { + require := require.New(t) + + cert, _ := newLocalhostTestCertificate(t) tlsConfig := &tls.Config{Certificates: []tls.Certificate{cert}} l, err := tls.Listen("tcp", "127.0.0.1:0", tlsConfig) @@ -121,10 +175,12 @@ func TestHTTPSProxyTLSHandshakeFailure(t *testing.T) { func TestHTTPSProxyAfterHook(t *testing.T) { require := require.New(t) + const timeout = 5 * time.Second + payload := []byte("hello") + errCh := make(chan error, 2) // Start a TLS server that speaks HTTP CONNECT (simulates an HTTPS proxy) - cert, err := tls.X509KeyPair(localhostCert, localhostKey) - require.NoError(err) + cert, certPool := newLocalhostTestCertificate(t) tlsConfig := &tls.Config{Certificates: []tls.Certificate{cert}} l, err := tls.Listen("tcp", "127.0.0.1:0", tlsConfig) @@ -139,67 +195,72 @@ func TestHTTPSProxyAfterHook(t *testing.T) { go func() { conn, err := backendL.Accept() if err != nil { + errCh <- err return } defer conn.Close() - buf := make([]byte, 5) - n, _ := conn.Read(buf) - conn.Write(buf[:n]) + if err := conn.SetDeadline(time.Now().Add(timeout)); err != nil { + errCh <- err + return + } + buf := make([]byte, len(payload)) + if _, err := io.ReadFull(conn, buf); err != nil { + errCh <- err + return + } + _, err = conn.Write(buf) + errCh <- err }() // proxy handler: accept CONNECT, then pipe to backend go func() { conn, err := l.Accept() if err != nil { + errCh <- err return } defer conn.Close() + if err := conn.SetDeadline(time.Now().Add(timeout)); err != nil { + errCh <- err + return + } req, err := http.ReadRequest(bufio.NewReader(conn)) if err != nil { + errCh <- err return } if req.Method != "CONNECT" { - resp := &http.Response{StatusCode: 400, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1} - resp.Write(conn) + errCh <- fmt.Errorf("unexpected method %q", req.Method) return } // connect to backend backend, err := net.Dial("tcp", backendL.Addr().String()) if err != nil { - resp := &http.Response{StatusCode: 502, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1} - resp.Write(conn) + errCh <- err return } defer backend.Close() + if err := backend.SetDeadline(time.Now().Add(timeout)); err != nil { + errCh <- err + return + } - fmt.Fprintf(conn, "HTTP/1.1 200 Connection Established\r\n\r\n") - - // bidirectional copy - go func() { - buf := make([]byte, 4096) - for { - n, err := conn.Read(buf) - if err != nil { - return - } - backend.Write(buf[:n]) - } - }() - buf := make([]byte, 4096) - for { - n, err := backend.Read(buf) - if err != nil { - return - } - conn.Write(buf[:n]) + if _, err := fmt.Fprintf(conn, "HTTP/1.1 200 Connection Established\r\n\r\n"); err != nil { + errCh <- err + return } + + if _, err := io.CopyN(backend, conn, int64(len(payload))); err != nil { + errCh <- err + return + } + _, err = io.CopyN(conn, backend, int64(len(payload))) + errCh <- err }() // Build a TLS config that trusts our test cert - certPool := x509.NewCertPool() - certPool.AppendCertsFromPEM(localhostCert) proxyTLSCfg := &tls.Config{ RootCAs: certPool, ServerName: "localhost", @@ -215,39 +276,225 @@ func TestHTTPSProxyAfterHook(t *testing.T) { rawConn, err := net.Dial("tcp", l.Addr().String()) require.NoError(err) defer rawConn.Close() + require.NoError(rawConn.SetDeadline(time.Now().Add(timeout))) // Run the https proxy hook which does TLS + CONNECT conn, err := httpsProxyAfterHook(ctx, rawConn, backendL.Addr().String(), l.Addr().String()) require.NoError(err) require.NotNil(conn) + defer conn.Close() + require.NoError(conn.SetDeadline(time.Now().Add(timeout))) // Verify data flows through the tunnel - _, err = conn.Write([]byte("hello")) + _, err = conn.Write(payload) require.NoError(err) - buf := make([]byte, 5) - n, err := conn.Read(buf) + buf := make([]byte, len(payload)) + n, err := io.ReadFull(conn, buf) require.NoError(err) - require.Equal("hello", string(buf[:n])) + require.Equal(payload, buf[:n]) + + waitErr := func() error { + select { + case err := <-errCh: + return err + case <-time.After(timeout): + return fmt.Errorf("timeout waiting for proxy test goroutine") + } + } + require.NoError(waitErr()) + require.NoError(waitErr()) } -// Self-signed cert for localhost testing (generated for test use only). -var localhostCert = []byte(`-----BEGIN CERTIFICATE----- -MIIBijCCATCgAwIBAgIBATAKBggqhkjOPQQDAjAUMRIwEAYDVQQDEwlsb2NhbGhv -c3QwHhcNMjYwMzI1MTE1MTIzWhcNMzYwMzIyMTI1MTIzWjAUMRIwEAYDVQQDEwls -b2NhbGhvc3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATr8o40uWZXI0ILr36n -UtZIeY/7X/mN44kYp1eFubnu1PtCMn0oRoI7XMLtb7ZH92fkzZQNJp3SqG7ntGC3 -MONao3MwcTAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYD -VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUo3ixEmbOr6h+yl49udjuFp0GnSQwGgYD -VR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMAoGCCqGSM49BAMCA0gAMEUCIBJqFcYA -bOUh2xhwwiNAJYf+ndsLQwcG/Xvq6vh0pgJRAiEA5Q3XUs0jcHwiXxsDulXCCP5m -ezw1NQfI1c+EHa4NGzk= ------END CERTIFICATE----- -`) - -var localhostKey = []byte(`-----BEGIN EC PRIVATE KEY----- -MHcCAQEEIOBWZAzhhtudR+FUfk2QY4+tCLix4s+vMiQOx/Vi6fKBoAoGCCqGSM49 -AwEHoUQDQgAE6/KONLlmVyNCC69+p1LWSHmP+1/5jeOJGKdXhbm57tT7QjJ9KEaC -O1zC7W+2R/dn5M2UDSad0qhu57RgtzDjWg== ------END EC PRIVATE KEY----- -`) +func TestNTLMHTTPProxyAfterHookUsesUsernameDirectly(t *testing.T) { + require := require.New(t) + const timeout = 5 * time.Second + + clientConn, serverConn := net.Pipe() + defer clientConn.Close() + defer serverConn.Close() + require.NoError(clientConn.SetDeadline(time.Now().Add(timeout))) + require.NoError(serverConn.SetDeadline(time.Now().Add(timeout))) + + errCh := make(chan error, 1) + go func() { + reader := bufio.NewReader(serverConn) + + req, err := http.ReadRequest(reader) + if err != nil { + errCh <- err + return + } + if req.Method != "CONNECT" { + errCh <- fmt.Errorf("unexpected method %q", req.Method) + return + } + + negotiateHeader := req.Header.Get("Proxy-Authorization") + if negotiateHeader == "" { + errCh <- fmt.Errorf("missing Proxy-Authorization on negotiate request") + return + } + negotiateMessage, err := decodeNTLMNegotiateHeader(negotiateHeader) + if err != nil { + errCh <- err + return + } + if len(negotiateMessage) < 40 { + errCh <- fmt.Errorf("negotiate message too short: %d", len(negotiateMessage)) + return + } + flags := binary.LittleEndian.Uint32(negotiateMessage[12:16]) + if flags&ntlmNegotiateOEMDomainSupplied != 0 { + errCh <- fmt.Errorf("unexpected OEM domain supplied flag in negotiate message") + return + } + if bytes.Contains(negotiateMessage[40:], []byte("DOMAIN")) { + errCh <- fmt.Errorf("unexpected user domain in negotiate payload") + return + } + + challenge, err := createNTLMTestChallenge() + if err != nil { + errCh <- err + return + } + challengeResp := "HTTP/1.1 407 Proxy Authentication Required\r\n" + + "Proxy-Authenticate: Negotiate " + challenge + "\r\n" + + "Content-Length: 0\r\n\r\n" + if _, err := fmt.Fprint(serverConn, challengeResp); err != nil { + errCh <- err + return + } + + req, err = http.ReadRequest(reader) + if err != nil { + errCh <- err + return + } + authenticateHeader := req.Header.Get("Proxy-Authorization") + if authenticateHeader == "" { + errCh <- fmt.Errorf("missing Proxy-Authorization on authenticate request") + return + } + authenticateMessage, err := decodeNTLMNegotiateHeader(authenticateHeader) + if err != nil { + errCh <- err + return + } + if len(authenticateMessage) < 8 || !bytes.Equal(authenticateMessage[:8], []byte{'N', 'T', 'L', 'M', 'S', 'S', 'P', 0}) { + errCh <- fmt.Errorf("unexpected NTLM authenticate message signature") + return + } + + _, err = fmt.Fprintf(serverConn, "HTTP/1.1 200 Connection Established\r\n\r\n") + errCh <- err + }() + + ctx := context.Background() + dialMetas := make(DialMetas) + ctx = context.WithValue(ctx, dialCtxKey, dialMetas) + dialMetas[proxyAuthKey] = &ProxyAuth{ + Username: `DOMAIN\alice`, + Passwd: "password", + } + + conn, err := ntlmHTTPProxyAfterHook(ctx, clientConn, "10.0.0.1:7000") + require.NoError(err) + require.NotNil(conn) + + select { + case err := <-errCh: + require.NoError(err) + case <-time.After(timeout): + t.Fatal("timeout waiting for NTLM proxy test goroutine") + } +} + +const ntlmNegotiateOEMDomainSupplied uint32 = 1 << 12 + +func decodeNTLMNegotiateHeader(header string) ([]byte, error) { + const prefix = "Negotiate " + if !strings.HasPrefix(header, prefix) { + return nil, fmt.Errorf("unexpected negotiate header %q", header) + } + return base64.StdEncoding.DecodeString(header[len(prefix):]) +} + +type ntlmTestMessageHeader struct { + Signature [8]byte + MessageType uint32 +} + +type ntlmTestVarField struct { + Len uint16 + MaxLen uint16 + BufferOffset uint32 +} + +type ntlmTestChallengeMessageFields struct { + MessageHeader ntlmTestMessageHeader + TargetName ntlmTestVarField + NegotiateFlags uint32 + ServerChallenge [8]byte + Reserved [8]byte + TargetInfo ntlmTestVarField +} + +func createNTLMTestChallenge() (string, error) { + msg := ntlmTestChallengeMessageFields{ + MessageHeader: ntlmTestMessageHeader{ + Signature: [8]byte{'N', 'T', 'L', 'M', 'S', 'S', 'P', 0}, + MessageType: 2, + }, + NegotiateFlags: 0xa0888201, + ServerChallenge: [8]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, + } + + var buf bytes.Buffer + if err := binary.Write(&buf, binary.LittleEndian, &msg); err != nil { + return "", err + } + return base64.StdEncoding.EncodeToString(buf.Bytes()), nil +} + +func newLocalhostTestCertificate(t *testing.T) (tls.Certificate, *x509.CertPool) { + t.Helper() + + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + + serialLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialLimit) + require.NoError(t, err) + + template := &x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + CommonName: "localhost", + }, + NotBefore: time.Now().Add(-time.Hour), + NotAfter: time.Now().Add(24 * time.Hour), + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + DNSNames: []string{"localhost"}, + IPAddresses: []net.IP{net.ParseIP("127.0.0.1")}, + } + + certDER, err := x509.CreateCertificate(rand.Reader, template, template, &privateKey.PublicKey, privateKey) + require.NoError(t, err) + + certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) + keyDER, err := x509.MarshalECPrivateKey(privateKey) + require.NoError(t, err) + keyPEM := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER}) + + cert, err := tls.X509KeyPair(certPEM, keyPEM) + require.NoError(t, err) + + certPool := x509.NewCertPool() + require.True(t, certPool.AppendCertsFromPEM(certPEM)) + + return cert, certPool +} diff --git a/net/mux/mux.go b/net/mux/mux.go index b835d32..b69e05c 100644 --- a/net/mux/mux.go +++ b/net/mux/mux.go @@ -147,8 +147,8 @@ func (mux *Mux) Serve() error { } else { tempDelay *= 2 } - if max := 1 * time.Second; tempDelay > max { - tempDelay = max + if maxDelay := 1 * time.Second; tempDelay > maxDelay { + tempDelay = maxDelay } time.Sleep(tempDelay) continue diff --git a/net/util_test.go b/net/util_test.go index f6a22f0..b637253 100644 --- a/net/util_test.go +++ b/net/util_test.go @@ -1,11 +1,23 @@ package net import ( + "net/url" "testing" "github.com/stretchr/testify/require" ) +func mustTestProxyURL(t *testing.T, scheme, username, passwd, host string) string { + t.Helper() + + u := &url.URL{ + Scheme: scheme, + Host: host, + User: url.UserPassword(username, passwd), + } + return u.String() +} + func TestParseProxyURL(t *testing.T) { require := require.New(t) @@ -23,7 +35,7 @@ func TestParseProxyURL(t *testing.T) { expectAddress: "127.0.0.1", }, { - proxyURL: "http://admin:abc@127.0.0.1:8080", + proxyURL: mustTestProxyURL(t, "http", "admin", "abc", "127.0.0.1:8080"), expectProxyType: "http", expectAddress: "127.0.0.1:8080", expectAuth: &ProxyAuth{ @@ -40,6 +52,15 @@ func TestParseProxyURL(t *testing.T) { Passwd: "abc", }, }, + { + proxyURL: mustTestProxyURL(t, "https", "admin", "abc", "127.0.0.1:8443"), + expectProxyType: "https", + expectAddress: "127.0.0.1:8443", + expectAuth: &ProxyAuth{ + Username: "admin", + Passwd: "abc", + }, + }, { proxyURL: "http://127.0.0.1:8080@admin:abc", expectErr: true, diff --git a/pool/buf.go b/pool/buf.go index da5db78..045ea7b 100644 --- a/pool/buf.go +++ b/pool/buf.go @@ -27,7 +27,7 @@ var ( ) func GetBuf(size int) []byte { - var x interface{} + var x any switch { case size >= 16*1024: x = bufPool16k.Get() @@ -74,7 +74,7 @@ type Buffer struct { func NewBuffer(size int) *Buffer { return &Buffer{ pool: sync.Pool{ - New: func() interface{} { + New: func() any { return make([]byte, size) }, },