Skip to content

Commit 3d806b5

Browse files
committed
chore: add inbound test for shadowsocks/trojan
1 parent b5fcd1d commit 3d806b5

File tree

4 files changed

+324
-31
lines changed

4 files changed

+324
-31
lines changed

listener/inbound/common_test.go

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ import (
2424
"github.com/stretchr/testify/assert"
2525
)
2626

27+
var httpPath = "/inbound_test"
28+
var httpData = make([]byte, 10240)
29+
var remoteAddr = netip.MustParseAddr("1.2.3.4")
2730
var tlsCertificate, tlsPrivateKey, tlsFingerprint, _ = N.NewRandomTLSKeyPair()
2831
var tlsConfigCert, _ = tls.X509KeyPair([]byte(tlsCertificate), []byte(tlsPrivateKey))
2932
var tlsConfig = &tls.Config{Certificates: []tls.Certificate{tlsConfigCert}, NextProtos: []string{"h2", "http/1.1"}}
@@ -33,6 +36,7 @@ var realityDest = "itunes.apple.com"
3336
var realityShortid = "10f897e26c4b9478"
3437

3538
func init() {
39+
rand.Read(httpData)
3640
privateKey, err := generater.GeneratePrivateKey()
3741
if err != nil {
3842
panic(err)
@@ -115,30 +119,17 @@ func (c *WaitCloseConn) Close() error {
115119
var _ C.Tunnel = (*TestTunnel)(nil)
116120
var _ net.Listener = (*TestTunnelListener)(nil)
117121

118-
type HttpTestConfig struct {
119-
RemoteAddr netip.Addr
120-
HttpPath string
121-
HttpData []byte
122-
}
123-
124122
func NewHttpTestTunnel() *TestTunnel {
125-
httpData := make([]byte, 10240)
126-
rand.Read(httpData)
127-
config := &HttpTestConfig{
128-
HttpPath: "/inbound_test",
129-
HttpData: httpData,
130-
RemoteAddr: netip.MustParseAddr("1.2.3.4"),
131-
}
132123
ctx, cancel := context.WithCancel(context.Background())
133-
ln := &TestTunnelListener{ch: make(chan net.Conn), ctx: ctx, cancel: cancel, addr: net.TCPAddrFromAddrPort(netip.AddrPortFrom(config.RemoteAddr, 0))}
124+
ln := &TestTunnelListener{ch: make(chan net.Conn), ctx: ctx, cancel: cancel, addr: net.TCPAddrFromAddrPort(netip.AddrPortFrom(remoteAddr, 0))}
134125

135126
r := chi.NewRouter()
136-
r.Get(config.HttpPath, func(w http.ResponseWriter, r *http.Request) {
137-
render.Data(w, r, config.HttpData)
127+
r.Get(httpPath, func(w http.ResponseWriter, r *http.Request) {
128+
render.Data(w, r, httpData)
138129
})
139130
go http.Serve(ln, r)
140131
testFn := func(t *testing.T, proxy C.ProxyAdapter, proto string) {
141-
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s://%s%s", proto, config.RemoteAddr, config.HttpPath), nil)
132+
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s://%s%s", proto, remoteAddr, httpPath), nil)
142133
assert.NoError(t, err)
143134
req = req.WithContext(ctx)
144135

@@ -148,7 +139,7 @@ func NewHttpTestTunnel() *TestTunnel {
148139
}
149140
metadata := &C.Metadata{
150141
NetWork: C.TCP,
151-
DstIP: config.RemoteAddr,
142+
DstIP: remoteAddr,
152143
DstPort: dstPort,
153144
}
154145
instance, err := proxy.DialContext(ctx, metadata)
@@ -165,7 +156,7 @@ func NewHttpTestTunnel() *TestTunnel {
165156
TLSHandshakeTimeout: 10 * time.Second,
166157
ExpectContinueTimeout: 1 * time.Second,
167158
// for our self-signed cert
168-
TLSClientConfig: tlsClientConfig,
159+
TLSClientConfig: tlsClientConfig.Clone(),
169160
// open http2
170161
ForceAttemptHTTP2: true,
171162
}
@@ -189,20 +180,20 @@ func NewHttpTestTunnel() *TestTunnel {
189180

190181
data, err := io.ReadAll(resp.Body)
191182
assert.NoError(t, err)
192-
assert.Equal(t, config.HttpData, data)
183+
assert.Equal(t, httpData, data)
193184
}
194185
tunnel := &TestTunnel{
195186
HandleTCPConnFn: func(conn net.Conn, metadata *C.Metadata) {
196187
defer conn.Close()
197-
if metadata.DstIP != config.RemoteAddr && metadata.Host != realityDest {
188+
if metadata.DstIP != remoteAddr && metadata.Host != realityDest {
198189
return // not match, just return
199190
}
200191
c := &WaitCloseConn{
201192
Conn: conn,
202193
ch: make(chan struct{}),
203194
}
204195
if metadata.DstPort == 443 {
205-
tlsConn := tls.Server(c, tlsConfig)
196+
tlsConn := tls.Server(c, tlsConfig.Clone())
206197
if metadata.Host == realityDest { // ignore the tls handshake error for realityDest
207198
ctx, cancel := context.WithTimeout(ctx, C.DefaultTLSTimeout)
208199
defer cancel()

listener/inbound/shadowsocks_test.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package inbound_test
2+
3+
import (
4+
"crypto/rand"
5+
"encoding/base64"
6+
"net/netip"
7+
"strings"
8+
"testing"
9+
10+
"github.com/metacubex/mihomo/adapter/outbound"
11+
"github.com/metacubex/mihomo/listener/inbound"
12+
13+
shadowsocks "github.com/metacubex/sing-shadowsocks"
14+
"github.com/metacubex/sing-shadowsocks/shadowaead"
15+
"github.com/metacubex/sing-shadowsocks/shadowaead_2022"
16+
"github.com/metacubex/sing-shadowsocks/shadowstream"
17+
"github.com/stretchr/testify/assert"
18+
)
19+
20+
var shadowsocksCipherList = []string{shadowsocks.MethodNone}
21+
var shadowsocksPassword32 string
22+
var shadowsocksPassword16 string
23+
24+
func init() {
25+
shadowsocksCipherList = append(shadowsocksCipherList, shadowaead.List...)
26+
shadowsocksCipherList = append(shadowsocksCipherList, shadowaead_2022.List...)
27+
shadowsocksCipherList = append(shadowsocksCipherList, shadowstream.List...)
28+
passwordBytes := make([]byte, 32)
29+
rand.Read(passwordBytes)
30+
shadowsocksPassword32 = base64.StdEncoding.EncodeToString(passwordBytes)
31+
shadowsocksPassword16 = base64.StdEncoding.EncodeToString(passwordBytes[:16])
32+
}
33+
34+
func testInboundShadowSocks(t *testing.T, inboundOptions inbound.ShadowSocksOption, outboundOptions outbound.ShadowSocksOption) {
35+
for _, cipher := range shadowsocksCipherList {
36+
t.Run(cipher, func(t *testing.T) {
37+
t.Parallel()
38+
inboundOptions.Cipher = cipher
39+
outboundOptions.Cipher = cipher
40+
testInboundShadowSocks0(t, inboundOptions, outboundOptions)
41+
})
42+
}
43+
}
44+
45+
func testInboundShadowSocks0(t *testing.T, inboundOptions inbound.ShadowSocksOption, outboundOptions outbound.ShadowSocksOption) {
46+
password := shadowsocksPassword32
47+
if strings.Contains(inboundOptions.Cipher, "-128-") {
48+
password = shadowsocksPassword16
49+
}
50+
inboundOptions.BaseOption = inbound.BaseOption{
51+
NameStr: "shadowsocks_inbound",
52+
Listen: "127.0.0.1",
53+
Port: "0",
54+
}
55+
inboundOptions.Password = password
56+
in, err := inbound.NewShadowSocks(&inboundOptions)
57+
assert.NoError(t, err)
58+
59+
tunnel := NewHttpTestTunnel()
60+
defer tunnel.Close()
61+
62+
err = in.Listen(tunnel)
63+
assert.NoError(t, err)
64+
defer in.Close()
65+
66+
addrPort, err := netip.ParseAddrPort(in.Address())
67+
assert.NoError(t, err)
68+
69+
outboundOptions.Name = "shadowsocks_outbound"
70+
outboundOptions.Server = addrPort.Addr().String()
71+
outboundOptions.Port = int(addrPort.Port())
72+
outboundOptions.Password = password
73+
74+
out, err := outbound.NewShadowSocks(outboundOptions)
75+
assert.NoError(t, err)
76+
defer out.Close()
77+
78+
tunnel.DoTest(t, out)
79+
}
80+
81+
func TestInboundShadowSocks_Basic(t *testing.T) {
82+
inboundOptions := inbound.ShadowSocksOption{}
83+
outboundOptions := outbound.ShadowSocksOption{}
84+
testInboundShadowSocks(t, inboundOptions, outboundOptions)
85+
}

listener/inbound/trojan_test.go

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
package inbound_test
2+
3+
import (
4+
"net"
5+
"net/netip"
6+
"testing"
7+
8+
"github.com/metacubex/mihomo/adapter/outbound"
9+
"github.com/metacubex/mihomo/common/utils"
10+
"github.com/metacubex/mihomo/listener/inbound"
11+
"github.com/stretchr/testify/assert"
12+
)
13+
14+
func testInboundTrojan(t *testing.T, inboundOptions inbound.TrojanOption, outboundOptions outbound.TrojanOption) {
15+
userUUID := utils.NewUUIDV4().String()
16+
inboundOptions.BaseOption = inbound.BaseOption{
17+
NameStr: "trojan_inbound",
18+
Listen: "127.0.0.1",
19+
Port: "0",
20+
}
21+
inboundOptions.Users = []inbound.TrojanUser{
22+
{Username: "test", Password: userUUID},
23+
}
24+
in, err := inbound.NewTrojan(&inboundOptions)
25+
assert.NoError(t, err)
26+
27+
tunnel := NewHttpTestTunnel()
28+
defer tunnel.Close()
29+
30+
err = in.Listen(tunnel)
31+
assert.NoError(t, err)
32+
defer in.Close()
33+
34+
addrPort, err := netip.ParseAddrPort(in.Address())
35+
assert.NoError(t, err)
36+
37+
outboundOptions.Name = "trojan_outbound"
38+
outboundOptions.Server = addrPort.Addr().String()
39+
outboundOptions.Port = int(addrPort.Port())
40+
outboundOptions.Password = userUUID
41+
42+
out, err := outbound.NewTrojan(outboundOptions)
43+
assert.NoError(t, err)
44+
defer out.Close()
45+
46+
tunnel.DoTest(t, out)
47+
}
48+
49+
func TestInboundTrojan_Tls(t *testing.T) {
50+
inboundOptions := inbound.TrojanOption{
51+
Certificate: tlsCertificate,
52+
PrivateKey: tlsPrivateKey,
53+
}
54+
outboundOptions := outbound.TrojanOption{
55+
Fingerprint: tlsFingerprint,
56+
}
57+
testInboundTrojan(t, inboundOptions, outboundOptions)
58+
}
59+
60+
func TestInboundTrojan_Wss1(t *testing.T) {
61+
inboundOptions := inbound.TrojanOption{
62+
Certificate: tlsCertificate,
63+
PrivateKey: tlsPrivateKey,
64+
WsPath: "/ws",
65+
}
66+
outboundOptions := outbound.TrojanOption{
67+
Fingerprint: tlsFingerprint,
68+
Network: "ws",
69+
WSOpts: outbound.WSOptions{
70+
Path: "/ws",
71+
},
72+
}
73+
testInboundTrojan(t, inboundOptions, outboundOptions)
74+
}
75+
76+
func TestInboundTrojan_Wss2(t *testing.T) {
77+
inboundOptions := inbound.TrojanOption{
78+
Certificate: tlsCertificate,
79+
PrivateKey: tlsPrivateKey,
80+
WsPath: "/ws",
81+
GrpcServiceName: "GunService",
82+
}
83+
outboundOptions := outbound.TrojanOption{
84+
Fingerprint: tlsFingerprint,
85+
Network: "ws",
86+
WSOpts: outbound.WSOptions{
87+
Path: "/ws",
88+
},
89+
}
90+
testInboundTrojan(t, inboundOptions, outboundOptions)
91+
}
92+
93+
func TestInboundTrojan_Grpc1(t *testing.T) {
94+
inboundOptions := inbound.TrojanOption{
95+
Certificate: tlsCertificate,
96+
PrivateKey: tlsPrivateKey,
97+
GrpcServiceName: "GunService",
98+
}
99+
outboundOptions := outbound.TrojanOption{
100+
Fingerprint: tlsFingerprint,
101+
Network: "grpc",
102+
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
103+
}
104+
testInboundTrojan(t, inboundOptions, outboundOptions)
105+
}
106+
107+
func TestInboundTrojan_Grpc2(t *testing.T) {
108+
inboundOptions := inbound.TrojanOption{
109+
Certificate: tlsCertificate,
110+
PrivateKey: tlsPrivateKey,
111+
WsPath: "/ws",
112+
GrpcServiceName: "GunService",
113+
}
114+
outboundOptions := outbound.TrojanOption{
115+
Fingerprint: tlsFingerprint,
116+
Network: "grpc",
117+
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
118+
}
119+
testInboundTrojan(t, inboundOptions, outboundOptions)
120+
}
121+
122+
func TestInboundTrojan_Reality(t *testing.T) {
123+
inboundOptions := inbound.TrojanOption{
124+
RealityConfig: inbound.RealityConfig{
125+
Dest: net.JoinHostPort(realityDest, "443"),
126+
PrivateKey: realityPrivateKey,
127+
ShortID: []string{realityShortid},
128+
ServerNames: []string{realityDest},
129+
},
130+
}
131+
outboundOptions := outbound.TrojanOption{
132+
SNI: realityDest,
133+
RealityOpts: outbound.RealityOptions{
134+
PublicKey: realityPublickey,
135+
ShortID: realityShortid,
136+
},
137+
ClientFingerprint: "chrome",
138+
}
139+
testInboundTrojan(t, inboundOptions, outboundOptions)
140+
}
141+
142+
func TestInboundTrojan_Reality_Grpc(t *testing.T) {
143+
inboundOptions := inbound.TrojanOption{
144+
RealityConfig: inbound.RealityConfig{
145+
Dest: net.JoinHostPort(realityDest, "443"),
146+
PrivateKey: realityPrivateKey,
147+
ShortID: []string{realityShortid},
148+
ServerNames: []string{realityDest},
149+
},
150+
GrpcServiceName: "GunService",
151+
}
152+
outboundOptions := outbound.TrojanOption{
153+
SNI: realityDest,
154+
RealityOpts: outbound.RealityOptions{
155+
PublicKey: realityPublickey,
156+
ShortID: realityShortid,
157+
},
158+
ClientFingerprint: "chrome",
159+
Network: "grpc",
160+
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
161+
}
162+
testInboundTrojan(t, inboundOptions, outboundOptions)
163+
}
164+
165+
func TestInboundTrojan_Tls_TrojanSS(t *testing.T) {
166+
inboundOptions := inbound.TrojanOption{
167+
Certificate: tlsCertificate,
168+
PrivateKey: tlsPrivateKey,
169+
SSOption: inbound.TrojanSSOption{
170+
Enabled: true,
171+
Method: "",
172+
Password: "password",
173+
},
174+
}
175+
outboundOptions := outbound.TrojanOption{
176+
Fingerprint: tlsFingerprint,
177+
SSOpts: outbound.TrojanSSOption{
178+
Enabled: true,
179+
Method: "",
180+
Password: "password",
181+
},
182+
}
183+
testInboundTrojan(t, inboundOptions, outboundOptions)
184+
}
185+
186+
func TestInboundTrojan_Wss_TrojanSS(t *testing.T) {
187+
inboundOptions := inbound.TrojanOption{
188+
Certificate: tlsCertificate,
189+
PrivateKey: tlsPrivateKey,
190+
SSOption: inbound.TrojanSSOption{
191+
Enabled: true,
192+
Method: "",
193+
Password: "password",
194+
},
195+
WsPath: "/ws",
196+
}
197+
outboundOptions := outbound.TrojanOption{
198+
Fingerprint: tlsFingerprint,
199+
SSOpts: outbound.TrojanSSOption{
200+
Enabled: true,
201+
Method: "",
202+
Password: "password",
203+
},
204+
Network: "ws",
205+
WSOpts: outbound.WSOptions{
206+
Path: "/ws",
207+
},
208+
}
209+
testInboundTrojan(t, inboundOptions, outboundOptions)
210+
}

0 commit comments

Comments
 (0)