diff --git a/docs/configuration/shared/v2ray-transport.md b/docs/configuration/shared/v2ray-transport.md index b078bac8..f8911fdb 100644 --- a/docs/configuration/shared/v2ray-transport.md +++ b/docs/configuration/shared/v2ray-transport.md @@ -53,9 +53,15 @@ The client will choose randomly and the server will verify if not empty. #### 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. -The server will verify if not empty. +The server will verify. #### method @@ -77,7 +83,10 @@ Specifies the time until idle clients should be closed with a GOAWAY frame. PING 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. @@ -85,7 +94,9 @@ Zero is used by default. 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 @@ -103,12 +114,14 @@ Specifies the timeout duration after sending a PING frame, within which a respon Path of HTTP request. -The server will verify if not empty. +The server will verify. #### headers Extra headers of HTTP request. +The server will write in response if not empty. + #### max_early_data Allowed payload size is in the request. Enabled if not zero. @@ -162,7 +175,8 @@ Service name of gRPC. 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: @@ -172,7 +186,8 @@ It has the same behavior as the corresponding setting in HTTP transport. 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: @@ -182,7 +197,9 @@ It has the same behavior as the corresponding setting in HTTP transport. 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. @@ -207,7 +224,7 @@ The server will verify if not empty. Path of HTTP request. -The server will verify if not empty. +The server will verify. #### headers diff --git a/docs/configuration/shared/v2ray-transport.zh.md b/docs/configuration/shared/v2ray-transport.zh.md index 2ea93562..91b740fa 100644 --- a/docs/configuration/shared/v2ray-transport.zh.md +++ b/docs/configuration/shared/v2ray-transport.zh.md @@ -48,25 +48,30 @@ V2Ray Transport 是 v2ray 发明的一组私有协议,并污染了其他协议 主机域名列表。 -客户端将随机选择,默认服务器将验证。 +如果设置,客户端将随机选择,服务器将验证。 #### path +!!! warning + + V2Ray 文档称服务端和客户端的路径必须一致,但实际代码允许客户端向路径添加任何后缀。 + sing-box 使用与 V2Ray 相同的行为,但请注意,该行为在 `WebSocket` 和 `HTTPUpgrade` 传输层中不存在。 + HTTP 请求路径 -默认服务器将验证。 +服务器将验证。 #### method HTTP 请求方法 -默认服务器将验证。 +如果设置,服务器将验证。 #### headers HTTP 请求的额外标头 -默认服务器将写入响应。 +如果设置,服务器将写入响应。 #### idle_timeout @@ -102,11 +107,13 @@ HTTP 请求的额外标头 HTTP 请求路径 -默认服务器将验证。 +服务器将验证。 #### headers -HTTP 请求的额外标头。 +HTTP 请求的额外标头 + +如果设置,服务器将写入响应。 #### max_early_data @@ -200,16 +207,16 @@ gRPC 服务名称。 主机域名。 -默认服务器将验证。 +服务器将验证。 #### path HTTP 请求路径 -默认服务器将验证。 +服务器将验证。 #### headers HTTP 请求的额外标头。 -默认服务器将写入响应。 +如果设置,服务器将写入响应。 diff --git a/transport/v2rayhttp/client.go b/transport/v2rayhttp/client.go index 44c135ef..4fa141cc 100644 --- a/transport/v2rayhttp/client.go +++ b/transport/v2rayhttp/client.go @@ -7,6 +7,7 @@ import ( "net" "net/http" "net/url" + "strings" "time" "github.com/sagernet/sing-box/adapter" @@ -28,7 +29,7 @@ type Client struct { serverAddr M.Socksaddr transport http.RoundTripper http2 bool - url *url.URL + requestURL url.URL host []string method string 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, dialer: dialer, serverAddr: serverAddr, + requestURL: requestURL, host: options.Host, method: options.Method, headers: options.Headers.Build(), transport: transport, http2: tlsConfig != 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 + }, nil } 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{ Method: c.method, - URL: c.url, + URL: &c.requestURL, Header: c.headers.Clone(), } switch hostLen := len(c.host); hostLen { @@ -123,7 +126,7 @@ func (c *Client) dialHTTP2(ctx context.Context) (net.Conn, error) { request := &http.Request{ Method: c.method, Body: pipeInReader, - URL: c.url, + URL: &c.requestURL, Header: c.headers.Clone(), } request = request.WithContext(ctx) diff --git a/transport/v2rayhttpupgrade/server.go b/transport/v2rayhttpupgrade/server.go index 653778f9..a3b5d23e 100644 --- a/transport/v2rayhttpupgrade/server.go +++ b/transport/v2rayhttpupgrade/server.go @@ -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)) 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)) return } diff --git a/transport/v2raywebsocket/server.go b/transport/v2raywebsocket/server.go index db078675..86f2de9c 100644 --- a/transport/v2raywebsocket/server.go +++ b/transport/v2raywebsocket/server.go @@ -33,6 +33,7 @@ type Server struct { path string maxEarlyData uint32 earlyDataHeaderName string + upgrader ws.HTTPUpgrader } 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, maxEarlyData: options.MaxEarlyData, earlyDataHeaderName: options.EarlyDataHeaderName, + upgrader: ws.HTTPUpgrader{ + Timeout: C.TCPTimeout, + Header: options.Headers.Build(), + }, } if !strings.HasPrefix(server.path, "/") { server.path = "/" + server.path @@ -79,6 +84,10 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) { return } } 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) if earlyDataStr != "" { earlyData, err = base64.RawURLEncoding.DecodeString(earlyDataStr)