mirror of
https://github.com/cedar2025/hysteria.git
synced 2025-09-11 19:04:32 +00:00
@@ -21,6 +21,7 @@ import (
|
|||||||
|
|
||||||
"github.com/apernet/hysteria/app/internal/forwarding"
|
"github.com/apernet/hysteria/app/internal/forwarding"
|
||||||
"github.com/apernet/hysteria/app/internal/http"
|
"github.com/apernet/hysteria/app/internal/http"
|
||||||
|
"github.com/apernet/hysteria/app/internal/proxymux"
|
||||||
"github.com/apernet/hysteria/app/internal/redirect"
|
"github.com/apernet/hysteria/app/internal/redirect"
|
||||||
"github.com/apernet/hysteria/app/internal/socks5"
|
"github.com/apernet/hysteria/app/internal/socks5"
|
||||||
"github.com/apernet/hysteria/app/internal/tproxy"
|
"github.com/apernet/hysteria/app/internal/tproxy"
|
||||||
@@ -531,7 +532,7 @@ func clientSOCKS5(config socks5Config, c client.Client) error {
|
|||||||
if config.Listen == "" {
|
if config.Listen == "" {
|
||||||
return configError{Field: "listen", Err: errors.New("listen address is empty")}
|
return configError{Field: "listen", Err: errors.New("listen address is empty")}
|
||||||
}
|
}
|
||||||
l, err := correctnet.Listen("tcp", config.Listen)
|
l, err := proxymux.ListenSOCKS(config.Listen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return configError{Field: "listen", Err: err}
|
return configError{Field: "listen", Err: err}
|
||||||
}
|
}
|
||||||
@@ -556,7 +557,7 @@ func clientHTTP(config httpConfig, c client.Client) error {
|
|||||||
if config.Listen == "" {
|
if config.Listen == "" {
|
||||||
return configError{Field: "listen", Err: errors.New("listen address is empty")}
|
return configError{Field: "listen", Err: errors.New("listen address is empty")}
|
||||||
}
|
}
|
||||||
l, err := correctnet.Listen("tcp", config.Listen)
|
l, err := proxymux.ListenHTTP(config.Listen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return configError{Field: "listen", Err: err}
|
return configError{Field: "listen", Err: err}
|
||||||
}
|
}
|
||||||
|
12
app/internal/proxymux/.mockery.yaml
Normal file
12
app/internal/proxymux/.mockery.yaml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
with-expecter: true
|
||||||
|
dir: internal/mocks
|
||||||
|
outpkg: mocks
|
||||||
|
packages:
|
||||||
|
net:
|
||||||
|
interfaces:
|
||||||
|
Listener:
|
||||||
|
config:
|
||||||
|
mockname: MockListener
|
||||||
|
Conn:
|
||||||
|
config:
|
||||||
|
mockname: MockConn
|
427
app/internal/proxymux/internal/mocks/mock_Conn.go
Normal file
427
app/internal/proxymux/internal/mocks/mock_Conn.go
Normal file
@@ -0,0 +1,427 @@
|
|||||||
|
// Code generated by mockery v2.42.2. DO NOT EDIT.
|
||||||
|
|
||||||
|
package mocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
net "net"
|
||||||
|
|
||||||
|
mock "github.com/stretchr/testify/mock"
|
||||||
|
|
||||||
|
time "time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockConn is an autogenerated mock type for the Conn type
|
||||||
|
type MockConn struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
type MockConn_Expecter struct {
|
||||||
|
mock *mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_m *MockConn) EXPECT() *MockConn_Expecter {
|
||||||
|
return &MockConn_Expecter{mock: &_m.Mock}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close provides a mock function with given fields:
|
||||||
|
func (_m *MockConn) Close() error {
|
||||||
|
ret := _m.Called()
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Close")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if rf, ok := ret.Get(0).(func() error); ok {
|
||||||
|
r0 = rf()
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockConn_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close'
|
||||||
|
type MockConn_Close_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close is a helper method to define mock.On call
|
||||||
|
func (_e *MockConn_Expecter) Close() *MockConn_Close_Call {
|
||||||
|
return &MockConn_Close_Call{Call: _e.mock.On("Close")}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockConn_Close_Call) Run(run func()) *MockConn_Close_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run()
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockConn_Close_Call) Return(_a0 error) *MockConn_Close_Call {
|
||||||
|
_c.Call.Return(_a0)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockConn_Close_Call) RunAndReturn(run func() error) *MockConn_Close_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalAddr provides a mock function with given fields:
|
||||||
|
func (_m *MockConn) LocalAddr() net.Addr {
|
||||||
|
ret := _m.Called()
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for LocalAddr")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 net.Addr
|
||||||
|
if rf, ok := ret.Get(0).(func() net.Addr); ok {
|
||||||
|
r0 = rf()
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(net.Addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockConn_LocalAddr_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LocalAddr'
|
||||||
|
type MockConn_LocalAddr_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalAddr is a helper method to define mock.On call
|
||||||
|
func (_e *MockConn_Expecter) LocalAddr() *MockConn_LocalAddr_Call {
|
||||||
|
return &MockConn_LocalAddr_Call{Call: _e.mock.On("LocalAddr")}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockConn_LocalAddr_Call) Run(run func()) *MockConn_LocalAddr_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run()
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockConn_LocalAddr_Call) Return(_a0 net.Addr) *MockConn_LocalAddr_Call {
|
||||||
|
_c.Call.Return(_a0)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockConn_LocalAddr_Call) RunAndReturn(run func() net.Addr) *MockConn_LocalAddr_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read provides a mock function with given fields: b
|
||||||
|
func (_m *MockConn) Read(b []byte) (int, error) {
|
||||||
|
ret := _m.Called(b)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Read")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 int
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func([]byte) (int, error)); ok {
|
||||||
|
return rf(b)
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func([]byte) int); ok {
|
||||||
|
r0 = rf(b)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func([]byte) error); ok {
|
||||||
|
r1 = rf(b)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockConn_Read_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Read'
|
||||||
|
type MockConn_Read_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read is a helper method to define mock.On call
|
||||||
|
// - b []byte
|
||||||
|
func (_e *MockConn_Expecter) Read(b interface{}) *MockConn_Read_Call {
|
||||||
|
return &MockConn_Read_Call{Call: _e.mock.On("Read", b)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockConn_Read_Call) Run(run func(b []byte)) *MockConn_Read_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].([]byte))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockConn_Read_Call) Return(n int, err error) *MockConn_Read_Call {
|
||||||
|
_c.Call.Return(n, err)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockConn_Read_Call) RunAndReturn(run func([]byte) (int, error)) *MockConn_Read_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteAddr provides a mock function with given fields:
|
||||||
|
func (_m *MockConn) RemoteAddr() net.Addr {
|
||||||
|
ret := _m.Called()
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for RemoteAddr")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 net.Addr
|
||||||
|
if rf, ok := ret.Get(0).(func() net.Addr); ok {
|
||||||
|
r0 = rf()
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(net.Addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockConn_RemoteAddr_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoteAddr'
|
||||||
|
type MockConn_RemoteAddr_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteAddr is a helper method to define mock.On call
|
||||||
|
func (_e *MockConn_Expecter) RemoteAddr() *MockConn_RemoteAddr_Call {
|
||||||
|
return &MockConn_RemoteAddr_Call{Call: _e.mock.On("RemoteAddr")}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockConn_RemoteAddr_Call) Run(run func()) *MockConn_RemoteAddr_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run()
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockConn_RemoteAddr_Call) Return(_a0 net.Addr) *MockConn_RemoteAddr_Call {
|
||||||
|
_c.Call.Return(_a0)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockConn_RemoteAddr_Call) RunAndReturn(run func() net.Addr) *MockConn_RemoteAddr_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDeadline provides a mock function with given fields: t
|
||||||
|
func (_m *MockConn) SetDeadline(t time.Time) error {
|
||||||
|
ret := _m.Called(t)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for SetDeadline")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if rf, ok := ret.Get(0).(func(time.Time) error); ok {
|
||||||
|
r0 = rf(t)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockConn_SetDeadline_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetDeadline'
|
||||||
|
type MockConn_SetDeadline_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDeadline is a helper method to define mock.On call
|
||||||
|
// - t time.Time
|
||||||
|
func (_e *MockConn_Expecter) SetDeadline(t interface{}) *MockConn_SetDeadline_Call {
|
||||||
|
return &MockConn_SetDeadline_Call{Call: _e.mock.On("SetDeadline", t)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockConn_SetDeadline_Call) Run(run func(t time.Time)) *MockConn_SetDeadline_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(time.Time))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockConn_SetDeadline_Call) Return(_a0 error) *MockConn_SetDeadline_Call {
|
||||||
|
_c.Call.Return(_a0)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockConn_SetDeadline_Call) RunAndReturn(run func(time.Time) error) *MockConn_SetDeadline_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReadDeadline provides a mock function with given fields: t
|
||||||
|
func (_m *MockConn) SetReadDeadline(t time.Time) error {
|
||||||
|
ret := _m.Called(t)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for SetReadDeadline")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if rf, ok := ret.Get(0).(func(time.Time) error); ok {
|
||||||
|
r0 = rf(t)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockConn_SetReadDeadline_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetReadDeadline'
|
||||||
|
type MockConn_SetReadDeadline_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReadDeadline is a helper method to define mock.On call
|
||||||
|
// - t time.Time
|
||||||
|
func (_e *MockConn_Expecter) SetReadDeadline(t interface{}) *MockConn_SetReadDeadline_Call {
|
||||||
|
return &MockConn_SetReadDeadline_Call{Call: _e.mock.On("SetReadDeadline", t)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockConn_SetReadDeadline_Call) Run(run func(t time.Time)) *MockConn_SetReadDeadline_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(time.Time))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockConn_SetReadDeadline_Call) Return(_a0 error) *MockConn_SetReadDeadline_Call {
|
||||||
|
_c.Call.Return(_a0)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockConn_SetReadDeadline_Call) RunAndReturn(run func(time.Time) error) *MockConn_SetReadDeadline_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetWriteDeadline provides a mock function with given fields: t
|
||||||
|
func (_m *MockConn) SetWriteDeadline(t time.Time) error {
|
||||||
|
ret := _m.Called(t)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for SetWriteDeadline")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if rf, ok := ret.Get(0).(func(time.Time) error); ok {
|
||||||
|
r0 = rf(t)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockConn_SetWriteDeadline_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetWriteDeadline'
|
||||||
|
type MockConn_SetWriteDeadline_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetWriteDeadline is a helper method to define mock.On call
|
||||||
|
// - t time.Time
|
||||||
|
func (_e *MockConn_Expecter) SetWriteDeadline(t interface{}) *MockConn_SetWriteDeadline_Call {
|
||||||
|
return &MockConn_SetWriteDeadline_Call{Call: _e.mock.On("SetWriteDeadline", t)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockConn_SetWriteDeadline_Call) Run(run func(t time.Time)) *MockConn_SetWriteDeadline_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(time.Time))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockConn_SetWriteDeadline_Call) Return(_a0 error) *MockConn_SetWriteDeadline_Call {
|
||||||
|
_c.Call.Return(_a0)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockConn_SetWriteDeadline_Call) RunAndReturn(run func(time.Time) error) *MockConn_SetWriteDeadline_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write provides a mock function with given fields: b
|
||||||
|
func (_m *MockConn) Write(b []byte) (int, error) {
|
||||||
|
ret := _m.Called(b)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Write")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 int
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func([]byte) (int, error)); ok {
|
||||||
|
return rf(b)
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func([]byte) int); ok {
|
||||||
|
r0 = rf(b)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func([]byte) error); ok {
|
||||||
|
r1 = rf(b)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockConn_Write_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Write'
|
||||||
|
type MockConn_Write_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write is a helper method to define mock.On call
|
||||||
|
// - b []byte
|
||||||
|
func (_e *MockConn_Expecter) Write(b interface{}) *MockConn_Write_Call {
|
||||||
|
return &MockConn_Write_Call{Call: _e.mock.On("Write", b)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockConn_Write_Call) Run(run func(b []byte)) *MockConn_Write_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].([]byte))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockConn_Write_Call) Return(n int, err error) *MockConn_Write_Call {
|
||||||
|
_c.Call.Return(n, err)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockConn_Write_Call) RunAndReturn(run func([]byte) (int, error)) *MockConn_Write_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockConn creates a new instance of MockConn. 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 NewMockConn(t interface {
|
||||||
|
mock.TestingT
|
||||||
|
Cleanup(func())
|
||||||
|
}) *MockConn {
|
||||||
|
mock := &MockConn{}
|
||||||
|
mock.Mock.Test(t)
|
||||||
|
|
||||||
|
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||||
|
|
||||||
|
return mock
|
||||||
|
}
|
185
app/internal/proxymux/internal/mocks/mock_Listener.go
Normal file
185
app/internal/proxymux/internal/mocks/mock_Listener.go
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
// Code generated by mockery v2.42.2. DO NOT EDIT.
|
||||||
|
|
||||||
|
package mocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
net "net"
|
||||||
|
|
||||||
|
mock "github.com/stretchr/testify/mock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockListener is an autogenerated mock type for the Listener type
|
||||||
|
type MockListener struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
type MockListener_Expecter struct {
|
||||||
|
mock *mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_m *MockListener) EXPECT() *MockListener_Expecter {
|
||||||
|
return &MockListener_Expecter{mock: &_m.Mock}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept provides a mock function with given fields:
|
||||||
|
func (_m *MockListener) Accept() (net.Conn, error) {
|
||||||
|
ret := _m.Called()
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Accept")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 net.Conn
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func() (net.Conn, error)); ok {
|
||||||
|
return rf()
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func() net.Conn); ok {
|
||||||
|
r0 = rf()
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(net.Conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func() error); ok {
|
||||||
|
r1 = rf()
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockListener_Accept_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Accept'
|
||||||
|
type MockListener_Accept_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept is a helper method to define mock.On call
|
||||||
|
func (_e *MockListener_Expecter) Accept() *MockListener_Accept_Call {
|
||||||
|
return &MockListener_Accept_Call{Call: _e.mock.On("Accept")}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockListener_Accept_Call) Run(run func()) *MockListener_Accept_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run()
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockListener_Accept_Call) Return(_a0 net.Conn, _a1 error) *MockListener_Accept_Call {
|
||||||
|
_c.Call.Return(_a0, _a1)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockListener_Accept_Call) RunAndReturn(run func() (net.Conn, error)) *MockListener_Accept_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addr provides a mock function with given fields:
|
||||||
|
func (_m *MockListener) Addr() net.Addr {
|
||||||
|
ret := _m.Called()
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Addr")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 net.Addr
|
||||||
|
if rf, ok := ret.Get(0).(func() net.Addr); ok {
|
||||||
|
r0 = rf()
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(net.Addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockListener_Addr_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Addr'
|
||||||
|
type MockListener_Addr_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addr is a helper method to define mock.On call
|
||||||
|
func (_e *MockListener_Expecter) Addr() *MockListener_Addr_Call {
|
||||||
|
return &MockListener_Addr_Call{Call: _e.mock.On("Addr")}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockListener_Addr_Call) Run(run func()) *MockListener_Addr_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run()
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockListener_Addr_Call) Return(_a0 net.Addr) *MockListener_Addr_Call {
|
||||||
|
_c.Call.Return(_a0)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockListener_Addr_Call) RunAndReturn(run func() net.Addr) *MockListener_Addr_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close provides a mock function with given fields:
|
||||||
|
func (_m *MockListener) Close() error {
|
||||||
|
ret := _m.Called()
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Close")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if rf, ok := ret.Get(0).(func() error); ok {
|
||||||
|
r0 = rf()
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockListener_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close'
|
||||||
|
type MockListener_Close_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close is a helper method to define mock.On call
|
||||||
|
func (_e *MockListener_Expecter) Close() *MockListener_Close_Call {
|
||||||
|
return &MockListener_Close_Call{Call: _e.mock.On("Close")}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockListener_Close_Call) Run(run func()) *MockListener_Close_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run()
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockListener_Close_Call) Return(_a0 error) *MockListener_Close_Call {
|
||||||
|
_c.Call.Return(_a0)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockListener_Close_Call) RunAndReturn(run func() error) *MockListener_Close_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockListener creates a new instance of MockListener. 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 NewMockListener(t interface {
|
||||||
|
mock.TestingT
|
||||||
|
Cleanup(func())
|
||||||
|
}) *MockListener {
|
||||||
|
mock := &MockListener{}
|
||||||
|
mock.Mock.Test(t)
|
||||||
|
|
||||||
|
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||||
|
|
||||||
|
return mock
|
||||||
|
}
|
72
app/internal/proxymux/manager.go
Normal file
72
app/internal/proxymux/manager.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package proxymux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/apernet/hysteria/extras/correctnet"
|
||||||
|
)
|
||||||
|
|
||||||
|
type muxManager struct {
|
||||||
|
listeners map[string]*muxListener
|
||||||
|
lock sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
var globalMuxManager *muxManager
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
globalMuxManager = &muxManager{
|
||||||
|
listeners: make(map[string]*muxListener),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *muxManager) GetOrCreate(address string) (*muxListener, error) {
|
||||||
|
key, err := m.canonicalizeAddrPort(address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
m.lock.Lock()
|
||||||
|
defer m.lock.Unlock()
|
||||||
|
|
||||||
|
if ml, ok := m.listeners[key]; ok {
|
||||||
|
return ml, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
listener, err := correctnet.Listen("tcp", key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ml := newMuxListener(listener, func() {
|
||||||
|
m.lock.Lock()
|
||||||
|
defer m.lock.Unlock()
|
||||||
|
delete(m.listeners, key)
|
||||||
|
})
|
||||||
|
m.listeners[key] = ml
|
||||||
|
return ml, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *muxManager) canonicalizeAddrPort(address string) (string, error) {
|
||||||
|
taddr, err := net.ResolveTCPAddr("tcp", address)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return taddr.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListenHTTP(address string) (net.Listener, error) {
|
||||||
|
ml, err := globalMuxManager.GetOrCreate(address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ml.ListenHTTP()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListenSOCKS(address string) (net.Listener, error) {
|
||||||
|
ml, err := globalMuxManager.GetOrCreate(address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ml.ListenSOCKS()
|
||||||
|
}
|
110
app/internal/proxymux/manager_test.go
Normal file
110
app/internal/proxymux/manager_test.go
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
package proxymux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestListenSOCKS(t *testing.T) {
|
||||||
|
address := "127.2.39.129:11081"
|
||||||
|
|
||||||
|
sl, err := ListenSOCKS(address)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
sl.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
hl, err := ListenHTTP(address)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer hl.Close()
|
||||||
|
|
||||||
|
_, err = ListenSOCKS(address)
|
||||||
|
if !assert.ErrorIs(t, err, ErrProtocolInUse) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sl.Close()
|
||||||
|
|
||||||
|
sl, err = ListenSOCKS(address)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListenHTTP(t *testing.T) {
|
||||||
|
address := "127.2.39.129:11082"
|
||||||
|
|
||||||
|
hl, err := ListenHTTP(address)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
hl.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
sl, err := ListenSOCKS(address)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer sl.Close()
|
||||||
|
|
||||||
|
_, err = ListenHTTP(address)
|
||||||
|
if !assert.ErrorIs(t, err, ErrProtocolInUse) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hl.Close()
|
||||||
|
|
||||||
|
hl, err = ListenHTTP(address)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRelease(t *testing.T) {
|
||||||
|
address := "127.2.39.129:11083"
|
||||||
|
|
||||||
|
hl, err := ListenHTTP(address)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sl, err := ListenSOCKS(address)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !assert.True(t, globalMuxManager.testAddressExists(address)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = net.Listen("tcp", address)
|
||||||
|
if !assert.Error(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hl.Close()
|
||||||
|
sl.Close()
|
||||||
|
|
||||||
|
// Wait for muxListener released
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
if !assert.False(t, globalMuxManager.testAddressExists(address)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lis, err := net.Listen("tcp", address)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer lis.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *muxManager) testAddressExists(address string) bool {
|
||||||
|
m.lock.Lock()
|
||||||
|
defer m.lock.Unlock()
|
||||||
|
|
||||||
|
_, ok := m.listeners[address]
|
||||||
|
return ok
|
||||||
|
}
|
320
app/internal/proxymux/mux.go
Normal file
320
app/internal/proxymux/mux.go
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
package proxymux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newMuxListener(listener net.Listener, deleteFunc func()) *muxListener {
|
||||||
|
l := &muxListener{
|
||||||
|
base: listener,
|
||||||
|
acceptChan: make(chan net.Conn),
|
||||||
|
closeChan: make(chan struct{}),
|
||||||
|
deleteFunc: deleteFunc,
|
||||||
|
}
|
||||||
|
go l.acceptLoop()
|
||||||
|
go l.mainLoop()
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
type muxListener struct {
|
||||||
|
lock sync.Mutex
|
||||||
|
base net.Listener
|
||||||
|
acceptErr error
|
||||||
|
|
||||||
|
acceptChan chan net.Conn
|
||||||
|
closeChan chan struct{}
|
||||||
|
|
||||||
|
socksListener *subListener
|
||||||
|
httpListener *subListener
|
||||||
|
|
||||||
|
deleteFunc func()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *muxListener) acceptLoop() {
|
||||||
|
defer close(l.acceptChan)
|
||||||
|
|
||||||
|
for {
|
||||||
|
conn, err := l.base.Accept()
|
||||||
|
if err != nil {
|
||||||
|
l.lock.Lock()
|
||||||
|
l.acceptErr = err
|
||||||
|
l.lock.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-l.closeChan:
|
||||||
|
return
|
||||||
|
case l.acceptChan <- conn:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *muxListener) mainLoop() {
|
||||||
|
defer func() {
|
||||||
|
l.deleteFunc()
|
||||||
|
l.base.Close()
|
||||||
|
|
||||||
|
close(l.closeChan)
|
||||||
|
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
if sl := l.httpListener; sl != nil {
|
||||||
|
close(sl.acceptChan)
|
||||||
|
l.httpListener = nil
|
||||||
|
}
|
||||||
|
if sl := l.socksListener; sl != nil {
|
||||||
|
close(sl.acceptChan)
|
||||||
|
l.socksListener = nil
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
var socksCloseChan, httpCloseChan chan struct{}
|
||||||
|
if l.httpListener != nil {
|
||||||
|
httpCloseChan = l.httpListener.closeChan
|
||||||
|
}
|
||||||
|
if l.socksListener != nil {
|
||||||
|
socksCloseChan = l.socksListener.closeChan
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-l.closeChan:
|
||||||
|
return
|
||||||
|
case conn, ok := <-l.acceptChan:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go l.dispatch(conn)
|
||||||
|
case <-socksCloseChan:
|
||||||
|
l.lock.Lock()
|
||||||
|
if socksCloseChan == l.socksListener.closeChan {
|
||||||
|
// not replaced by another ListenSOCKS()
|
||||||
|
l.socksListener = nil
|
||||||
|
}
|
||||||
|
l.lock.Unlock()
|
||||||
|
if l.checkIdle() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case <-httpCloseChan:
|
||||||
|
l.lock.Lock()
|
||||||
|
if httpCloseChan == l.httpListener.closeChan {
|
||||||
|
// not replaced by another ListenHTTP()
|
||||||
|
l.httpListener = nil
|
||||||
|
}
|
||||||
|
l.lock.Unlock()
|
||||||
|
if l.checkIdle() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *muxListener) dispatch(conn net.Conn) {
|
||||||
|
var b [1]byte
|
||||||
|
if _, err := io.ReadFull(conn, b[:]); err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l.lock.Lock()
|
||||||
|
var target *subListener
|
||||||
|
if b[0] == 5 {
|
||||||
|
target = l.socksListener
|
||||||
|
} else {
|
||||||
|
target = l.httpListener
|
||||||
|
}
|
||||||
|
l.lock.Unlock()
|
||||||
|
|
||||||
|
if target == nil {
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wconn := &connWithOneByte{Conn: conn, b: b[0]}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-target.closeChan:
|
||||||
|
case target.acceptChan <- wconn:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *muxListener) checkIdle() bool {
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
return l.httpListener == nil && l.socksListener == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *muxListener) getAndClearAcceptError() error {
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
if l.acceptErr == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err := l.acceptErr
|
||||||
|
l.acceptErr = nil
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *muxListener) ListenHTTP() (net.Listener, error) {
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
if l.httpListener != nil {
|
||||||
|
subListenerPendingClosed := false
|
||||||
|
select {
|
||||||
|
case <-l.httpListener.closeChan:
|
||||||
|
subListenerPendingClosed = true
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
if !subListenerPendingClosed {
|
||||||
|
return nil, OpErr{
|
||||||
|
Addr: l.base.Addr(),
|
||||||
|
Protocol: "http",
|
||||||
|
Op: "bind-protocol",
|
||||||
|
Err: ErrProtocolInUse,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l.httpListener = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-l.closeChan:
|
||||||
|
return nil, net.ErrClosed
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
sl := newSubListener(l.getAndClearAcceptError, l.base.Addr)
|
||||||
|
l.httpListener = sl
|
||||||
|
return sl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *muxListener) ListenSOCKS() (net.Listener, error) {
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
if l.socksListener != nil {
|
||||||
|
subListenerPendingClosed := false
|
||||||
|
select {
|
||||||
|
case <-l.socksListener.closeChan:
|
||||||
|
subListenerPendingClosed = true
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
if !subListenerPendingClosed {
|
||||||
|
return nil, OpErr{
|
||||||
|
Addr: l.base.Addr(),
|
||||||
|
Protocol: "socks",
|
||||||
|
Op: "bind-protocol",
|
||||||
|
Err: ErrProtocolInUse,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l.socksListener = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-l.closeChan:
|
||||||
|
return nil, net.ErrClosed
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
sl := newSubListener(l.getAndClearAcceptError, l.base.Addr)
|
||||||
|
l.socksListener = sl
|
||||||
|
return sl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSubListener(acceptErrorFunc func() error, addrFunc func() net.Addr) *subListener {
|
||||||
|
return &subListener{
|
||||||
|
acceptChan: make(chan net.Conn),
|
||||||
|
acceptErrorFunc: acceptErrorFunc,
|
||||||
|
closeChan: make(chan struct{}),
|
||||||
|
addrFunc: addrFunc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type subListener struct {
|
||||||
|
// receive connections or closure from upstream
|
||||||
|
acceptChan chan net.Conn
|
||||||
|
// get an error of Accept() from upstream
|
||||||
|
acceptErrorFunc func() error
|
||||||
|
// notify upstream that we are closed
|
||||||
|
closeChan chan struct{}
|
||||||
|
|
||||||
|
// Listener.Addr() implementation of base listener
|
||||||
|
addrFunc func() net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *subListener) Accept() (net.Conn, error) {
|
||||||
|
select {
|
||||||
|
case <-l.closeChan:
|
||||||
|
// closed by ourselves
|
||||||
|
return nil, net.ErrClosed
|
||||||
|
case conn, ok := <-l.acceptChan:
|
||||||
|
if !ok {
|
||||||
|
// closed by upstream
|
||||||
|
if acceptErr := l.acceptErrorFunc(); acceptErr != nil {
|
||||||
|
return nil, acceptErr
|
||||||
|
}
|
||||||
|
return nil, net.ErrClosed
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *subListener) Addr() net.Addr {
|
||||||
|
return l.addrFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements net.Listener.Close.
|
||||||
|
// Upstream should use close(l.acceptChan) instead.
|
||||||
|
func (l *subListener) Close() error {
|
||||||
|
select {
|
||||||
|
case <-l.closeChan:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
close(l.closeChan)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// connWithOneByte is a net.Conn that returns b for the first read
|
||||||
|
// request, then forwards everything else to Conn.
|
||||||
|
type connWithOneByte struct {
|
||||||
|
net.Conn
|
||||||
|
|
||||||
|
b byte
|
||||||
|
bRead bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *connWithOneByte) Read(bs []byte) (int, error) {
|
||||||
|
if c.bRead {
|
||||||
|
return c.Conn.Read(bs)
|
||||||
|
}
|
||||||
|
if len(bs) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
c.bRead = true
|
||||||
|
bs[0] = c.b
|
||||||
|
return 1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type OpErr struct {
|
||||||
|
Addr net.Addr
|
||||||
|
Protocol string
|
||||||
|
Op string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m OpErr) Error() string {
|
||||||
|
return fmt.Sprintf("mux-listen: %s[%s]: %s: %v", m.Addr, m.Protocol, m.Op, m.Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m OpErr) Unwrap() error {
|
||||||
|
return m.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrProtocolInUse = errors.New("protocol already in use")
|
154
app/internal/proxymux/mux_test.go
Normal file
154
app/internal/proxymux/mux_test.go
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
package proxymux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/apernet/hysteria/app/internal/proxymux/internal/mocks"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate mockery
|
||||||
|
|
||||||
|
func testMockListener(t *testing.T, connChan <-chan net.Conn) net.Listener {
|
||||||
|
closedChan := make(chan struct{})
|
||||||
|
mockListener := mocks.NewMockListener(t)
|
||||||
|
mockListener.EXPECT().Accept().RunAndReturn(func() (net.Conn, error) {
|
||||||
|
select {
|
||||||
|
case <-closedChan:
|
||||||
|
return nil, net.ErrClosed
|
||||||
|
case conn, ok := <-connChan:
|
||||||
|
if !ok {
|
||||||
|
panic("unexpected closed channel (connChan)")
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
})
|
||||||
|
mockListener.EXPECT().Close().RunAndReturn(func() error {
|
||||||
|
select {
|
||||||
|
case <-closedChan:
|
||||||
|
default:
|
||||||
|
close(closedChan)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return mockListener
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMockConn(t *testing.T, b []byte) net.Conn {
|
||||||
|
buf := bytes.NewReader(b)
|
||||||
|
isClosed := false
|
||||||
|
mockConn := mocks.NewMockConn(t)
|
||||||
|
mockConn.EXPECT().Read(mock.Anything).RunAndReturn(func(b []byte) (int, error) {
|
||||||
|
if isClosed {
|
||||||
|
return 0, net.ErrClosed
|
||||||
|
}
|
||||||
|
return buf.Read(b)
|
||||||
|
})
|
||||||
|
mockConn.EXPECT().Close().RunAndReturn(func() error {
|
||||||
|
isClosed = true
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return mockConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMuxHTTP(t *testing.T) {
|
||||||
|
connChan := make(chan net.Conn)
|
||||||
|
mockListener := testMockListener(t, connChan)
|
||||||
|
mockConn := testMockConn(t, []byte("CONNECT example.com:443 HTTP/1.1\r\n\r\n"))
|
||||||
|
|
||||||
|
mux := newMuxListener(mockListener, func() {})
|
||||||
|
hl, err := mux.ListenHTTP()
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sl, err := mux.ListenSOCKS()
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
connChan <- mockConn
|
||||||
|
|
||||||
|
var socksConn, httpConn net.Conn
|
||||||
|
var socksErr, httpErr error
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(2)
|
||||||
|
go func() {
|
||||||
|
socksConn, socksErr = sl.Accept()
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
httpConn, httpErr = hl.Accept()
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
sl.Close()
|
||||||
|
hl.Close()
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
assert.Nil(t, socksConn)
|
||||||
|
assert.ErrorIs(t, socksErr, net.ErrClosed)
|
||||||
|
assert.NotNil(t, httpConn)
|
||||||
|
httpConn.Close()
|
||||||
|
assert.NoError(t, httpErr)
|
||||||
|
|
||||||
|
// Wait for muxListener released
|
||||||
|
<-mux.acceptChan
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMuxSOCKS(t *testing.T) {
|
||||||
|
connChan := make(chan net.Conn)
|
||||||
|
mockListener := testMockListener(t, connChan)
|
||||||
|
mockConn := testMockConn(t, []byte{0x05, 0x02, 0x00, 0x01}) // SOCKS5 Connect Request: NOAUTH+GSSAPI
|
||||||
|
|
||||||
|
mux := newMuxListener(mockListener, func() {})
|
||||||
|
hl, err := mux.ListenHTTP()
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sl, err := mux.ListenSOCKS()
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
connChan <- mockConn
|
||||||
|
|
||||||
|
var socksConn, httpConn net.Conn
|
||||||
|
var socksErr, httpErr error
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(2)
|
||||||
|
go func() {
|
||||||
|
socksConn, socksErr = sl.Accept()
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
httpConn, httpErr = hl.Accept()
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
sl.Close()
|
||||||
|
hl.Close()
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
assert.NotNil(t, socksConn)
|
||||||
|
socksConn.Close()
|
||||||
|
assert.NoError(t, socksErr)
|
||||||
|
assert.Nil(t, httpConn)
|
||||||
|
assert.ErrorIs(t, httpErr, net.ErrClosed)
|
||||||
|
|
||||||
|
// Wait for muxListener released
|
||||||
|
<-mux.acceptChan
|
||||||
|
}
|
Reference in New Issue
Block a user