mirror of
https://github.com/SagerNet/sing-box.git
synced 2024-11-22 00:21:30 +00:00
Add custom TLS server support for http based v2ray transports
This commit is contained in:
parent
f15f525c5c
commit
ed50257735
|
@ -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) {
|
||||
|
|
6
go.mod
6
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
|
||||
|
|
12
go.sum
12
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=
|
||||
|
|
|
@ -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
|
||||
|
|
12
test/go.sum
12
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=
|
||||
|
|
291
test/reality_test.go
Normal file
291
test/reality_test.go
Normal file
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//go:build !go1.20
|
||||
|
||||
package v2raygrpclite
|
||||
|
||||
import (
|
||||
|
|
122
transport/v2raygrpclite/server_badhttp.go
Normal file
122
transport/v2raygrpclite/server_badhttp.go
Normal file
|
@ -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))
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
//go:build !go1.20
|
||||
|
||||
package v2rayhttp
|
||||
|
||||
import (
|
||||
|
|
182
transport/v2rayhttp/server_badhttp.go
Normal file
182
transport/v2rayhttp/server_badhttp.go
Normal file
|
@ -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))
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
//go:build !go1.20
|
||||
|
||||
package v2raywebsocket
|
||||
|
||||
import (
|
||||
|
|
141
transport/v2raywebsocket/server_badhttp.go
Normal file
141
transport/v2raywebsocket/server_badhttp.go
Normal file
|
@ -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))
|
||||
}
|
Loading…
Reference in a new issue