c5f91232cb
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.
279 lines
7.2 KiB
Go
279 lines
7.2 KiB
Go
package masque
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"net"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/quic-go/quic-go/http3"
|
|
)
|
|
|
|
// DatagramStreamer is an interface for sending and receiving HTTP/3 datagrams.
|
|
// Both http3.Stream and http3.RequestStream implement this interface.
|
|
type DatagramStreamer interface {
|
|
SendDatagram(b []byte) error
|
|
ReceiveDatagram(ctx context.Context) ([]byte, error)
|
|
}
|
|
|
|
// DatagramConn wraps an HTTP/3 stream's datagram methods as net.PacketConn.
|
|
// This allows UDP packets to be tunneled over HTTP/3 datagrams per RFC 9297/9298.
|
|
type DatagramConn struct {
|
|
stream DatagramStreamer
|
|
closer io.Closer // Optional closer for the underlying stream
|
|
localAddr net.Addr
|
|
remoteAddr net.Addr
|
|
closed chan struct{}
|
|
closeOnce sync.Once
|
|
|
|
mu sync.RWMutex
|
|
readDeadline time.Time
|
|
}
|
|
|
|
// NewDatagramConn creates a new DatagramConn wrapping an HTTP/3 stream.
|
|
func NewDatagramConn(stream *http3.Stream, laddr, raddr net.Addr) *DatagramConn {
|
|
return &DatagramConn{
|
|
stream: stream,
|
|
localAddr: laddr,
|
|
remoteAddr: raddr,
|
|
closed: make(chan struct{}),
|
|
}
|
|
}
|
|
|
|
// NewDatagramConnFromRequestStream creates a new DatagramConn wrapping an HTTP/3 request stream.
|
|
// This is used by the client-side connector. The stream will be closed when Close() is called.
|
|
func NewDatagramConnFromRequestStream(stream *http3.RequestStream, laddr, raddr net.Addr) *DatagramConn {
|
|
return &DatagramConn{
|
|
stream: stream,
|
|
closer: stream, // RequestStream implements io.Closer
|
|
localAddr: laddr,
|
|
remoteAddr: raddr,
|
|
closed: make(chan struct{}),
|
|
}
|
|
}
|
|
|
|
// ReadFrom reads a UDP datagram from the HTTP/3 stream.
|
|
// Per RFC 9297, HTTP datagrams have a context ID prefix.
|
|
// For CONNECT-UDP (RFC 9298), the context ID is 0.
|
|
func (c *DatagramConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
|
|
select {
|
|
case <-c.closed:
|
|
return 0, nil, net.ErrClosed
|
|
default:
|
|
}
|
|
|
|
ctx := context.Background()
|
|
c.mu.RLock()
|
|
deadline := c.readDeadline
|
|
c.mu.RUnlock()
|
|
|
|
if !deadline.IsZero() {
|
|
var cancel context.CancelFunc
|
|
ctx, cancel = context.WithDeadline(ctx, deadline)
|
|
defer cancel()
|
|
}
|
|
|
|
data, err := c.stream.ReceiveDatagram(ctx)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
|
|
if len(data) == 0 {
|
|
return 0, c.remoteAddr, nil
|
|
}
|
|
|
|
// Per RFC 9297: datagram format is context-id (varint) + payload
|
|
// For CONNECT-UDP with context ID 0, the first byte is 0x00
|
|
// We strip the context ID prefix
|
|
if data[0] == 0x00 {
|
|
data = data[1:]
|
|
}
|
|
|
|
n = copy(b, data)
|
|
return n, c.remoteAddr, nil
|
|
}
|
|
|
|
// WriteTo sends a UDP datagram via the HTTP/3 stream.
|
|
// Per RFC 9297, we prepend the context ID (0x00 for CONNECT-UDP).
|
|
func (c *DatagramConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
|
select {
|
|
case <-c.closed:
|
|
return 0, net.ErrClosed
|
|
default:
|
|
}
|
|
|
|
// Prepend context ID (0x00 for context ID 0)
|
|
datagram := make([]byte, 1+len(b))
|
|
datagram[0] = 0x00 // Context ID = 0
|
|
copy(datagram[1:], b)
|
|
|
|
if err := c.stream.SendDatagram(datagram); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return len(b), nil
|
|
}
|
|
|
|
// Read reads data from the connection (net.Conn interface).
|
|
func (c *DatagramConn) Read(b []byte) (n int, err error) {
|
|
n, _, err = c.ReadFrom(b)
|
|
return
|
|
}
|
|
|
|
// Write writes data to the connection (net.Conn interface).
|
|
func (c *DatagramConn) Write(b []byte) (n int, err error) {
|
|
return c.WriteTo(b, c.remoteAddr)
|
|
}
|
|
|
|
// Close closes the datagram connection and the underlying stream.
|
|
func (c *DatagramConn) Close() error {
|
|
var err error
|
|
c.closeOnce.Do(func() {
|
|
close(c.closed)
|
|
if c.closer != nil {
|
|
err = c.closer.Close()
|
|
}
|
|
})
|
|
return err
|
|
}
|
|
|
|
// LocalAddr returns the local network address.
|
|
func (c *DatagramConn) LocalAddr() net.Addr {
|
|
return c.localAddr
|
|
}
|
|
|
|
// RemoteAddr returns the remote network address.
|
|
func (c *DatagramConn) RemoteAddr() net.Addr {
|
|
return c.remoteAddr
|
|
}
|
|
|
|
// SetDeadline sets the read and write deadlines.
|
|
func (c *DatagramConn) SetDeadline(t time.Time) error {
|
|
c.SetReadDeadline(t)
|
|
c.SetWriteDeadline(t)
|
|
return nil
|
|
}
|
|
|
|
// SetReadDeadline sets the read deadline.
|
|
func (c *DatagramConn) SetReadDeadline(t time.Time) error {
|
|
c.mu.Lock()
|
|
c.readDeadline = t
|
|
c.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
// SetWriteDeadline sets the write deadline.
|
|
// Note: Write deadlines are not used for HTTP/3 datagrams as SendDatagram is non-blocking.
|
|
func (c *DatagramConn) SetWriteDeadline(t time.Time) error {
|
|
return nil
|
|
}
|
|
|
|
// Ensure DatagramConn implements both net.Conn and net.PacketConn
|
|
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)
|