Skip to content

Commit d0d0c39

Browse files
committed
chore: add inbound test for vmess/vless
1 parent a75e570 commit d0d0c39

File tree

3 files changed

+668
-0
lines changed

3 files changed

+668
-0
lines changed

listener/inbound/common_test.go

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
package inbound_test
2+
3+
import (
4+
"context"
5+
"crypto/rand"
6+
"crypto/tls"
7+
"encoding/base64"
8+
"fmt"
9+
"io"
10+
"net"
11+
"net/http"
12+
"net/netip"
13+
"sync"
14+
"testing"
15+
"time"
16+
17+
N "github.com/metacubex/mihomo/common/net"
18+
"github.com/metacubex/mihomo/component/ca"
19+
"github.com/metacubex/mihomo/component/generater"
20+
C "github.com/metacubex/mihomo/constant"
21+
22+
"github.com/go-chi/chi/v5"
23+
"github.com/go-chi/render"
24+
"github.com/stretchr/testify/assert"
25+
)
26+
27+
var tlsCertificate, tlsPrivateKey, tlsFingerprint, _ = N.NewRandomTLSKeyPair()
28+
var tlsConfigCert, _ = tls.X509KeyPair([]byte(tlsCertificate), []byte(tlsPrivateKey))
29+
var tlsConfig = &tls.Config{Certificates: []tls.Certificate{tlsConfigCert}, NextProtos: []string{"h2", "http/1.1"}}
30+
var tlsClientConfig, _ = ca.GetTLSConfig(nil, tlsFingerprint, "", "")
31+
var realityPrivateKey, realityPublickey string
32+
var realityDest = "itunes.apple.com"
33+
var realityShortid = "10f897e26c4b9478"
34+
35+
func init() {
36+
privateKey, err := generater.GeneratePrivateKey()
37+
if err != nil {
38+
panic(err)
39+
}
40+
publicKey := privateKey.PublicKey()
41+
realityPrivateKey = base64.RawURLEncoding.EncodeToString(privateKey[:])
42+
realityPublickey = base64.RawURLEncoding.EncodeToString(publicKey[:])
43+
}
44+
45+
type TestTunnel struct {
46+
HandleTCPConnFn func(conn net.Conn, metadata *C.Metadata)
47+
HandleUDPPacketFn func(packet C.UDPPacket, metadata *C.Metadata)
48+
NatTableFn func() C.NatTable
49+
CloseFn func() error
50+
DoTestFn func(t *testing.T, proxy C.ProxyAdapter)
51+
}
52+
53+
func (tt *TestTunnel) HandleTCPConn(conn net.Conn, metadata *C.Metadata) {
54+
tt.HandleTCPConnFn(conn, metadata)
55+
}
56+
57+
func (tt *TestTunnel) HandleUDPPacket(packet C.UDPPacket, metadata *C.Metadata) {
58+
tt.HandleUDPPacketFn(packet, metadata)
59+
}
60+
61+
func (tt *TestTunnel) NatTable() C.NatTable {
62+
return tt.NatTableFn()
63+
}
64+
65+
func (tt *TestTunnel) Close() error {
66+
return tt.CloseFn()
67+
}
68+
69+
func (tt *TestTunnel) DoTest(t *testing.T, proxy C.ProxyAdapter) {
70+
tt.DoTestFn(t, proxy)
71+
}
72+
73+
type TestTunnelListener struct {
74+
ch chan net.Conn
75+
ctx context.Context
76+
cancel context.CancelFunc
77+
addr net.Addr
78+
}
79+
80+
func (t *TestTunnelListener) Accept() (net.Conn, error) {
81+
select {
82+
case conn, ok := <-t.ch:
83+
if !ok {
84+
return nil, net.ErrClosed
85+
}
86+
return conn, nil
87+
case <-t.ctx.Done():
88+
return nil, t.ctx.Err()
89+
}
90+
}
91+
92+
func (t *TestTunnelListener) Close() error {
93+
t.cancel()
94+
return nil
95+
}
96+
97+
func (t *TestTunnelListener) Addr() net.Addr {
98+
return t.addr
99+
}
100+
101+
type WaitCloseConn struct {
102+
net.Conn
103+
ch chan struct{}
104+
once sync.Once
105+
}
106+
107+
func (c *WaitCloseConn) Close() error {
108+
err := c.Conn.Close()
109+
c.once.Do(func() {
110+
close(c.ch)
111+
})
112+
return err
113+
}
114+
115+
var _ C.Tunnel = (*TestTunnel)(nil)
116+
var _ net.Listener = (*TestTunnelListener)(nil)
117+
118+
type HttpTestConfig struct {
119+
RemoteAddr netip.Addr
120+
HttpPath string
121+
HttpData []byte
122+
}
123+
124+
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+
}
132+
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))}
134+
135+
r := chi.NewRouter()
136+
r.Get(config.HttpPath, func(w http.ResponseWriter, r *http.Request) {
137+
render.Data(w, r, config.HttpData)
138+
})
139+
go http.Serve(ln, r)
140+
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)
142+
assert.NoError(t, err)
143+
req = req.WithContext(ctx)
144+
145+
var dstPort uint16 = 80
146+
if proto == "https" {
147+
dstPort = 443
148+
}
149+
metadata := &C.Metadata{
150+
NetWork: C.TCP,
151+
DstIP: config.RemoteAddr,
152+
DstPort: dstPort,
153+
}
154+
instance, err := proxy.DialContext(ctx, metadata)
155+
assert.NoError(t, err)
156+
defer instance.Close()
157+
158+
transport := &http.Transport{
159+
DialContext: func(context.Context, string, string) (net.Conn, error) {
160+
return instance, nil
161+
},
162+
// from http.DefaultTransport
163+
MaxIdleConns: 100,
164+
IdleConnTimeout: 90 * time.Second,
165+
TLSHandshakeTimeout: 10 * time.Second,
166+
ExpectContinueTimeout: 1 * time.Second,
167+
// for our self-signed cert
168+
TLSClientConfig: tlsClientConfig,
169+
// open http2
170+
ForceAttemptHTTP2: true,
171+
}
172+
173+
client := http.Client{
174+
Timeout: 30 * time.Second,
175+
Transport: transport,
176+
CheckRedirect: func(req *http.Request, via []*http.Request) error {
177+
return http.ErrUseLastResponse
178+
},
179+
}
180+
181+
defer client.CloseIdleConnections()
182+
183+
resp, err := client.Do(req)
184+
assert.NoError(t, err)
185+
186+
defer resp.Body.Close()
187+
188+
assert.Equal(t, http.StatusOK, resp.StatusCode)
189+
190+
data, err := io.ReadAll(resp.Body)
191+
assert.NoError(t, err)
192+
assert.Equal(t, config.HttpData, data)
193+
}
194+
tunnel := &TestTunnel{
195+
HandleTCPConnFn: func(conn net.Conn, metadata *C.Metadata) {
196+
defer conn.Close()
197+
if metadata.DstIP != config.RemoteAddr && metadata.Host != realityDest {
198+
return // not match, just return
199+
}
200+
c := &WaitCloseConn{
201+
Conn: conn,
202+
ch: make(chan struct{}),
203+
}
204+
if metadata.DstPort == 443 {
205+
tlsConn := tls.Server(c, tlsConfig)
206+
if metadata.Host == realityDest { // ignore the tls handshake error for realityDest
207+
ctx, cancel := context.WithTimeout(ctx, C.DefaultTLSTimeout)
208+
defer cancel()
209+
if err := tlsConn.HandshakeContext(ctx); err != nil {
210+
return
211+
}
212+
}
213+
ln.ch <- tlsConn
214+
} else {
215+
ln.ch <- c
216+
}
217+
<-c.ch
218+
},
219+
CloseFn: ln.Close,
220+
DoTestFn: func(t *testing.T, proxy C.ProxyAdapter) {
221+
wg := sync.WaitGroup{}
222+
num := 50
223+
for i := 0; i < num; i++ {
224+
wg.Add(1)
225+
go func() {
226+
testFn(t, proxy, "https")
227+
defer wg.Done()
228+
}()
229+
}
230+
for i := 0; i < num; i++ {
231+
wg.Add(1)
232+
go func() {
233+
testFn(t, proxy, "http")
234+
defer wg.Done()
235+
}()
236+
}
237+
wg.Wait()
238+
},
239+
}
240+
return tunnel
241+
}

0 commit comments

Comments
 (0)