diff --git a/apps/sdlan/src/sdlan_register_worker.erl b/apps/sdlan/src/sdlan_register_worker.erl new file mode 100644 index 0000000..63dd049 --- /dev/null +++ b/apps/sdlan/src/sdlan_register_worker.erl @@ -0,0 +1,127 @@ +%%%------------------------------------------------------------------- +%%% @author anlicheng +%%% @copyright (C) 2026, +%%% @doc +%%% +%%% @end +%%% Created : 23. 1月 2026 15:20 +%%%------------------------------------------------------------------- +-module(sdlan_register_worker). +-author("anlicheng"). +-include("sdlan_pb.hrl"). +-include("sdlan.hrl"). + +%% token不存在 +-define(NAK_INVALID_TOKEN, 1). +%% 节点被禁用 +-define(NAK_NODE_DISABLE, 2). +%% 没有IP地址可以用 +-define(NAK_NO_IP, 3). +%% 网络错误 +-define(NAK_NETWORK_FAULT, 4). +%% 内部错误 +-define(NAK_INTERNAL_FAULT, 5). +%% hostname被占用 +-define(NAK_HOSTNAME_USED, 6). + +%% API +-export([start_link/4, do_work/4]). + +-spec start_link(Sock :: inet:socket(), Ip :: inet:ip4_address(), Port :: integer(), Packet :: binary()) -> {ok, pid()}. +start_link(Sock, Ip, Port, Packet) -> + {ok, erlang:spawn_link(?MODULE, do_work, [Sock, Ip, Port, Packet])}. + +do_work(Sock, Ip, Port, <>) -> + #sdl_register_super{version = Version, client_id = ClientId, dev_addr = #sdl_dev_addr{mac = Mac}, network_code = NetworkCode, token = Token, pub_key = PubKey} = sdlan_pb:decode_msg(Body, sdl_register_super), + + %% 参数检查 + logger:debug("[sdlan_channel] client_id: ~p, public_key: ~p, token: ~p, network_code: ~p", [ClientId, PubKey, Token, NetworkCode]), + true = (Mac =/= <<>> andalso PubKey =/= <<>> andalso ClientId =/= <<>>), + %% Mac地址不能是广播地址 + true = not (sdlan_util:is_multicast_mac(Mac) orelse sdlan_util:is_broadcast_mac(Mac)), + + AuthResult = if + Token /= <<>> -> + logger:debug("[sdlan_channel] auth token: ~p", [Token]), + sdlan_api:auth_token(ClientId, Token, Version); + NetworkCode /= <<>> -> + logger:debug("[sdlan_channel] auth network code: ~p", [NetworkCode]), + sdlan_api:auth_network_code(ClientId, NetworkCode, Version) + end, + + case AuthResult of + {ok, #{<<"result">> := #{<<"network_id">> := NetworkId, <<"upgrade_type">> := UpgradeType, <<"upgrade_prompt">> := UpgradePrompt, <<"upgrade_address">> := UpgradeAddress}}} when is_integer(NetworkId) -> + logger:debug("[sdlan_channel] client_id: ~p, mac: ~p, token: ~p, version: ~p, registerd, alloc network_id: ~p", [ClientId, sdlan_util:format_mac(Mac), Token, Version, NetworkId]), + %% 建立到network的对应关系 + case sdlan_network:get_pid(NetworkId) of + NetworkPid when is_pid(NetworkPid) -> + try sdlan_network:assign_ip_addr(NetworkPid, self(), ClientId, Mac) of + {ok, Domain, NetAddr, NetBitLen, AesKey} -> + RsaPubKey = sdlan_cipher:rsa_pem_decode(PubKey), + EncodedAesKey = rsa_encode(AesKey, RsaPubKey), + + RegisterSuperAck = sdlan_pb:encode_msg(#sdl_register_super_ack { + dev_addr = #sdl_dev_addr{ + network_id = NetworkId, + net_addr = NetAddr, + mac = Mac, + net_bit_len = NetBitLen, + network_domain = Domain + }, + aes_key = EncodedAesKey, + upgrade_type = UpgradeType, + upgrade_prompt = UpgradePrompt, + upgrade_address = UpgradeAddress + }), + + %% 发送确认信息 + Reply = <>, + gen_udp:send(Sock, Ip, Port, Reply), + + logger:debug("[sdlan_channel] client_id: ~p, mac: ~p, alloc ip: ~p, register will send ack, aes_key: ~p, enc_aes_key: ~p", + [ClientId, sdlan_util:format_mac(Mac), sdlan_ipaddr:int_to_ipv4(NetAddr), AesKey, EncodedAesKey]), + + %% 设置节点的在线状态 + Result = sdlan_api:node_online(ClientId, NetworkId, sdlan_ipaddr:int_to_ipv4(NetAddr)), + logger:debug("[sdlan_channel] client_id: ~p, set none online, result is: ~p", [ClientId, Result]), + + %% TODO 这里要处理session_token的逻辑 + ok; + {error, no_ip} -> + logger:warning("[sdlan_channel] client_id: ~p, token: ~p, register get error: no_ip", [ClientId, Token]), + gen_udp:send(Sock, Ip, Port, register_nak_reply(PacketId, ?NAK_NO_IP, <<"No Ip address">>)); + {error, host_name_used} -> + logger:warning("[sdlan_channel] client_id: ~p, token: ~p, register get error: host_name_used", [ClientId, Token]), + gen_udp:send(Sock, Ip, Port, register_nak_reply(PacketId, ?NAK_HOSTNAME_USED, <<"Host Name Used">>)); + {error, client_disabled} -> + logger:warning("[sdlan_channel] client_id: ~p, token: ~p, register get error: client_disabled", [ClientId, Token]), + gen_udp:send(Sock, Ip, Port, register_nak_reply(PacketId, ?NAK_NODE_DISABLE, <<"Client Connection Disable">>)) + catch _:Error:Stack -> + logger:warning("[sdlan_channel] get error: ~p, stack: ~p", [Error, Stack]) + end; + undefined -> + logger:warning("[sdlan_channel] client_id: ~p, token: ~p, register get error: network not found", [ClientId, Token]), + gen_udp:send(Sock, Ip, Port, register_nak_reply(PacketId, ?NAK_INTERNAL_FAULT, <<"Internal Error">>)) + end; + {ok, #{<<"error">> := #{<<"code">> := 1, <<"message">> := Message}}} -> + logger:warning("[sdlan_channel] client_id: ~p, token: ~p, register get error: ~ts, error_code: 1", [ClientId, Token, Message]), + gen_udp:send(Sock, Ip, Port, register_nak_reply(PacketId, ?NAK_INVALID_TOKEN, Message)); + {ok, #{<<"error">> := #{<<"code">> := 2, <<"message">> := Message}}} -> + logger:warning("[sdlan_channel] client_id: ~p, token: ~p, register get error: ~p, error_code: 2", [ClientId, Token, Message]), + gen_udp:send(Sock, Ip, Port, register_nak_reply(PacketId, ?NAK_NODE_DISABLE, Message)); + {error, Reason} -> + logger:warning("[sdlan_channel] client_id: ~p, token: ~p, register get error: ~p", [ClientId, Token, Reason]), + gen_udp:send(Sock, Ip, Port, register_nak_reply(PacketId, ?NAK_NETWORK_FAULT, <<"Network Error">>)) + end, + exit(normal). + +-spec register_nak_reply(PacketId :: integer(), ErrorCode :: integer(), ErrorMsg :: binary()) -> binary(). +register_nak_reply(PacketId, ErrorCode, ErrorMsg) when is_integer(PacketId), is_integer(ErrorCode), is_binary(ErrorMsg) -> + RegisterNakReply = sdlan_pb:encode_msg(#sdl_register_super_nak { + error_code = ErrorCode, + error_message = ErrorMsg + }), + <>. + +rsa_encode(PlainText, RsaPubKey) when is_binary(PlainText) -> + iolist_to_binary(sdlan_cipher:rsa_encrypt(PlainText, RsaPubKey)). \ No newline at end of file diff --git a/apps/sdlan/src/sdlan_register_worker_sup.erl b/apps/sdlan/src/sdlan_register_worker_sup.erl new file mode 100644 index 0000000..63b5465 --- /dev/null +++ b/apps/sdlan/src/sdlan_register_worker_sup.erl @@ -0,0 +1,65 @@ +%%%------------------------------------------------------------------- +%%% @author anlicheng +%%% @copyright (C) 2026, +%%% @doc +%%% +%%% @end +%%% Created : 23. 1月 2026 15:19 +%%%------------------------------------------------------------------- +-module(sdlan_register_worker_sup). +-author("anlicheng"). + +-behaviour(supervisor). + +%% API +-export([start_link/0]). +-export([start_worker/4]). + +%% Supervisor callbacks +-export([init/1]). + +-define(SERVER, ?MODULE). + +%%%=================================================================== +%%% API functions +%%%=================================================================== + +%% @doc Starts the supervisor +-spec(start_link() -> {ok, Pid :: pid()} | ignore | {error, Reason :: term()}). +start_link() -> + supervisor:start_link({local, ?SERVER}, ?MODULE, []). + +%%%=================================================================== +%%% Supervisor callbacks +%%%=================================================================== + +%% @private +%% @doc Whenever a supervisor is started using supervisor:start_link/[2,3], +%% this function is called by the new process to find out about +%% restart strategy, maximum restart frequency and child +%% specifications. +-spec(init(Args :: term()) -> + {ok, {SupFlags :: {RestartStrategy :: supervisor:strategy(), + MaxR :: non_neg_integer(), MaxT :: non_neg_integer()}, + [ChildSpec :: supervisor:child_spec()]}} + | ignore | {error, Reason :: term()}). +init([]) -> + SupFlags = #{strategy => simple_one_for_one, intensity => 1000, period => 3600}, + + ChildSpec = #{id => sdlan_register_worker, + start => {sdlan_register_worker, start_link, []}, + restart => temporary, + shutdown => 2000, + type => worker, + modules => [sdlan_register_worker]}, + + {ok, {SupFlags, ChildSpec}}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== + +-spec start_worker(Sock :: inet:socket(), Ip :: inet:ip4_address(), Port :: integer(), Packet :: binary()) -> supervisor:startchild_ret(). +start_worker(Sock, Ip, Port, Packet) -> + %% Start a temporary worker under the supervisor + supervisor:start_child(?SERVER, [Sock, Ip, Port, Packet]). \ No newline at end of file diff --git a/apps/sdlan/src/sdlan_stun.erl b/apps/sdlan/src/sdlan_stun.erl index 8be0762..9060c89 100644 --- a/apps/sdlan/src/sdlan_stun.erl +++ b/apps/sdlan/src/sdlan_stun.erl @@ -132,6 +132,37 @@ code_change(_OldVsn, State = #state{}, _Extra) -> %%%=================================================================== -spec handle_packet(Sock :: inet:socket(), Ip :: inet:ip4_address(), Port :: integer(), Packet :: binary()) -> no_return(). + +%% 带上token或者网络id来注册 +handle_packet(Sock, Ip, Port, <>) -> + sdlan_register_worker_sup:start_worker(Sock, Ip, Port, Body); + +%handle_packet(Sock, Ip, Port, <>) -> +% #sdl_query_info{dst_mac = DstMac} = sdlan_pb:decode_msg(Body, sdl_query_info), +% case sdlan_network:peer_info(NetworkPid, SrcMac, DstMac) of +% error -> +% logger:debug("[sdlan_channel] query_info src_mac is: ~p, dst_mac: ~p, nat_peer not found", +% [sdlan_util:format_mac(SrcMac), sdlan_util:format_mac(DstMac)]), +% +% Transport:send(Sock, <>), +% {noreply, State}; +% {ok, {NatPeer = {{Ip0, Ip1, Ip2, Ip3}, NatPort}, NatType}, V6Info} -> +% logger:debug("[sdlan_channel] query_info src_mac is: ~p, dst_mac: ~p, nat_peer: ~p", +% [sdlan_util:format_mac(SrcMac), sdlan_util:format_mac(DstMac), NatPeer]), +% +% PeerInfo = sdlan_pb:encode_msg(#sdl_peer_info{ +% dst_mac = DstMac, +% v4_info = #sdl_v4_info { +% port = NatPort, +% v4 = <>, +% nat_type = NatType +% }, +% v6_info = V6Info +% }), +% Transport:send(Sock, <>), +% {noreply, State} +% end; + handle_packet(Sock, Ip, Port, <>) -> #sdl_stun_request{cookie = Cookie, client_id = ClientId, network_id = NetworkId, mac = Mac, nat_type = NatType, v6_info = V6Info} = sdlan_pb:decode_msg(Body, sdl_stun_request), %% 告知网络当前的ip对应的nat的映射关系