Add support for v2ray http upgrade transport

This commit is contained in:
世界 2023-10-31 18:24:05 +08:00
parent 53e88eb4a1
commit 5ade21b174
No known key found for this signature in database
GPG Key ID: CD109927C34A63C4
15 changed files with 391 additions and 34 deletions

View File

@ -5,4 +5,5 @@ const (
V2RayTransportTypeWebsocket = "ws" V2RayTransportTypeWebsocket = "ws"
V2RayTransportTypeQUIC = "quic" V2RayTransportTypeQUIC = "quic"
V2RayTransportTypeGRPC = "grpc" V2RayTransportTypeGRPC = "grpc"
V2RayTransportTypeHTTPUpgrade = "httpupgrade"
) )

View File

@ -15,6 +15,7 @@ Available transports:
* WebSocket * WebSocket
* QUIC * QUIC
* gRPC * gRPC
* HTTPUpgrade
!!! warning "Difference from v2ray-core" !!! warning "Difference from v2ray-core"
@ -184,3 +185,32 @@ In standard gRPC client:
If enabled, the client transport sends keepalive pings even with no active connections. If disabled, when there are no active connections, `idle_timeout` and `ping_timeout` will be ignored and no keepalive pings will be sent. If enabled, the client transport sends keepalive pings even with no active connections. If disabled, when there are no active connections, `idle_timeout` and `ping_timeout` will be ignored and no keepalive pings will be sent.
Disabled by default. Disabled by default.
### HTTPUpgrade
```json
{
"type": "httpupgrade",
"host": "",
"path": "",
"headers": {}
}
```
#### host
Host domain.
The server will verify if not empty.
#### path
Path of HTTP request.
The server will verify if not empty.
#### headers
Extra headers of HTTP request.
The server will write in response if not empty.

View File

@ -14,6 +14,7 @@ V2Ray Transport 是 v2ray 发明的一组私有协议,并污染了其他协议
* WebSocket * WebSocket
* QUIC * QUIC
* gRPC * gRPC
* HTTPUpgrade
!!! warning "与 v2ray-core 的区别" !!! warning "与 v2ray-core 的区别"
@ -183,3 +184,32 @@ gRPC 服务名称。
如果启用,客户端传输即使没有活动连接也会发送 keepalive ping。如果禁用则在没有活动连接时将忽略 `idle_timeout``ping_timeout`,并且不会发送 keepalive ping。 如果启用,客户端传输即使没有活动连接也会发送 keepalive ping。如果禁用则在没有活动连接时将忽略 `idle_timeout``ping_timeout`,并且不会发送 keepalive ping。
默认禁用。 默认禁用。
### HTTPUpgrade
```json
{
"type": "httpupgrade",
"host": "",
"path": "",
"headers": {}
}
```
#### host
主机域名。
默认服务器将验证。
#### path
HTTP 请求路径
默认服务器将验证。
#### headers
HTTP 请求的额外标头。
默认服务器将写入响应。

View File

@ -12,6 +12,7 @@ type _V2RayTransportOptions struct {
WebsocketOptions V2RayWebsocketOptions `json:"-"` WebsocketOptions V2RayWebsocketOptions `json:"-"`
QUICOptions V2RayQUICOptions `json:"-"` QUICOptions V2RayQUICOptions `json:"-"`
GRPCOptions V2RayGRPCOptions `json:"-"` GRPCOptions V2RayGRPCOptions `json:"-"`
HTTPUpgradeOptions V2RayHTTPUpgradeOptions `json:"-"`
} }
type V2RayTransportOptions _V2RayTransportOptions type V2RayTransportOptions _V2RayTransportOptions
@ -29,6 +30,8 @@ func (o V2RayTransportOptions) MarshalJSON() ([]byte, error) {
v = o.QUICOptions v = o.QUICOptions
case C.V2RayTransportTypeGRPC: case C.V2RayTransportTypeGRPC:
v = o.GRPCOptions v = o.GRPCOptions
case C.V2RayTransportTypeHTTPUpgrade:
v = o.HTTPUpgradeOptions
default: default:
return nil, E.New("unknown transport type: " + o.Type) return nil, E.New("unknown transport type: " + o.Type)
} }
@ -50,6 +53,8 @@ func (o *V2RayTransportOptions) UnmarshalJSON(bytes []byte) error {
v = &o.QUICOptions v = &o.QUICOptions
case C.V2RayTransportTypeGRPC: case C.V2RayTransportTypeGRPC:
v = &o.GRPCOptions v = &o.GRPCOptions
case C.V2RayTransportTypeHTTPUpgrade:
v = &o.HTTPUpgradeOptions
default: default:
return E.New("unknown transport type: " + o.Type) return E.New("unknown transport type: " + o.Type)
} }
@ -85,3 +90,9 @@ type V2RayGRPCOptions struct {
PermitWithoutStream bool `json:"permit_without_stream,omitempty"` PermitWithoutStream bool `json:"permit_without_stream,omitempty"`
ForceLite bool `json:"-"` // for test ForceLite bool `json:"-"` // for test
} }
type V2RayHTTPUpgradeOptions struct {
Host string `json:"host,omitempty"`
Path string `json:"path,omitempty"`
Headers HTTPHeader `json:"headers,omitempty"`
}

View File

@ -11,7 +11,7 @@ require (
github.com/docker/go-connections v0.4.0 github.com/docker/go-connections v0.4.0
github.com/gofrs/uuid/v5 v5.0.0 github.com/gofrs/uuid/v5 v5.0.0
github.com/sagernet/quic-go v0.0.0-20231008035953-32727fef9460 github.com/sagernet/quic-go v0.0.0-20231008035953-32727fef9460
github.com/sagernet/sing v0.2.16-0.20231021090846-8002db54c028 github.com/sagernet/sing v0.2.16-0.20231028125948-afcc9cb766c2
github.com/sagernet/sing-dns v0.1.10 github.com/sagernet/sing-dns v0.1.10
github.com/sagernet/sing-quic v0.1.3-0.20231026034240-fa3d997246b6 github.com/sagernet/sing-quic v0.1.3-0.20231026034240-fa3d997246b6
github.com/sagernet/sing-shadowsocks v0.2.5 github.com/sagernet/sing-shadowsocks v0.2.5
@ -40,6 +40,8 @@ require (
github.com/go-chi/render v1.0.3 // indirect github.com/go-chi/render v1.0.3 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect github.com/golang/protobuf v1.5.3 // indirect
github.com/google/btree v1.1.2 // indirect github.com/google/btree v1.1.2 // indirect
@ -75,13 +77,13 @@ require (
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect
github.com/sagernet/sing-mux v0.1.3 // indirect github.com/sagernet/sing-mux v0.1.3 // indirect
github.com/sagernet/sing-shadowtls v0.1.4 // indirect github.com/sagernet/sing-shadowtls v0.1.4 // indirect
github.com/sagernet/sing-tun v0.1.17-0.20231026060825-efd9884154a6 // indirect github.com/sagernet/sing-tun v0.1.17-0.20231030120513-2e85725657c1 // indirect
github.com/sagernet/sing-vmess v0.1.8 // indirect github.com/sagernet/sing-vmess v0.1.8 // indirect
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 // indirect github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 // indirect
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 // indirect github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 // indirect
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e // indirect
github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f // indirect github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f // indirect
github.com/sagernet/ws v0.0.0-20231030053741-7d481eb31bed // indirect
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect

View File

@ -43,6 +43,10 @@ github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M= github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
@ -127,8 +131,8 @@ github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byL
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/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/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk= github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
github.com/sagernet/sing v0.2.16-0.20231021090846-8002db54c028 h1:6GbQt7SC9y5Imrq5jDMbXDSaNiMhJ8KBjhjtQRuqQvE= github.com/sagernet/sing v0.2.16-0.20231028125948-afcc9cb766c2 h1:PW18IgRodvppd09d4mewYM3Hedu3PtFERN8yOqkTVk0=
github.com/sagernet/sing v0.2.16-0.20231021090846-8002db54c028/go.mod h1:AhNEHu0GXrpqkuzvTwvC8+j2cQUU/dh+zLEmq4C99pg= github.com/sagernet/sing v0.2.16-0.20231028125948-afcc9cb766c2/go.mod h1:AhNEHu0GXrpqkuzvTwvC8+j2cQUU/dh+zLEmq4C99pg=
github.com/sagernet/sing-dns v0.1.10 h1:iIU7nRBlUYj+fF2TaktGIvRiTFFrHwSMedLQsvlTZCI= github.com/sagernet/sing-dns v0.1.10 h1:iIU7nRBlUYj+fF2TaktGIvRiTFFrHwSMedLQsvlTZCI=
github.com/sagernet/sing-dns v0.1.10/go.mod h1:vtUimtf7Nq9EdvD5WTpfCr69KL1M7bcgOVKiYBiAY/c= github.com/sagernet/sing-dns v0.1.10/go.mod h1:vtUimtf7Nq9EdvD5WTpfCr69KL1M7bcgOVKiYBiAY/c=
github.com/sagernet/sing-mux v0.1.3 h1:fAf7PZa2A55mCeh0KKM02f1k2Y4vEmxuZZ/51ahkkLA= github.com/sagernet/sing-mux v0.1.3 h1:fAf7PZa2A55mCeh0KKM02f1k2Y4vEmxuZZ/51ahkkLA=
@ -141,8 +145,8 @@ github.com/sagernet/sing-shadowsocks2 v0.1.4 h1:vht2M8t3m5DTgXR2j24KbYOygG5aOp+M
github.com/sagernet/sing-shadowsocks2 v0.1.4/go.mod h1:Mgdee99NxxNd5Zld3ixIs18yVs4x2dI2VTDDE1N14Wc= github.com/sagernet/sing-shadowsocks2 v0.1.4/go.mod h1:Mgdee99NxxNd5Zld3ixIs18yVs4x2dI2VTDDE1N14Wc=
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
github.com/sagernet/sing-tun v0.1.17-0.20231026060825-efd9884154a6 h1:4yEXBqQoUgXj7qPSLD6lr+z9/KfsvixO9JUA2i5xnM8= github.com/sagernet/sing-tun v0.1.17-0.20231030120513-2e85725657c1 h1:QxC+myHDZ0BnkIEqXE0lWUzfYEVlhhQdSCo7mOMm7x4=
github.com/sagernet/sing-tun v0.1.17-0.20231026060825-efd9884154a6/go.mod h1:w2+S+uWE94E/pQWSDdDdMIjwAEb645kuGPunr6ZllUg= github.com/sagernet/sing-tun v0.1.17-0.20231030120513-2e85725657c1/go.mod h1:4ACZp3C6TDSy1rsMrfwtSyLrKPtm9Wm2eKHwhYIojbU=
github.com/sagernet/sing-vmess v0.1.8 h1:XVWad1RpTy9b5tPxdm5MCU8cGfrTGdR8qCq6HV2aCNc= github.com/sagernet/sing-vmess v0.1.8 h1:XVWad1RpTy9b5tPxdm5MCU8cGfrTGdR8qCq6HV2aCNc=
github.com/sagernet/sing-vmess v0.1.8/go.mod h1:vhx32UNzTDUkNwOyIjcZQohre1CaytquC5mPplId8uA= github.com/sagernet/sing-vmess v0.1.8/go.mod h1:vhx32UNzTDUkNwOyIjcZQohre1CaytquC5mPplId8uA=
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as= github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
@ -151,10 +155,10 @@ github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 h1:Px+hN4Vzgx+iCGV
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6/go.mod h1:zovq6vTvEM6ECiqE3Eeb9rpIylPpamPcmrJ9tv0Bt0M= github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6/go.mod h1:zovq6vTvEM6ECiqE3Eeb9rpIylPpamPcmrJ9tv0Bt0M=
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 h1:kDUqhc9Vsk5HJuhfIATJ8oQwBmpOZJuozQG7Vk88lL4= github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 h1:kDUqhc9Vsk5HJuhfIATJ8oQwBmpOZJuozQG7Vk88lL4=
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM= github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM=
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e h1:7uw2njHFGE+VpWamge6o56j2RWk4omF6uLKKxMmcWvs=
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e/go.mod h1:45TUl8+gH4SIKr4ykREbxKWTxkDlSzFENzctB1dVRRY=
github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f h1:Kvo8w8Y9lzFGB/7z09MJ3TR99TFtfI/IuY87Ygcycho= github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f h1:Kvo8w8Y9lzFGB/7z09MJ3TR99TFtfI/IuY87Ygcycho=
github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f/go.mod h1:mySs0abhpc/gLlvhoq7HP1RzOaRmIXVeZGCh++zoApk= github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f/go.mod h1:mySs0abhpc/gLlvhoq7HP1RzOaRmIXVeZGCh++zoApk=
github.com/sagernet/ws v0.0.0-20231030053741-7d481eb31bed h1:90a510OeE9siSJoYsI8nSjPmA+u5ROMDts/ZkdNsuXY=
github.com/sagernet/ws v0.0.0-20231030053741-7d481eb31bed/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA=
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg= github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg=
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s= github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s=
github.com/spyzhov/ajson v0.9.0 h1:tF46gJGOenYVj+k9K1U1XpCxVWhmiyY5PsVCAs1+OJ0= github.com/spyzhov/ajson v0.9.0 h1:tF46gJGOenYVj+k9K1U1XpCxVWhmiyY5PsVCAs1+OJ0=
@ -222,6 +226,7 @@ golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=

View File

@ -0,0 +1,16 @@
package main
import (
"testing"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
)
func TestV2RayHTTPUpgrade(t *testing.T) {
t.Run("self", func(t *testing.T) {
testV2RayTransportSelf(t, &option.V2RayTransportOptions{
Type: C.V2RayTransportTypeHTTPUpgrade,
})
})
}

View File

@ -8,6 +8,7 @@ import (
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/transport/v2rayhttp" "github.com/sagernet/sing-box/transport/v2rayhttp"
"github.com/sagernet/sing-box/transport/v2rayhttpupgrade"
"github.com/sagernet/sing-box/transport/v2raywebsocket" "github.com/sagernet/sing-box/transport/v2raywebsocket"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
@ -35,6 +36,8 @@ func NewServerTransport(ctx context.Context, options option.V2RayTransportOption
return NewQUICServer(ctx, options.QUICOptions, tlsConfig, handler) return NewQUICServer(ctx, options.QUICOptions, tlsConfig, handler)
case C.V2RayTransportTypeGRPC: case C.V2RayTransportTypeGRPC:
return NewGRPCServer(ctx, options.GRPCOptions, tlsConfig, handler) return NewGRPCServer(ctx, options.GRPCOptions, tlsConfig, handler)
case C.V2RayTransportTypeHTTPUpgrade:
return v2rayhttpupgrade.NewServer(ctx, options.HTTPUpgradeOptions, tlsConfig, handler)
default: default:
return nil, E.New("unknown transport type: " + options.Type) return nil, E.New("unknown transport type: " + options.Type)
} }
@ -50,13 +53,14 @@ func NewClientTransport(ctx context.Context, dialer N.Dialer, serverAddr M.Socks
case C.V2RayTransportTypeGRPC: case C.V2RayTransportTypeGRPC:
return NewGRPCClient(ctx, dialer, serverAddr, options.GRPCOptions, tlsConfig) return NewGRPCClient(ctx, dialer, serverAddr, options.GRPCOptions, tlsConfig)
case C.V2RayTransportTypeWebsocket: case C.V2RayTransportTypeWebsocket:
return v2raywebsocket.NewClient(ctx, dialer, serverAddr, options.WebsocketOptions, tlsConfig), nil return v2raywebsocket.NewClient(ctx, dialer, serverAddr, options.WebsocketOptions, tlsConfig)
case C.V2RayTransportTypeQUIC: case C.V2RayTransportTypeQUIC:
if tlsConfig == nil { if tlsConfig == nil {
return nil, C.ErrTLSRequired return nil, C.ErrTLSRequired
} }
return NewQUICClient(ctx, dialer, serverAddr, options.QUICOptions, tlsConfig) return NewQUICClient(ctx, dialer, serverAddr, options.QUICOptions, tlsConfig)
case C.V2RayTransportTypeHTTPUpgrade:
return v2rayhttpupgrade.NewClient(ctx, dialer, serverAddr, options.HTTPUpgradeOptions, tlsConfig)
default: default:
return nil, E.New("unknown transport type: " + options.Type) return nil, E.New("unknown transport type: " + options.Type)
} }

View File

@ -100,7 +100,7 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
conn.setup(nil, err) conn.setup(nil, err)
} else if response.StatusCode != 200 { } else if response.StatusCode != 200 {
response.Body.Close() response.Body.Close()
conn.setup(nil, E.New("unexpected status: ", response.StatusCode, " ", response.Status)) conn.setup(nil, E.New("unexpected status: ", response.Status))
} else { } else {
conn.setup(response.Body, nil) conn.setup(response.Body, nil)
} }

View File

@ -35,10 +35,6 @@ type Server struct {
path string 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) { func NewServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) {
server := &Server{ server := &Server{
tlsConfig: tlsConfig, tlsConfig: tlsConfig,
@ -92,6 +88,10 @@ func (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Reques
s.handler.NewError(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr)) s.handler.NewError(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr))
} }
func (s *Server) Network() []string {
return []string{N.NetworkTCP}
}
func (s *Server) Serve(listener net.Listener) error { func (s *Server) Serve(listener net.Listener) error {
if s.tlsConfig != nil { if s.tlsConfig != nil {
if !common.Contains(s.tlsConfig.NextProtos(), http2.NextProtoTLS) { if !common.Contains(s.tlsConfig.NextProtos(), http2.NextProtoTLS) {

View File

@ -81,7 +81,7 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
uri.Path = options.Path uri.Path = options.Path
err := sHTTP.URLSetPath(&uri, options.Path) err := sHTTP.URLSetPath(&uri, options.Path)
if err != nil { if err != nil {
return nil, E.New("failed to set path: " + err.Error()) return nil, E.New("parse path: " + err.Error())
} }
client.url = &uri client.url = &uri
return client, nil return client, nil
@ -143,7 +143,7 @@ func (c *Client) dialHTTP2(ctx context.Context) (net.Conn, error) {
conn.Setup(nil, err) conn.Setup(nil, err)
} else if response.StatusCode != 200 { } else if response.StatusCode != 200 {
response.Body.Close() response.Body.Close()
conn.Setup(nil, E.New("unexpected status: ", response.StatusCode, " ", response.Status)) conn.Setup(nil, E.New("unexpected status: ", response.Status))
} else { } else {
conn.Setup(response.Body, nil) conn.Setup(response.Body, nil)
} }

View File

@ -40,10 +40,6 @@ type Server struct {
headers http.Header 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) { func NewServer(ctx context.Context, options option.V2RayHTTPOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) {
server := &Server{ server := &Server{
ctx: ctx, ctx: ctx,
@ -153,6 +149,10 @@ func (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Reques
s.handler.NewError(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr)) s.handler.NewError(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr))
} }
func (s *Server) Network() []string {
return []string{N.NetworkTCP}
}
func (s *Server) Serve(listener net.Listener) error { func (s *Server) Serve(listener net.Listener) error {
if s.tlsConfig != nil { if s.tlsConfig != nil {
if len(s.tlsConfig.NextProtos()) == 0 { if len(s.tlsConfig.NextProtos()) == 0 {

View File

@ -0,0 +1,118 @@
package v2rayhttpupgrade
import (
std_bufio "bufio"
"context"
"net"
"net/http"
"net/url"
"strings"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/tls"
"github.com/sagernet/sing-box/option"
"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"
)
var _ adapter.V2RayClientTransport = (*Client)(nil)
type Client struct {
dialer N.Dialer
tlsConfig tls.Config
serverAddr M.Socksaddr
requestURL url.URL
headers http.Header
host string
}
func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayHTTPUpgradeOptions, tlsConfig tls.Config) (*Client, error) {
if tlsConfig != nil {
if len(tlsConfig.NextProtos()) == 0 {
tlsConfig.SetNextProtos([]string{"http/1.1"})
}
}
var host string
if options.Host != "" {
host = options.Host
} else if tlsConfig != nil && tlsConfig.ServerName() != "" {
host = tlsConfig.ServerName()
} else {
host = serverAddr.String()
}
var requestURL url.URL
if tlsConfig == nil {
requestURL.Scheme = "http"
} else {
requestURL.Scheme = "https"
}
requestURL.Host = serverAddr.String()
requestURL.Path = options.Path
err := sHTTP.URLSetPath(&requestURL, options.Path)
if err != nil {
return nil, E.Cause(err, "parse path")
}
if !strings.HasPrefix(requestURL.Path, "/") {
requestURL.Path = "/" + requestURL.Path
}
headers := make(http.Header)
for key, value := range options.Headers {
headers[key] = value
}
return &Client{
dialer: dialer,
tlsConfig: tlsConfig,
serverAddr: serverAddr,
requestURL: requestURL,
headers: headers,
host: host,
}, nil
}
func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
conn, err := c.dialer.DialContext(ctx, N.NetworkTCP, c.serverAddr)
if err != nil {
return nil, err
}
if c.tlsConfig != nil {
conn, err = tls.ClientHandshake(ctx, conn, c.tlsConfig)
if err != nil {
return nil, err
}
}
request := &http.Request{
Method: http.MethodGet,
URL: &c.requestURL,
Header: c.headers.Clone(),
Host: c.host,
}
request.Header.Set("Connection", "Upgrade")
request.Header.Set("Upgrade", "websocket")
err = request.Write(conn)
if err != nil {
return nil, err
}
bufReader := std_bufio.NewReader(conn)
response, err := http.ReadResponse(bufReader, request)
if err != nil {
return nil, err
}
if response.StatusCode != 101 ||
!strings.EqualFold(response.Header.Get("Connection"), "upgrade") ||
!strings.EqualFold(response.Header.Get("Upgrade"), "websocket") {
return nil, E.New("unexpected status: ", response.Status)
}
if bufReader.Buffered() > 0 {
buffer := buf.NewSize(bufReader.Buffered())
_, err = buffer.ReadFullFrom(bufReader, buffer.Len())
if err != nil {
return nil, err
}
conn = bufio.NewCachedConn(conn, buffer)
}
return conn, nil
}

View File

@ -0,0 +1,139 @@
package v2rayhttpupgrade
import (
"context"
"net"
"net/http"
"os"
"strings"
"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"
aTLS "github.com/sagernet/sing/common/tls"
sHttp "github.com/sagernet/sing/protocol/http"
)
var _ adapter.V2RayServerTransport = (*Server)(nil)
type Server struct {
ctx context.Context
tlsConfig tls.ServerConfig
handler adapter.V2RayServerTransportHandler
httpServer *http.Server
host string
path string
headers http.Header
}
func NewServer(ctx context.Context, options option.V2RayHTTPUpgradeOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) {
server := &Server{
ctx: ctx,
tlsConfig: tlsConfig,
handler: handler,
host: options.Host,
path: options.Path,
headers: options.Headers.Build(),
}
if !strings.HasPrefix(server.path, "/") {
server.path = "/" + server.path
}
server.httpServer = &http.Server{
Handler: server,
ReadHeaderTimeout: C.TCPTimeout,
MaxHeaderBytes: http.DefaultMaxHeaderBytes,
BaseContext: func(net.Listener) context.Context {
return ctx
},
TLSNextProto: make(map[string]func(*http.Server, *tls.STDConn, http.Handler)),
}
return server, nil
}
type httpFlusher interface {
FlushError() error
}
func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
host := request.Host
if len(s.host) > 0 && host != s.host {
s.invalidRequest(writer, request, http.StatusBadRequest, E.New("bad host: ", host))
return
}
if !strings.HasPrefix(request.URL.Path, s.path) {
s.invalidRequest(writer, request, http.StatusNotFound, E.New("bad path: ", request.URL.Path))
return
}
if request.Method != http.MethodGet {
s.invalidRequest(writer, request, http.StatusNotFound, E.New("bad method: ", request.Method))
return
}
if !strings.EqualFold(request.Header.Get("Connection"), "upgrade") {
s.invalidRequest(writer, request, http.StatusNotFound, E.New("not a upgrade request"))
return
}
if !strings.EqualFold(request.Header.Get("Upgrade"), "websocket") {
s.invalidRequest(writer, request, http.StatusNotFound, E.New("not a websocket request"))
return
}
if request.Header.Get("Sec-WebSocket-Key") != "" {
s.invalidRequest(writer, request, http.StatusNotFound, E.New("real websocket request received"))
return
}
writer.Header().Set("Connection", "upgrade")
writer.Header().Set("Upgrade", "websocket")
writer.WriteHeader(http.StatusSwitchingProtocols)
if flusher, isFlusher := writer.(httpFlusher); isFlusher {
err := flusher.FlushError()
if err != nil {
s.invalidRequest(writer, request, http.StatusInternalServerError, E.New("flush response"))
}
}
hijacker, canHijack := writer.(http.Hijacker)
if !canHijack {
s.invalidRequest(writer, request, http.StatusInternalServerError, E.New("invalid connection, maybe HTTP/2"))
return
}
conn, _, err := hijacker.Hijack()
if err != nil {
s.invalidRequest(writer, request, http.StatusInternalServerError, E.Cause(err, "hijack failed"))
return
}
var metadata M.Metadata
metadata.Source = sHttp.SourceAddress(request)
s.handler.NewConnection(request.Context(), conn, metadata)
}
func (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Request, statusCode int, err error) {
if statusCode > 0 {
writer.WriteHeader(statusCode)
}
s.handler.NewError(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr))
}
func (s *Server) Network() []string {
return []string{N.NetworkTCP}
}
func (s *Server) Serve(listener net.Listener) error {
if s.tlsConfig != nil {
if len(s.tlsConfig.NextProtos()) == 0 {
s.tlsConfig.SetNextProtos([]string{"http/1.1"})
}
listener = aTLS.NewListener(listener, s.tlsConfig)
}
return s.httpServer.Serve(listener)
}
func (s *Server) ServePacket(listener net.PacketConn) error {
return os.ErrInvalid
}
func (s *Server) Close() error {
return common.Close(common.PtrOrNil(s.httpServer))
}

View File

@ -12,6 +12,7 @@ import (
"github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
sHTTP "github.com/sagernet/sing/protocol/http" sHTTP "github.com/sagernet/sing/protocol/http"
@ -30,7 +31,7 @@ type Client struct {
earlyDataHeaderName string earlyDataHeaderName string
} }
func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayWebsocketOptions, tlsConfig tls.Config) adapter.V2RayClientTransport { func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayWebsocketOptions, tlsConfig tls.Config) (*Client, error) {
if tlsConfig != nil { if tlsConfig != nil {
if len(tlsConfig.NextProtos()) == 0 { if len(tlsConfig.NextProtos()) == 0 {
tlsConfig.SetNextProtos([]string{"http/1.1"}) tlsConfig.SetNextProtos([]string{"http/1.1"})
@ -46,7 +47,7 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
requestURL.Path = options.Path requestURL.Path = options.Path
err := sHTTP.URLSetPath(&requestURL, options.Path) err := sHTTP.URLSetPath(&requestURL, options.Path)
if err != nil { if err != nil {
return nil return nil, E.Cause(err, "parse path")
} }
if !strings.HasPrefix(requestURL.Path, "/") { if !strings.HasPrefix(requestURL.Path, "/") {
requestURL.Path = "/" + requestURL.Path requestURL.Path = "/" + requestURL.Path
@ -66,7 +67,7 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
headers, headers,
options.MaxEarlyData, options.MaxEarlyData,
options.EarlyDataHeaderName, options.EarlyDataHeaderName,
} }, nil
} }
func (c *Client) dialContext(ctx context.Context, requestURL *url.URL, headers http.Header) (*WebsocketConn, error) { func (c *Client) dialContext(ctx context.Context, requestURL *url.URL, headers http.Header) (*WebsocketConn, error) {