Files
Xray-core/testing/scenarios/masque_test.go
T

469 lines
13 KiB
Go

package scenarios
import (
"bufio"
"bytes"
"context"
gotls "crypto/tls"
"crypto/x509"
go_errors "errors"
"io"
"net/http"
"net/netip"
"net/url"
"strconv"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/apernet/quic-go"
"github.com/apernet/quic-go/http3"
"golang.org/x/net/http2"
"golang.org/x/net/http2/hpack"
"golang.org/x/sync/errgroup"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"github.com/xtls/xray-core/app/log"
"github.com/xtls/xray-core/app/proxyman"
"github.com/xtls/xray-core/common"
clog "github.com/xtls/xray-core/common/log"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/protocol/tls/cert"
"github.com/xtls/xray-core/common/serial"
core "github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/proxy/dokodemo"
"github.com/xtls/xray-core/proxy/masque"
"github.com/xtls/xray-core/proxy/wireguard"
"github.com/xtls/xray-core/testing/servers/tcp"
"github.com/xtls/xray-core/testing/servers/udp"
"github.com/xtls/xray-core/transport/internet"
transmasque "github.com/xtls/xray-core/transport/internet/masque"
"github.com/xtls/xray-core/transport/internet/masque/connectip"
"github.com/xtls/xray-core/transport/internet/tls"
)
var (
masqueServerV4 = netip.MustParseAddr("10.13.0.1")
masqueServerV6 = netip.MustParseAddr("fd13::1")
masqueClientV4 = netip.MustParsePrefix("10.13.0.2/32")
masqueClientV6 = netip.MustParsePrefix("fd13::2/128")
)
const (
masqueEchoPort = 7
masqueAuthorization = "Basic dTpw"
)
func startMasqueServer(t *testing.T, h2 bool) (net.Port, [32]byte) {
dev, _, gstack, err := wireguard.CreateNetTUN([]netip.Addr{masqueServerV4, masqueServerV6}, nil, transmasque.MinPacketSize, false)
common.Must(err)
t.Cleanup(func() { dev.Close() })
for _, addr := range []netip.Addr{masqueServerV4, masqueServerV6} {
proto := ipv4.ProtocolNumber
if addr.Is6() {
proto = ipv6.ProtocolNumber
}
local := tcpip.FullAddress{NIC: 1, Addr: tcpip.AddrFromSlice(addr.AsSlice()), Port: masqueEchoPort}
l, err := gonet.ListenTCP(gstack, local, proto)
common.Must(err)
go func() {
for {
c, err := l.Accept()
if err != nil {
return
}
go func() {
defer c.Close()
b := make([]byte, 2048)
for {
n, err := c.Read(b)
if err != nil {
return
}
if _, err := c.Write(xor(b[:n])); err != nil {
return
}
}
}()
}
}()
u, err := gonet.DialUDP(gstack, &local, nil, proto)
common.Must(err)
go func() {
b := make([]byte, 2048)
for {
n, addr, err := u.ReadFrom(b)
if err != nil {
return
}
u.WriteTo(xor(b[:n]), addr)
}
}()
}
var current atomic.Pointer[connectip.Conn]
go func() {
bufs := [][]byte{make([]byte, transmasque.MinPacketSize)}
sizes := []int{0}
for {
if _, err := dev.Read(bufs, sizes, 0); err != nil {
return
}
if conn := current.Load(); conn != nil {
if icmp, _ := conn.WritePacket(bufs[0][:sizes[0]]); len(icmp) > 0 {
go dev.Write([][]byte{icmp}, 0)
}
}
}
}()
handler := func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != transmasque.DefaultPath {
w.WriteHeader(http.StatusNotFound)
return
}
if r.Header.Get("Authorization") != masqueAuthorization {
w.WriteHeader(http.StatusUnauthorized)
return
}
req, err := connectip.ParseProxyRequest(r)
if err != nil {
var perr *connectip.ProxyRequestParseError
if go_errors.As(err, &perr) {
w.WriteHeader(perr.HTTPStatus)
}
return
}
conn, err := (&connectip.Proxy{}).Proxy(w, req)
if err != nil {
return
}
defer conn.Close()
common.Must(conn.AssignAddresses([]netip.Prefix{masqueClientV4, masqueClientV6}))
common.Must(conn.AdvertiseRoute([]connectip.IPRoute{
{StartIP: netip.IPv4Unspecified(), EndIP: netip.AddrFrom4([4]byte{255, 255, 255, 255})},
{StartIP: netip.IPv6Unspecified(), EndIP: netip.AddrFrom16([16]byte{0: 0xff, 1: 0xff, 2: 0xff, 3: 0xff, 4: 0xff, 5: 0xff, 6: 0xff, 7: 0xff, 8: 0xff, 9: 0xff, 10: 0xff, 11: 0xff, 12: 0xff, 13: 0xff, 14: 0xff, 15: 0xff})},
}))
go func() {
for {
ar, err := conn.ReceiveAddressRequest(context.Background())
if err != nil {
return
}
assigned := make([]netip.Prefix, len(ar.Prefixes))
for i, p := range ar.Prefixes {
if p.Addr().Is4() {
assigned[i] = masqueClientV4
} else {
assigned[i] = masqueClientV6
}
}
ar.Respond(assigned, nil)
}
}()
current.Store(conn)
b := make([]byte, 2048)
for {
n, err := conn.ReadPacket(b)
if err != nil {
if go_errors.Is(err, io.ErrShortBuffer) {
continue
}
return
}
dev.Write([][]byte{b[:n]}, 0)
}
}
certificate, certHash := cert.MustGenerate(nil, cert.CommonName("localhost"))
key := common.Must2(x509.ParsePKCS8PrivateKey(certificate.PrivateKey))
tlsConfig := &gotls.Config{
Certificates: []gotls.Certificate{{Certificate: [][]byte{certificate.Certificate}, PrivateKey: key}},
NextProtos: []string{http3.NextProtoH3},
}
if h2 {
tlsConfig.NextProtos = []string{http2.NextProtoTLS}
ln := common.Must2(gotls.Listen("tcp", "127.0.0.1:0", tlsConfig))
t.Cleanup(func() { ln.Close() })
go serveHTTP2(ln, http.HandlerFunc(handler))
return net.Port(ln.Addr().(*net.TCPAddr).Port), certHash
}
pktConn := common.Must2(net.ListenUDP("udp", &net.UDPAddr{IP: net.LocalHostIP.IP()}))
tr := &quic.Transport{Conn: pktConn}
ln := common.Must2(tr.ListenEarly(tlsConfig, &quic.Config{EnableDatagrams: true, InitialPacketSize: 1350}))
server := &http3.Server{Handler: http.HandlerFunc(handler), EnableDatagrams: true}
go server.ServeListener(ln)
t.Cleanup(func() {
server.Close()
ln.Close()
tr.Close()
pktConn.Close()
})
return net.Port(pktConn.LocalAddr().(*net.UDPAddr).Port), certHash
}
func serveHTTP2(ln net.Listener, handler http.Handler) {
for {
conn, err := ln.Accept()
if err != nil {
return
}
go serveHTTP2Conn(conn, handler)
}
}
type http2ServerConn struct {
mu sync.Mutex
fr *http2.Framer
hbuf bytes.Buffer
henc *hpack.Encoder
}
func (c *http2ServerConn) write(f func(*http2.Framer) error) error {
c.mu.Lock()
defer c.mu.Unlock()
return f(c.fr)
}
func (c *http2ServerConn) writeHeaders(streamID uint32, status int, header http.Header) error {
c.mu.Lock()
defer c.mu.Unlock()
c.hbuf.Reset()
c.henc.WriteField(hpack.HeaderField{Name: ":status", Value: strconv.Itoa(status)})
for k, vv := range header {
for _, v := range vv {
c.henc.WriteField(hpack.HeaderField{Name: strings.ToLower(k), Value: v})
}
}
return c.fr.WriteHeaders(http2.HeadersFrameParam{StreamID: streamID, BlockFragment: c.hbuf.Bytes(), EndHeaders: true})
}
func (c *http2ServerConn) writeData(streamID uint32, endStream bool, data []byte) error {
c.mu.Lock()
defer c.mu.Unlock()
for {
n := min(len(data), 16384)
if err := c.fr.WriteData(streamID, endStream && n == len(data), data[:n]); err != nil {
return err
}
if data = data[n:]; len(data) == 0 {
return nil
}
}
}
func serveHTTP2Conn(conn net.Conn, handler http.Handler) {
defer conn.Close()
br := bufio.NewReader(conn)
preface := make([]byte, len(http2.ClientPreface))
if _, err := io.ReadFull(br, preface); err != nil || string(preface) != http2.ClientPreface {
return
}
sc := &http2ServerConn{fr: http2.NewFramer(conn, br)}
sc.henc = hpack.NewEncoder(&sc.hbuf)
sc.fr.ReadMetaHeaders = hpack.NewDecoder(4096, nil)
if err := sc.write(func(fr *http2.Framer) error {
if err := fr.WriteSettings(
http2.Setting{ID: http2.SettingEnableConnectProtocol, Val: 1},
http2.Setting{ID: http2.SettingInitialWindowSize, Val: 1 << 30},
); err != nil {
return err
}
return fr.WriteWindowUpdate(0, 1<<30)
}); err != nil {
return
}
bodies := make(map[uint32]*io.PipeWriter)
defer func() {
for _, body := range bodies {
body.Close()
}
}()
for {
f, err := sc.fr.ReadFrame()
if err != nil {
return
}
switch f := f.(type) {
case *http2.SettingsFrame:
if !f.IsAck() {
err = sc.write((*http2.Framer).WriteSettingsAck)
}
case *http2.PingFrame:
if !f.IsAck() {
err = sc.write(func(fr *http2.Framer) error { return fr.WritePing(true, f.Data) })
}
case *http2.MetaHeadersFrame:
u, err := url.ParseRequestURI(f.PseudoValue("path"))
if err != nil {
return
}
pr, pw := io.Pipe()
bodies[f.StreamID] = pw
req := &http.Request{
Method: f.PseudoValue("method"),
URL: u,
Proto: "HTTP/2.0",
ProtoMajor: 2,
Header: http.Header{},
Host: f.PseudoValue("authority"),
Body: pr,
}
for _, hf := range f.RegularFields() {
req.Header.Add(hf.Name, hf.Value)
}
if protocol := f.PseudoValue("protocol"); protocol != "" {
req.Header.Set(":protocol", protocol)
}
streamID := f.StreamID
w := &http2ResponseWriter{conn: sc, streamID: streamID, header: http.Header{}}
go func() {
handler.ServeHTTP(w, req)
w.WriteHeader(http.StatusOK)
sc.writeData(streamID, true, nil)
}()
case *http2.DataFrame:
if body := bodies[f.StreamID]; body != nil {
if _, err := body.Write(f.Data()); err != nil || f.StreamEnded() {
body.Close()
delete(bodies, f.StreamID)
}
}
case *http2.RSTStreamFrame:
if body := bodies[f.StreamID]; body != nil {
body.CloseWithError(http2.StreamError{StreamID: f.StreamID, Code: f.ErrCode})
delete(bodies, f.StreamID)
}
}
if err != nil {
return
}
}
}
type http2ResponseWriter struct {
conn *http2ServerConn
streamID uint32
header http.Header
wroteHeader bool
}
func (w *http2ResponseWriter) Header() http.Header { return w.header }
func (w *http2ResponseWriter) WriteHeader(code int) {
if !w.wroteHeader {
w.wroteHeader = true
w.conn.writeHeaders(w.streamID, code, w.header)
}
}
func (w *http2ResponseWriter) Write(b []byte) (int, error) {
w.WriteHeader(http.StatusOK)
if err := w.conn.writeData(w.streamID, false, b); err != nil {
return 0, err
}
return len(b), nil
}
func (w *http2ResponseWriter) Flush() {}
func TestMasque(t *testing.T) {
testMasque(t, false)
}
func TestMasqueHTTP2(t *testing.T) {
testMasque(t, true)
}
func testMasque(t *testing.T, h2 bool) {
serverPort, certHash := startMasqueServer(t, h2)
tcpPort := tcp.PickPort()
tcp6Port := tcp.PickPort()
udpPort := udp.PickPort()
dokodemoTo := func(port net.Port, addr netip.Addr, network net.Network) *core.InboundHandlerConfig {
return &core.InboundHandlerConfig{
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(port)}},
Listen: net.NewIPOrDomain(net.LocalHostIP),
}),
ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
RewriteAddress: net.NewIPOrDomain(net.IPAddress(addr.AsSlice())),
RewritePort: masqueEchoPort,
AllowedNetworks: []net.Network{network},
}),
}
}
tlsConfig := &tls.Config{
ServerName: "localhost",
PinnedPeerCertSha256: [][]byte{certHash[:]},
}
if h2 {
tlsConfig.NextProtocol = []string{http2.NextProtoTLS}
}
clientConfig := &core.Config{
App: []*serial.TypedMessage{
serial.ToTypedMessage(&log.Config{
ErrorLogLevel: clog.Severity_Debug,
ErrorLogType: log.LogType_Console,
}),
},
Inbound: []*core.InboundHandlerConfig{
dokodemoTo(tcpPort, masqueServerV4, net.Network_TCP),
dokodemoTo(tcp6Port, masqueServerV6, net.Network_TCP),
dokodemoTo(udpPort, masqueServerV4, net.Network_UDP),
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&masque.ClientConfig{
Server: &protocol.ServerEndpoint{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(serverPort),
},
}),
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
StreamSettings: &internet.StreamConfig{
ProtocolName: "masque",
TransportSettings: []*internet.TransportConfig{
{
ProtocolName: "masque",
Settings: serial.ToTypedMessage(&transmasque.Config{
Path: transmasque.DefaultPath,
Headers: map[string]string{"Authorization": masqueAuthorization},
}),
},
},
SecurityType: serial.GetMessageType(&tls.Config{}),
SecuritySettings: []*serial.TypedMessage{
serial.ToTypedMessage(tlsConfig),
},
},
}),
},
},
}
servers, err := InitializeServerConfigs(clientConfig)
common.Must(err)
defer CloseAllServers(servers)
var errg errgroup.Group
for range 3 {
errg.Go(testTCPConn(tcpPort, 1024*1024, time.Second*20))
}
errg.Go(testTCPConn(tcp6Port, 1024*1024, time.Second*20))
errg.Go(testUDPConn(udpPort, 1024, time.Second*5))
if err := errg.Wait(); err != nil {
t.Error(err)
}
}