Add an overlay transport
An overlay wraps one connection at a time: TLS, WebSocket, HTTPUpgrade, or a new obfuscation. The examples are module/transport/overlay/tls (a handshake, then crypto/tls does the rest), module/transport/overlay/websocket (a handshake, then framing) and module/transport/overlay/httpupgrade (a handshake only). A new one, say obfs, touches:
| File | Change |
|---|---|
module/transport/overlay/obfs/settings.go (new) | Settings, Validate, Warnings |
module/transport/overlay/obfs/transport.go (new) | Transport{Settings}, Info, NewStreamOverlayClient, NewStreamOverlayServer, NewOverlayStreamTransportModule |
module/transport/overlay/obfs/client.go, server.go, stream.go (new) | the two roles and the stream they return |
module/transport/overlay/obfs/obfs_test.go (new) | both roles over a loopback TCP pair, no core |
core/registry/default.go | one line: RegisterOverlayStream(obfs.NewOverlayStreamTransportModule) |
The shape:
go
func (t *Transport) NewStreamOverlayServer() (spec.StreamOverlayServer, error) {
// Once, at startup: load keys or certificates, refuse a missing one.
return &server{...}, nil
}
func (s *server) WrapServerStream(ctx context.Context, underlyingStream spec.Stream, transport *spec.TransportMetadata) (spec.Stream, error) {
// Once per connection, in that connection's goroutine, within ctx.
// Read and answer the handshake on underlyingStream. A layer that learns
// who the client really is replaces transport.Source (and writes no other
// field); most layers ignore transport. Then:
return &stream{underlyingStream: underlyingStream, ...}, nil
}Rules
- Per role, once.
NewStreamOverlayClientandNewStreamOverlayServerbuild what every connection shares and refuse what their role lacks, such as a server without a certificate.Validatecannot know the role and must not read files. - Per connection, within
ctx.WrapClientStreamandWrapServerStreamend their handshake beforectxdoes;streamdeadline.BindToContextmakes a blocking read honour it. - Core owns the connection. Never dial or start goroutines; on error leave
underlyingStreamopen. The returned stream'sClosecloses the one below. - Keep bytes read past the handshake and hand them on.
- Refuse like a web server (
404,400) if the protocol is HTTP-shaped. - Half-close. Pass
CloseWritedown, or returnspec.ErrNoHalfClose. - Measure. Anything that touches every byte is a hot path: benchmark it and allocate nothing per read or write.
A protocol that carries many streams over one connection (HTTP/2, gRPC) does not fit this kind; raise it first.
Code two overlays need, such as the WebSocket upgrade handshake, moves to a package under shared/ once the second one exists.