This commit is contained in:
anlicheng 2026-01-26 16:38:34 +08:00
parent 75fa239ae9
commit d64cb4235f
3 changed files with 76 additions and 61 deletions

View File

@ -24,8 +24,8 @@
%% API %% API
-export([start_link/2]). -export([start_link/2]).
-export([get_name/1, get_pid/1, lookup_pid/1, attach/6, peer_info/6, unregister/3, debug_info/1, get_network_id/1, arp_query/2]). -export([get_name/1, get_pid/1, lookup_pid/1, attach/6, peer_info/5, unregister/3, debug_info/1, get_network_id/1, arp_query/2]).
-export([forward/5, update_hole/6, disable_client/2, dropout_client/2]). -export([forward/5, stun_request/4, disable_client/2, dropout_client/2]).
-export([test_event/1]). -export([test_event/1]).
%% gen_server callbacks %% gen_server callbacks
@ -45,8 +45,8 @@
hole :: #hole{}, hole :: #hole{},
%% ip和ip_v6的映射关系, #{ip_addr :: integer() => {}} %% ip和ip_v6的映射关系, #{ip_addr :: integer() => {}}
v6_info :: undefined | #sdl_v6_info{}, v6_info :: undefined | #sdl_v6_info{},
session_token :: binary(),
last_seen :: integer() %% monotonic_time(second) last_seen :: integer() %% monotonic_time(second),
}). }).
-record(state, { -record(state, {
@ -107,18 +107,18 @@ arp_query(Pid, TargetIp) when is_pid(Pid), is_integer(TargetIp) ->
unregister(Pid, ClientId, Mac) when is_pid(Pid), is_binary(ClientId), is_binary(Mac) -> unregister(Pid, ClientId, Mac) when is_pid(Pid), is_binary(ClientId), is_binary(Mac) ->
gen_server:cast(Pid, {unregister, ClientId, Mac}). gen_server:cast(Pid, {unregister, ClientId, Mac}).
-spec peer_info(Pid :: pid(), Sock :: inet:socket(), SrcIp :: inet:ip4_address(), SrcPort :: integer(), PacketId :: integer(), Query :: #sdl_query_info{}) -> no_return(). -spec peer_info(Pid :: pid(), Sock :: inet:socket(), Peer :: {inet:ip4_address(), integer()}, PacketId :: integer(), Query :: #sdl_query_info{}) -> no_return().
peer_info(Pid, Sock, ClientIp, ClientPort, PacketId, Query) when is_pid(Pid) -> peer_info(Pid, Sock, ClientPeer, PacketId, Query) when is_pid(Pid) ->
gen_server:cast(Pid, {peer_info, Sock, ClientIp, ClientPort, PacketId, Query}). gen_server:cast(Pid, {peer_info, Sock, ClientPeer, PacketId, Query}).
-spec forward(pid(), Sock :: any(), SrcMac :: binary(), DstMac :: binary(), Packet :: binary()) -> no_return(). -spec forward(pid(), Sock :: any(), SrcMac :: binary(), DstMac :: binary(), Packet :: binary()) -> no_return().
forward(Pid, Sock, SrcMac, DstMac, Packet) when is_pid(Pid), is_binary(SrcMac), is_binary(DstMac), is_binary(Packet) -> forward(Pid, Sock, SrcMac, DstMac, Packet) when is_pid(Pid), is_binary(SrcMac), is_binary(DstMac), is_binary(Packet) ->
gen_server:cast(Pid, {forward, Sock, SrcMac, DstMac, Packet}). gen_server:cast(Pid, {forward, Sock, SrcMac, DstMac, Packet}).
%% ip地址对应的nat关系 %% ip地址对应的nat关系
-spec update_hole(Pid :: pid(), ClientId :: binary(), Mac :: binary(), Peer :: tuple(), NatType :: integer(), V6Info :: undefined | #sdl_v6_info{}) -> no_return(). -spec stun_request(Pid :: pid(), Sock :: inet:socket(), ClientPeer :: {inet:ip4_address(), integer()}, StunRequest :: #sdl_stun_request{}) -> no_return().
update_hole(Pid, ClientId, Mac, Peer, NatType, V6Info) when is_pid(Pid), is_binary(ClientId), is_binary(Mac), is_integer(NatType) -> stun_request(Pid, Sock, ClientPeer, StunRequest) when is_pid(Pid) ->
gen_server:cast(Pid, {update_hole, ClientId, Mac, Peer, NatType, V6Info}). gen_server:cast(Pid, {stun_request, Sock, ClientPeer, StunRequest}).
-spec disable_client(Pid :: pid(), ClientId :: binary()) -> ok. -spec disable_client(Pid :: pid(), ClientId :: binary()) -> ok.
disable_client(Pid, ClientId) when is_pid(Pid), is_binary(ClientId) -> disable_client(Pid, ClientId) when is_pid(Pid), is_binary(ClientId) ->
@ -208,10 +208,11 @@ handle_call({attach, Peer, ClientId, Mac, Ip, Hostname}, _From,
sdlan_stun_pool:send_packets(EndpointPeers, EventPacket) sdlan_stun_pool:send_packets(EndpointPeers, EventPacket)
end, end,
Endpoint = #endpoint{client_id = ClientId, mac = Mac, ip = Ip, hostname = Hostname, SessionToken = gen_session_token(),
Endpoint = #endpoint{client_id = ClientId, mac = Mac, ip = Ip, hostname = Hostname, session_token = SessionToken,
hole = #hole{peer = Peer, nat_type = 0}, last_seen = erlang:monotonic_time(second)}, hole = #hole{peer = Peer, nat_type = 0}, last_seen = erlang:monotonic_time(second)},
{reply, {ok, Domain, MaskLen, AesKey}, State#state{endpoints = maps:put(Mac, Endpoint, Endpoints)}}; {reply, {ok, AesKey, SessionToken}, State#state{endpoints = maps:put(Mac, Endpoint, Endpoints)}};
%% client设置为禁止状态 %% client设置为禁止状态
handle_call({disable_client, ClientId}, _From, State = #state{endpoints = Endpoints}) -> handle_call({disable_client, ClientId}, _From, State = #state{endpoints = Endpoints}) ->
@ -234,8 +235,6 @@ handle_call({arp_query, TargetIp}, _From, State = #state{endpoints = Endpoints})
{reply, error, State} {reply, error, State}
end; end;
handle_call(debug_info, _From, State = #state{network_id = NetworkId, ipaddr = IpAddr, mask_len = MaskLen, endpoints = Endpoints}) -> handle_call(debug_info, _From, State = #state{network_id = NetworkId, ipaddr = IpAddr, mask_len = MaskLen, endpoints = Endpoints}) ->
Reply = #{ Reply = #{
<<"network_id">> => NetworkId, <<"network_id">> => NetworkId,
@ -254,7 +253,7 @@ handle_call(debug_info, _From, State = #state{network_id = NetworkId, ipaddr = I
%% nat_peer信息 %% nat_peer信息
%% TODO SrcMac不存在的时候需要重新校验 %% TODO SrcMac不存在的时候需要重新校验
handle_cast({peer_info, Sock, ClientIp, ClientPort, PacketId, #sdl_query_info{src_mac = SrcMac, dst_mac = DstMac}}, State = #state{endpoints = Endpoints}) -> handle_cast({peer_info, Sock, {ClientIp, ClientPort}, PacketId, #sdl_query_info{src_mac = SrcMac, dst_mac = DstMac}}, State = #state{endpoints = Endpoints}) ->
case maps:find(DstMac, Endpoints) of case maps:find(DstMac, Endpoints) of
{ok, #endpoint{hole = #hole{peer = DstNatPeer = {{Ip0, Ip1, Ip2, Ip3}, DstNatPort}, nat_type = DstNatType}, v6_info = DstV6Info}} -> {ok, #endpoint{hole = #hole{peer = DstNatPeer = {{Ip0, Ip1, Ip2, Ip3}, DstNatPort}, nat_type = DstNatType}, v6_info = DstV6Info}} ->
%% sendRegister事件(2024-06-25 ) %% sendRegister事件(2024-06-25 )
@ -341,9 +340,12 @@ handle_cast({unregister, _ClientId, Mac}, State = #state{network_id = NetworkId,
%% client是属于当前网络的 %% client是属于当前网络的
%% endpoint需要通知其重新验证 %% endpoint需要通知其重新验证
handle_cast({update_hole, ClientId, Mac, Peer, NatType, V6Info}, State = #state{network_id = NetworkId, endpoints = Endpoints}) -> handle_cast({stun_request, Sock, Peer = {ClientIp, ClientPort}, #sdl_stun_request{mac = Mac, client_id = ClientId, nat_type = NatType, v6_info = V6Info, session_token = ST}},
State = #state{network_id = NetworkId, endpoints = Endpoints}) ->
case maps:find(Mac, Endpoints) of case maps:find(Mac, Endpoints) of
{ok, Endpoint0 = #endpoint{ip = Ip, client_id = ClientId0, hole = OldHole}} when ClientId =:= ClientId0 -> %% ClientId =:= ClientId0, SessionToken =:= SessionToken0
{ok, Endpoint0 = #endpoint{ip = Ip, client_id = ClientId, hole = OldHole, session_token = ST}} ->
NHole = #hole{peer = Peer, nat_type = NatType}, NHole = #hole{peer = Peer, nat_type = NatType},
maybe maybe
%% hole changed -> notify peers %% hole changed -> notify peers
@ -364,7 +366,7 @@ handle_cast({update_hole, ClientId, Mac, Peer, NatType, V6Info}, State = #state{
network_id = NetworkId network_id = NetworkId
}), }),
EventPacket = <<?PACKET_EVENT, ?PACKET_EVENT_REFRESH_AUTH, RefreshAuthEvent/binary>>, EventPacket = <<?PACKET_EVENT, ?PACKET_EVENT_REFRESH_AUTH, RefreshAuthEvent/binary>>,
sdlan_stun_pool:send_packet(Peer, EventPacket), gen_udp:send(Sock, ClientIp, ClientPort, EventPacket),
{noreply, State} {noreply, State}
end. end.
@ -549,3 +551,8 @@ endpoint_peers(ExcludeMacs, Endpoints) when is_list(ExcludeMacs), is_map(Endpoin
{true, Peer} {true, Peer}
end end
end, Endpoints)). end, Endpoints)).
-spec gen_session_token() -> binary().
gen_session_token() ->
Bytes = crypto:strong_rand_bytes(32),
base64:encode(Bytes).

View File

@ -89,7 +89,7 @@ handle_cast({stun_relay, Ip, Port, Reply}, State = #state{socket = Sock}) ->
{noreply, NewState :: #state{}, timeout() | hibernate} | {noreply, NewState :: #state{}, timeout() | hibernate} |
{stop, Reason :: term(), NewState :: #state{}}). {stop, Reason :: term(), NewState :: #state{}}).
handle_info({udp, Sock, PeerIp, PeerPort, Packet}, State = #state{socket = Sock}) -> handle_info({udp, Sock, PeerIp, PeerPort, Packet}, State = #state{socket = Sock}) ->
catch handle_packet(Sock, PeerIp, PeerPort, Packet), handle_packet(Sock, PeerIp, PeerPort, Packet),
{noreply, State}; {noreply, State};
handle_info({udp_error , Sock, Reason}, State = #state{socket = Sock}) -> handle_info({udp_error , Sock, Reason}, State = #state{socket = Sock}) ->
{stop, Reason, State}; {stop, Reason, State};
@ -120,41 +120,36 @@ code_change(_OldVsn, State = #state{}, _Extra) ->
%%%=================================================================== %%%===================================================================
-spec handle_packet(Sock :: inet:socket(), Ip :: inet:ip4_address(), Port :: integer(), Packet :: binary()) -> no_return(). -spec handle_packet(Sock :: inet:socket(), Ip :: inet:ip4_address(), Port :: integer(), Packet :: binary()) -> no_return().
%% token或者网络id来注册 %% token或者网络id来注册
handle_packet(Sock, Ip, Port, <<?PACKET_REGISTER_SUPER, Body/binary>>) -> handle_packet(Sock, Ip, Port, <<?PACKET_REGISTER_SUPER, Body/binary>>) ->
sdlan_register_worker_sup:start_worker(Sock, Ip, Port, Body); sdlan_register_worker_sup:start_worker(Sock, Ip, Port, Body);
%% %%
handle_packet(Sock, ClientIp, ClientPort, <<?PACKET_QUERY_INFO, PacketId:32, Body/binary>>) -> handle_packet(Sock, ClientIp, ClientPort, <<?PACKET_QUERY_INFO, PacketId:32, Body/binary>>) ->
Query = #sdl_query_info{network_id = NetworkId} = sdlan_pb:decode_msg(Body, sdl_query_info),
maybe maybe
Query = catch sdlan_pb:decode_msg(Body, sdl_query_info),
#sdl_query_info{network_id = NetworkId} ?= Query,
{ok, NetworkPid} ?= sdlan_network:lookup_pid(NetworkId), {ok, NetworkPid} ?= sdlan_network:lookup_pid(NetworkId),
sdlan_network:peer_info(NetworkPid, Sock, ClientIp, ClientPort, PacketId, Query) sdlan_network:peer_info(NetworkPid, Sock, {ClientIp, ClientPort}, PacketId, Query)
end; end;
handle_packet(Sock, Ip, Port, <<?PACKET_STUN_REQUEST:8, Body/binary>>) -> %%
#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), handle_packet(Sock, ClientIp, ClientPort, <<?PACKET_STUN_REQUEST:8, Body/binary>>) ->
maybe
StunRequest = catch sdlan_pb:decode_msg(Body, sdl_stun_request),
#sdl_stun_request{network_id = NetworkId} ?= StunRequest,
{ok, NetworkPid} ?= sdlan_network:lookup_pid(NetworkId),
%% ip对应的nat的映射关系 %% ip对应的nat的映射关系
case sdlan_network:get_pid(NetworkId) of sdlan_network:stun_request(NetworkPid, Sock, {ClientIp, ClientPort}, StunRequest)
undefined ->
logger:debug("[sdlan_stun] stun_request network_id: ~p, client_id: ~p, not found", [NetworkId, ClientId]),
ok;
NetworkPid when is_pid(NetworkPid) ->
sdlan_network:update_hole(NetworkPid, ClientId, Mac, {Ip, Port}, NatType, V6Info),
StunReply = sdlan_pb:encode_msg(#sdl_stun_reply{
cookie = Cookie
}),
ok = gen_udp:send(Sock, Ip, Port, <<?PACKET_STUN_REPLY, StunReply/binary>>),
logger:debug("[sdlan_stun] stun_request network_id: ~p, client_id: ~p, hole: ~p", [NetworkId, ClientId, {Ip, Port}])
end; end;
%% nat类型的探测机制, %% nat类型的探测机制,
%% assist的配置attr = 2 %% assist的配置attr = 2
handle_packet(Sock, Ip, Port, <<?PACKET_STUN_PROBE:8, Body/binary>>) -> handle_packet(Sock, Ip, Port, <<?PACKET_STUN_PROBE:8, Body/binary>>) ->
#sdl_stun_probe{cookie = Cookie, attr = Attr} = sdlan_pb:decode_msg(Body, sdl_stun_probe), maybe
#sdl_stun_probe{cookie = Cookie, attr = Attr} ?= sdlan_pb:decode_msg(Body, sdl_stun_probe),
logger:debug("[sdlan_stun] get stun_probe request, att: ~p", [Attr]), logger:debug("[sdlan_stun] get stun_probe request, att: ~p", [Attr]),
ProbeReply = sdlan_pb:encode_msg(#sdl_stun_probe_reply { ProbeReply = sdlan_pb:encode_msg(#sdl_stun_probe_reply {
cookie = Cookie, cookie = Cookie,
port = Port, port = Port,
@ -164,11 +159,12 @@ handle_packet(Sock, Ip, Port, <<?PACKET_STUN_PROBE:8, Body/binary>>) ->
case Attr of case Attr of
?STUN_ATTR_CHANGE_NONE -> ?STUN_ATTR_CHANGE_NONE ->
ok = gen_udp:send(Sock, Ip, Port, Packet); gen_udp:send(Sock, Ip, Port, Packet);
?STUN_ATTR_CHANGE_PORT -> ?STUN_ATTR_CHANGE_PORT ->
sdlan_stun_port_assist:stun_relay(Ip, Port, Packet); sdlan_stun_port_assist:stun_relay(Ip, Port, Packet);
?STUN_ATTR_CHANGE_PEER -> ?STUN_ATTR_CHANGE_PEER ->
sdlan_stun_peer_assist:stun_relay(Ip, Port, Packet) sdlan_stun_peer_assist:stun_relay(Ip, Port, Packet)
end
end; end;
%% , stun_reply的转发通过socket来转发 %% , stun_reply的转发通过socket来转发
@ -177,23 +173,29 @@ handle_packet(Sock, _, _, <<?PACKET_STUN_PROBE_RELAY:8, Ip0, Ip1, Ip2, Ip3, Port
gen_udp:send(Sock, {Ip0, Ip1, Ip2, Ip3}, Port, Reply); gen_udp:send(Sock, {Ip0, Ip1, Ip2, Ip3}, Port, Reply);
handle_packet(Sock, _Ip, _Port, <<?PACKET_STUN_DATA, Body/binary>>) -> handle_packet(Sock, _Ip, _Port, <<?PACKET_STUN_DATA, Body/binary>>) ->
Data = #sdl_data{network_id = NetworkId, src_mac = SrcMac, dst_mac = DstMac, ttl = TTL} = sdlan_pb:decode_msg(Body, sdl_data),
logger:debug("[sdlan_stun] stun data, src_mac: ~p, dst_mac: ~p", [sdlan_util:format_mac(SrcMac), sdlan_util:format_mac(DstMac)]),
maybe maybe
Data = catch sdlan_pb:decode_msg(Body, sdl_data),
#sdl_data{network_id = NetworkId, src_mac = SrcMac, dst_mac = DstMac, ttl = TTL} ?= Data,
{ok, NetworkPid} ?= sdlan_network:lookup_pid(NetworkId), {ok, NetworkPid} ?= sdlan_network:lookup_pid(NetworkId),
logger:debug("[sdlan_stun] stun data, src_mac: ~p, dst_mac: ~p", [sdlan_util:format_mac(SrcMac), sdlan_util:format_mac(DstMac)]),
%% ttl需要减1 %% ttl需要减1
NData = sdlan_pb:encode_msg(Data#sdl_data{ttl = TTL - 1, is_p2p = false}), NData = sdlan_pb:encode_msg(Data#sdl_data{ttl = TTL - 1, is_p2p = false}),
sdlan_network:forward(NetworkPid, Sock, SrcMac, DstMac, <<?PACKET_STUN_DATA, NData/binary>>) sdlan_network:forward(NetworkPid, Sock, SrcMac, DstMac, <<?PACKET_STUN_DATA, NData/binary>>)
end; end;
handle_packet(Sock, Ip, Port, <<?PACKET_ARP_REQUEST, Body/binary>>) -> %% arp查询
#sdl_arp_request{network_id = NetworkId, target_ip = TargetIp} = sdlan_pb:decode_msg(Body, sdl_arp_request), handle_packet(Sock, ClientIp, ClientPort, <<?PACKET_ARP_REQUEST, Body/binary>>) ->
logger:debug("[sdlan_stun] stun sdl_arp_request, network_id: ~p, target_ip: ~p", [NetworkId, TargetIp]),
maybe maybe
#sdl_arp_request{network_id = NetworkId, target_ip = TargetIp} ?= sdlan_pb:decode_msg(Body, sdl_arp_request),
logger:debug("[sdlan_stun] stun sdl_arp_request, network_id: ~p, target_ip: ~p", [NetworkId, TargetIp]),
{ok, NetworkPid} ?= sdlan_network:lookup_pid(NetworkId), {ok, NetworkPid} ?= sdlan_network:lookup_pid(NetworkId),
{ok, TargetMac} ?= sdlan_network:arp_query(NetworkPid, TargetIp), {ok, TargetMac} ?= sdlan_network:arp_query(NetworkPid, TargetIp),
ArpResponse = sdlan_pb:encode_msg(#sdl_arp_response{network_id = NetworkId, target_ip = TargetIp, target_mac = TargetMac}), ArpResponse = sdlan_pb:encode_msg(#sdl_arp_response{
ok ?= gen_udp:send(Sock, Ip, Port, ArpResponse) network_id = NetworkId,
target_ip = TargetIp,
target_mac = TargetMac
}),
gen_udp:send(Sock, ClientIp, ClientPort, ArpResponse)
end. end.
-spec int_ip(tuple()) -> integer(). -spec int_ip(tuple()) -> integer().

View File

@ -101,6 +101,12 @@ message SDLStunRequest {
bytes session_token = 7; bytes session_token = 7;
} }
message SDLStunReply {
uint32 network_id = 1;
uint32 code = 2;
string message = 3;
}
message SDLData { message SDLData {
uint32 network_id = 1; uint32 network_id = 1;
bytes src_mac = 2; bytes src_mac = 2;