From 798ad75e39c94c0eed431b1043ee1333cce51986 Mon Sep 17 00:00:00 2001 From: dalbodeule <11470513+dalbodeule@users.noreply.github.com> Date: Mon, 8 Dec 2025 22:38:34 +0900 Subject: [PATCH] [feat](protocol): enforce 4KiB hard limit on Protobuf body and stream payloads - Added safeguards to restrict HTTP body and stream payload sizes to 4KiB (`StreamChunkSize`) in the Protobuf codec. - Updated client logic to apply consistent limits for streaming and non-streaming scenarios. - Improved error handling with clear messages for oversized payloads. --- internal/protocol/codec.go | 18 ++++++++++++++++++ internal/proxy/client.go | 23 +++++++++++++++-------- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/internal/protocol/codec.go b/internal/protocol/codec.go index c982614..dcbdfdf 100644 --- a/internal/protocol/codec.go +++ b/internal/protocol/codec.go @@ -57,6 +57,24 @@ func (protobufCodec) Encode(w io.Writer, env *Envelope) error { if err != nil { return err } + + // Body/stream payload 하드 리밋: 4KiB (StreamChunkSize). + // HTTP 단일 Envelope 및 스트림 기반 프레임 모두에서 payload 가 이 값을 넘지 않도록 강제합니다. + // Enforce a 4KiB hard limit (StreamChunkSize) for HTTP bodies and stream payloads. + switch env.Type { + case MessageTypeHTTP: + if env.HTTPRequest != nil && len(env.HTTPRequest.Body) > int(StreamChunkSize) { + return fmt.Errorf("protobuf codec: http request body too large: %d bytes (max %d)", len(env.HTTPRequest.Body), StreamChunkSize) + } + if env.HTTPResponse != nil && len(env.HTTPResponse.Body) > int(StreamChunkSize) { + return fmt.Errorf("protobuf codec: http response body too large: %d bytes (max %d)", len(env.HTTPResponse.Body), StreamChunkSize) + } + case MessageTypeStreamData: + if env.StreamData != nil && len(env.StreamData.Data) > int(StreamChunkSize) { + return fmt.Errorf("protobuf codec: stream data payload too large: %d bytes (max %d)", len(env.StreamData.Data), StreamChunkSize) + } + } + data, err := proto.Marshal(pbEnv) if err != nil { return fmt.Errorf("protobuf marshal envelope: %w", err) diff --git a/internal/proxy/client.go b/internal/proxy/client.go index f7215a6..e6308df 100644 --- a/internal/proxy/client.go +++ b/internal/proxy/client.go @@ -192,18 +192,25 @@ func (p *ClientProxy) forwardToLocal(ctx context.Context, preq *protocol.Request } // DTLS over UDP has an upper bound on packet size (~64KiB). 전체 HTTP 바디를 - // 하나의 JSON Envelope 로 감싸 전송하는 현재 설계에서는 바디가 너무 크면 + // 하나의 Envelope 로 감싸 전송하는 현재 설계에서는, 바디가 너무 크면 // OS 레벨에서 "message too long" (EMSGSIZE) 가 발생할 수 있습니다. (ko) // - // 이를 피하기 위해, 터널링 가능한 바디 크기에 상한을 두고, 이를 초과하는 - // 응답은 502 Bad Gateway + HopGate 전용 에러 메시지로 대체합니다. (ko) + // 이를 피하기 위해, 터널링 가능한 **단일 HTTP 바디** 크기에 상한을 두고, + // 이를 초과하는 응답은 502 Bad Gateway + HopGate 전용 에러 메시지로 대체합니다. (ko) // // DTLS over UDP has an upper bound on datagram size (~64KiB). With the current - // design (wrapping the entire HTTP body into a single JSON envelope), very - // large bodies can trigger "message too long" (EMSGSIZE) at the OS level. - // To avoid this, we cap the tunneled body size and replace oversized responses - // with a 502 Bad Gateway + HopGate-specific error body. (en) - const maxTunnelBodyBytes = 48 * 1024 // 48KiB, conservative under UDP limits + // single-envelope design, very large bodies can still trigger "message too long" + // (EMSGSIZE) at the OS level. To avoid this, we cap the tunneled HTTP body size + // and replace oversized responses with a 502 Bad Gateway + HopGate-specific + // error body. (en) + // + // Protobuf 기반 터널링에서는 향후 StreamData(4KiB) 단위로 나누어 전송할 예정이지만, + // 그 전 단계에서도 body 자체를 4KiB( StreamChunkSize )로 하드 리밋하여 + // Proto message body 필드가 지나치게 커지지 않도록 합니다. (ko) + // + // Even before full stream tunneling is implemented, we hard-limit the protobuf + // body field to 4KiB (StreamChunkSize) so that individual messages remain small. (en) + const maxTunnelBodyBytes = protocol.StreamChunkSize limited := &io.LimitedReader{ R: res.Body,