diff --git a/src/sdlan_network.erl b/src/sdlan_network.erl index fe67914..e608daf 100644 --- a/src/sdlan_network.erl +++ b/src/sdlan_network.erl @@ -216,10 +216,10 @@ handle_call({attach, ChannelPid, ClientId, Mac, Ip, Hostname}, _From, %% 添加域名->ip的映射关系 sdlan_hostname_regedit:insert(Hostname, Domain, Ip), + OldEndpoint = maps:get(Mac, Endpoints, undefined), %% mac对应的Endpoint存在,并且对应的ip变了,需要通知端上清理arp - %% 重复attach需要清理之前的绑定信息 maybe - {ok, #endpoint{ip = OldIp, channel_pid = OldChannelPid, channel_ref = OldChannelRef}} ?= maps:find(Mac, Endpoints), + #endpoint{ip = OldIp} ?= OldEndpoint, true ?= OldIp =/= Ip, Event = sdlan_pb:encode_msg(#'SDLEvent'{ @@ -232,17 +232,10 @@ handle_call({attach, ChannelPid, ClientId, Mac, Ip, Hostname}, _From, broadcast(fun(#endpoint{channel_pid = ChannelPid0}) -> sdlan_quic_channel:send_event(ChannelPid0, Event) - end, [Mac], Endpoints), - - %% 清理就的绑定关系 - is_reference(OldChannelRef) andalso demonitor(OldChannelRef), - case OldChannelPid /= undefined andalso is_process_alive(OldChannelPid) of - true -> - sdlan_quic_channel:stop(OldChannelPid, rebind); - false -> - ok - end + end, [Mac], Endpoints) end, + %% 重复attach需要清理之前的绑定信息;即使IP未变化,也不能保留旧channel。 + cleanup_endpoint(OldEndpoint, ChannelPid, rebind), ChannelRef = monitor(process, ChannelPid), SessionToken = gen_session_token(), @@ -257,7 +250,8 @@ handle_call({attach, ChannelPid, ClientId, Mac, Ip, Hostname}, _From, %% client设置为禁止状态,不允许重连 handle_call({disable_client, ClientId}, _From, State = #state{endpoints = Endpoints}) -> case search_endpoint(fun(_, #endpoint{client_id = ClientId0}) -> ClientId =:= ClientId0 end, Endpoints) of - {ok, Mac, _} -> + {ok, Mac, Endpoint} -> + cleanup_endpoint(Endpoint, undefined, disabled), {reply, ok, State#state{endpoints = maps:remove(Mac, Endpoints)}}; error -> {reply, ok, State} @@ -317,7 +311,7 @@ handle_call({command, ReceiverPid, ClientId, SubCommand}, _From, State = #state{ sdlan_quic_channel:command(ChannelPid, Ref, ReceiverPid, SubCommand), {reply, {ok, Ref}, State}; error -> - {reply, {error, <<"目标Node不在线"/utf8>>}} + {reply, {error, <<"目标Node不在线"/utf8>>}, State} end; handle_call(debug_info, _From, State = #state{network_id = NetworkId, ipaddr = IpAddr, mask_len = MaskLen, owner_id = OwnerId, endpoints = Endpoints}) -> @@ -375,8 +369,11 @@ handle_cast({forward, Sock, SrcMac, DstMac, Packet}, State = #state{network_id = true -> PacketBytes = byte_size(Packet), %% 消息广播 - broadcast(fun(#endpoint{hole = #hole{peer = {NatIp, NatPort}}}) -> - gen_udp:send(Sock, NatIp, NatPort, Packet) + broadcast(fun + (#endpoint{hole = #hole{peer = {NatIp, NatPort}}}) -> + gen_udp:send(Sock, NatIp, NatPort, Packet); + (#endpoint{}) -> + ok end, [SrcMac], Endpoints), %% client和stun之间必须有心跳机制保持nat映射可用,并且通过服务转发的udp包肯定可以到达对端的nat logger:debug("[sdlan_network] broadcast data networkd_id: ~p, src_mac: ~p, dst_mac: ~p", @@ -397,7 +394,13 @@ 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)]), - {noreply, State#state{endpoints = maps:remove(Mac, Endpoints)}}; + case maps:take(Mac, Endpoints) of + {Endpoint, NEndpoints} -> + cleanup_endpoint(Endpoint, Endpoint#endpoint.channel_pid, unregister), + {noreply, State#state{endpoints = NEndpoints}}; + error -> + {noreply, State} + end; %% 需要判断,client是属于当前网络的 handle_cast({update_hole, SessionToken, ClientId, Mac, Peer, NatType, V6Info}, State = #state{endpoints = Endpoints}) -> @@ -455,7 +458,7 @@ handle_info({'DOWN', _MRef, process, ChannelPid, Reason}, State = #state{network State :: #state{}) -> none()). terminate(Reason, #state{network_id = NetworkId, endpoints = Endpoints}) -> broadcast(fun(#endpoint{channel_pid = ChannelPid}) -> - case is_process_alive(ChannelPid) of + case is_pid(ChannelPid) andalso is_process_alive(ChannelPid) of true -> NetworkShutdownEvent = sdlan_pb:encode_msg(#'SDLEvent'{ event = {shutdown, #'SDLEvent.NetworkShutdown'{ @@ -512,6 +515,23 @@ limiting_check(ThrottleKey) -> end end. +cleanup_endpoint(undefined, _KeepChannelPid, _Reason) -> + ok; +cleanup_endpoint(#endpoint{channel_ref = ChannelRef, channel_pid = ChannelPid}, KeepChannelPid, Reason) -> + is_reference(ChannelRef) andalso erlang:demonitor(ChannelRef, [flush]), + case should_stop_channel(ChannelPid, KeepChannelPid) of + true -> + catch sdlan_quic_channel:stop(ChannelPid, Reason), + ok; + false -> + ok + end. + +should_stop_channel(ChannelPid, KeepChannelPid) when is_pid(ChannelPid), ChannelPid =/= KeepChannelPid -> + is_process_alive(ChannelPid); +should_stop_channel(_, _) -> + false. + -spec broadcast(Fun :: fun((#endpoint{}) -> no_return()), 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) ->