Add TCP CONNECT support to MASQUE (RFC 9114)

Extend MASQUE implementation to support TCP tunneling via standard HTTP/3
CONNECT method, in addition to existing UDP support via CONNECT-UDP:

- Add StreamConn type for TCP data transfer over HTTP/3 stream body
- Update handler to dispatch based on :protocol pseudo-header:
  - "connect-udp" for UDP (RFC 9298)
  - Empty/"HTTP/3.0" for TCP (RFC 9114)
- Add handleConnectTCP() using bidirectional stream relay
- Update connector to support both TCP and UDP networks
- Add connectTCP() for standard CONNECT requests

TCP uses :authority for target address and stream body for data.
UDP uses path template and HTTP/3 datagrams for data.
This commit is contained in:
David Manouchehri
2025-12-28 23:18:41 +00:00
parent 1752b29df9
commit c5f91232cb
3 changed files with 382 additions and 25 deletions
+102
View File
@@ -174,3 +174,105 @@ var (
_ net.Conn = (*DatagramConn)(nil)
_ net.PacketConn = (*DatagramConn)(nil)
)
// StreamReadWriter is an interface for reading and writing to HTTP/3 streams.
// Both http3.Stream and http3.RequestStream implement this interface.
type StreamReadWriter interface {
io.Reader
io.Writer
io.Closer
SetDeadline(t time.Time) error
SetReadDeadline(t time.Time) error
SetWriteDeadline(t time.Time) error
}
// StreamConn wraps an HTTP/3 stream as net.Conn for TCP tunneling (RFC 9114).
// Unlike DatagramConn which uses HTTP/3 datagrams, StreamConn reads/writes
// directly to the HTTP/3 stream body for reliable, ordered byte streams.
type StreamConn struct {
stream StreamReadWriter
localAddr net.Addr
remoteAddr net.Addr
closed chan struct{}
closeOnce sync.Once
}
// NewStreamConn creates a new StreamConn wrapping an HTTP/3 stream (server-side).
// This is used for TCP CONNECT tunneling where data flows through the stream body.
func NewStreamConn(stream *http3.Stream, laddr, raddr net.Addr) *StreamConn {
return &StreamConn{
stream: stream,
localAddr: laddr,
remoteAddr: raddr,
closed: make(chan struct{}),
}
}
// NewStreamConnFromRequestStream creates a new StreamConn wrapping an HTTP/3 request stream (client-side).
// This is used for TCP CONNECT tunneling where data flows through the stream body.
func NewStreamConnFromRequestStream(stream *http3.RequestStream, laddr, raddr net.Addr) *StreamConn {
return &StreamConn{
stream: stream,
localAddr: laddr,
remoteAddr: raddr,
closed: make(chan struct{}),
}
}
// Read reads data from the HTTP/3 stream body.
func (c *StreamConn) Read(b []byte) (n int, err error) {
select {
case <-c.closed:
return 0, net.ErrClosed
default:
}
return c.stream.Read(b)
}
// Write writes data to the HTTP/3 stream body.
func (c *StreamConn) Write(b []byte) (n int, err error) {
select {
case <-c.closed:
return 0, net.ErrClosed
default:
}
return c.stream.Write(b)
}
// Close closes the stream connection.
func (c *StreamConn) Close() error {
var err error
c.closeOnce.Do(func() {
close(c.closed)
err = c.stream.Close()
})
return err
}
// LocalAddr returns the local network address.
func (c *StreamConn) LocalAddr() net.Addr {
return c.localAddr
}
// RemoteAddr returns the remote network address.
func (c *StreamConn) RemoteAddr() net.Addr {
return c.remoteAddr
}
// SetDeadline sets the read and write deadlines.
func (c *StreamConn) SetDeadline(t time.Time) error {
return c.stream.SetDeadline(t)
}
// SetReadDeadline sets the read deadline.
func (c *StreamConn) SetReadDeadline(t time.Time) error {
return c.stream.SetReadDeadline(t)
}
// SetWriteDeadline sets the write deadline.
func (c *StreamConn) SetWriteDeadline(t time.Time) error {
return c.stream.SetWriteDeadline(t)
}
// Ensure StreamConn implements net.Conn
var _ net.Conn = (*StreamConn)(nil)