fix network

This commit is contained in:
anlicheng 2026-01-25 22:26:02 +08:00
parent a4f2ed8428
commit eb11c05ba5
3 changed files with 57 additions and 125 deletions

View File

@ -15,23 +15,6 @@
%% API %% API
-export([handle_request/4]). -export([handle_request/4]).
handle_request("POST", "/node/list", _, #{<<"network_id">> := NetworkId}) when NetworkId > 0 ->
Pid = sdlan_network:get_pid(NetworkId),
UsedMap = sdlan_network:get_used_map(Pid),
Clients = client_model:get_clients(NetworkId),
ClientInfos = lists:map(fun(#client{client_id = ClientId, mac = Mac, ip = Ip, status = Status}) ->
Info = #{
<<"client_id">> => ClientId,
<<"mac">> => Mac,
<<"ip">> => sdlan_ipaddr:int_to_ipv4(Ip),
<<"status">> => atom_to_binary(Status)
},
maps:merge(Info, maps:get(Mac, UsedMap, #{}))
end, Clients),
{ok, 200, sdlan_util:json_data(ClientInfos)};
handle_request("POST", "/node/disable", _, #{<<"network_id">> := NetworkId, <<"client_id">> := ClientId}) when NetworkId > 0 -> handle_request("POST", "/node/disable", _, #{<<"network_id">> := NetworkId, <<"client_id">> := ClientId}) when NetworkId > 0 ->
case sdlan_network:get_pid(NetworkId) of case sdlan_network:get_pid(NetworkId) of
undefined -> undefined ->

View File

@ -23,7 +23,7 @@
%% API %% API
-export([start_link/2]). -export([start_link/2]).
-export([get_name/1, get_pid/1, lookup_pid/1, attach/6, peer_info/3, unregister/3, debug_info/1, get_network_id/1, get_used_map/1, arp_query/2]). -export([get_name/1, get_pid/1, lookup_pid/1, attach/6, peer_info/3, 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, update_hole/6, disable_client/2, dropout_client/2]).
-export([test_event/1]). -export([test_event/1]).
@ -38,6 +38,7 @@
%% ip的使用信息, Node的运行时状态信息 %% ip的使用信息, Node的运行时状态信息
-record(endpoint, { -record(endpoint, {
client_id :: binary(), client_id :: binary(),
mac :: binary(),
ip :: integer(), ip :: integer(),
hostname :: binary(), hostname :: binary(),
hole :: #hole{}, hole :: #hole{},
@ -51,18 +52,13 @@
domain :: binary(), domain :: binary(),
ipaddr :: binary(), ipaddr :: binary(),
mask_len :: integer(), mask_len :: integer(),
owner_id :: integer(),
%% %%
throttle_key :: atom(), throttle_key :: atom(),
%% %%
forward_bytes = 0, forward_bytes = 0,
%% , AES-256 %% , AES-256
aes_key :: binary(), aes_key :: binary(),
%% 使ip, #{mac => #endpoint{}}
%% 使ip, #{mac :: integer() => #endpoint{}}
endpoints = #{} endpoints = #{}
}). }).
@ -122,7 +118,7 @@ forward(Pid, Sock, SrcMac, DstMac, Packet) when is_pid(Pid), is_binary(SrcMac),
update_hole(Pid, ClientId, Mac, Peer, NatType, V6Info) when is_pid(Pid), is_binary(ClientId), is_binary(Mac), is_integer(NatType) -> update_hole(Pid, ClientId, Mac, Peer, NatType, V6Info) when is_pid(Pid), is_binary(ClientId), is_binary(Mac), is_integer(NatType) ->
gen_server:cast(Pid, {update_hole, ClientId, Mac, Peer, NatType, V6Info}). gen_server:cast(Pid, {update_hole, ClientId, Mac, Peer, NatType, V6Info}).
-spec disable_client(Pid :: pid(), ClientId :: binary()) -> ok | error. -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) ->
gen_server:call(Pid, {disable_client, ClientId}). gen_server:call(Pid, {disable_client, ClientId}).
@ -135,12 +131,6 @@ dropout_client(Pid, ClientId) when is_pid(Pid), is_binary(ClientId) ->
debug_info(Pid) when is_pid(Pid) -> debug_info(Pid) when is_pid(Pid) ->
gen_server:call(Pid, debug_info). gen_server:call(Pid, debug_info).
-spec get_used_map(Pid :: pid()) -> map().
get_used_map(Pid) when is_pid(Pid) ->
gen_server:call(Pid, get_used_map);
get_used_map(undefined) ->
#{}.
%% @doc Spawns the server and registers the local name (unique) %% @doc Spawns the server and registers the local name (unique)
-spec(start_link(Name :: atom(), Id :: integer()) -> -spec(start_link(Name :: atom(), Id :: integer()) ->
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}). {ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
@ -160,7 +150,7 @@ init([Id]) when is_integer(Id) ->
case sdlan_api:get_network(Id) of case sdlan_api:get_network(Id) of
{ok, #{<<"ipaddr">> := Null}} when Null == <<"null">>; Null == <<"NULL">> -> {ok, #{<<"ipaddr">> := Null}} when Null == <<"null">>; Null == <<"NULL">> ->
ignore; ignore;
{ok, #{<<"id">> := Id, <<"name">> := Name, <<"domain">> := Domain, <<"ipaddr">> := IpAddr0, <<"owner_id">> := OwnerId}} -> {ok, #{<<"id">> := Id, <<"name">> := Name, <<"domain">> := Domain, <<"ipaddr">> := IpAddr0}} ->
{IpAddr, MaskLen} = parse_ipaddr(IpAddr0), {IpAddr, MaskLen} = parse_ipaddr(IpAddr0),
AesKey = sdlan_util:rand_byte(32), AesKey = sdlan_util:rand_byte(32),
@ -173,8 +163,7 @@ init([Id]) when is_integer(Id) ->
erlang:start_timer(?FLOW_REPORT_INTERVAL, self(), flow_report_ticker), erlang:start_timer(?FLOW_REPORT_INTERVAL, self(), flow_report_ticker),
sdlan_domain_regedit:insert(Domain), sdlan_domain_regedit:insert(Domain),
{ok, #state{network_id = Id, name = Name, domain = Domain, ipaddr = IpAddr, owner_id = OwnerId, {ok, #state{network_id = Id, name = Name, domain = Domain, ipaddr = IpAddr, mask_len = MaskLen, aes_key = AesKey, throttle_key = ThrottleKey}};
mask_len = MaskLen, aes_key = AesKey, throttle_key = ThrottleKey}};
{error, Reason} -> {error, Reason} ->
logger:warning("[sdlan_network] load network: ~p, get error: ~p", [Id, Reason]), logger:warning("[sdlan_network] load network: ~p, get error: ~p", [Id, Reason]),
ignore ignore
@ -198,43 +187,17 @@ handle_call({attach, Peer, ClientId, Mac, Ip, Hostname}, _From,
[NetworkId, ClientId, sdlan_util:format_mac(Mac), sdlan_ipaddr:int_to_ipv4(Ip)]), [NetworkId, ClientId, sdlan_util:format_mac(Mac), sdlan_ipaddr:int_to_ipv4(Ip)]),
%% ->ip的映射关系 %% ->ip的映射关系
sdlan_hostname_regedit:insert(Hostname, Domain, Ip), sdlan_hostname_regedit:insert(Hostname, Domain, Ip),
NEndpoints = maps:put(Mac, #endpoint{client_id = ClientId, ip = Ip, hostname = Hostname, hole = #hole{peer = Peer, nat_type = 0}}, Endpoints), Endpoint = #endpoint{client_id = ClientId, mac = Mac, ip = Ip, hostname = Hostname, hole = #hole{peer = Peer, nat_type = 0}},
{reply, {ok, Domain, MaskLen, AesKey}, State#state{endpoints = NEndpoints}}; {reply, {ok, Domain, MaskLen, AesKey}, State#state{endpoints = maps:put(Mac, Endpoint, Endpoints)}};
handle_call(get_used_map, _From, State = #state{endpoints = Endpoints}) ->
UsedInfos = maps:map(fun(_, #endpoint{hole = Hole, v6_info = V6Info}) ->
HoleMap = case Hole of
#hole{peer = {NatIp, NatPort}} ->
#{
<<"nat_ip">> => sdlan_ipaddr:int_to_ipv4(sdlan_ipaddr:ipv4_to_int(NatIp)),
<<"nat_port">> => NatPort
};
_ ->
#{}
end,
V6Map = case V6Info of
#sdl_v6_info{v6 = IpV6, port = Port} ->
#{
<<"v6_ip">> => sdlan_ipaddr:ipv6_bytes_to_binary(IpV6),
<<"v6_port">> => Port
};
_ ->
#{}
end,
#{<<"hole">> => HoleMap, <<"v6_info">> => V6Map}
end, Endpoints),
{reply, {ok, UsedInfos}, State};
%% client设置为禁止状态 %% client设置为禁止状态
handle_call({disable_client, ClientId}, _From, State = #state{endpoints = Endpoints}) -> handle_call({disable_client, ClientId}, _From, State = #state{endpoints = Endpoints}) ->
case lists:search(fun({_, #endpoint{client_id = ClientId0}}) -> ClientId =:= ClientId0 end, maps:to_list(Endpoints)) of case search_endpoint(fun(_, #endpoint{client_id = ClientId0}) -> ClientId =:= ClientId0 end, Endpoints) of
{value, {Mac, #endpoint{}}} -> {ok, Mac, _} ->
{reply, ok, State#state{endpoints = maps:remove(Mac, Endpoints)}}; {reply, ok, State#state{endpoints = maps:remove(Mac, Endpoints)}};
false -> error ->
{reply, error, State} {reply, ok, State}
end; end;
handle_call(get_network_id, _From, State = #state{network_id = NetworkId}) -> handle_call(get_network_id, _From, State = #state{network_id = NetworkId}) ->
@ -265,19 +228,18 @@ handle_call({peer_info, SrcMac, DstMac}, _From, State = #state{endpoints = Endpo
}), }),
EventPacket = <<?PACKET_EVENT, ?PACKET_EVENT_SEND_REGISTER, Event/binary>>, EventPacket = <<?PACKET_EVENT, ?PACKET_EVENT_SEND_REGISTER, Event/binary>>,
sdlan_stun_pool:send_packets([{DstNatPeer, EventPacket}]) sdlan_stun_pool:send_packet(DstNatPeer, EventPacket)
end, end,
{reply, {ok, {DstNatPeer, DstNatType}, DstV6Info}, State}; {reply, {ok, {DstNatPeer, DstNatType}, DstV6Info}, State};
_ -> _ ->
{reply, error, State} {reply, error, State}
end; end;
handle_call(debug_info, _From, State = #state{network_id = NetworkId, ipaddr = IpAddr, mask_len = MaskLen, owner_id = OwnerId, 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,
<<"ipaddr">> => IpAddr, <<"ipaddr">> => IpAddr,
<<"mask_len">> => MaskLen, <<"mask_len">> => MaskLen,
<<"owner_id">> => OwnerId,
<<"used_ips">> => lists:map(fun format_endpoint/1, maps:to_list(Endpoints)) <<"used_ips">> => lists:map(fun format_endpoint/1, maps:to_list(Endpoints))
}, },
{reply, Reply, State}. {reply, Reply, State}.
@ -326,15 +288,9 @@ handle_cast({forward, Sock, SrcMac, DstMac, Packet}, State = #state{network_id =
true -> true ->
PacketBytes = byte_size(Packet), PacketBytes = byte_size(Packet),
%% 广 %% 广
broadcast(fun(#endpoint{hole = Hole}) -> broadcast(fun(#endpoint{hole = #hole{peer = {NatIp, NatPort}}}) ->
case Hole of gen_udp:send(Sock, NatIp, NatPort, Packet)
#hole{peer = {NatIp, NatPort}} ->
gen_udp:send(Sock, NatIp, NatPort, Packet);
_ ->
ok
end
end, [SrcMac], Endpoints), end, [SrcMac], Endpoints),
%% client和stun之间必须有心跳机制保持nat映射可用udp包肯定可以到达对端的nat %% client和stun之间必须有心跳机制保持nat映射可用udp包肯定可以到达对端的nat
logger:debug("[sdlan_network] broadcast data networkd_id: ~p, src_mac: ~p, dst_mac: ~p", logger:debug("[sdlan_network] broadcast data networkd_id: ~p, src_mac: ~p, dst_mac: ~p",
[NetworkId, sdlan_util:format_mac(SrcMac), sdlan_util:format_mac(DstMac)]), [NetworkId, sdlan_util:format_mac(SrcMac), sdlan_util:format_mac(DstMac)]),
@ -354,35 +310,25 @@ handle_cast({forward, _Sock, SrcMac, DstMac, _Packet}, State = #state{network_id
%% ip的占用并关闭channel %% ip的占用并关闭channel
handle_cast({unregister, _ClientId, Mac}, State = #state{network_id = NetworkId, endpoints = Endpoints}) -> handle_cast({unregister, _ClientId, Mac}, State = #state{network_id = NetworkId, endpoints = Endpoints}) ->
logger:debug("[sdlan_network] networkd_id: ~p, unregister Mac: ~p", [NetworkId, sdlan_util:format_mac(Mac)]), logger:debug("[sdlan_network] networkd_id: ~p, unregister Mac: ~p", [NetworkId, sdlan_util:format_mac(Mac)]),
case maps:take(Mac, Endpoints) of {noreply, State#state{endpoints = maps:remove(Mac, Endpoints)}};
error ->
{noreply, State};
{#endpoint{monitor_ref = MRef}, NEndpoints} ->
is_reference(MRef) andalso demonitor(MRef),
{noreply, State#state{endpoints = NEndpoints}}
end;
%% client是属于当前网络的 %% client是属于当前网络的
handle_cast({update_hole, ClientId, Mac, Peer, NatType, V6Info}, State = #state{endpoints = Endpoints}) -> handle_cast({update_hole, ClientId, Mac, Peer, NatType, V6Info}, State = #state{endpoints = Endpoints}) ->
case maps:find(Mac, Endpoints) of case maps:find(Mac, Endpoints) of
{ok, Endpoint0 = #endpoint{client_id = ClientId0, ip = Ip, hole = OldHole}} when ClientId =:= ClientId0 -> {ok, Endpoint0 = #endpoint{ip = Ip, client_id = ClientId0, hole = OldHole}} when ClientId =:= ClientId0 ->
case OldHole =:= undefined orelse (OldHole#hole.peer =/= Peer orelse OldHole#hole.nat_type =/= NatType) of NHole = #hole{peer = Peer, nat_type = NatType},
true -> maybe
true ?= same_hole(OldHole, NHole),
NatChangedEvent = sdlan_pb:encode_msg(#sdl_nat_changed_event{ NatChangedEvent = sdlan_pb:encode_msg(#sdl_nat_changed_event{
mac = Mac, mac = Mac,
ip = Ip ip = Ip
}), }),
EventPacket = <<?PACKET_EVENT, ?PACKET_EVENT_NAT_CHANGED, NatChangedEvent>>, EventPacket = <<?PACKET_EVENT, ?PACKET_EVENT_NAT_CHANGED, NatChangedEvent/binary>>,
broadcast(fun(#endpoint{hole = #hole{peer = Peer}}) -> EndpointPeers = endpoint_peers([Mac], Endpoints),
sdlan_stun_pool:send_packet(Peer, EventPacket) sdlan_stun_pool:send_packets(EndpointPeers, EventPacket)
end, [Mac], Endpoints);
false ->
ok
end, end,
Endpoint = Endpoint0#endpoint{hole = #hole{peer = Peer, nat_type = NatType}, v6_info = V6Info}, {noreply, State#state{endpoints = maps:put(Mac, Endpoint0#endpoint{hole = NHole, v6_info = V6Info}, Endpoints)}};
{noreply, State#state{endpoints = maps:put(Mac, Endpoint, Endpoints)}};
_ -> _ ->
{noreply, State} {noreply, State}
end. end.
@ -411,10 +357,9 @@ terminate(Reason, #state{network_id = NetworkId, endpoints = Endpoints}) ->
NetworkShutdownEvent = sdlan_pb:encode_msg(#sdl_network_shutdown_event { NetworkShutdownEvent = sdlan_pb:encode_msg(#sdl_network_shutdown_event {
message = <<"Network shutdown">> message = <<"Network shutdown">>
}), }),
EventPacket = <<?PACKET_EVENT, ?PACKET_EVENT_NETWORK_SHUTDOWN, NetworkShutdownEvent>>, EventPacket = <<?PACKET_EVENT, ?PACKET_EVENT_NETWORK_SHUTDOWN, NetworkShutdownEvent/binary>>,
broadcast(fun(#endpoint{hole = #hole{peer = Peer}}) -> EndpointPeers = endpoint_peers([], Endpoints),
sdlan_stun_pool:send_packet(Peer, EventPacket) sdlan_stun_pool:send_packets(EndpointPeers, EventPacket),
end, Endpoints),
ok. ok.
%% @private %% @private
@ -443,10 +388,6 @@ limiting_check(ThrottleKey) ->
end end
end. end.
-spec broadcast(Fun :: binary(), Endpoints :: map()) -> no_return().
broadcast(Fun, Endpoints) when is_function(Fun, 1), is_map(Endpoints) ->
broadcast(Fun, [], Endpoints).
-spec broadcast(Fun :: binary(), ExcludeMacs :: [binary()], Endpoints :: map()) -> no_return(). -spec broadcast(Fun :: binary(), ExcludeMacs :: [binary()], Endpoints :: map()) -> no_return().
broadcast(Fun, ExcludeMacs, Endpoints) when is_function(Fun, 1), is_map(Endpoints), is_list(ExcludeMacs) -> broadcast(Fun, ExcludeMacs, Endpoints) when is_function(Fun, 1), is_map(Endpoints), is_list(ExcludeMacs) ->
maps:foreach(fun(Mac, Endpoint) -> maps:foreach(fun(Mac, Endpoint) ->
@ -458,17 +399,6 @@ broadcast(Fun, ExcludeMacs, Endpoints) when is_function(Fun, 1), is_map(Endpoint
end end
end, Endpoints). end, Endpoints).
-spec broadcast_peers(Fun :: binary(), ExcludeMacs :: [binary()], Endpoints :: map()) -> no_return().
broadcast_peers(Fun, ExcludeMacs, Endpoints) when is_function(Fun, 1), is_map(Endpoints), is_list(ExcludeMacs) ->
maps:filtermap(fun(Mac, Endpoint) ->
case lists:member(Mac, ExcludeMacs) of
true ->
ok;
false ->
Fun(Endpoint)
end
end, Endpoints).
%% IpAddr: <<"192.168.172/24">> %% IpAddr: <<"192.168.172/24">>
-spec parse_ipaddr(IpAddr0 :: binary()) -> {IpAddr :: binary(), MaskLen :: integer()}. -spec parse_ipaddr(IpAddr0 :: binary()) -> {IpAddr :: binary(), MaskLen :: integer()}.
parse_ipaddr(IpAddr0) when is_binary(IpAddr0) -> parse_ipaddr(IpAddr0) when is_binary(IpAddr0) ->
@ -523,3 +453,20 @@ search_endpoint0(F, Iter) when is_function(F, 1) ->
'none' -> 'none' ->
error error
end. end.
-spec same_hole(Hole :: #hole{}, Hole :: #hole{}) -> boolean().
same_hole(#hole{peer = OldPeer, nat_type = OldNatType}, #hole{peer = Peer, nat_type = NatType}) when OldPeer =:= Peer, OldNatType =:= NatType ->
true;
same_hole(_, _) ->
false.
-spec endpoint_peers(ExcludeMacs :: list(), Endpoints :: map()) -> Peers :: list().
endpoint_peers(ExcludeMacs, Endpoints) when is_list(ExcludeMacs), is_map(Endpoints) ->
maps:values(maps:filtermap(fun(Mac, #endpoint{hole = #hole{peer = Peer}}) ->
case lists:member(Mac, ExcludeMacs) of
true ->
false;
false ->
{true, Peer}
end
end, Endpoints)).

View File

@ -16,7 +16,7 @@
-behaviour(gen_server). -behaviour(gen_server).
%% API %% API
-export([start_link/0, send_packets/1, send_packet/2]). -export([start_link/0, send_packets/2, send_packet/2]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
@ -33,11 +33,13 @@
%%% API %%% API
%%%=================================================================== %%%===================================================================
-spec send_packet(Peer :: {Ip :: inet:ip4_address(), Port :: integer()}, Packet :: binary()) -> no_return().
send_packet(Peer, Packet) -> send_packet(Peer, Packet) ->
gen_server:cast(?SERVER, {send_packet, Peer, Packet}). gen_server:cast(?SERVER, {send_packet, Peer, Packet}).
send_packets(Packets) when is_list(Packets) -> -spec send_packet(Peers :: [{Ip :: inet:ip4_address(), Port :: integer()}], Packet :: binary()) -> no_return().
gen_server:cast(?SERVER, {send_packets, Packets}). send_packets(Peers, Packet) when is_list(Peers), is_binary(Packet) ->
gen_server:cast(?SERVER, {send_packets, Peers, Packet}).
%% @doc Spawns the server and registers the local name (unique) %% @doc Spawns the server and registers the local name (unique)
-spec(start_link() -> -spec(start_link() ->
@ -103,9 +105,9 @@ handle_cast({send_packet, {Ip, Port}, Packet}, State = #state{workers = Workers,
NewIdx = (Idx rem Num) + 1, NewIdx = (Idx rem Num) + 1,
{noreply, State#state{idx = NewIdx}}; {noreply, State#state{idx = NewIdx}};
handle_cast({send_packets, Packets}, State = #state{workers = Workers, idx = Idx, num = Num}) -> handle_cast({send_packets, Peers, Packet}, State = #state{workers = Workers, idx = Idx, num = Num}) ->
{Sock, _} = element(Idx, Workers), {Sock, _} = element(Idx, Workers),
lists:foreach(fun({{Ip, Port}, Data}) -> gen_udp:send(Sock, Ip, Port, Data) end, Packets), lists:foreach(fun({Ip, Port}) -> gen_udp:send(Sock, Ip, Port, Packet) end, Peers),
NewIdx = (Idx rem Num) + 1, NewIdx = (Idx rem Num) + 1,
{noreply, State#state{idx = NewIdx}}. {noreply, State#state{idx = NewIdx}}.