mirror of
https://github.com/SagerNet/sing-box.git
synced 2024-11-25 10:01:30 +00:00
Fix V2Ray transport path
validation behavior
This commit is contained in:
parent
a0cab4f563
commit
082e3fb8df
|
@ -53,9 +53,15 @@ The client will choose randomly and the server will verify if not empty.
|
||||||
|
|
||||||
#### path
|
#### path
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
|
||||||
|
V2Ray's documentation says that the path between the server and the client must be consistent,
|
||||||
|
but the actual code allows the client to add any suffix to the path.
|
||||||
|
sing-box uses the same behavior as V2Ray, but note that the behavior does not exist in `WebSocket` and `HTTPUpgrade` transport.
|
||||||
|
|
||||||
Path of HTTP request.
|
Path of HTTP request.
|
||||||
|
|
||||||
The server will verify if not empty.
|
The server will verify.
|
||||||
|
|
||||||
#### method
|
#### method
|
||||||
|
|
||||||
|
@ -77,7 +83,10 @@ Specifies the time until idle clients should be closed with a GOAWAY frame. PING
|
||||||
|
|
||||||
In HTTP2 client:
|
In HTTP2 client:
|
||||||
|
|
||||||
Specifies the period of time after which a health check will be performed using a ping frame if no frames have been received on the connection. Please note that a ping response is considered a received frame, so if there is no other traffic on the connection, the health check will be executed every interval. If the value is zero, no health check will be performed.
|
Specifies the period of time after which a health check will be performed using a ping frame if no frames have been
|
||||||
|
received on the connection.Please note that a ping response is considered a received frame, so if there is no other
|
||||||
|
traffic on the connection, the health check will be executed every interval. If the value is zero, no health check will
|
||||||
|
be performed.
|
||||||
|
|
||||||
Zero is used by default.
|
Zero is used by default.
|
||||||
|
|
||||||
|
@ -85,7 +94,9 @@ Zero is used by default.
|
||||||
|
|
||||||
In HTTP2 client:
|
In HTTP2 client:
|
||||||
|
|
||||||
Specifies the timeout duration after sending a PING frame, within which a response must be received. If a response to the PING frame is not received within the specified timeout duration, the connection will be closed. The default timeout duration is 15 seconds.
|
Specifies the timeout duration after sending a PING frame, within which a response must be received.
|
||||||
|
If a response to the PING frame is not received within the specified timeout duration, the connection will be closed.
|
||||||
|
The default timeout duration is 15 seconds.
|
||||||
|
|
||||||
### WebSocket
|
### WebSocket
|
||||||
|
|
||||||
|
@ -103,12 +114,14 @@ Specifies the timeout duration after sending a PING frame, within which a respon
|
||||||
|
|
||||||
Path of HTTP request.
|
Path of HTTP request.
|
||||||
|
|
||||||
The server will verify if not empty.
|
The server will verify.
|
||||||
|
|
||||||
#### headers
|
#### headers
|
||||||
|
|
||||||
Extra headers of HTTP request.
|
Extra headers of HTTP request.
|
||||||
|
|
||||||
|
The server will write in response if not empty.
|
||||||
|
|
||||||
#### max_early_data
|
#### max_early_data
|
||||||
|
|
||||||
Allowed payload size is in the request. Enabled if not zero.
|
Allowed payload size is in the request. Enabled if not zero.
|
||||||
|
@ -162,7 +175,8 @@ Service name of gRPC.
|
||||||
|
|
||||||
In standard gRPC server/client:
|
In standard gRPC server/client:
|
||||||
|
|
||||||
If the transport doesn't see any activity after a duration of this time, it pings the client to check if the connection is still active.
|
If the transport doesn't see any activity after a duration of this time,
|
||||||
|
it pings the client to check if the connection is still active.
|
||||||
|
|
||||||
In default gRPC server/client:
|
In default gRPC server/client:
|
||||||
|
|
||||||
|
@ -172,7 +186,8 @@ It has the same behavior as the corresponding setting in HTTP transport.
|
||||||
|
|
||||||
In standard gRPC server/client:
|
In standard gRPC server/client:
|
||||||
|
|
||||||
The timeout that after performing a keepalive check, the client will wait for activity. If no activity is detected, the connection will be closed.
|
The timeout that after performing a keepalive check, the client will wait for activity.
|
||||||
|
If no activity is detected, the connection will be closed.
|
||||||
|
|
||||||
In default gRPC server/client:
|
In default gRPC server/client:
|
||||||
|
|
||||||
|
@ -182,7 +197,9 @@ It has the same behavior as the corresponding setting in HTTP transport.
|
||||||
|
|
||||||
In standard gRPC client:
|
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.
|
||||||
|
|
||||||
|
@ -207,7 +224,7 @@ The server will verify if not empty.
|
||||||
|
|
||||||
Path of HTTP request.
|
Path of HTTP request.
|
||||||
|
|
||||||
The server will verify if not empty.
|
The server will verify.
|
||||||
|
|
||||||
#### headers
|
#### headers
|
||||||
|
|
||||||
|
|
|
@ -48,25 +48,30 @@ V2Ray Transport 是 v2ray 发明的一组私有协议,并污染了其他协议
|
||||||
|
|
||||||
主机域名列表。
|
主机域名列表。
|
||||||
|
|
||||||
客户端将随机选择,默认服务器将验证。
|
如果设置,客户端将随机选择,服务器将验证。
|
||||||
|
|
||||||
#### path
|
#### path
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
|
||||||
|
V2Ray 文档称服务端和客户端的路径必须一致,但实际代码允许客户端向路径添加任何后缀。
|
||||||
|
sing-box 使用与 V2Ray 相同的行为,但请注意,该行为在 `WebSocket` 和 `HTTPUpgrade` 传输层中不存在。
|
||||||
|
|
||||||
HTTP 请求路径
|
HTTP 请求路径
|
||||||
|
|
||||||
默认服务器将验证。
|
服务器将验证。
|
||||||
|
|
||||||
#### method
|
#### method
|
||||||
|
|
||||||
HTTP 请求方法
|
HTTP 请求方法
|
||||||
|
|
||||||
默认服务器将验证。
|
如果设置,服务器将验证。
|
||||||
|
|
||||||
#### headers
|
#### headers
|
||||||
|
|
||||||
HTTP 请求的额外标头
|
HTTP 请求的额外标头
|
||||||
|
|
||||||
默认服务器将写入响应。
|
如果设置,服务器将写入响应。
|
||||||
|
|
||||||
#### idle_timeout
|
#### idle_timeout
|
||||||
|
|
||||||
|
@ -102,11 +107,13 @@ HTTP 请求的额外标头
|
||||||
|
|
||||||
HTTP 请求路径
|
HTTP 请求路径
|
||||||
|
|
||||||
默认服务器将验证。
|
服务器将验证。
|
||||||
|
|
||||||
#### headers
|
#### headers
|
||||||
|
|
||||||
HTTP 请求的额外标头。
|
HTTP 请求的额外标头
|
||||||
|
|
||||||
|
如果设置,服务器将写入响应。
|
||||||
|
|
||||||
#### max_early_data
|
#### max_early_data
|
||||||
|
|
||||||
|
@ -200,16 +207,16 @@ gRPC 服务名称。
|
||||||
|
|
||||||
主机域名。
|
主机域名。
|
||||||
|
|
||||||
默认服务器将验证。
|
服务器将验证。
|
||||||
|
|
||||||
#### path
|
#### path
|
||||||
|
|
||||||
HTTP 请求路径
|
HTTP 请求路径
|
||||||
|
|
||||||
默认服务器将验证。
|
服务器将验证。
|
||||||
|
|
||||||
#### headers
|
#### headers
|
||||||
|
|
||||||
HTTP 请求的额外标头。
|
HTTP 请求的额外标头。
|
||||||
|
|
||||||
默认服务器将写入响应。
|
如果设置,服务器将写入响应。
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
@ -28,7 +29,7 @@ type Client struct {
|
||||||
serverAddr M.Socksaddr
|
serverAddr M.Socksaddr
|
||||||
transport http.RoundTripper
|
transport http.RoundTripper
|
||||||
http2 bool
|
http2 bool
|
||||||
url *url.URL
|
requestURL url.URL
|
||||||
host []string
|
host []string
|
||||||
method string
|
method string
|
||||||
headers http.Header
|
headers http.Header
|
||||||
|
@ -58,33 +59,35 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
client := &Client{
|
if options.Method == "" {
|
||||||
|
options.Method = http.MethodPut
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
return &Client{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
dialer: dialer,
|
dialer: dialer,
|
||||||
serverAddr: serverAddr,
|
serverAddr: serverAddr,
|
||||||
|
requestURL: requestURL,
|
||||||
host: options.Host,
|
host: options.Host,
|
||||||
method: options.Method,
|
method: options.Method,
|
||||||
headers: options.Headers.Build(),
|
headers: options.Headers.Build(),
|
||||||
transport: transport,
|
transport: transport,
|
||||||
http2: tlsConfig != nil,
|
http2: tlsConfig != nil,
|
||||||
}
|
}, nil
|
||||||
if client.method == "" {
|
|
||||||
client.method = "PUT"
|
|
||||||
}
|
|
||||||
var uri url.URL
|
|
||||||
if tlsConfig == nil {
|
|
||||||
uri.Scheme = "http"
|
|
||||||
} else {
|
|
||||||
uri.Scheme = "https"
|
|
||||||
}
|
|
||||||
uri.Host = serverAddr.String()
|
|
||||||
uri.Path = options.Path
|
|
||||||
err := sHTTP.URLSetPath(&uri, options.Path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "parse path")
|
|
||||||
}
|
|
||||||
client.url = &uri
|
|
||||||
return client, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
|
func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
|
||||||
|
@ -103,7 +106,7 @@ func (c *Client) dialHTTP(ctx context.Context) (net.Conn, error) {
|
||||||
|
|
||||||
request := &http.Request{
|
request := &http.Request{
|
||||||
Method: c.method,
|
Method: c.method,
|
||||||
URL: c.url,
|
URL: &c.requestURL,
|
||||||
Header: c.headers.Clone(),
|
Header: c.headers.Clone(),
|
||||||
}
|
}
|
||||||
switch hostLen := len(c.host); hostLen {
|
switch hostLen := len(c.host); hostLen {
|
||||||
|
@ -123,7 +126,7 @@ func (c *Client) dialHTTP2(ctx context.Context) (net.Conn, error) {
|
||||||
request := &http.Request{
|
request := &http.Request{
|
||||||
Method: c.method,
|
Method: c.method,
|
||||||
Body: pipeInReader,
|
Body: pipeInReader,
|
||||||
URL: c.url,
|
URL: &c.requestURL,
|
||||||
Header: c.headers.Clone(),
|
Header: c.headers.Clone(),
|
||||||
}
|
}
|
||||||
request = request.WithContext(ctx)
|
request = request.WithContext(ctx)
|
||||||
|
|
|
@ -65,7 +65,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||||
s.invalidRequest(writer, request, http.StatusBadRequest, E.New("bad host: ", host))
|
s.invalidRequest(writer, request, http.StatusBadRequest, E.New("bad host: ", host))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !strings.HasPrefix(request.URL.Path, s.path) {
|
if request.URL.Path != s.path {
|
||||||
s.invalidRequest(writer, request, http.StatusNotFound, E.New("bad path: ", request.URL.Path))
|
s.invalidRequest(writer, request, http.StatusNotFound, E.New("bad path: ", request.URL.Path))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ type Server struct {
|
||||||
path string
|
path string
|
||||||
maxEarlyData uint32
|
maxEarlyData uint32
|
||||||
earlyDataHeaderName string
|
earlyDataHeaderName string
|
||||||
|
upgrader ws.HTTPUpgrader
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(ctx context.Context, options option.V2RayWebsocketOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) {
|
func NewServer(ctx context.Context, options option.V2RayWebsocketOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) {
|
||||||
|
@ -43,6 +44,10 @@ func NewServer(ctx context.Context, options option.V2RayWebsocketOptions, tlsCon
|
||||||
path: options.Path,
|
path: options.Path,
|
||||||
maxEarlyData: options.MaxEarlyData,
|
maxEarlyData: options.MaxEarlyData,
|
||||||
earlyDataHeaderName: options.EarlyDataHeaderName,
|
earlyDataHeaderName: options.EarlyDataHeaderName,
|
||||||
|
upgrader: ws.HTTPUpgrader{
|
||||||
|
Timeout: C.TCPTimeout,
|
||||||
|
Header: options.Headers.Build(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
if !strings.HasPrefix(server.path, "/") {
|
if !strings.HasPrefix(server.path, "/") {
|
||||||
server.path = "/" + server.path
|
server.path = "/" + server.path
|
||||||
|
@ -79,6 +84,10 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if request.URL.Path != s.path {
|
||||||
|
s.invalidRequest(writer, request, http.StatusNotFound, E.New("bad path: ", request.URL.Path))
|
||||||
|
return
|
||||||
|
}
|
||||||
earlyDataStr := request.Header.Get(s.earlyDataHeaderName)
|
earlyDataStr := request.Header.Get(s.earlyDataHeaderName)
|
||||||
if earlyDataStr != "" {
|
if earlyDataStr != "" {
|
||||||
earlyData, err = base64.RawURLEncoding.DecodeString(earlyDataStr)
|
earlyData, err = base64.RawURLEncoding.DecodeString(earlyDataStr)
|
||||||
|
|
Loading…
Reference in a new issue