[feat](protocol): introduce stream-based DTLS tunneling and body size handling

- Designed a stream/frame-based protocol leveraging `StreamOpen`, `StreamData`, and `StreamClose` fields for chunked transmission.
- Addressed DTLS/UDP MTU limits by capping tunneled body sizes to 48 KiB and replacing oversized responses with `502 Bad Gateway`.
- Updated `internal/protocol` to enable safe handling of large HTTP bodies via streaming.
- Documented future work on replacing JSON with binary encoding for improved performance.
This commit is contained in:
dalbodeule
2025-12-03 01:34:34 +09:00
parent 499c346e79
commit c6b3632784
4 changed files with 114 additions and 26 deletions

View File

@@ -192,10 +192,45 @@ func (p *ClientProxy) forwardToLocal(ctx context.Context, preq *protocol.Request
for k, vs := range res.Header {
presp.Header[k] = append([]string(nil), vs...)
}
body, err := io.ReadAll(res.Body)
// DTLS over UDP has an upper bound on packet size (~64KiB). 전체 HTTP 바디를
// 하나의 JSON Envelope 로 감싸 전송하는 현재 설계에서는 바디가 너무 크면
// OS 레벨에서 "message too long" (EMSGSIZE) 가 발생할 수 있습니다. (ko)
//
// 이를 피하기 위해, 터널링 가능한 바디 크기에 상한을 두고, 이를 초과하는
// 응답은 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
limited := &io.LimitedReader{
R: res.Body,
N: maxTunnelBodyBytes + 1, // read up to limit+1 to detect overflow
}
body, err := io.ReadAll(limited)
if err != nil {
return fmt.Errorf("read http response body: %w", err)
}
if len(body) > maxTunnelBodyBytes {
// 응답 바디가 너무 커서 DTLS/UDP 로 안전하게 전송하기 어렵기 때문에,
// 원본 바디 대신 HopGate 에러 응답으로 대체합니다. (ko)
//
// The response body is too large to be safely tunneled over DTLS/UDP.
// Replace it with a HopGate error response instead of attempting to
// send an oversized datagram. (en)
presp.Status = http.StatusBadGateway
presp.Header = map[string][]string{
"Content-Type": {"text/plain; charset=utf-8"},
}
presp.Body = []byte("HopGate: response body too large for DTLS tunnel (over max_tunnel_body_bytes)")
presp.Error = "response body too large for DTLS tunnel"
return nil
}
presp.Body = body
return nil