diff --git a/apps/sdlan/src/http_handler/node_handler.erl b/apps/sdlan/src/http_handler/node_handler.erl index d00aff3..946f868 100644 --- a/apps/sdlan/src/http_handler/node_handler.erl +++ b/apps/sdlan/src/http_handler/node_handler.erl @@ -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 -> diff --git a/apps/sdlan/src/sdlan_network.erl b/apps/sdlan/src/sdlan_network.erl index 22696e8..7f881db 100644 --- a/apps/sdlan/src/sdlan_network.erl +++ b/apps/sdlan/src/sdlan_network.erl @@ -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 = <>, - 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 = <>, + {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 = <>, - 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 = <>, - broadcast(fun(#endpoint{hole = #hole{peer = Peer}}) -> - sdlan_stun_pool:send_packet(Peer, EventPacket) - end, Endpoints), + EventPacket = <>, + 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. \ No newline at end of file + 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)). \ No newline at end of file diff --git a/apps/sdlan/src/sdlan_stun_pool.erl b/apps/sdlan/src/sdlan_stun_pool.erl index 729d91f..3cbdcc1 100644 --- a/apps/sdlan/src/sdlan_stun_pool.erl +++ b/apps/sdlan/src/sdlan_stun_pool.erl @@ -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}}.