From ed502577352c248f91396c10f10f4a41226d0324 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 28 Feb 2023 12:29:02 +0800 Subject: [PATCH] Add custom TLS server support for http based v2ray transports --- common/tls/reality_server.go | 5 +- go.mod | 6 +- go.sum | 12 +- test/go.mod | 6 +- test/go.sum | 12 +- test/reality_test.go | 291 +++++++++++++++++++++ test/vless_test.go | 131 ---------- transport/v2raygrpclite/server.go | 2 + transport/v2raygrpclite/server_badhttp.go | 122 +++++++++ transport/v2rayhttp/server.go | 2 + transport/v2rayhttp/server_badhttp.go | 182 +++++++++++++ transport/v2raywebsocket/server.go | 2 + transport/v2raywebsocket/server_badhttp.go | 141 ++++++++++ 13 files changed, 768 insertions(+), 146 deletions(-) create mode 100644 test/reality_test.go create mode 100644 transport/v2raygrpclite/server_badhttp.go create mode 100644 transport/v2rayhttp/server_badhttp.go create mode 100644 transport/v2raywebsocket/server_badhttp.go diff --git a/common/tls/reality_server.go b/common/tls/reality_server.go index 7d560c86..1724f1cc 100644 --- a/common/tls/reality_server.go +++ b/common/tls/reality_server.go @@ -8,7 +8,6 @@ import ( "encoding/base64" "encoding/hex" "net" - "os" "time" "github.com/sagernet/reality" @@ -135,7 +134,7 @@ func (c *RealityServerConfig) Config() (*tls.Config, error) { } func (c *RealityServerConfig) Client(conn net.Conn) (Conn, error) { - return nil, os.ErrInvalid + return ClientHandshake(context.Background(), conn, c) } func (c *RealityServerConfig) Start() error { @@ -147,7 +146,7 @@ func (c *RealityServerConfig) Close() error { } func (c *RealityServerConfig) Server(conn net.Conn) (Conn, error) { - return nil, os.ErrInvalid + return ServerHandshake(context.Background(), conn, c) } func (c *RealityServerConfig) ServerHandshake(ctx context.Context, conn net.Conn) (Conn, error) { diff --git a/go.mod b/go.mod index 8c8a54a0..9bfdff8f 100644 --- a/go.mod +++ b/go.mod @@ -20,11 +20,13 @@ require ( github.com/miekg/dns v1.1.50 github.com/oschwald/maxminddb-golang v1.10.0 github.com/pires/go-proxyproto v0.6.2 + github.com/sagernet/badhttp v0.0.0-20230228035330-e77eb9a689fd + github.com/sagernet/badhttp2 v0.0.0-20230228040529-408b0b8e774d github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0 github.com/sagernet/gomobile v0.0.0-20221130124640-349ebaa752ca github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32 - github.com/sagernet/reality v0.0.0-20230226124550-f98d51fa21b5 - github.com/sagernet/sing v0.1.8-0.20230228031050-b60f6390dfe8 + github.com/sagernet/reality v0.0.0-20230228045158-d3e085a8e5d1 + github.com/sagernet/sing v0.1.8-0.20230228034829-bb617490652c github.com/sagernet/sing-dns v0.1.4 github.com/sagernet/sing-shadowsocks v0.1.2-0.20230221080503-769c01d6bba9 github.com/sagernet/sing-shadowtls v0.0.0-20230221123345-78e50cd7b587 diff --git a/go.sum b/go.sum index 926e60aa..dc467d64 100644 --- a/go.sum +++ b/go.sum @@ -115,6 +115,10 @@ github.com/quic-go/qtls-go1-19 v0.2.0/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05 github.com/quic-go/qtls-go1-20 v0.1.0 h1:d1PK3ErFy9t7zxKsG3NXBJXZjp/kMLoIb3y/kV54oAI= github.com/quic-go/qtls-go1-20 v0.1.0/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagernet/badhttp v0.0.0-20230228035330-e77eb9a689fd h1:nv3WtVfPGX+i2Ip/TR+Yd3LO1xFSpKUgWmYsXxKJ6vM= +github.com/sagernet/badhttp v0.0.0-20230228035330-e77eb9a689fd/go.mod h1:geEm+9ZyRMZ8THRH0XSexeStaMDtkFBf4J1nMK92mAY= +github.com/sagernet/badhttp2 v0.0.0-20230228040529-408b0b8e774d h1:RmBTGU4SvqxX57SDvpQtrkiQDaCnr4J/DMYMrUBL7OQ= +github.com/sagernet/badhttp2 v0.0.0-20230228040529-408b0b8e774d/go.mod h1:Ag8QdZjLwuy3V2pyOcqlKz4Cdh0wKEOFlYgR3wPUGkI= github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0 h1:KyhtFFt1Jtp5vW2ohNvstvQffTOQ/s5vENuGXzdA+TM= github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0/go.mod h1:D4SFEOkJK+4W1v86ZhX0jPM0rAL498fyQAChqMtes/I= github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA= @@ -125,12 +129,12 @@ github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6E github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32 h1:tztuJB+giOWNRKQEBVY2oI3PsheTooMdh+/yxemYQYY= github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32/go.mod h1:QMCkxXAC3CvBgDZVIJp43NWTuwGBScCzMLVLynjERL8= -github.com/sagernet/reality v0.0.0-20230226124550-f98d51fa21b5 h1:yDic66vLGsY3zqEyOyRj5tyGfHevLeNv/tXjHUWVzkE= -github.com/sagernet/reality v0.0.0-20230226124550-f98d51fa21b5/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= +github.com/sagernet/reality v0.0.0-20230228045158-d3e085a8e5d1 h1:8mSzchN6DkM26JKLalPwj2KLMIsEjzlp/pYgznlKE2Q= +github.com/sagernet/reality v0.0.0-20230228045158-d3e085a8e5d1/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/sing v0.0.0-20220812082120-05f9836bff8f/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY= github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY= -github.com/sagernet/sing v0.1.8-0.20230228031050-b60f6390dfe8 h1:ZBb6CW6bFovBoW950v0eiitQKYEkB2GGot8tkVfu0gM= -github.com/sagernet/sing v0.1.8-0.20230228031050-b60f6390dfe8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk= +github.com/sagernet/sing v0.1.8-0.20230228034829-bb617490652c h1:+YUwfoIkKlMi3Y1QrOy+OlIELC9KWV0+/5F3NX72q8U= +github.com/sagernet/sing v0.1.8-0.20230228034829-bb617490652c/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk= github.com/sagernet/sing-dns v0.1.4 h1:7VxgeoSCiiazDSaXXQVcvrTBxFpOePPq/4XdgnUDN+0= github.com/sagernet/sing-dns v0.1.4/go.mod h1:1+6pCa48B1AI78lD+/i/dLgpw4MwfnsSpZo0Ds8wzzk= github.com/sagernet/sing-shadowsocks v0.1.2-0.20230221080503-769c01d6bba9 h1:qS39eA4C7x+zhEkySbASrtmb6ebdy5v0y2M6mgkmSO0= diff --git a/test/go.mod b/test/go.mod index 1ad8abce..3e11034a 100644 --- a/test/go.mod +++ b/test/go.mod @@ -10,7 +10,7 @@ require ( github.com/docker/docker v20.10.18+incompatible github.com/docker/go-connections v0.4.0 github.com/gofrs/uuid v4.4.0+incompatible - github.com/sagernet/sing v0.1.8-0.20230221060643-3401d210384b + github.com/sagernet/sing v0.1.8-0.20230228034829-bb617490652c github.com/sagernet/sing-shadowsocks v0.1.2-0.20230221080503-769c01d6bba9 github.com/spyzhov/ajson v0.7.1 github.com/stretchr/testify v1.8.1 @@ -63,11 +63,13 @@ require ( github.com/quic-go/qtls-go1-18 v0.2.0 // indirect github.com/quic-go/qtls-go1-19 v0.2.0 // indirect github.com/quic-go/qtls-go1-20 v0.1.0 // indirect + github.com/sagernet/badhttp v0.0.0-20230228035330-e77eb9a689fd // indirect + github.com/sagernet/badhttp2 v0.0.0-20230228040529-408b0b8e774d // indirect github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0 // indirect github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32 // indirect - github.com/sagernet/reality v0.0.0-20230226124550-f98d51fa21b5 // indirect + github.com/sagernet/reality v0.0.0-20230228045158-d3e085a8e5d1 // indirect github.com/sagernet/sing-dns v0.1.4 // indirect github.com/sagernet/sing-shadowtls v0.0.0-20230221123345-78e50cd7b587 // indirect github.com/sagernet/sing-tun v0.1.2-0.20230226091124-0cdb0eed74d9 // indirect diff --git a/test/go.sum b/test/go.sum index 79632f37..c1450f4f 100644 --- a/test/go.sum +++ b/test/go.sum @@ -130,6 +130,10 @@ github.com/quic-go/qtls-go1-19 v0.2.0 h1:Cvn2WdhyViFUHoOqK52i51k4nDX8EwIh5VJiVM4 github.com/quic-go/qtls-go1-19 v0.2.0/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= github.com/quic-go/qtls-go1-20 v0.1.0 h1:d1PK3ErFy9t7zxKsG3NXBJXZjp/kMLoIb3y/kV54oAI= github.com/quic-go/qtls-go1-20 v0.1.0/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= +github.com/sagernet/badhttp v0.0.0-20230228035330-e77eb9a689fd h1:nv3WtVfPGX+i2Ip/TR+Yd3LO1xFSpKUgWmYsXxKJ6vM= +github.com/sagernet/badhttp v0.0.0-20230228035330-e77eb9a689fd/go.mod h1:geEm+9ZyRMZ8THRH0XSexeStaMDtkFBf4J1nMK92mAY= +github.com/sagernet/badhttp2 v0.0.0-20230228040529-408b0b8e774d h1:RmBTGU4SvqxX57SDvpQtrkiQDaCnr4J/DMYMrUBL7OQ= +github.com/sagernet/badhttp2 v0.0.0-20230228040529-408b0b8e774d/go.mod h1:Ag8QdZjLwuy3V2pyOcqlKz4Cdh0wKEOFlYgR3wPUGkI= github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0 h1:KyhtFFt1Jtp5vW2ohNvstvQffTOQ/s5vENuGXzdA+TM= github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0/go.mod h1:D4SFEOkJK+4W1v86ZhX0jPM0rAL498fyQAChqMtes/I= github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA= @@ -138,12 +142,12 @@ github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6E github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32 h1:tztuJB+giOWNRKQEBVY2oI3PsheTooMdh+/yxemYQYY= github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32/go.mod h1:QMCkxXAC3CvBgDZVIJp43NWTuwGBScCzMLVLynjERL8= -github.com/sagernet/reality v0.0.0-20230226124550-f98d51fa21b5 h1:yDic66vLGsY3zqEyOyRj5tyGfHevLeNv/tXjHUWVzkE= -github.com/sagernet/reality v0.0.0-20230226124550-f98d51fa21b5/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= +github.com/sagernet/reality v0.0.0-20230228045158-d3e085a8e5d1 h1:8mSzchN6DkM26JKLalPwj2KLMIsEjzlp/pYgznlKE2Q= +github.com/sagernet/reality v0.0.0-20230228045158-d3e085a8e5d1/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/sing v0.0.0-20220812082120-05f9836bff8f/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY= github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY= -github.com/sagernet/sing v0.1.8-0.20230221060643-3401d210384b h1:Ji2AfGlc4j9AitobOx4k3BCj7eS5nSxL1cgaL81zvlo= -github.com/sagernet/sing v0.1.8-0.20230221060643-3401d210384b/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk= +github.com/sagernet/sing v0.1.8-0.20230228034829-bb617490652c h1:+YUwfoIkKlMi3Y1QrOy+OlIELC9KWV0+/5F3NX72q8U= +github.com/sagernet/sing v0.1.8-0.20230228034829-bb617490652c/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk= github.com/sagernet/sing-dns v0.1.4 h1:7VxgeoSCiiazDSaXXQVcvrTBxFpOePPq/4XdgnUDN+0= github.com/sagernet/sing-dns v0.1.4/go.mod h1:1+6pCa48B1AI78lD+/i/dLgpw4MwfnsSpZo0Ds8wzzk= github.com/sagernet/sing-shadowsocks v0.1.2-0.20230221080503-769c01d6bba9 h1:qS39eA4C7x+zhEkySbASrtmb6ebdy5v0y2M6mgkmSO0= diff --git a/test/reality_test.go b/test/reality_test.go new file mode 100644 index 00000000..3d8c1894 --- /dev/null +++ b/test/reality_test.go @@ -0,0 +1,291 @@ +package main + +import ( + "net/netip" + "testing" + + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/transport/vless" +) + +func TestVLESSVisionReality(t *testing.T) { + _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") + + userUUID := newUUID() + startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeMixed, + Tag: "mixed-in", + MixedOptions: option.HTTPMixedInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: option.ListenAddress(netip.IPv4Unspecified()), + ListenPort: clientPort, + }, + }, + }, + { + Type: C.TypeVLESS, + VLESSOptions: option.VLESSInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: option.ListenAddress(netip.IPv4Unspecified()), + ListenPort: serverPort, + }, + Users: []option.VLESSUser{ + { + Name: "sekai", + UUID: userUUID.String(), + }, + }, + TLS: &option.InboundTLSOptions{ + Enabled: true, + ServerName: "google.com", + Reality: &option.InboundRealityOptions{ + Enabled: true, + Handshake: option.InboundRealityHandshakeOptions{ + ServerOptions: option.ServerOptions{ + Server: "google.com", + ServerPort: 443, + }, + }, + ShortID: []string{"0123456789abcdef"}, + PrivateKey: "UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc", + }, + }, + }, + }, + { + Type: C.TypeTrojan, + Tag: "trojan", + TrojanOptions: option.TrojanInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: option.ListenAddress(netip.IPv4Unspecified()), + ListenPort: otherPort, + }, + Users: []option.TrojanUser{ + { + Name: "sekai", + Password: userUUID.String(), + }, + }, + TLS: &option.InboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + CertificatePath: certPem, + KeyPath: keyPem, + }, + }, + }, + }, + Outbounds: []option.Outbound{ + { + Type: C.TypeDirect, + }, + { + Type: C.TypeTrojan, + Tag: "trojan-out", + TrojanOptions: option.TrojanOutboundOptions{ + ServerOptions: option.ServerOptions{ + Server: "127.0.0.1", + ServerPort: otherPort, + }, + Password: userUUID.String(), + TLS: &option.OutboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + CertificatePath: certPem, + }, + DialerOptions: option.DialerOptions{ + Detour: "vless-out", + }, + }, + }, + { + Type: C.TypeVLESS, + Tag: "vless-out", + VLESSOptions: option.VLESSOutboundOptions{ + ServerOptions: option.ServerOptions{ + Server: "127.0.0.1", + ServerPort: serverPort, + }, + UUID: userUUID.String(), + Flow: vless.FlowVision, + TLS: &option.OutboundTLSOptions{ + Enabled: true, + ServerName: "google.com", + Reality: &option.OutboundRealityOptions{ + Enabled: true, + ShortID: "0123456789abcdef", + PublicKey: "jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0", + }, + UTLS: &option.OutboundUTLSOptions{ + Enabled: true, + }, + }, + }, + }, + }, + Route: &option.RouteOptions{ + Rules: []option.Rule{ + { + DefaultOptions: option.DefaultRule{ + Inbound: []string{"mixed-in"}, + Outbound: "trojan-out", + }, + }, + }, + }, + }) + testSuit(t, clientPort, testPort) +} + +func TestVLESSRealityTransport(t *testing.T) { + t.Run("grpc", func(t *testing.T) { + testVLESSRealityTransport(t, &option.V2RayTransportOptions{ + Type: C.V2RayTransportTypeGRPC, + }) + }) + t.Run("websocket", func(t *testing.T) { + testVLESSRealityTransport(t, &option.V2RayTransportOptions{ + Type: C.V2RayTransportTypeWebsocket, + }) + }) + t.Run("h2", func(t *testing.T) { + testVLESSRealityTransport(t, &option.V2RayTransportOptions{ + Type: C.V2RayTransportTypeHTTP, + }) + }) +} + +func testVLESSRealityTransport(t *testing.T, transport *option.V2RayTransportOptions) { + _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") + + userUUID := newUUID() + startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeMixed, + Tag: "mixed-in", + MixedOptions: option.HTTPMixedInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: option.ListenAddress(netip.IPv4Unspecified()), + ListenPort: clientPort, + }, + }, + }, + { + Type: C.TypeVLESS, + VLESSOptions: option.VLESSInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: option.ListenAddress(netip.IPv4Unspecified()), + ListenPort: serverPort, + }, + Users: []option.VLESSUser{ + { + Name: "sekai", + UUID: userUUID.String(), + }, + }, + TLS: &option.InboundTLSOptions{ + Enabled: true, + ServerName: "google.com", + Reality: &option.InboundRealityOptions{ + Enabled: true, + Handshake: option.InboundRealityHandshakeOptions{ + ServerOptions: option.ServerOptions{ + Server: "google.com", + ServerPort: 443, + }, + }, + ShortID: []string{"0123456789abcdef"}, + PrivateKey: "UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc", + }, + }, + Transport: transport, + }, + }, + { + Type: C.TypeTrojan, + Tag: "trojan", + TrojanOptions: option.TrojanInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: option.ListenAddress(netip.IPv4Unspecified()), + ListenPort: otherPort, + }, + Users: []option.TrojanUser{ + { + Name: "sekai", + Password: userUUID.String(), + }, + }, + TLS: &option.InboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + CertificatePath: certPem, + KeyPath: keyPem, + }, + }, + }, + }, + Outbounds: []option.Outbound{ + { + Type: C.TypeDirect, + }, + { + Type: C.TypeTrojan, + Tag: "trojan-out", + TrojanOptions: option.TrojanOutboundOptions{ + ServerOptions: option.ServerOptions{ + Server: "127.0.0.1", + ServerPort: otherPort, + }, + Password: userUUID.String(), + TLS: &option.OutboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + CertificatePath: certPem, + }, + DialerOptions: option.DialerOptions{ + Detour: "vless-out", + }, + }, + }, + { + Type: C.TypeVLESS, + Tag: "vless-out", + VLESSOptions: option.VLESSOutboundOptions{ + ServerOptions: option.ServerOptions{ + Server: "127.0.0.1", + ServerPort: serverPort, + }, + UUID: userUUID.String(), + TLS: &option.OutboundTLSOptions{ + Enabled: true, + ServerName: "google.com", + Reality: &option.OutboundRealityOptions{ + Enabled: true, + ShortID: "0123456789abcdef", + PublicKey: "jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0", + }, + UTLS: &option.OutboundUTLSOptions{ + Enabled: true, + }, + }, + Transport: transport, + }, + }, + }, + Route: &option.RouteOptions{ + Rules: []option.Rule{ + { + DefaultOptions: option.DefaultRule{ + Inbound: []string{"mixed-in"}, + Outbound: "trojan-out", + }, + }, + }, + }, + }) + testSuit(t, clientPort, testPort) +} diff --git a/test/vless_test.go b/test/vless_test.go index 988f04fa..52005f7a 100644 --- a/test/vless_test.go +++ b/test/vless_test.go @@ -395,134 +395,3 @@ func testVLESSSelfTLS(t *testing.T, flow string) { }) testSuit(t, clientPort, testPort) } - -func TestVLESSVisionReality(t *testing.T) { - _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") - - userUUID := newUUID() - startInstance(t, option.Options{ - Inbounds: []option.Inbound{ - { - Type: C.TypeMixed, - Tag: "mixed-in", - MixedOptions: option.HTTPMixedInboundOptions{ - ListenOptions: option.ListenOptions{ - Listen: option.ListenAddress(netip.IPv4Unspecified()), - ListenPort: clientPort, - }, - }, - }, - { - Type: C.TypeVLESS, - VLESSOptions: option.VLESSInboundOptions{ - ListenOptions: option.ListenOptions{ - Listen: option.ListenAddress(netip.IPv4Unspecified()), - ListenPort: serverPort, - }, - Users: []option.VLESSUser{ - { - Name: "sekai", - UUID: userUUID.String(), - }, - }, - TLS: &option.InboundTLSOptions{ - Enabled: true, - ServerName: "google.com", - Reality: &option.InboundRealityOptions{ - Enabled: true, - Handshake: option.InboundRealityHandshakeOptions{ - ServerOptions: option.ServerOptions{ - Server: "google.com", - ServerPort: 443, - }, - }, - ShortID: []string{"0123456789abcdef"}, - PrivateKey: "UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc", - }, - }, - }, - }, - { - Type: C.TypeTrojan, - Tag: "trojan", - TrojanOptions: option.TrojanInboundOptions{ - ListenOptions: option.ListenOptions{ - Listen: option.ListenAddress(netip.IPv4Unspecified()), - ListenPort: otherPort, - }, - Users: []option.TrojanUser{ - { - Name: "sekai", - Password: userUUID.String(), - }, - }, - TLS: &option.InboundTLSOptions{ - Enabled: true, - ServerName: "example.org", - CertificatePath: certPem, - KeyPath: keyPem, - }, - }, - }, - }, - Outbounds: []option.Outbound{ - { - Type: C.TypeDirect, - }, - { - Type: C.TypeTrojan, - Tag: "trojan-out", - TrojanOptions: option.TrojanOutboundOptions{ - ServerOptions: option.ServerOptions{ - Server: "127.0.0.1", - ServerPort: otherPort, - }, - Password: userUUID.String(), - TLS: &option.OutboundTLSOptions{ - Enabled: true, - ServerName: "example.org", - CertificatePath: certPem, - }, - DialerOptions: option.DialerOptions{ - Detour: "vless-out", - }, - }, - }, - { - Type: C.TypeVLESS, - Tag: "vless-out", - VLESSOptions: option.VLESSOutboundOptions{ - ServerOptions: option.ServerOptions{ - Server: "127.0.0.1", - ServerPort: serverPort, - }, - UUID: userUUID.String(), - Flow: vless.FlowVision, - TLS: &option.OutboundTLSOptions{ - Enabled: true, - ServerName: "google.com", - Reality: &option.OutboundRealityOptions{ - Enabled: true, - ShortID: "0123456789abcdef", - PublicKey: "jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0", - }, - UTLS: &option.OutboundUTLSOptions{ - Enabled: true, - }, - }, - }, - }, - }, - Route: &option.RouteOptions{ - Rules: []option.Rule{ - { - DefaultOptions: option.DefaultRule{ - Inbound: []string{"mixed-in"}, - Outbound: "trojan-out", - }, - }, - }, - }, - }) - testSuit(t, clientPort, testPort) -} diff --git a/transport/v2raygrpclite/server.go b/transport/v2raygrpclite/server.go index 02ca0ae9..dd635cda 100644 --- a/transport/v2raygrpclite/server.go +++ b/transport/v2raygrpclite/server.go @@ -1,3 +1,5 @@ +//go:build !go1.20 + package v2raygrpclite import ( diff --git a/transport/v2raygrpclite/server_badhttp.go b/transport/v2raygrpclite/server_badhttp.go new file mode 100644 index 00000000..326838fe --- /dev/null +++ b/transport/v2raygrpclite/server_badhttp.go @@ -0,0 +1,122 @@ +//go:build go1.20 && !go1.21 + +package v2raygrpclite + +import ( + "context" + "fmt" + "net" + "net/url" + "os" + "strings" + + "github.com/sagernet/badhttp" + "github.com/sagernet/badhttp2" + "github.com/sagernet/badhttp2/h2c" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/tls" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/transport/v2rayhttp" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + sHttp "github.com/sagernet/sing/protocol/http" +) + +var _ adapter.V2RayServerTransport = (*Server)(nil) + +type Server struct { + handler adapter.V2RayServerTransportHandler + errorHandler E.Handler + httpServer *http.Server + h2Server *http2.Server + h2cHandler http.Handler + path string +} + +func (s *Server) Network() []string { + return []string{N.NetworkTCP} +} + +func NewServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { + server := &Server{ + handler: handler, + path: fmt.Sprintf("/%s/Tun", url.QueryEscape(options.ServiceName)), + h2Server: new(http2.Server), + } + server.httpServer = &http.Server{ + Handler: server, + } + server.h2cHandler = h2c.NewHandler(server, server.h2Server) + if tlsConfig != nil { + if len(tlsConfig.NextProtos()) == 0 { + tlsConfig.SetNextProtos([]string{http2.NextProtoTLS}) + } + server.httpServer.TLSConfig = tlsConfig + } + return server, nil +} + +func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) { + if request.Method == "PRI" && len(request.Header) == 0 && request.URL.Path == "*" && request.Proto == "HTTP/2.0" { + s.h2cHandler.ServeHTTP(writer, request) + return + } + if request.URL.Path != s.path { + s.fallbackRequest(request.Context(), writer, request, http.StatusNotFound, E.New("bad path: ", request.URL.Path)) + return + } + if request.Method != http.MethodPost { + s.fallbackRequest(request.Context(), writer, request, http.StatusNotFound, E.New("bad method: ", request.Method)) + return + } + if ct := request.Header.Get("Content-Type"); !strings.HasPrefix(ct, "application/grpc") { + s.fallbackRequest(request.Context(), writer, request, http.StatusNotFound, E.New("bad content type: ", ct)) + return + } + writer.Header().Set("Content-Type", "application/grpc") + writer.Header().Set("TE", "trailers") + writer.WriteHeader(http.StatusOK) + var metadata M.Metadata + metadata.Source = sHttp.SourceAddress(v2rayhttp.BadRequest(request)) + conn := v2rayhttp.NewHTTP2Wrapper(newGunConn(request.Body, writer, writer.(http.Flusher))) + s.handler.NewConnection(request.Context(), conn, metadata) + conn.CloseWrapper() +} + +func (s *Server) fallbackRequest(ctx context.Context, writer http.ResponseWriter, request *http.Request, statusCode int, err error) { + conn := v2rayhttp.NewHTTPConn(request.Body, writer) + fErr := s.handler.FallbackConnection(ctx, &conn, M.Metadata{}) + if fErr == nil { + return + } else if fErr == os.ErrInvalid { + fErr = nil + } + writer.WriteHeader(statusCode) + s.handler.NewError(request.Context(), E.Cause(E.Errors(err, E.Cause(fErr, "fallback connection")), "process connection from ", request.RemoteAddr)) +} + +func (s *Server) Serve(listener net.Listener) error { + fixTLSConfig := s.httpServer.TLSConfig == nil + err := http2.ConfigureServer(s.httpServer, s.h2Server) + if err != nil { + return err + } + if fixTLSConfig { + s.httpServer.TLSConfig = nil + } + if s.httpServer.TLSConfig == nil { + return s.httpServer.Serve(listener) + } else { + return s.httpServer.ServeTLS(listener, "", "") + } +} + +func (s *Server) ServePacket(listener net.PacketConn) error { + return os.ErrInvalid +} + +func (s *Server) Close() error { + return common.Close(common.PtrOrNil(s.httpServer)) +} diff --git a/transport/v2rayhttp/server.go b/transport/v2rayhttp/server.go index 25c4e4be..2e82a7c3 100644 --- a/transport/v2rayhttp/server.go +++ b/transport/v2rayhttp/server.go @@ -1,3 +1,5 @@ +//go:build !go1.20 + package v2rayhttp import ( diff --git a/transport/v2rayhttp/server_badhttp.go b/transport/v2rayhttp/server_badhttp.go new file mode 100644 index 00000000..00cadfa7 --- /dev/null +++ b/transport/v2rayhttp/server_badhttp.go @@ -0,0 +1,182 @@ +//go:build go1.20 && !go1.21 + +package v2rayhttp + +import ( + std_bufio "bufio" + "context" + "net" + stdHTTP "net/http" + "os" + "strings" + "unsafe" + + "github.com/sagernet/badhttp" + "github.com/sagernet/badhttp2" + "github.com/sagernet/badhttp2/h2c" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + sHttp "github.com/sagernet/sing/protocol/http" +) + +var _ adapter.V2RayServerTransport = (*Server)(nil) + +type Server struct { + ctx context.Context + handler adapter.V2RayServerTransportHandler + httpServer *http.Server + h2Server *http2.Server + h2cHandler http.Handler + host []string + path string + method string + headers http.Header +} + +func (s *Server) Network() []string { + return []string{N.NetworkTCP} +} + +func NewServer(ctx context.Context, options option.V2RayHTTPOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { + server := &Server{ + ctx: ctx, + handler: handler, + h2Server: new(http2.Server), + host: options.Host, + path: options.Path, + method: options.Method, + headers: make(http.Header), + } + if server.method == "" { + server.method = "PUT" + } + if !strings.HasPrefix(server.path, "/") { + server.path = "/" + server.path + } + for key, value := range options.Headers { + server.headers.Set(key, value) + } + server.httpServer = &http.Server{ + Handler: server, + ReadHeaderTimeout: C.TCPTimeout, + MaxHeaderBytes: http.DefaultMaxHeaderBytes, + TLSConfig: tlsConfig, + } + server.h2cHandler = h2c.NewHandler(server, server.h2Server) + return server, nil +} + +func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) { + if request.Method == "PRI" && len(request.Header) == 0 && request.URL.Path == "*" && request.Proto == "HTTP/2.0" { + s.h2cHandler.ServeHTTP(writer, request) + return + } + host := request.Host + if len(s.host) > 0 && !common.Contains(s.host, host) { + s.fallbackRequest(request.Context(), writer, request, http.StatusBadRequest, E.New("bad host: ", host)) + return + } + if !strings.HasPrefix(request.URL.Path, s.path) { + s.fallbackRequest(request.Context(), writer, request, http.StatusNotFound, E.New("bad path: ", request.URL.Path)) + return + } + if request.Method != s.method { + s.fallbackRequest(request.Context(), writer, request, http.StatusNotFound, E.New("bad method: ", request.Method)) + return + } + + writer.Header().Set("Cache-Control", "no-store") + + for key, values := range s.headers { + for _, value := range values { + writer.Header().Set(key, value) + } + } + + writer.WriteHeader(http.StatusOK) + writer.(http.Flusher).Flush() + + var metadata M.Metadata + metadata.Source = sHttp.SourceAddress(BadRequest(request)) + if h, ok := writer.(http.Hijacker); ok { + conn, _, err := h.Hijack() + if err != nil { + s.fallbackRequest(request.Context(), writer, request, http.StatusInternalServerError, E.Cause(err, "hijack conn")) + return + } + s.handler.NewConnection(request.Context(), conn, metadata) + } else { + conn := NewHTTP2Wrapper(&ServerHTTPConn{ + NewHTTPConn(request.Body, writer), + writer.(http.Flusher), + }) + s.handler.NewConnection(request.Context(), conn, metadata) + conn.CloseWrapper() + } +} + +func (s *Server) fallbackRequest(ctx context.Context, writer http.ResponseWriter, request *http.Request, statusCode int, err error) { + conn := NewHTTPConn(request.Body, writer) + fErr := s.handler.FallbackConnection(ctx, &conn, M.Metadata{}) + if fErr == nil { + return + } else if fErr == os.ErrInvalid { + fErr = nil + } + writer.WriteHeader(statusCode) + s.handler.NewError(request.Context(), E.Cause(E.Errors(err, E.Cause(fErr, "fallback connection")), "process connection from ", request.RemoteAddr)) +} + +func (s *Server) Serve(listener net.Listener) error { + fixTLSConfig := s.httpServer.TLSConfig == nil + err := http2.ConfigureServer(s.httpServer, s.h2Server) + if err != nil { + return err + } + if fixTLSConfig { + s.httpServer.TLSConfig = nil + } + if s.httpServer.TLSConfig == nil { + return s.httpServer.Serve(listener) + } else { + return s.httpServer.ServeTLS(listener, "", "") + } +} + +func (s *Server) ServePacket(listener net.PacketConn) error { + return os.ErrInvalid +} + +func (s *Server) Close() error { + return common.Close(common.PtrOrNil(s.httpServer)) +} + +var ( + _ stdHTTP.ResponseWriter = (*BadResponseWriter)(nil) + _ stdHTTP.Hijacker = (*BadResponseWriter)(nil) +) + +type BadResponseWriter struct { + http.ResponseWriter +} + +func (w *BadResponseWriter) Header() stdHTTP.Header { + return stdHTTP.Header(w.ResponseWriter.Header()) +} + +func (w *BadResponseWriter) Hijack() (net.Conn, *std_bufio.ReadWriter, error) { + if hijacker, loaded := common.Cast[http.Hijacker](w.ResponseWriter); loaded { + return hijacker.Hijack() + } + return nil, nil, os.ErrInvalid +} + +func BadRequest(r *http.Request) *stdHTTP.Request { + return (*stdHTTP.Request)(unsafe.Pointer(r)) +} diff --git a/transport/v2raywebsocket/server.go b/transport/v2raywebsocket/server.go index 8fa38f2b..fa2d40fb 100644 --- a/transport/v2raywebsocket/server.go +++ b/transport/v2raywebsocket/server.go @@ -1,3 +1,5 @@ +//go:build !go1.20 + package v2raywebsocket import ( diff --git a/transport/v2raywebsocket/server_badhttp.go b/transport/v2raywebsocket/server_badhttp.go new file mode 100644 index 00000000..af7aef9f --- /dev/null +++ b/transport/v2raywebsocket/server_badhttp.go @@ -0,0 +1,141 @@ +//go:build go1.20 && !go1.21 + +package v2raywebsocket + +import ( + "context" + "encoding/base64" + "net" + stdHTTP "net/http" + "os" + "strings" + + "github.com/sagernet/badhttp" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/transport/v2rayhttp" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + sHttp "github.com/sagernet/sing/protocol/http" + "github.com/sagernet/websocket" +) + +var _ adapter.V2RayServerTransport = (*Server)(nil) + +type Server struct { + ctx context.Context + handler adapter.V2RayServerTransportHandler + httpServer *http.Server + path string + maxEarlyData uint32 + earlyDataHeaderName string +} + +func NewServer(ctx context.Context, options option.V2RayWebsocketOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) { + server := &Server{ + ctx: ctx, + handler: handler, + path: options.Path, + maxEarlyData: options.MaxEarlyData, + earlyDataHeaderName: options.EarlyDataHeaderName, + } + if !strings.HasPrefix(server.path, "/") { + server.path = "/" + server.path + } + server.httpServer = &http.Server{ + Handler: server, + ReadHeaderTimeout: C.TCPTimeout, + MaxHeaderBytes: http.DefaultMaxHeaderBytes, + TLSConfig: tlsConfig, + } + return server, nil +} + +var upgrader = websocket.Upgrader{ + HandshakeTimeout: C.TCPTimeout, + CheckOrigin: func(r *stdHTTP.Request) bool { + return true + }, +} + +func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) { + if s.maxEarlyData == 0 || s.earlyDataHeaderName != "" { + if request.URL.Path != s.path { + s.fallbackRequest(request.Context(), writer, request, http.StatusNotFound, E.New("bad path: ", request.URL.Path)) + return + } + } + var ( + earlyData []byte + err error + conn net.Conn + ) + if s.earlyDataHeaderName == "" { + if strings.HasPrefix(request.URL.RequestURI(), s.path) { + earlyDataStr := request.URL.RequestURI()[len(s.path):] + earlyData, err = base64.RawURLEncoding.DecodeString(earlyDataStr) + } else { + s.fallbackRequest(request.Context(), writer, request, http.StatusNotFound, E.New("bad path: ", request.URL.Path)) + return + } + } else { + earlyDataStr := request.Header.Get(s.earlyDataHeaderName) + if earlyDataStr != "" { + earlyData, err = base64.RawURLEncoding.DecodeString(earlyDataStr) + } + } + if err != nil { + s.fallbackRequest(request.Context(), writer, request, http.StatusBadRequest, E.Cause(err, "decode early data")) + return + } + wsConn, err := upgrader.Upgrade(&v2rayhttp.BadResponseWriter{ResponseWriter: writer}, v2rayhttp.BadRequest(request), nil) + if err != nil { + s.fallbackRequest(request.Context(), writer, request, http.StatusBadRequest, E.Cause(err, "upgrade websocket connection")) + return + } + var metadata M.Metadata + metadata.Source = sHttp.SourceAddress(v2rayhttp.BadRequest(request)) + conn = NewServerConn(wsConn, metadata.Source.TCPAddr()) + if len(earlyData) > 0 { + conn = bufio.NewCachedConn(conn, buf.As(earlyData)) + } + s.handler.NewConnection(request.Context(), conn, metadata) +} + +func (s *Server) fallbackRequest(ctx context.Context, writer http.ResponseWriter, request *http.Request, statusCode int, err error) { + conn := v2rayhttp.NewHTTPConn(request.Body, writer) + fErr := s.handler.FallbackConnection(ctx, &conn, M.Metadata{}) + if fErr == nil { + return + } else if fErr == os.ErrInvalid { + fErr = nil + } + writer.WriteHeader(statusCode) + s.handler.NewError(request.Context(), E.Cause(E.Errors(err, E.Cause(fErr, "fallback connection")), "process connection from ", request.RemoteAddr)) +} + +func (s *Server) Network() []string { + return []string{N.NetworkTCP} +} + +func (s *Server) Serve(listener net.Listener) error { + if s.httpServer.TLSConfig == nil { + return s.httpServer.Serve(listener) + } else { + return s.httpServer.ServeTLS(listener, "", "") + } +} + +func (s *Server) ServePacket(listener net.PacketConn) error { + return os.ErrInvalid +} + +func (s *Server) Close() error { + return common.Close(common.PtrOrNil(s.httpServer)) +}