Files
2026-09-04 01:08:30 +03:00

201 lines
5.0 KiB
Go

package main
import (
"fmt"
"net"
"net/netip"
"testing"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/json/badoption"
N "github.com/sagernet/sing/common/network"
"github.com/stretchr/testify/require"
)
func findFreePort(t *testing.T) uint16 {
t.Helper()
l, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1)})
require.NoError(t, err)
port := uint16(l.Addr().(*net.TCPAddr).Port)
l.Close()
return port
}
// TestTrustTunnel runs e2e tests with combinations of:
// - Transport: H2, QUIC
// - Congestion controllers (QUIC only): bbr, cubic, reno
// - Multiplex: enabled/disabled
// - Network: tcp-only, tcp+udp
func TestTrustTunnel(t *testing.T) {
type combo struct {
name string
quic bool
congestionController string
multiplex bool
network option.NetworkList
testUDP bool
}
var combos []combo
// H2 transport combos
for _, mux := range []bool{false, true} {
for _, net := range []struct {
name string
list option.NetworkList
testUDP bool
}{
{"tcp", option.NetworkList(N.NetworkTCP), false},
{"tcp+udp", option.NetworkList(N.NetworkTCP + "\n" + N.NetworkUDP), true},
} {
name := fmt.Sprintf("h2/mux=%v/%s", mux, net.name)
combos = append(combos, combo{
name: name,
quic: false,
multiplex: mux,
network: net.list,
testUDP: net.testUDP,
})
}
}
// QUIC transport combos (requires UDP in network for HTTP/3 listener)
congestionControllers := []string{"bbr", "cubic", "reno"}
for _, cc := range congestionControllers {
for _, mux := range []bool{false, true} {
name := fmt.Sprintf("quic/%s/mux=%v", cc, mux)
combos = append(combos, combo{
name: name,
quic: true,
congestionController: cc,
multiplex: mux,
network: option.NetworkList(N.NetworkTCP + "\n" + N.NetworkUDP),
testUDP: true,
})
}
}
for _, c := range combos {
c := c
t.Run(c.name, func(t *testing.T) {
t.Parallel()
testTrustTunnelCombo(t, c.quic, c.congestionController, c.multiplex, c.network, c.testUDP)
})
}
}
func testTrustTunnelCombo(t *testing.T, useQUIC bool, cc string, multiplex bool, network option.NetworkList, testUDP bool) {
t.Helper()
_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
srvPort := findFreePort(t)
cliPort := findFreePort(t)
tstPort := findFreePort(t)
var muxOpts *option.TrustTunnelMultiplexOptions
if multiplex {
muxOpts = &option.TrustTunnelMultiplexOptions{
Enabled: true,
MaxConnections: 4,
MinStreams: 2,
}
}
startInstance(t, option.Options{
Inbounds: []option.Inbound{
{
Type: C.TypeMixed,
Tag: "mixed-in",
Options: &option.HTTPMixedInboundOptions{
ListenOptions: option.ListenOptions{
Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
ListenPort: cliPort,
},
},
},
{
Type: C.TypeTrustTunnel,
Tag: "tt-in",
Options: &option.TrustTunnelInboundOptions{
ListenOptions: option.ListenOptions{
Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
ListenPort: srvPort,
},
Users: []option.TrustTunnelUser{{
Name: "testuser",
Password: "testpass",
}},
Network: network,
CongestionController: cc,
CWND: 32,
InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
TLS: &option.InboundTLSOptions{
Enabled: true,
ServerName: "example.org",
ALPN: badoption.Listable[string]{"h2", "h3"},
CertificatePath: certPem,
KeyPath: keyPem,
},
},
},
},
},
Outbounds: []option.Outbound{
{
Type: C.TypeDirect,
},
{
Type: C.TypeTrustTunnel,
Tag: "tt-out",
Options: &option.TrustTunnelOutboundOptions{
ServerOptions: option.ServerOptions{
Server: "127.0.0.1",
ServerPort: srvPort,
},
Username: "testuser",
Password: "testpass",
Network: network,
QUIC: useQUIC,
CongestionController: cc,
CWND: 32,
Multiplex: muxOpts,
OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
TLS: &option.OutboundTLSOptions{
Enabled: true,
ServerName: "example.org",
Insecure: true,
},
},
},
},
},
Route: &option.RouteOptions{
Rules: []option.Rule{
{
Type: C.RuleTypeDefault,
DefaultOptions: option.DefaultRule{
RawDefaultRule: option.RawDefaultRule{
Inbound: []string{"mixed-in"},
},
RuleAction: option.RuleAction{
Action: C.RuleActionTypeRoute,
RouteOptions: option.RouteActionOptions{
Outbound: "tt-out",
},
},
},
},
},
},
})
if testUDP {
testSuit(t, cliPort, tstPort)
} else {
testTCP(t, cliPort, tstPort)
}
}