Documentation
¶
Overview ¶
Package testclient is the client side of every door the proxy serves -- FakeTLS (ee), obfuscated2 (dd, plain) and WEB -- for tests and for the load tool, tools/mtgbench. Nothing in the release binary imports it.
It imports only mtglib/obfuscation, essentials, internal/webrelay/frame and the standard library. mtglib/internal/... is out of reach by Go's internal rule, and importing mtglib or internal/webrelay would put their in-package tests in an import cycle. The few TLS constants it needs are therefore copied, each beside the name of its source.
It returns errors rather than taking a testing.TB, so a program can use it as readily as a test.
Index ¶
- Constants
- func ApplicationRecord(payload []byte) []byte
- func ClientHello(key [16]byte, sni string, sessionID []byte) ([]byte, error)
- func ClientHelloPadded(key [16]byte, sni string, sessionID []byte, size int) ([]byte, error)
- func ClientHelloWith(key [16]byte, sni string, sessionID []byte, opts HelloOptions) ([]byte, error)
- func NewRecordConn(c essentials.Conn) essentials.Conn
- func NewRecordConnSize(c essentials.Conn, readBuffer int) essentials.Conn
- func ObfuscatedFrame(key [16]byte, tag obfuscation.ConnectionTag, dc int) ([]byte, error)
- func ResetOnClose(c net.Conn) error
- type HelloOptions
- type Record
- type WebOptions
- type WebSession
- type WebStream
- func (st *WebStream) Close() error
- func (st *WebStream) CloseRead() error
- func (st *WebStream) CloseWrite() error
- func (st *WebStream) LocalAddr() net.Addr
- func (st *WebStream) Read(p []byte) (int, error)
- func (st *WebStream) RemoteAddr() net.Addr
- func (st *WebStream) SetDeadline(t time.Time) error
- func (st *WebStream) SetReadDeadline(t time.Time) error
- func (st *WebStream) SetWriteDeadline(t time.Time) error
- func (st *WebStream) Write(p []byte) (int, error)
Constants ¶
const ( RecordHeaderSize = 5 TypeChangeCipherSpec = 0x14 TypeAlert = 0x15 TypeHandshake = 0x16 TypeApplicationData = 0x17 // MaxRecordPayload is the most the proxy writes into one record // (tls.MaxRecordPayloadSize): 16384 less the header. MaxRecordPayload = 16384 - RecordHeaderSize )
Copied from mtglib/internal/tls (conn.go), which this package cannot import.
Variables ¶
This section is empty.
Functions ¶
func ApplicationRecord ¶
ApplicationRecord frames payload as one TLS 1.2 application-data record, the way a client sends its obfuscated2 handshake after the ServerHello.
func ClientHello ¶
ClientHello forges the exact bytes a Telegram client sends to an ee door: a TLS 1.0 record carrying a TLS 1.2 ClientHello naming sni, whose client random is the HMAC-SHA256 of the whole message under key (computed with the random zeroed) XORed with a little-endian timestamp. It is the inverse of the proxy's FakeTLS reader.
It refuses an empty SNI or one over 255 bytes -- a guard: a host name that long is no cover anyone uses -- and a session ID over 32 bytes, which TLS cannot carry.
func ClientHelloPadded ¶
ClientHelloPadded is ClientHello for a hello of an exact size, see HelloOptions.PadTo. size is a request for padding, so 0 is an error here and not "none".
func ClientHelloWith ¶
ClientHelloWith is ClientHello shaped by opts.
func NewRecordConn ¶
func NewRecordConn(c essentials.Conn) essentials.Conn
NewRecordConn wraps c, whose ServerHello flight has been read, in the client side of the FakeTLS record layer, reading through a 64 KiB buffer.
func NewRecordConnSize ¶
func NewRecordConnSize(c essentials.Conn, readBuffer int) essentials.Conn
NewRecordConnSize is NewRecordConn with a read buffer of readBuffer bytes. A run that holds thousands of idle connections makes it small. The record buffers grow to what the connection carries, so an idle one holds little.
func ObfuscatedFrame ¶
func ObfuscatedFrame(key [16]byte, tag obfuscation.ConnectionTag, dc int) ([]byte, error)
ObfuscatedFrame returns exactly the 64 bytes a client sends to open an obfuscated2 stream for this key, transport tag and data centre. The cipher state is not kept: a client that goes on talking uses obfuscation.Obfuscator.SendHandshake on its connection instead.
func ResetOnClose ¶
ResetOnClose makes closing c send a reset instead of a FIN, so a run that ends thousands of connections does not fill the kernel's TIME_WAIT table.
Types ¶
type HelloOptions ¶
type HelloOptions struct {
// PadTo is the length of the hello as ONE record, its 5-byte header
// included: that is the form the proxy's reader rebuilds before it takes
// the digest, and the length a real client's hello is quoted in (517). The
// difference from the unpadded hello is made up by a TLS padding extension
// (type 0x0015, zeros) after the SNI extension, inside the digest's input.
// 0 means no padding.
//
// A hello that does not fit in one record -- PadTo above 16,389, the 5-byte
// header and 2^14 bytes of payload -- is sent as the same handshake message
// cut across as many records as it takes, none over 2^14, and the reader
// puts it back together. The digest is still over the single record, so the
// bytes on the wire are then PadTo plus 5 for each record after the first.
//
// A PadTo of the unpadded length adds no extension. Below it, or in the 1..3
// bytes above it (an extension header alone is 4), or giving a handshake
// body above 65,531 bytes (PadTo 65,540), which the reader refuses, is an
// error.
PadTo int
// At is the time the random carries. The zero value is the present, which
// is what a client sends; a test sets it to send a hello the reader's clock
// check will refuse.
At time.Time
}
HelloOptions shape a hello beyond what ClientHello sends. The zero value is ClientHello.
type Record ¶
Record is one TLS record as it arrived. Raw is the whole record, header included, because the ServerHello digest is computed over the records' bytes.
func ReadRecord ¶
ReadRecord reads exactly one TLS record. A header declaring more than a record may carry, or nothing at all, is refused by name: that is how raw obfuscated2 or raw relayed bytes show up when a record layer was expected.
func ReadServerFlight ¶
ReadServerFlight reads the whole ServerHello flight: exactly three records -- ServerHello, ChangeCipherSpec and one application-data record standing in for the certificate. The concatenated bytes are returned too, because that is what the client's digest check covers.
type WebOptions ¶
type WebOptions struct {
// Origin is the relay's own host name ([web] host), sent as the Origin
// header of the carrier upgrade.
Origin string
// ForwardedFor, when set, is sent as X-Forwarded-For, for a relay that takes
// the client address from it (client-ip = "x-forwarded-for").
ForwardedFor string
// DialTimeout bounds every HTTP step and the upgrade. Default 5s.
DialTimeout time.Duration
// ResetOnClose makes closing the carrier send a reset, so a run that opens
// thousands of sessions leaves no TIME_WAIT behind.
ResetOnClose bool
// ReadBufferSize is the carrier's read buffer. Default 256 KiB, for bulk
// transfers; a run that holds thousands of idle sessions sets it small, so
// the memory it measures is the relay's and not the client's.
ReadBufferSize int
}
WebOptions shapes how a WEB client presents itself to the relay.
type WebSession ¶
type WebSession struct {
// contains filtered or unexported fields
}
WebSession is one WEB proxy session: a bootstrap spent on a session, and the carrier WebSocket its streams share.
func OpenWebSession ¶
func OpenWebSession(ctx context.Context, baseURL, capability string, o WebOptions) (*WebSession, error)
OpenWebSession walks what the bridge script does: fetch the bridge page for capability, spend the bootstrap token it embeds on a session, and open the carrier. baseURL is the relay origin, e.g. "http://127.0.0.1:47003".
func (*WebSession) Close ¶
func (s *WebSession) Close() error
Close drops the carrier; every stream on it fails.
func (*WebSession) Delete ¶
func (s *WebSession) Delete(ctx context.Context) error
Delete ends the session the way a browser does when its tab closes: a DELETE, so the relay reaps it at once rather than after the reconnect grace, and then the carrier goes too, as the closing tab takes its WebSocket with it. (The relay's DELETE alone does not end a carrier the client keeps open.)
func (*WebSession) OpenStream ¶
func (s *WebSession) OpenStream(id uint32) (*WebStream, error)
OpenStream opens stream id on the carrier. Its credit starts at the relay's initial window in both directions.
func (*WebSession) Token ¶
func (s *WebSession) Token() string
Token is the session's bearer, for a test that drives the session API itself.
type WebStream ¶
type WebStream struct {
// contains filtered or unexported fields
}
WebStream is one logical connection on a WEB carrier, with the relay's flow control honoured both ways: it returns credit as it consumes, and writes no more than the relay has granted. It is an ordered byte stream, so an obfuscated2 client runs over it unchanged.
func (*WebStream) Close ¶
Close aborts the stream: the protocol has no half-close, and CLOSE drops what is still queued on either side.
func (*WebStream) CloseRead ¶
CloseRead and CloseWrite exist so a stream satisfies essentials.Conn. The protocol has no half-close, so they do nothing.