%%% @doc IP address classification for SSRF protection. %%% %%% `is_public/1' returns `false' for loopback, RFC 1918 private, %%% link-local, multicast, and reserved address ranges in both %%% IPv4 and IPv6. The built-in proxy handlers call this after DNS %%% resolution to reject tunnels targeting internal networks. -module(masque_ip). -export([is_public/1, reject_requests/1, inject_packet/2]). -include("masque_ip.hrl"). %% @doc Build the RFC 9484 ยง5.2 "reject all" answer to a batch of %% inbound ADDRESS_REQUEST entries: each reply carries the same %% Request ID and IP Version, an all-zero address, and the maximum %% prefix length for that version (32 for v4, 128 for v6). -spec reject_requests([#ip_prefix_request{}]) -> [#ip_assignment{}]. reject_requests(Requests) -> [reject_one(R) || R <- Requests]. reject_one(#ip_prefix_request{request_id = Id, version = 4}) -> #ip_assignment{request_id = Id, version = 4, address = {0,0,0,0}, prefix_len = 32}; reject_one(#ip_prefix_request{request_id = Id, version = 6}) -> #ip_assignment{request_id = Id, version = 6, address = {0,0,0,0,0,0,0,0}, prefix_len = 128}. %% @doc Push an IP packet into a server session for delivery to its %% connected client. Non-blocking. Intended for out-of-band injectors %% (e.g. a TUN device owner that holds the session pid via the %% address registry); accepted by both `masque_ip_server_session' %% (h2/h3) and `masque_ip_h1_server_session' (h1). -spec inject_packet(pid(), binary()) -> ok. inject_packet(SessionPid, Packet) when is_pid(SessionPid), is_binary(Packet) -> gen_server:cast(SessionPid, {inject_packet, Packet}). -spec is_public(inet:ip_address()) -> boolean(). %% IPv4 is_public({0,_,_,_}) -> false; %% 0.0.0.0/8 is_public({10,_,_,_}) -> false; %% RFC 1918 is_public({100,B,_,_}) when B >= 64, B =< 127 -> false; %% RFC 6598 CGN is_public({127,_,_,_}) -> false; %% loopback is_public({169,254,_,_}) -> false; %% link-local is_public({172,B,_,_}) when B >= 16, B =< 31 -> false; %% RFC 1918 is_public({192,0,0,_}) -> false; %% RFC 6890 is_public({192,0,2,_}) -> false; %% TEST-NET-1 is_public({192,88,99,_}) -> false; %% 6to4 relay is_public({192,168,_,_}) -> false; %% RFC 1918 is_public({198,18,_,_}) -> false; %% benchmarking is_public({198,19,_,_}) -> false; %% benchmarking is_public({198,51,100,_}) -> false; %% TEST-NET-2 is_public({203,0,113,_}) -> false; %% TEST-NET-3 is_public({A,_,_,_}) when A >= 224 -> false; %% multicast + reserved %% IPv6 is_public({0,0,0,0,0,0,0,0}) -> false; %% :: unspecified is_public({0,0,0,0,0,0,0,1}) -> false; %% ::1 loopback is_public({0,0,0,0,0,16#FFFF,_,_}) -> false; %% ::ffff:0:0/96 mapped v4 is_public({16#64,16#FF9B,_,_,_,_,_,_}) -> false; %% 64:ff9b::/96 NAT64 is_public({16#100,_,_,_,_,_,_,_}) -> false; %% discard 100::/64 is_public({16#2001,0,_,_,_,_,_,_}) -> false; %% teredo is_public({16#2001,16#DB8,_,_,_,_,_,_}) -> false; %% documentation is_public({A,_,_,_,_,_,_,_}) when A >= 16#FC00, A =< 16#FDFF -> false; %% ULA fc00::/7 is_public({A,_,_,_,_,_,_,_}) when A >= 16#FE80, A =< 16#FEBF -> false; %% link-local fe80::/10 is_public({A,_,_,_,_,_,_,_}) when A >= 16#FF00 -> false; %% multicast ff00::/8 is_public(_) -> true.