diff --git a/apps/sdlan/include/sdlan.hrl b/apps/sdlan/include/sdlan.hrl index 87e350f..81ffc4d 100644 --- a/apps/sdlan/include/sdlan.hrl +++ b/apps/sdlan/include/sdlan.hrl @@ -33,7 +33,7 @@ %% 定义事件信息 -define(PACKET_EVENT_KNOWN_IP, 16#01). --define(PACKET_EVENT_DROP_IP, 16#02). +-define(PACKET_EVENT_DROP_IPS, 16#02). -define(PACKET_EVENT_NAT_CHANGED, 16#03). -define(PACKET_EVENT_SEND_REGISTER, 16#04). diff --git a/apps/sdlan/src/sdlan_network.erl b/apps/sdlan/src/sdlan_network.erl index 7f881db..b72037d 100644 --- a/apps/sdlan/src/sdlan_network.erl +++ b/apps/sdlan/src/sdlan_network.erl @@ -16,7 +16,8 @@ -behaviour(gen_server). --define(FLOW_REPORT_INTERVAL, 60 * 1000). +-define(FLOW_REPORT_INTERVAL, 60_000). +-define(ENDPOINT_GC_INTERVAL, 30_000). %% broadcast, "FF-FF-FF-FF-FF-FF" -define(BROADCAST_MAC, <<16#FF,16#FF,16#FF,16#FF,16#FF,16#FF>>). @@ -43,7 +44,9 @@ hostname :: binary(), hole :: #hole{}, %% 记录ip和ip_v6的映射关系, #{ip_addr :: integer() => {}} - v6_info :: undefined | #sdl_v6_info{} + v6_info :: undefined | #sdl_v6_info{}, + + last_seen :: integer() %% monotonic_time(second) }). -record(state, { @@ -161,6 +164,10 @@ init([Id]) when is_integer(Id) -> %% 每分钟汇报一次转发的流量 erlang:start_timer(?FLOW_REPORT_INTERVAL, self(), flow_report_ticker), + + %% endpoint定时扫描器 + erlang:start_timer(?ENDPOINT_GC_INTERVAL, self(), endpoint_gc), + sdlan_domain_regedit:insert(Domain), {ok, #state{network_id = Id, name = Name, domain = Domain, ipaddr = IpAddr, mask_len = MaskLen, aes_key = AesKey, throttle_key = ThrottleKey}}; @@ -187,7 +194,23 @@ 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), - Endpoint = #endpoint{client_id = ClientId, mac = Mac, ip = Ip, hostname = Hostname, hole = #hole{peer = Peer, nat_type = 0}}, + + %% mac对应的Endpoint存在,并且对应的ip变了,需要通知端上清理arp + maybe + {ok, #endpoint{ip = OldIp}} ?= maps:find(Mac, Endpoints), + true ?= OldIp =/= Ip, + NatChangedEvent = sdlan_pb:encode_msg(#sdl_nat_changed_event{ + mac = Mac, + ip = Ip + }), + EventPacket = <>, + + EndpointPeers = endpoint_peers([Mac], Endpoints), + sdlan_stun_pool:send_packets(EndpointPeers, EventPacket) + end, + + Endpoint = #endpoint{client_id = ClientId, mac = Mac, ip = Ip, hostname = Hostname, + 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)}}; @@ -313,6 +336,7 @@ handle_cast({unregister, _ClientId, Mac}, State = #state{network_id = NetworkId, {noreply, State#state{endpoints = maps:remove(Mac, Endpoints)}}; %% 需要判断,client是属于当前网络的 +%% TODO 被拒绝的endpoint需要通知其重新验证 handle_cast({update_hole, ClientId, Mac, Peer, NatType, V6Info}, State = #state{endpoints = Endpoints}) -> case maps:find(Mac, Endpoints) of {ok, Endpoint0 = #endpoint{ip = Ip, client_id = ClientId0, hole = OldHole}} when ClientId =:= ClientId0 -> @@ -328,7 +352,7 @@ handle_cast({update_hole, ClientId, Mac, Peer, NatType, V6Info}, State = #state{ EndpointPeers = endpoint_peers([Mac], Endpoints), sdlan_stun_pool:send_packets(EndpointPeers, EventPacket) end, - {noreply, State#state{endpoints = maps:put(Mac, Endpoint0#endpoint{hole = NHole, v6_info = V6Info}, Endpoints)}}; + {noreply, State#state{endpoints = maps:put(Mac, Endpoint0#endpoint{hole = NHole, v6_info = V6Info, last_seen = erlang:monotonic_time(second)}, Endpoints)}}; _ -> {noreply, State} end. @@ -342,7 +366,35 @@ handle_cast({update_hole, ClientId, Mac, Peer, NatType, V6Info}, State = #state{ handle_info({timeout, _, flow_report_ticker}, State = #state{network_id = NetworkId, forward_bytes = ForwardBytes}) -> erlang:start_timer(?FLOW_REPORT_INTERVAL, self(), flow_report_ticker), catch sdlan_api:network_forward_report(NetworkId, ForwardBytes), - {noreply, State#state{forward_bytes = 0}}. + {noreply, State#state{forward_bytes = 0}}; + +handle_info({timeout, _, endpoint_gc}, State = #state{endpoints = Endpoints}) -> + erlang:start_timer(?ENDPOINT_GC_INTERVAL, self(), endpoint_gc), + + Now = erlang:monotonic_time(second), + + {AliveEndpoints, ExpiredEndpoints} = maps:fold(fun(Mac, Ep = #endpoint{last_seen = Last}, {Alive, Expired}) -> + case Now - Last =< ?ENDPOINT_GC_INTERVAL of + true -> + {maps:put(Mac, Ep, Alive), Expired}; + false -> + {Alive, maps:put(Mac, Ep, Expired)} + end + end, + {#{}, #{}}, Endpoints), + + ExpiredIps = lists:map(fun(#endpoint{ip = Ip0}) -> Ip0 end, maps:values(ExpiredEndpoints)), + %% 通知活着的Endpoints + NatChangedEvent = sdlan_pb:encode_msg(#sdl_nat_changed_event{ + mac = Mac, + ip = Ip + }), + EventPacket = <>, + + EndpointPeers = endpoint_peers([Mac], Endpoints), + sdlan_stun_pool:send_packets(EndpointPeers, EventPacket), + + {noreply, State#state{endpoints = AliveEndpoints}}. %% @private %% @doc This function is called by a gen_server when it is about to diff --git a/message.proto b/message.proto index ce09b30..248ae86 100644 --- a/message.proto +++ b/message.proto @@ -64,6 +64,12 @@ message SDLNatChangedEvent { uint32 ip = 3; } +// 被清理掉的Endpoints +message SDLDropMacsEvent { + uint32 network_id = 1; + repeated bytes mac = 2; +} + message SDLSendRegisterEvent { uint32 network_id = 1; bytes dst_mac = 2;