From 4f6f12616ccf0a715397b1dfcec4ce1c996fb96f Mon Sep 17 00:00:00 2001 From: hr567 Date: Fri, 29 Nov 2024 10:08:08 +0800 Subject: [PATCH] WebSocket config: Add `heartbeatPeriod` for client & server (#4065) https://github.com/XTLS/Xray-core/pull/4065#issuecomment-2502627154 --------- Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com> --- infra/conf/transport_internet.go | 2 + .../internet/splithttp/browser_client.go | 2 +- transport/internet/websocket/config.pb.go | 41 ++++++++++++------- transport/internet/websocket/config.proto | 1 + transport/internet/websocket/connection.go | 13 +++++- transport/internet/websocket/dialer.go | 4 +- transport/internet/websocket/hub.go | 2 +- 7 files changed, 45 insertions(+), 20 deletions(-) diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index c1b06ea7..428ba353 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -149,6 +149,7 @@ type WebSocketConfig struct { Path string `json:"path"` Headers map[string]string `json:"headers"` AcceptProxyProtocol bool `json:"acceptProxyProtocol"` + HeartbeatPeriod uint32 `json:"heartbeatPeriod"` } // Build implements Buildable. @@ -178,6 +179,7 @@ func (c *WebSocketConfig) Build() (proto.Message, error) { Header: c.Headers, AcceptProxyProtocol: c.AcceptProxyProtocol, Ed: ed, + HeartbeatPeriod: c.HeartbeatPeriod, } return config, nil } diff --git a/transport/internet/splithttp/browser_client.go b/transport/internet/splithttp/browser_client.go index d56ba139..a0a584a9 100644 --- a/transport/internet/splithttp/browser_client.go +++ b/transport/internet/splithttp/browser_client.go @@ -29,7 +29,7 @@ func (c *BrowserDialerClient) OpenDownload(ctx context.Context, baseURL string) return nil, dummyAddr, dummyAddr, err } - return websocket.NewConnection(conn, dummyAddr, nil), conn.RemoteAddr(), conn.LocalAddr(), nil + return websocket.NewConnection(conn, dummyAddr, nil, 0), conn.RemoteAddr(), conn.LocalAddr(), nil } func (c *BrowserDialerClient) SendUploadRequest(ctx context.Context, url string, payload io.ReadWriteCloser, contentLength int64) error { diff --git a/transport/internet/websocket/config.pb.go b/transport/internet/websocket/config.pb.go index 518cf6e8..ff6a3868 100644 --- a/transport/internet/websocket/config.pb.go +++ b/transport/internet/websocket/config.pb.go @@ -30,6 +30,7 @@ type Config struct { Header map[string]string `protobuf:"bytes,3,rep,name=header,proto3" json:"header,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` AcceptProxyProtocol bool `protobuf:"varint,4,opt,name=accept_proxy_protocol,json=acceptProxyProtocol,proto3" json:"accept_proxy_protocol,omitempty"` Ed uint32 `protobuf:"varint,5,opt,name=ed,proto3" json:"ed,omitempty"` + HeartbeatPeriod uint32 `protobuf:"varint,6,opt,name=heartbeatPeriod,proto3" json:"heartbeatPeriod,omitempty"` } func (x *Config) Reset() { @@ -97,6 +98,13 @@ func (x *Config) GetEd() uint32 { return 0 } +func (x *Config) GetHeartbeatPeriod() uint32 { + if x != nil { + return x.HeartbeatPeriod + } + return 0 +} + var File_transport_internet_websocket_config_proto protoreflect.FileDescriptor var file_transport_internet_websocket_config_proto_rawDesc = []byte{ @@ -104,8 +112,8 @@ var file_transport_internet_websocket_config_proto_rawDesc = []byte{ 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x77, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x21, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x77, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x22, 0xfe, - 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, + 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x77, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x22, 0xa8, + 0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x4d, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x03, 0x20, 0x03, 0x28, @@ -117,19 +125,22 @@ var file_transport_internet_websocket_config_proto_rawDesc = []byte{ 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x0e, 0x0a, 0x02, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x02, 0x65, 0x64, 0x1a, 0x39, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, - 0x85, 0x01, 0x0a, 0x25, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, - 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, - 0x77, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x50, 0x01, 0x5a, 0x36, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, - 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, - 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x77, 0x65, 0x62, 0x73, 0x6f, 0x63, - 0x6b, 0x65, 0x74, 0xaa, 0x02, 0x21, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, - 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x57, 0x65, - 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x52, 0x02, 0x65, 0x64, 0x12, 0x28, 0x0a, 0x0f, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, + 0x74, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0f, 0x68, + 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x1a, 0x39, + 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x85, 0x01, 0x0a, 0x25, 0x63, 0x6f, + 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, + 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x77, 0x65, 0x62, 0x73, 0x6f, 0x63, + 0x6b, 0x65, 0x74, 0x50, 0x01, 0x5a, 0x36, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, + 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, + 0x6e, 0x65, 0x74, 0x2f, 0x77, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0xaa, 0x02, 0x21, + 0x58, 0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, + 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, + 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/transport/internet/websocket/config.proto b/transport/internet/websocket/config.proto index 6c6ce7dd..0ae73075 100644 --- a/transport/internet/websocket/config.proto +++ b/transport/internet/websocket/config.proto @@ -12,4 +12,5 @@ message Config { map header = 3; bool accept_proxy_protocol = 4; uint32 ed = 5; + uint32 heartbeatPeriod = 6; } diff --git a/transport/internet/websocket/connection.go b/transport/internet/websocket/connection.go index 3ccead47..26082fc6 100644 --- a/transport/internet/websocket/connection.go +++ b/transport/internet/websocket/connection.go @@ -22,7 +22,18 @@ type connection struct { remoteAddr net.Addr } -func NewConnection(conn *websocket.Conn, remoteAddr net.Addr, extraReader io.Reader) *connection { +func NewConnection(conn *websocket.Conn, remoteAddr net.Addr, extraReader io.Reader, heartbeatPeriod uint32) *connection { + if heartbeatPeriod != 0 { + go func() { + for { + time.Sleep(time.Duration(heartbeatPeriod) * time.Second) + if err := conn.WriteControl(websocket.PingMessage, []byte{}, time.Time{}); err != nil { + break + } + } + }() + } + return &connection{ conn: conn, remoteAddr: remoteAddr, diff --git a/transport/internet/websocket/dialer.go b/transport/internet/websocket/dialer.go index aa15c1a6..0659c7de 100644 --- a/transport/internet/websocket/dialer.go +++ b/transport/internet/websocket/dialer.go @@ -99,7 +99,7 @@ func dialWebSocket(ctx context.Context, dest net.Destination, streamSettings *in return nil, err } - return NewConnection(conn, conn.RemoteAddr(), nil), nil + return NewConnection(conn, conn.RemoteAddr(), nil, wsSettings.HeartbeatPeriod), nil } header := wsSettings.GetRequestHeader() @@ -117,7 +117,7 @@ func dialWebSocket(ctx context.Context, dest net.Destination, streamSettings *in return nil, errors.New("failed to dial to (", uri, "): ", reason).Base(err) } - return NewConnection(conn, conn.RemoteAddr(), nil), nil + return NewConnection(conn, conn.RemoteAddr(), nil, wsSettings.HeartbeatPeriod), nil } type delayDialConn struct { diff --git a/transport/internet/websocket/hub.go b/transport/internet/websocket/hub.go index 6e07d9a0..feefe2af 100644 --- a/transport/internet/websocket/hub.go +++ b/transport/internet/websocket/hub.go @@ -73,7 +73,7 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req } } - h.ln.addConn(NewConnection(conn, remoteAddr, extraReader)) + h.ln.addConn(NewConnection(conn, remoteAddr, extraReader, h.ln.config.HeartbeatPeriod)) } type Listener struct {