%%%------------------------------------------------------------------- %% @doc Runtime peer abstraction over vpn_link. %%%------------------------------------------------------------------- -module(vpn_peer). -behaviour(gen_server). -export([start_link/1, stop/1, stats/1, reset_stats/1, identity/1, identity_info/1, config/1]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]). start_link(Config) -> gen_server:start_link(?MODULE, Config, []). stop(Pid) -> gen_server:stop(Pid). stats(Pid) -> gen_server:call(Pid, stats). reset_stats(Pid) -> gen_server:call(Pid, reset_stats). identity(Pid) -> gen_server:call(Pid, identity). identity_info(Pid) -> gen_server:call(Pid, identity_info). config(Pid) -> gen_server:call(Pid, config). init(Config) -> process_flag(trap_exit, true), case validate_config(Config) of ok -> start_link_with_identity(Config); {error, Reason} -> {stop, Reason} end. handle_call(stats, _From, State = #{id := Id, link_pid := LinkPid}) -> LinkStats = vpn_link:stats(LinkPid), {reply, #{id => Id, link => LinkStats}, State}; handle_call(reset_stats, _From, State = #{link_pid := LinkPid}) -> {reply, vpn_link:reset_stats(LinkPid), State}; handle_call(identity, _From, State = #{identity := Identity}) -> {reply, Identity, State}; handle_call(identity_info, _From, State = #{identity_info := IdentityInfo}) -> {reply, vpn_identity:safe_info(IdentityInfo), State}; handle_call(config, _From, State = #{config := Config}) -> {reply, runtime_config(Config), State}; handle_call(_Request, _From, State) -> {reply, {error, not_implemented}, State}. handle_cast(_Request, State) -> {noreply, State}. handle_info({'EXIT', LinkPid, Reason}, State = #{link_pid := LinkPid}) -> {stop, {link_exit, Reason}, State}; handle_info(_Message, State) -> {noreply, State}. terminate(_Reason, State) -> stop_link(maps:get(link_pid, State, undefined)), ok. start_link_with_identity(Config) -> case vpn_identity:load(Config) of {ok, IdentityInfo} -> start_link_from_config(Config, IdentityInfo); {error, Reason} -> {stop, Reason} end. start_link_from_config(Config, IdentityInfo) -> Id = maps:get(id, Config), Mode = maps:get(mode, Config), IfName = maps:get(ifname, Config), Ip = maps:get(ip, Config), LocalUdpPort = maps:get(local_udp_port, Config), RemoteIp = maps:get(remote_ip, Config), RemoteUdpPort = maps:get(remote_udp_port, Config), RemotePeerId = maps:get(remote_peer_id, Config), Psk = maps:get(psk, Config), Identity = identity_from_config(Config), case vpn_link:start_link(IfName, Ip, Mode, LocalUdpPort, RemoteIp, RemoteUdpPort, Id, RemotePeerId, Psk) of {ok, LinkPid} -> logger:info("vpn_peer started: ~p", [Id]), {ok, #{id => Id, config => Config, identity => Identity, identity_info => IdentityInfo, link_pid => LinkPid}}; {error, Reason} -> {stop, Reason} end. validate_config(Config) when is_map(Config) -> case missing_key(Config) of none -> validate_mode(maps:get(mode, Config)); {missing, Key} -> {error, {missing_config_key, Key}} end; validate_config(_Config) -> {error, invalid_config}. missing_key(Config) -> Required = [id, mode, ifname, ip, local_udp_port, remote_ip, remote_udp_port, remote_peer_id, psk, certificate_path, private_key_path, ca_certificate_path], missing_key(Config, Required). missing_key(_Config, []) -> none; missing_key(Config, [Key | Rest]) -> case maps:is_key(Key, Config) of true -> missing_key(Config, Rest); false -> {missing, Key} end. validate_mode(tap) -> ok; validate_mode(tun) -> ok; validate_mode(Mode) -> {error, {invalid_mode, Mode}}. identity_from_config(Config) -> #{id => maps:get(id, Config), name => maps:get(name, Config, undefined), certificate_path => maps:get(certificate_path, Config, undefined), private_key_path => maps:get(private_key_path, Config, undefined), ca_certificate_path => maps:get(ca_certificate_path, Config, undefined)}. runtime_config(Config) -> maps:with([id, mode, ifname, ip, local_udp_port, remote_ip, remote_udp_port, remote_peer_id], Config). stop_link(undefined) -> ok; stop_link(LinkPid) -> case is_process_alive(LinkPid) of true -> _ = vpn_link:stop(LinkPid), ok; false -> ok end.