seriyps/mtproto_proxy

repository·master·Indexed 21 days ago

https://github.com/seriyps/mtproto_proxy

An Erlang-based MTProto proxy for high performance and censorship circumvention. It features fake-TLS, domain fronting, randomized packet sizes to bypass Deep Packet Inspection (DPI), and support for Per-SNI derived secrets. The proxy can be deployed via interactive scripts, Docker, or direct OS installation on Ubuntu, Debian, and CentOS. It includes advanced configuration options for connection policies, IPv6 support, resource tuning for low/high RAM profiles, and a split-mode (Front + Back) architecture.

Tokens
7K
Snippets
19
Records
28
Agent score
67%

What's inside seriyps/mtproto_proxy

  1. How split mode works in mtproto_proxy

    master

    In split mode, the proxy architecture is divided into front and back nodes using the node_role configuration:

    • Front Node (node_role = front): Runs the mtp_handler processes. These nodes handle the incoming Telegram client TCP connections and protocol decoding.
    • Back Node (node_role = back): Runs the mtp_dc_pool and mtp_down_conn processes. These nodes manage the multiplexed connections to the actual Telegram Data Centres.

    Key Characteristics:

    • Transparency: Because the system uses Erlang distribution, gen_server:call and other messages sent from a front node to a back node are transparent to the developer.
    • Scalability: Multiple front nodes can be deployed to handle high volumes of client connections while sharing a single back node (or a set of back nodes) to manage upstream DC connections efficiently through multiplexing.
  2. How transparent client migration works during DC connection rotation

    master

    Telegram periodically closes TCP connections to the proxy (DC connection rotation), typically every 30–70 seconds. To prevent dropping all multiplexed clients, the proxy performs a transparent migration.

    The Migration Process

    1. Detection: The dying downstream connection (mtp_down_conn (old)) notifies the connection pool (mtp_dc_pool) that it is closing.
    2. Replacement: The pool removes the old connection and spawns/connects a new downstream connection (mtp_down_conn (new)).
    3. Notification: The old connection sends a migrate(OldDown) cast to all connected client handlers (mtp_handler).
    4. Handler Behavior:
      • If the handler is idle: It performs a synchronous migrate(OldDown, self(), Opts) call to the pool. The pool removes the handler from the old connection's upstreams, sends an upstream_new cast to the new connection, and returns the new PID to the handler. The handler then updates its downstream reference.
      • If the handler is blocked in down_send: The handler receives an {error, migrating} response. In this case, the client is expected to reconnect and resend the data.

    Split-mode Architecture

    In front/back split mode, the mtp_handler resides on the front node, while mtp_dc_pool and mtp_down_conn reside on the back node. Because the proxy uses Erlang distribution, all inter-process communication (e.g., migrate casts, upstream_new casts, and gen_server calls) works transparently across nodes without requiring code changes. If a back-node restarts, process monitors ensure that all affected front-node handlers exit cleanly.

  3. How handler and downstream connections interact

    master

    The mtproto_proxy uses a multi-layered architecture to manage connections between Telegram clients and Telegram Data Centres (DCs). The flow involves three primary actors:

    1. mtp_handler: A process dedicated to a single Telegram client TCP connection. It handles protocol decoding (fake-TLS, obfuscated, or secure) and manages the transition from the hello stage to the tunnel stage.
    2. mtp_dc_pool: A manager that maintains a pool of downstream connections for a specific DC.
    3. mtp_down_conn: A multiplexed TCP connection that communicates directly with the upstream Telegram DC.

    Connection Lifecycle

    1. Handshake and Lookup

    When a client connects, the mtp_handler performs a lookup to find the appropriate mtp_dc_pool.

    • Single-node mode: The handler uses whereis(dc_to_pool_name(DcId)) to find the pool locally.
    • Split mode (node_role = front / back): The handler uses Erlang distribution (erpc:call) to locate the pool on the BackNode. If the pool is not found, it falls back to the default DC defined in mtp_config.
    • Once located, the handler calls mtp_dc_pool:get(Pool, self(), Opts) to receive a downstream process ID (Downstream pid).

    2. Steady-State Data Exchange

    Once the tunnel is established, data flows as follows:

    • Client to Telegram: Client sends TCP data $\rightarrow$ mtp_handler calls mtp_down_conn:send(Down, Data) $\rightarrow$ mtp_down_conn sends RPC-framed data to the Telegram DC.
    • Telegram to Client: Telegram DC sends data $\rightarrow$ mtp_down_conn casts {proxy_ans, Down, Data} to the mtp_handler $\rightarrow$ mtp_handler sends TCP data to the client.
    • Acknowledgements: The handler sends mtp_down_conn:ack(Down, Count, Size) to the downstream connection to manage flow control.

    3. Termination

    When the client closes the TCP connection, the mtp_handler notifies the pool via mtp_dc_pool:return(Pool, self()), and the pool notifies the downstream connection via upstream_closed(Down, Handler).

  4. Configure Domain Fronting for fake-TLS

    master

    Domain fronting allows the proxy to forward unrecognized TLS connections (like browser probes or DPI) to a real HTTPS host, making the proxy indistinguishable from a normal web server. This is controlled via the domain_fronting key in config/prod-sys.config.

    Options for domain_fronting:

    • off: Default. No forwarding.
    • sni: Forward the connection to the host specified in the client's SNI field on port 443. Requires policy rules (like a whitelist) to prevent loops or abuse.
    • "host:port": Forward all unrecognized connections to a specific fixed third-party host.

    Example: Forwarding to a local Nginx server

    To run the proxy alongside a real website on the same machine, configure Nginx to listen on 127.0.0.1:1443 with valid TLS certificates, then set the proxy to forward to that local address:

     {mtproto_proxy,
      [
       {domain_fronting, "127.0.0.1:1443"},
       {ports, [...]} 
      ]
     }
     {mtproto_proxy,
      [
       {domain_fronting, "127.0.0.1:1443"},
       {ports,
        [#{name => mtp_handler_1,
          ... 
        }]}
       ]
     }
  5. Configure connection policies and limits

    master

    The proxy supports several connection policies to control access via mtproto_proxy configuration. Policies are defined in the policy list within the mtproto_proxy configuration block.

    Supported Policies

    • {in_table, KEY, TABLE_NAME}: Whitelist. Only allow connections if KEY is present in TABLE_NAME.
    • {not_in_table, KEY, TABLE_NAME}: Blacklist. Only allow connections if KEY is not present in TABLE_NAME.
    • {max_connections, KEYS, NUMBER}: (Experimental) Rejects new connections if the number of existing connections matching KEYS exceeds NUMBER.

    Note on max_connections: A single Telegram client typically opens 3 to 8 connections. To support $N$ unique users, set the limit to at least $8 imes N$.

    Available Keys

    • port_name: The proxy port name.
    • client_ipv4: Client's IPv4 address (ignored on IPv6 ports).
    • client_ipv6: Client's IPv6 address (ignored on IPv4 ports).
    • {client_ipv4_subnet, MASK}: Client's IPv4 subnet (mask 8-32).
    • {client_ipv6_subnet, MASK}: Client's IPv6 subnet (mask 32-128).
    • tls_domain: Lowercase domain name from fake-TLS secret (ignored if not using fake-TLS).
  6. Understand mtp_ping output and summary

    master

    The mtp_ping output provides detailed latency metrics for each DC and a summary section:

    • Protocols: Indicates if each protocol achieved at least one successful ping (OK) or if all attempts failed (DISABLED), along with the success ratio of DCs.
    • Avg timings per DC: Displays the average time (in ms) for TCP connection, Handshake, Ping (MTProto req_pq round-trip), and the Total time, averaged over all protocols and repeats.
  7. Set up Split-mode (Front + Back) architecture

    master

    Split-mode uses a Front server (domestic/neutral IP) to accept client connections and a Back server (foreign IP) to connect to Telegram. This helps bypass aggressive censorship.

    Prerequisites

    • Erlang/OTP 25+ on both servers.
    • TCP connectivity between servers.
    • Back server must have outbound access to Telegram.

    Choose one of two methods:

    1. DPI-resistant tunnel (Recommended): Use Shadowsocks, VLESS/XRay, or Hysteria2. Use the tunnel interface addresses (e.g., front@10.8.0.1) in the node names.
    2. TLS distribution: Use the provided scripts to generate mutual-TLS certificates.
      • Run ./scripts/gen_dist_certs.sh init /etc/mtproto-proxy/dist on the back server.
      • Run ./scripts/gen_dist_certs.sh add-node /etc/mtproto-proxy/dist <front_name> for each front server.
      • Distribute the generated .pem and .conf files to the respective servers.
      • Uncomment -proto_dist and -ssl_dist_optfile in vm.args.

    Step 2: Configure the Back Server

    1. Run make init-config ROLE=back.
    2. In vm.args: Set -name back@<BACK_IP> and a strong -setcookie.
    3. In sys.config: Set external_ip.

    Step 3: Configure the Front Server

    1. Run make init-config ROLE=front.
    2. In vm.args: Set -name front@<FRONT_IP> and the same cookie as the back.
    3. In sys.config: Set back_node to the back node name (e.g., 'back@10.8.0.2') and configure ports.

    Step 4: Start the servers

    Always start the back server first.

    # On back server:
    make ROLE=back && sudo make install && systemctl start mtproto-proxy
    
    # On front server:
    make ROLE=front && sudo make install && systemctl start mtproto-proxy
  8. Enable and use Per-SNI derived secrets

    master

    Per-SNI derived secrets improve security by ensuring that each user's link contains a token derived from their specific SNI domain, rather than the raw base secret. This prevents users from easily sharing credentials or constructing links for other domains.

    Configuration

    Enable this in sys.config:

    {per_sni_secrets, on},
    {per_sni_secret_salt, "<your-private-salt>"},

    ⚠️ Warning: Switching per_sni_secrets to on invalidates all existing fake-TLS user links. You must re-issue all links.

    The link format is: ee | derived (16 bytes) | sni_domain_hex. derived = SHA256(salt || hex(base_secret) || sni_domain)[0:16]

    {per_sni_secrets, on},
    {per_sni_secret_salt, "my-private-salt-change-me"},
  9. Use mtp_ping to test proxy connectivity and latency

    master

    The mtp_ping command-line tool measures connectivity and latency from a Telegram MTProto proxy to each Telegram Data Center (DC). It performs a full client→proxy→DC round-trip using the req_pq/res_pq handshake.

    It accepts standard Telegram proxy links (tg://proxy?… or https://t.me/proxy?…) and supports all secret formats including Normal, Secure (dd), Fake-TLS hex (ee), and Fake-TLS base64.

    ./_build/default/bin/mtp_ping [OPTIONS] <proxy-url>
    
    # Example usage:
    ./_build/default/bin/mtp_ping --proto fake-tls --dc 1,2,3 --repeat 3 \
        "https://t.me/proxy?server=tg.example.com&port=443&secret=ee..."
  10. Manage policy tables dynamically via CLI

    master

    Policy tables (e.g., ip_blacklist or customer_domains) are internal databases created automatically on startup. Data in these tables is not preserved when the proxy restarts. You can manage these tables at runtime using the eval command.

    Add a value to a table

    /opt/mtp_proxy/bin/mtp_proxy eval 'mtp_policy_table:add(TABLE_NAME, KEY, "VALUE").'

    Remove a value from a table

    /opt/mtp_proxy/bin/mtp_proxy eval 'mtp_policy_table:del(TABLE_NAME, KEY, "VALUE").'

    Example: Blacklisting an IP

    1. Configure policy: {not_in_table, client_ipv4, ip_blacklist}
    2. Add IP: /opt/mtp_proxy/bin/mtp_proxy eval 'mtp_policy_table:add(ip_blacklist, client_ipv4, "203.0.113.1").'
    # Add an IP to a blacklist
    /opt/mtp_proxy/bin/mtp_proxy eval 'mtp_policy_table:add(ip_blacklist, client_ipv4, "203.0.113.1").'
    
    # Remove an IP from a blacklist
    /opt/mtp_proxy/bin/mtp_proxy eval 'mtp_policy_table:del(ip_blacklist, client_ipv4, "203.0.113.1").'
  11. Tune proxy resource consumption

    master

    Adjust settings based on your server's available RAM.

    Low RAM Profile

    Reduces memory usage at the cost of speed, CPU, and replay protection:

    • {upstream_socket_buffer_size, 5120}
    • {downstream_socket_buffer_size, 51200}
    • {replay_check_session_storage, off}
    • {init_timeout_sec, 10}
    • {hibernate_timeout_sec, 30}
    • {ready_timeout_sec, 120}
    • Avoid using max_connections policy.

    High RAM Profile

    Optimizes for speed and security:

    • {max_connections, 128000}
    • {upstream_socket_buffer_size, 20480}
    • {downstream_socket_buffer_size, 512000}
    • {replay_check_session_storage, on}
    • {replay_check_session_storage_opts, #{max_memory_mb => 2048, max_age_minutes => 1440}}

    CPU Optimization

    Disable CRC32 checksum check:

    • {mtp_full_check_crc32, false}
  12. Apply configuration changes without restarting the service

    master

    On OS-installed instances (not supported in Docker), you can reload the configuration without a full service restart. This allows updating settings like the ad_tag on existing ports, though note that updating a tag on an active port will disconnect existing clients on that port.

    1. Edit config/prod-sys.config.
    2. Run:
    sudo make update-sysconfig && sudo systemctl reload mtproto-proxy