masque_chain_handler (masque v0.7.0)

View Source

MASQUE handler that chains to an upstream proxy.

Instead of opening a gen_udp / gen_tcp socket to the resolved target (what the built-in UDP / TCP / IP proxy handlers do), this handler opens a MASQUE client session to an upstream proxy and relays traffic both ways. The result is a two-hop tunnel:

   Client -> Ingress (this handler) -> Egress (upstream) -> Target

This is the server-side chaining pattern used by Apple Private Relay: the client connects to the Ingress; the Ingress chains to the Egress transparently.

Covers all three tunnel protocols:

  • CONNECT-UDP (protocol = udp): packets forwarded via masque:send/2 both ways.
  • CONNECT-TCP (protocol = tcp): bytes forwarded via masque:send/2 both ways.
  • CONNECT-IP (protocol = ip): IP packets forwarded via masque:send_ip_packet/2; ROUTE_ADVERTISEMENT and unprompted ADDRESS_ASSIGN (request_id 0) from the upstream are forwarded to the client. Client-initiated ADDRESS_REQUEST forwarding with request-id mapping is not yet implemented; clients that need a round-trip address allocation through the chain have to wait for that follow-up.

Configure via handler_opts:

  • upstream_proxy := binary() - URI of the upstream proxy (e.g. <<"https://egress:4434">>). Required.
  • upstream_opts => map() - options forwarded to masque:connect/3 for the upstream leg (verify, transports, timeout, etc.). Default #{verify => verify_none}.
  • allow => fun(target()) -> boolean() - optional policy gate, same as masque_udp_proxy_handler.

Summary

Functions

accept(Req)

handle_capsule(Type, Value, State)

-spec handle_capsule(non_neg_integer(), binary(), #state{upstream :: pid(), protocol :: udp | tcp | ip}) ->
                        {ok, #state{upstream :: pid(), protocol :: udp | tcp | ip}}.

handle_data(Data, State)

-spec handle_data(binary(), #state{upstream :: pid(), protocol :: udp | tcp | ip}) ->
                     {ok, #state{upstream :: pid(), protocol :: udp | tcp | ip}}.

handle_eof(State)

-spec handle_eof(#state{upstream :: pid(), protocol :: udp | tcp | ip}) ->
                    {ok, #state{upstream :: pid(), protocol :: udp | tcp | ip}} |
                    {stop, term(), #state{upstream :: pid(), protocol :: udp | tcp | ip}}.

handle_info(Other, State)

-spec handle_info(term(), #state{upstream :: pid(), protocol :: udp | tcp | ip}) ->
                     {ok, #state{upstream :: pid(), protocol :: udp | tcp | ip}} |
                     {ok, #state{upstream :: pid(), protocol :: udp | tcp | ip}, [term()]} |
                     {stop, term(), #state{upstream :: pid(), protocol :: udp | tcp | ip}}.

handle_ip_packet(Packet, State)

-spec handle_ip_packet(binary(), #state{upstream :: pid(), protocol :: udp | tcp | ip}) ->
                          {ok, #state{upstream :: pid(), protocol :: udp | tcp | ip}}.

handle_packet(Data, State)

-spec handle_packet(binary(), #state{upstream :: pid(), protocol :: udp | tcp | ip}) ->
                       {ok, #state{upstream :: pid(), protocol :: udp | tcp | ip}}.

init(Req, Opts)

-spec init(masque_handler:req(), map()) ->
              {ok, #state{upstream :: pid(), protocol :: udp | tcp | ip}} | {stop, term()}.

terminate(Reason, State)

-spec terminate(term(), #state{upstream :: pid(), protocol :: udp | tcp | ip}) -> ok.