Packages
macula
4.4.1
7.1.0
7.0.0
6.0.0
5.2.2
5.2.1
5.2.0
5.1.0
5.0.0
4.8.0
4.7.1
4.7.0
4.6.0
4.5.0
4.4.10
4.4.9
4.4.8
4.4.7
4.4.6
4.4.5
4.4.4
4.4.3
4.4.2
4.4.1
4.4.0
4.3.1
4.3.0
4.2.9
4.2.8
4.2.7
4.2.6
4.2.5
4.2.4
4.2.3
4.2.2
4.2.1
4.2.0
4.1.1
4.1.0
4.0.0
3.16.0
3.15.3
3.15.2
3.15.1
3.14.0
3.13.0
3.12.1
3.12.0
3.11.1
3.11.0
3.10.3
3.10.2
3.10.1
3.9.0
3.8.0
3.7.0
3.5.0
3.4.0
3.3.0
3.2.0
3.1.0
3.0.0
2.1.1
2.1.0
2.0.0
1.5.2
1.5.1
1.4.30
1.4.29
1.4.28
1.4.27
1.4.26
1.4.25
1.4.24
1.4.23
1.4.22
1.4.21
1.4.20
1.4.19
1.4.18
1.4.17
1.4.16
1.4.15
1.4.14
1.4.13
1.4.11
1.4.10
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.1
1.3.0
1.2.0
1.1.0
1.0.10
1.0.9
1.0.8
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.48.6
0.48.5
0.48.4
0.48.3
0.48.2
0.48.1
0.48.0
0.47.1
0.47.0
0.46.3
0.46.1
0.46.0
0.45.3
0.45.2
0.45.1
0.45.0
0.44.2
0.44.1
0.44.0
0.43.3
0.43.2
0.43.1
0.43.0
0.42.9
0.42.8
0.42.7
0.42.6
0.42.5
0.42.4
0.42.3
0.42.2
0.42.1
0.42.0
0.41.1
0.41.0
0.40.1
0.40.0
0.39.9
0.39.8
0.39.7
0.39.6
0.39.5
0.39.4
0.39.3
0.39.2
0.39.1
0.39.0
0.38.8
0.38.7
0.38.6
0.38.5
0.38.4
0.38.3
0.38.2
0.38.1
0.38.0
0.37.7
0.37.6
0.37.5
0.37.4
0.37.3
0.37.2
0.37.1
0.37.0
0.36.6
0.36.5
0.36.4
0.36.3
0.36.2
0.36.1
0.36.0
0.35.4
0.35.3
0.35.2
0.35.1
0.35.0
0.34.1
0.34.0
0.33.1
0.33.0
0.32.5
0.32.4
0.32.3
0.32.2
0.32.1
0.32.0
0.31.9
0.31.8
0.31.7
0.31.6
0.31.5
0.31.4
0.31.3
0.31.2
0.31.1
0.31.0
0.30.10
0.30.9
0.30.8
0.30.7
0.30.6
0.30.5
0.30.4
0.30.3
0.30.2
0.30.1
0.30.0
0.29.0
0.28.3
0.28.2
0.28.1
0.28.0
0.27.1
0.27.0
0.26.1
0.26.0
0.25.6
0.25.5
0.25.4
0.25.3
0.25.2
0.25.1
0.25.0
0.24.6
0.24.5
0.24.4
0.24.3
0.24.2
0.24.1
0.24.0
0.23.3
0.23.2
0.23.1
0.23.0
0.22.12
0.22.11
0.22.10
0.22.9
0.22.8
0.22.7
0.22.6
0.22.5
0.22.4
0.22.3
0.22.2
0.22.1
0.22.0
0.21.7
0.21.6
0.21.5
0.21.4
0.21.2
0.21.1
0.21.0
0.20.25
0.20.24
0.20.23
0.20.22
0.20.21
0.20.20
0.20.19
0.20.18
0.20.17
0.20.16
0.20.15
0.20.14
0.20.13
0.20.12
0.20.11
0.20.10
0.20.9
0.20.8
0.20.7
0.20.6
0.20.5
0.20.3
0.20.2
0.20.1
0.20.0
0.19.2
0.19.1
0.19.0
0.18.1
0.18.0
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.6
0.16.5
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.1
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.12.6
0.12.5
0.12.3
0.11.3
0.10.2
0.10.1
0.10.0
0.9.2
0.9.1
0.9.0
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.10
0.8.9
0.8.8
0.8.7
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.30
0.7.29
0.7.28
0.7.27
0.7.26
0.7.25
0.7.24
0.7.23
0.7.22
0.7.21
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.0
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.4
0.3.3
0.3.2
0.3.1
Macula HTTP/3 Mesh SDK — connect, subscribe, publish, call, advertise
Current section
Files
Jump to
Current section
Files
src/macula_net/macula_net.erl
%%%-------------------------------------------------------------------
%%% @doc macula-net facade.
%%%
%%% Sovereign IPv6 substrate for the macula realm. Crypto-derived
%%% addresses, multi-realm identity, no central allocation authority.
%%% See PLAN_MACULA_NET.md for the full spec.
%%%
%%% This module orchestrates the slices:
%%%
%%% <ul>
%%% <li>{@link macula_address} — pubkey -> IPv6</li>
%%% <li>{@link macula_tun} — kernel-side TUN</li>
%%% <li>{@link macula_net_transport_quic} — wire transport (Quinn)</li>
%%% <li>{@link macula_route_packet} — egress: TUN packet -> station</li>
%%% <li>{@link macula_deliver_packet} — ingress: envelope -> TUN</li>
%%% </ul>
%%%
%%% == Lifecycle ==
%%%
%%% ```
%%% {ok, _} = macula_net:start(#{
%%% realm_pubkey => <<...32 bytes...>>,
%%% identity_pubkey => <<...32 bytes...>>,
%%% tun_name => <<"macula0">>,
%%% tun_mtu => 1280,
%%% quic_port => 4400,
%%% peers => [
%%% #{station => <<"beam02">>,
%%% host => "192.168.1.12", port => 4400,
%%% addresses => [<<...16 bytes...>>]}
%%% ]
%%% }).
%%% '''
%%% @end
%%%-------------------------------------------------------------------
-module(macula_net).
-export([
start/1,
stop/0,
derive_address/2,
format_address/1,
own_address/0
]).
-export_type([
node_config/0,
peer_spec/0
]).
-type node_config() :: #{
realm_pubkey := <<_:256>>,
identity_pubkey := <<_:256>>,
tun_name := binary(),
tun_mtu := 1280..65535,
quic_port := inet:port_number(),
peers := [peer_spec()]
}.
-type peer_spec() :: #{
station := binary(),
host := binary() | string(),
port := inet:port_number(),
addresses := [<<_:128>>]
}.
-define(OWN_ADDR, macula_net_own_address).
%% =============================================================================
%% Lifecycle
%% =============================================================================
%% @doc Bring up macula-net for this node with the supplied config.
-spec start(node_config()) -> {ok, pid()} | {error, term()}.
start(#{realm_pubkey := Realm,
identity_pubkey := Identity,
tun_name := TunName,
tun_mtu := Mtu,
quic_port := QuicPort,
peers := Peers}) ->
OwnAddr = macula_address:derive(Realm, Identity),
persistent_term:put(?OWN_ADDR, OwnAddr),
%% 1. QUIC transport listener.
{ok, _Tr} = ensure_started(macula_net_transport_quic, fun() ->
macula_net_transport_quic:start_link(#{port => QuicPort})
end),
%% 2. TUN device (skipped if no CAP_NET_ADMIN; demo will surface).
TunHandle = open_tun(TunName, Mtu),
%% 3. Ingress: which addresses are local + how to write to TUN.
LocalAddrs = [OwnAddr],
TunWriter = make_tun_writer(TunHandle),
ok = macula_deliver_packet:configure(#{
local_addresses => LocalAddrs,
tun_writer => TunWriter
}),
%% 4. Wire the transport's inbound handler to deliver_packet.
%% Phase 3.5 promoted the handler to arity-2 (Cbor, StreamRef);
%% station-only nodes ignore the StreamRef. Host stations replace
%% this wiring with macula_host_attach_controller:handle/2 — see
%% PLAN_MACULA_NET_PHASE3_5_TRANSPORT_SEAM.md.
ok = macula_net_transport_quic:set_handler(
fun(Cbor, _StreamRef) ->
macula_deliver_packet:handle_envelope(Cbor)
end),
%% 5. Connect outbound to each peer + populate egress route table.
SendFun = fun(StationId, Cbor) ->
macula_net_transport_quic:send(StationId, Cbor)
end,
Stations = lists:flatmap(
fun(#{station := S, host := H, port := P, addresses := As}) ->
_ = macula_net_transport_quic:connect(S, H, P), %% best effort
[#{address => A, station => S, send => SendFun} || A <- As]
end, Peers),
ok = macula_route_packet:configure(#{own_address => OwnAddr, stations => Stations}),
%% 6. Start TUN reader (if we have a TUN). Each packet -> route_packet:dispatch.
start_tun_reader(TunHandle),
{ok, self()}.
%% @doc Bring everything down.
-spec stop() -> ok.
stop() ->
catch macula_net_transport_quic:stop(),
persistent_term:erase(?OWN_ADDR),
ok.
%% =============================================================================
%% Convenience helpers
%% =============================================================================
%% @doc Derive a macula-net IPv6 address. See {@link macula_address:derive/2}.
-spec derive_address(<<_:256>>, <<_:256>>) -> <<_:128>>.
derive_address(RealmPk, IdentityPk) ->
macula_address:derive(RealmPk, IdentityPk).
%% @doc Format a 16-byte IPv6 binary as RFC 5952 lowercase text.
-spec format_address(<<_:128>>) -> binary().
format_address(Addr) ->
macula_address:format(Addr).
%% @doc Return THIS node's macula-net address (set by {@link start/1}).
-spec own_address() -> <<_:128>> | undefined.
own_address() ->
persistent_term:get(?OWN_ADDR, undefined).
%% =============================================================================
%% Internals
%% =============================================================================
ensure_started(Name, StartFun) ->
decide_start(whereis(Name), StartFun).
decide_start(undefined, StartFun) ->
pid_or_already(StartFun());
decide_start(Pid, _StartFun) ->
{ok, Pid}.
pid_or_already({ok, Pid}) -> {ok, Pid};
pid_or_already({error, {already_started, Pid}}) -> {ok, Pid};
pid_or_already(Other) -> Other.
%% Try to open the TUN device. If it fails (no CAP_NET_ADMIN, no
%% /dev/net/tun, wrong platform), log and continue — the demo runner
%% will surface the missing device.
open_tun(Name, Mtu) ->
open_tun_result(macula_tun:open(Name, Mtu), Name, Mtu).
open_tun_result({ok, Handle}, Name, Mtu) ->
error_logger:info_msg("macula-net: TUN ~s up (mtu ~p)", [Name, Mtu]),
Handle;
open_tun_result({error, Reason}, _Name, _Mtu) ->
error_logger:warning_msg(
"macula-net: TUN open failed: ~p — running without TUN", [Reason]),
skipped.
make_tun_writer(skipped) ->
fun(_Pkt) -> {error, no_tun} end;
make_tun_writer(Handle) ->
fun(Pkt) -> macula_tun:write(Handle, Pkt) end.
start_tun_reader(skipped) ->
ok;
start_tun_reader(Handle) ->
{ok, ReaderPid} = macula_tun_reader_proxy:start_link(#{
dispatch_fn => fun macula_route_packet:dispatch/1
}),
ok = macula_tun:start_reader(Handle, ReaderPid).