Skip to content

MTGo Transport Layer ​

The transport layer handles the raw byte-level communication between the client and Telegram servers. MTGo supports multiple transport modes and provides a flexible interface for custom transports.

TCP Transport Options ​

MTProto defines several TCP transport modes that determine how frames are encoded on the wire. MTGo supports all of them:

ModeDescriptionOverhead
FullFull-length framing with padding12 bytes
IntermediateCompact length-prefix framing4 bytes
AbridgedMinimal overhead framing1–4 bytes
ObfuscatedXOR-obfuscated framing for bypassing DPIVariable

Default: TCP Intermediate ​

MTGo uses Intermediate mode by default. This provides a good balance between efficiency and compatibility:

go
// Intermediate is the default — no config needed
client, err := tg.NewClient(apiID, apiHash, &tg.Config{
    SessionName: "my_bot",
})

The intermediate transport prepends each message with a 4-byte little-endian length, followed by the payload. The first byte of the length is not 0x00, which distinguishes it from the abridged format.

Using Other TCP Modes ​

go
import "github.com/mtgo-labs/mtgo/telegram/transport"

client, err := tg.NewClient(apiID, apiHash, &tg.Config{
    SessionName: "my_bot",
    Transport:   transport.NewAbridged(),
})

Transport Interface ​

MTGo's transport layer is defined by a simple interface:

go
type Transport interface {
    Connect(ctx context.Context, addr string) error
    Send(data []byte) error
    Recv() ([]byte, error)
    Close() error
}
MethodDescription
ConnectEstablish a connection to the given address
SendSend a raw byte payload
RecvReceive the next complete frame
CloseClose the connection and release resources

This interface allows you to implement custom transports for specialized environments.

Dialer Interface and NetDialer ​

The dialer controls how TCP connections are established:

go
type Dialer interface {
    DialContext(ctx context.Context, network, addr string) (net.Conn, error)
}

MTGo uses NetDialer by default, which wraps Go's standard net.Dialer:

go
dialer := &net.Dialer{
    Timeout:   30 * time.Second,
    KeepAlive: 30 * time.Second,
}

You can provide a custom dialer for advanced use cases:

go
client, err := tg.NewClient(apiID, apiHash, &tg.Config{
    Dialer: customDialer,
})

Proxy Support ​

MTGo supports proxying through the Proxy config option. This works with the standard Go proxy dialer pattern:

SOCKS5 Proxy ​

go
import "golang.org/x/net/proxy"

dialer, err := proxy.SOCKS5("tcp", "127.0.0.1:1080", nil, proxy.Direct)
if err != nil {
    log.Fatal(err)
}

client, err := tg.NewClient(apiID, apiHash, &tg.Config{
    SessionName: "my_bot",
    Proxy:       dialer,
})

HTTP Proxy ​

go
import "net/http"

proxyURL, _ := url.Parse("http://proxy.example.com:8080")
transport := &http.Transport{Proxy: http.ProxyURL(proxyURL)}

client, err := tg.NewClient(apiID, apiHash, &tg.Config{
    SessionName: "my_bot",
    Proxy:       transport,
})

Connection Flow ​

The complete connection flow from dial to encrypted session:

1. Dial          → TCP connection to DC
2. Transport     → Transport handshake (send transport tag)
3. DH Exchange   → Key exchange (PQ request → DH result)
4. Auth Key      → Generate 256-byte auth key
5. Encrypted     → All subsequent messages are encrypted
6. Init          → Send init connection with device info

Step-by-Step ​

  1. Dial: The dialer opens a TCP connection to the DC address (e.g., 149.154.167.50:443)

  2. Transport handshake: The transport sends its tag byte(s) to indicate the framing mode

  3. DH exchange: MTGo performs the Diffie-Hellman key exchange:

    • Client sends req_pq_multi with a nonce
    • Server responds with resPQ containing its public key fingerprint
    • Client sends req_DH_params
    • Server responds with server_DH_params
    • Both sides derive the shared auth key
  4. Auth key generation: The 256-byte auth key is derived from the DH parameters

  5. Encrypted session: All messages from this point use MTProto encryption with the auth key

  6. Init connection: The client sends InvokeWithLayer containing device info, API layer, and the first RPC call

This entire flow happens inside client.Connect() and is transparent to the user.

Released under the Apache-2.0 License.