masque_uri_udp_bind (masque v0.7.0)

View Source

Connect-UDP-Bind URI and header helpers.

Sibling of masque_uri that adds bind-specific bits the existing UDP matcher must not learn about:

  • The percent-encoded * wildcard for target_host / target_port, meaning "unscoped" - the bind socket can talk to any peer the proxy's policy allows. The standard masque_uri:match/2 rejects * as a host/port, so the dispatch path uses this matcher instead when the Connect-UDP-Bind request header is present.
  • Parse and format the two HTTP fields the draft adds:
    • Connect-UDP-Bind - RFC 9651 Boolean. Both endpoints send ?1 to indicate support; bind is only enabled once each side has both sent and received it.
    • Proxy-Public-Address - RFC 9651 List of String items, each a "ip:port" tuple (IPv6 literals bracketed). Required on a successful bind response; absent / malformed / empty must fail the handshake.

This module is pure - no I/O, no state. It is deliberately additive: nothing here changes the behaviour of masque_uri for legacy CONNECT-UDP requests.

Summary

Functions

Classify a bind target without doing any URI work. Useful for callers that already hold the parsed values.

Expand a CONNECT-UDP URI template for a bind handshake. Accepts the typed bind_target() (unscoped or {Host, Port}) and a raw vars() map; the unscoped form encodes both target variables as the wildcard *.

The header pair to emit for Connect-UDP-Bind: ?1 on a request or response.

Render a list of {ip, port} tuples for emission on a response. Bracket IPv6 literals; quote each entry as a Structured Field String. Crashes on an empty list - draft-11 requires at least one valid entry on a successful bind 2xx.

Match a request path against a CONNECT-UDP URI template, accepting * (or its percent-encoded form %2A) for both target_host and target_port to mean "unscoped bind". Returns a map carrying the literal target plus a bind classification so the caller can dispatch accordingly. Falls back to the legacy masque_uri:match/2 when neither variable is the wildcard.

Read the Connect-UDP-Bind field from a Headers list. Per draft-11, invalid value types are treated as absent (the library returns invalid so the caller can choose to log or reject; the dispatch path treats invalid the same as absent).

Parse the Proxy-Public-Address field. Distinguishes absent, malformed (the field is present but does not parse as a list of "ip:port" strings), and empty (the list parses but has zero usable entries) so the bind client can fail the handshake on any of those.

Types

bind_header_value/0

-type bind_header_value() :: bind | absent | invalid.

bind_match/0

-type bind_match() ::
          #{target_host := binary() | '*', target_port := 1..65535 | '*', bind := unscoped | scoped}.

bind_target/0

-type bind_target() :: unscoped | {Host :: binary(), Port :: 1..65535}.

proxy_public_address_error/0

-type proxy_public_address_error() :: absent | malformed | empty.

Functions

classify(_)

-spec classify(bind_match() | bind_target()) -> unscoped | scoped.

Classify a bind target without doing any URI work. Useful for callers that already hold the parsed values.

expand(Template, Vars)

-spec expand(binary(), bind_target() | masque_uri_template:vars()) -> binary().

Expand a CONNECT-UDP URI template for a bind handshake. Accepts the typed bind_target() (unscoped or {Host, Port}) and a raw vars() map; the unscoped form encodes both target variables as the wildcard *.

The unscoped path goes directly through masque_uri_template:expand/2 rather than masque_uri:expand/2 because the latter's vars() typespec narrows target_port to an integer port number, while we need a wildcard string for an unscoped bind.

format_bind_header()

-spec format_bind_header() -> {binary(), binary()}.

The header pair to emit for Connect-UDP-Bind: ?1 on a request or response.

format_proxy_public_address(Addrs)

-spec format_proxy_public_address([{inet:ip_address(), inet:port_number()}]) -> binary().

Render a list of {ip, port} tuples for emission on a response. Bracket IPv6 literals; quote each entry as a Structured Field String. Crashes on an empty list - draft-11 requires at least one valid entry on a successful bind 2xx.

match(Template, Path)

-spec match(binary(), binary()) ->
               {ok, bind_match()} | {error, no_match | bad_port | bad_host | bad_template}.

Match a request path against a CONNECT-UDP URI template, accepting * (or its percent-encoded form %2A) for both target_host and target_port to mean "unscoped bind". Returns a map carrying the literal target plus a bind classification so the caller can dispatch accordingly. Falls back to the legacy masque_uri:match/2 when neither variable is the wildcard.

parse_bind_header(Headers)

-spec parse_bind_header([{binary(), binary()}]) -> bind_header_value().

Read the Connect-UDP-Bind field from a Headers list. Per draft-11, invalid value types are treated as absent (the library returns invalid so the caller can choose to log or reject; the dispatch path treats invalid the same as absent).

parse_proxy_public_address(Headers)

-spec parse_proxy_public_address([{binary(), binary()}]) ->
                                    {ok, [{inet:ip_address(), inet:port_number()}]} |
                                    {error, proxy_public_address_error()}.

Parse the Proxy-Public-Address field. Distinguishes absent, malformed (the field is present but does not parse as a list of "ip:port" strings), and empty (the list parses but has zero usable entries) so the bind client can fail the handshake on any of those.