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
-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 ->
case sdlan_network:get_pid(NetworkId) of
undefined ->

View File

@ -23,7 +23,7 @@
%% API
-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([test_event/1]).
@ -38,6 +38,7 @@
%% ip的使用信息, Node的运行时状态信息
-record(endpoint, {
client_id :: binary(),
mac :: binary(),
ip :: integer(),
hostname :: binary(),
hole :: #hole{},
@ -51,18 +52,13 @@
domain :: binary(),
ipaddr :: binary(),
mask_len :: integer(),
owner_id :: integer(),
%%
throttle_key :: atom(),
%%
forward_bytes = 0,
%% , AES-256
aes_key :: binary(),
%% 使ip, #{mac :: integer() => #endpoint{}}
%% 使ip, #{mac => #endpoint{}}
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) ->
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) ->
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) ->
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)
-spec(start_link(Name :: atom(), Id :: integer()) ->
{ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
@ -160,7 +150,7 @@ init([Id]) when is_integer(Id) ->
case sdlan_api:get_network(Id) of
{ok, #{<<"ipaddr">> := Null}} when Null == <<"null">>; Null == <<"NULL">> ->
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),
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),
sdlan_domain_regedit:insert(Domain),
{ok, #state{network_id = Id, name = Name, domain = Domain, ipaddr = IpAddr, owner_id = OwnerId,
mask_len = MaskLen, aes_key = AesKey, throttle_key = ThrottleKey}};
{ok, #state{network_id = Id, name = Name, domain = Domain, ipaddr = IpAddr, mask_len = MaskLen, aes_key = AesKey, throttle_key = ThrottleKey}};
{error, Reason} ->
logger:warning("[sdlan_network] load network: ~p, get error: ~p", [Id, Reason]),
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)]),
%% ->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}};
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};
{reply, {ok, Domain, MaskLen, AesKey}, State#state{endpoints = maps:put(Mac, Endpoint, Endpoints)}};
%% client设置为禁止状态
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
{value, {Mac, #endpoint{}}} ->
case search_endpoint(fun(_, #endpoint{client_id = ClientId0}) -> ClientId =:= ClientId0 end, Endpoints) of
{ok, Mac, _} ->
{reply, ok, State#state{endpoints = maps:remove(Mac, Endpoints)}};
false ->
{reply, error, State}
error ->
{reply, ok, State}
end;
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>>,
sdlan_stun_pool:send_packets([{DstNatPeer, EventPacket}])
sdlan_stun_pool:send_packet(DstNatPeer, EventPacket)
end,
{reply, {ok, {DstNatPeer, DstNatType}, DstV6Info}, State};
_ ->
{reply, error, State}
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 = #{
<<"network_id">> => NetworkId,
<<"ipaddr">> => IpAddr,
<<"mask_len">> => MaskLen,
<<"owner_id">> => OwnerId,
<<"used_ips">> => lists:map(fun format_endpoint/1, maps:to_list(Endpoints))
},
{reply, Reply, State}.
@ -326,15 +288,9 @@ handle_cast({forward, Sock, SrcMac, DstMac, Packet}, State = #state{network_id =
true ->
PacketBytes = byte_size(Packet),
%% 广
broadcast(fun(#endpoint{hole = Hole}) ->
case Hole of
#hole{peer = {NatIp, NatPort}} ->
gen_udp:send(Sock, NatIp, NatPort, Packet);
_ ->
ok
end
broadcast(fun(#endpoint{hole = #hole{peer = {NatIp, NatPort}}}) ->
gen_udp:send(Sock, NatIp, NatPort, Packet)
end, [SrcMac], Endpoints),
%% client和stun之间必须有心跳机制保持nat映射可用udp包肯定可以到达对端的nat
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)]),
@ -354,35 +310,25 @@ handle_cast({forward, _Sock, SrcMac, DstMac, _Packet}, State = #state{network_id
%% ip的占用并关闭channel
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)]),
case maps:take(Mac, Endpoints) of
error ->
{noreply, State};
{#endpoint{monitor_ref = MRef}, NEndpoints} ->
is_reference(MRef) andalso demonitor(MRef),
{noreply, State#state{endpoints = NEndpoints}}
end;
{noreply, State#state{endpoints = maps:remove(Mac, Endpoints)}};
%% client是属于当前网络的
handle_cast({update_hole, ClientId, Mac, Peer, NatType, V6Info}, State = #state{endpoints = Endpoints}) ->
case maps:find(Mac, Endpoints) of
{ok, Endpoint0 = #endpoint{client_id = ClientId0, ip = Ip, hole = OldHole}} when ClientId =:= ClientId0 ->
case OldHole =:= undefined orelse (OldHole#hole.peer =/= Peer orelse OldHole#hole.nat_type =/= NatType) of
true ->
NatChangedEvent = sdlan_pb:encode_msg(#sdl_nat_changed_event{
mac = Mac,
ip = Ip
}),
EventPacket = <<?PACKET_EVENT, ?PACKET_EVENT_NAT_CHANGED, NatChangedEvent>>,
{ok, Endpoint0 = #endpoint{ip = Ip, client_id = ClientId0, hole = OldHole}} when ClientId =:= ClientId0 ->
NHole = #hole{peer = Peer, nat_type = NatType},
maybe
true ?= same_hole(OldHole, NHole),
NatChangedEvent = sdlan_pb:encode_msg(#sdl_nat_changed_event{
mac = Mac,
ip = Ip
}),
EventPacket = <<?PACKET_EVENT, ?PACKET_EVENT_NAT_CHANGED, NatChangedEvent/binary>>,
broadcast(fun(#endpoint{hole = #hole{peer = Peer}}) ->
sdlan_stun_pool:send_packet(Peer, EventPacket)
end, [Mac], Endpoints);
false ->
ok
EndpointPeers = endpoint_peers([Mac], Endpoints),
sdlan_stun_pool:send_packets(EndpointPeers, EventPacket)
end,
Endpoint = Endpoint0#endpoint{hole = #hole{peer = Peer, nat_type = NatType}, v6_info = V6Info},
{noreply, State#state{endpoints = maps:put(Mac, Endpoint, Endpoints)}};
{noreply, State#state{endpoints = maps:put(Mac, Endpoint0#endpoint{hole = NHole, v6_info = V6Info}, Endpoints)}};
_ ->
{noreply, State}
end.
@ -411,10 +357,9 @@ terminate(Reason, #state{network_id = NetworkId, endpoints = Endpoints}) ->
NetworkShutdownEvent = sdlan_pb:encode_msg(#sdl_network_shutdown_event {
message = <<"Network shutdown">>
}),
EventPacket = <<?PACKET_EVENT, ?PACKET_EVENT_NETWORK_SHUTDOWN, NetworkShutdownEvent>>,
broadcast(fun(#endpoint{hole = #hole{peer = Peer}}) ->
sdlan_stun_pool:send_packet(Peer, EventPacket)
end, Endpoints),
EventPacket = <<?PACKET_EVENT, ?PACKET_EVENT_NETWORK_SHUTDOWN, NetworkShutdownEvent/binary>>,
EndpointPeers = endpoint_peers([], Endpoints),
sdlan_stun_pool:send_packets(EndpointPeers, EventPacket),
ok.
%% @private
@ -443,10 +388,6 @@ limiting_check(ThrottleKey) ->
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().
broadcast(Fun, ExcludeMacs, Endpoints) when is_function(Fun, 1), is_map(Endpoints), is_list(ExcludeMacs) ->
maps:foreach(fun(Mac, Endpoint) ->
@ -458,17 +399,6 @@ broadcast(Fun, ExcludeMacs, Endpoints) when is_function(Fun, 1), is_map(Endpoint
end
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">>
-spec parse_ipaddr(IpAddr0 :: binary()) -> {IpAddr :: binary(), MaskLen :: integer()}.
parse_ipaddr(IpAddr0) when is_binary(IpAddr0) ->
@ -522,4 +452,21 @@ search_endpoint0(F, Iter) when is_function(F, 1) ->
end;
'none' ->
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).
%% API
-export([start_link/0, send_packets/1, send_packet/2]).
-export([start_link/0, send_packets/2, send_packet/2]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
@ -33,11 +33,13 @@
%%% API
%%%===================================================================
-spec send_packet(Peer :: {Ip :: inet:ip4_address(), Port :: integer()}, Packet :: binary()) -> no_return().
send_packet(Peer, Packet) ->
gen_server:cast(?SERVER, {send_packet, Peer, Packet}).
send_packets(Packets) when is_list(Packets) ->
gen_server:cast(?SERVER, {send_packets, Packets}).
-spec send_packet(Peers :: [{Ip :: inet:ip4_address(), Port :: integer()}], Packet :: binary()) -> no_return().
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)
-spec(start_link() ->
@ -103,9 +105,9 @@ handle_cast({send_packet, {Ip, Port}, Packet}, State = #state{workers = Workers,
NewIdx = (Idx rem Num) + 1,
{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),
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,
{noreply, State#state{idx = NewIdx}}.