From 59b6607d4cd85c7445e76f8c2d71ef9c0aa6bfea Mon Sep 17 00:00:00 2001 From: anlicheng <244108715@qq.com> Date: Mon, 13 Apr 2026 14:38:24 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AE=80=E5=8C=96dns=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/sys-dev.config | 6 - config/sys-prod.config | 6 - src/dns_proxy/dns_cache.erl | 59 ----- src/dns_proxy/dns_handler.erl | 202 ---------------- src/dns_proxy/dns_handler_sup.erl | 66 ------ src/dns_proxy/dns_pending_wheel.erl | 64 ----- src/dns_proxy/dns_resolver.erl | 220 ++++++++---------- src/dns_proxy/dns_server.erl | 141 +++++++++-- .../{dns_proxy_sup.erl => dns_server_sup.erl} | 23 +- src/sdlan_app.erl | 1 - 10 files changed, 236 insertions(+), 552 deletions(-) delete mode 100644 src/dns_proxy/dns_cache.erl delete mode 100644 src/dns_proxy/dns_handler.erl delete mode 100644 src/dns_proxy/dns_handler_sup.erl delete mode 100644 src/dns_proxy/dns_pending_wheel.erl rename src/dns_proxy/{dns_proxy_sup.erl => dns_server_sup.erl} (75%) diff --git a/config/sys-dev.config b/config/sys-dev.config index 02f0c2e..0704c88 100644 --- a/config/sys-dev.config +++ b/config/sys-dev.config @@ -38,12 +38,6 @@ {port, 1366} ]}, - %% 公共的dns域名解析服务 - {public_dns_servers, [ - {{114, 114, 114, 114}, 53}, - {{8,8,8,8}, 53} - ]}, - {pools, [ %% mysql连接池配置 {mysql_sdlan, diff --git a/config/sys-prod.config b/config/sys-prod.config index 0671097..d27b8d1 100644 --- a/config/sys-prod.config +++ b/config/sys-prod.config @@ -38,12 +38,6 @@ {port, 1366} ]}, - %% 公共的dns域名解析服务 - {public_dns_servers, [ - {{114, 114, 114, 114}, 53}, - {{8,8,8,8}, 53} - ]}, - {pools, [ %% mysql连接池配置 {mysql_sdlan, diff --git a/src/dns_proxy/dns_cache.erl b/src/dns_proxy/dns_cache.erl deleted file mode 100644 index 98383a4..0000000 --- a/src/dns_proxy/dns_cache.erl +++ /dev/null @@ -1,59 +0,0 @@ --module(dns_cache). --include_lib("dns_proxy.hrl"). --include_lib("dns_erlang/include/dns.hrl"). --include_lib("dns_erlang/include/dns_records.hrl"). --include_lib("dns_erlang/include/dns_terms.hrl"). - --export([init/0, lookup/1, insert/2]). - --define(TABLE, dns_cache). - -init() -> - ets:new(?TABLE, [named_table, set, public, {keypos, 2}, {read_concurrency, true}]). - -lookup(#dns_query{name = Qname, type = QType, class = QClass}) -> - Key = {Qname, QType, QClass}, - case ets:lookup(?TABLE, Key) of - [Cache = #dns_cache{expire_at = ExpireAt}] -> - Now = os:system_time(second), - case ExpireAt > Now of - true -> - {hit, Cache}; - false -> - true = ets:delete(?TABLE, Key), - miss - end; - [] -> - miss - end. - -insert(#dns_query{name = Qname, type = QType, class = QClass}, - #dns_message{answers = Answers, authority = Authority, additional = Additional, rc = RCode, aa = AA}) -> - TTLs = lists:foldl(fun(Term, Acc) -> - case Term of - #dns_rr{ttl = TTL} -> - [TTL|Acc]; - _ -> - Acc - end - end, [], Answers ++ Authority ++ Additional), - case length(TTLs) > 0 of - true -> - TTL = lists:min(TTLs), - ExpireAt = os:system_time(second) + TTL, - logger:debug("min ttl is: ~p, expire_at: ~p", [TTL, ExpireAt]), - Key = {Qname, QType, QClass}, - Cache = #dns_cache{ - key = Key, - answers = Answers, - authority = Authority, - additional = Additional, - rc = RCode, - flags = #{aa => AA}, - % unix time - expire_at = ExpireAt - }, - true = ets:insert(?TABLE, Cache); - false -> - ok - end. \ No newline at end of file diff --git a/src/dns_proxy/dns_handler.erl b/src/dns_proxy/dns_handler.erl deleted file mode 100644 index 4f69386..0000000 --- a/src/dns_proxy/dns_handler.erl +++ /dev/null @@ -1,202 +0,0 @@ -%%%------------------------------------------------------------------- -%%% @author anlicheng -%%% @copyright (C) 2025, -%%% @doc -%%% -%%% @end -%%% Created : 03. 12月 2025 23:00 -%%%------------------------------------------------------------------- --module(dns_handler). --author("anlicheng"). - --include_lib("dns_erlang/include/dns.hrl"). --include_lib("pkt/include/pkt.hrl"). --include("dns_proxy.hrl"). - --export([handle/4]). - --define(SERVER, ?MODULE). --define(RESOLVER_POOL, dns_resolver_pool). - -%% 协议部分 --define(TCP_PROTOCOL, 6). --define(UDP_PROTOCOL, 17). - --record(state, {}). - -%%%=================================================================== -%%% API -%%%=================================================================== - -handle(Sock, SrcIp, SrcPort, IpPacket) -> - {#ipv4{saddr = ReqSAddr, daddr = ReqDAddr, p = Protocol}, ReqIpPayload} = pkt:ipv4(IpPacket), - case Protocol =:= ?UDP_PROTOCOL of - true -> - {#udp{sport = ReqSPort, dport = ReqDPort}, UdpPayload} = pkt:udp(ReqIpPayload), - case resolver(UdpPayload) of - {ok, DnsResp} -> - RespIpPacket = build_ip_packet(ReqDAddr, ReqSAddr, ReqDPort, ReqSPort, DnsResp), - gen_udp:send(Sock, SrcIp, SrcPort, RespIpPacket); - {error, Reason} -> - logger:notice("[dns_handler] resolver get error: ~p", [Reason]) - end; - false -> - logger:notice("[dns_handler] resolver invalid protocol: ~p", [Protocol]) - end. - -%%%=================================================================== -%%% Internal functions -%%%=================================================================== - --spec resolver(Packet :: binary()) -> {ok, Resp :: binary()} | {error, Reason :: any()}. -resolver(Packet) when is_binary(Packet) -> - resolver0(Packet, dns:decode_message(Packet)). -resolver0(Packet, QueryMsg = #dns_message{qc = 1, questions = [Question = #dns_query{name = QName, type = QType, class = QClass}|_]}) -> - %% 查找是否是内置的域名 - case sdlan_hostname_regedit:lookup(QName) of - {ok, Ip} -> - Answer = #dns_rr { - name = QName, - type = QType, - class = QClass, - ttl = 300, - data = #dns_rrdata_a { - ip = Ip - } - }, - RespMsg = QueryMsg#dns_message{ - qr = true, - ra = true, - anc = 1, - auc = 0, - adc = 0, - answers = [Answer], - authority = [], - additional = [] - }, - logger:debug("[dns_handler] punchnet inbuilt qnanme: ~p, ip: ~p", [QName, Ip]), - {ok, dns:encode_message(RespMsg)}; - error -> - %% 是否命中内部的域名后缀 - EmptyDnsResp = dns:encode_message(build_nxdomain_response(QueryMsg)), - case sdlan_domain_regedit:maybe_domain(QName) of - true -> - logger:debug("[dns_handler] punchnet inbuilt qnanme: ~p, nxdomain", [QName]), - {ok, EmptyDnsResp}; - false -> - case dns_cache:lookup(Question) of - {hit, Cache} -> - logger:debug("[dns_handler] qname: ~p, hit cache answers: ~p", [QName, Cache#dns_cache.answers]), - RespMsg = build_response(QueryMsg, Cache), - {ok, dns:encode_message(RespMsg)}; - miss -> - case forward_to_upstream(Packet) of - {ok, Resp} -> - case dns:decode_message(Resp) of - RespMsg = #dns_message{answers = Answers} -> - logger:debug("[dns_handler] get a response answers: ~p", [Answers]), - dns_cache:insert(Question, RespMsg), - {ok, Resp}; - Error -> - logger:debug("[dns_handler] parse reply get error: ~p", [Error]), - {ok, EmptyDnsResp} - end; - {error, Reason} -> - logger:debug("[dns_handler] parse reply get error: ~p", [Reason]), - {ok, EmptyDnsResp} - end - end - end - end; -resolver0(_, Error) -> - logger:warning("[dns_handler] decode dns_query get error: ~p", [Error]), - {error, Error}. - --spec build_response(QueryMsg :: #dns_message{}, Dns_cache :: #dns_cache{}) -> RespMsg :: #dns_message{}. -build_response(QueryMsg, #dns_cache{expire_at = ExpireAt, answers = Answers, authority = Authority, additional = Additional, rc = RCode, flags = #{aa := AA}}) -> - Now = os:system_time(second), - RemainingTTL = ExpireAt - Now, - - Answers2 = [adjust_ttl(RR, RemainingTTL) || RR <- Answers], - Authority2 = [adjust_ttl(RR, RemainingTTL) || RR <- Authority], - Additional2 = [adjust_ttl(RR, RemainingTTL) || RR <- Additional], - - QueryMsg#dns_message{ - qr = true, - ra = true, - aa = AA, - rc = RCode, - anc = length(Answers2), - auc = length(Authority2), - adc = length(Additional2), - answers = Answers2, - authority = Authority2, - additional = Additional2 - }. - --spec adjust_ttl(RR :: any(), RemainingTTL :: integer()) -> any(). -adjust_ttl(RR = #dns_rr{}, RemainingTTL) -> - RR#dns_rr{ttl = max(0, RemainingTTL)}; -adjust_ttl(RR, _RemainingTTL) -> - RR. - --spec build_nxdomain_response(QueryMsg :: #dns_message{}) -> EmptyResp :: #dns_message{}. -build_nxdomain_response(QueryMsg) -> - QueryMsg#dns_message{ - qr = true, - aa = true, - ra = true, - rc = ?DNS_RCODE_NXDOMAIN, - anc = 0, - auc = 0, - adc = 0, - answers = [], - authority = [], - additional = [] - }. - --spec build_ip_packet(SAddr :: inet:ip4_address(), DAddr :: inet:ip4_address(), SPort :: integer(), DPort :: integer(), Payload :: binary()) -> IpPacket :: binary(). -build_ip_packet(SAddr, DAddr, SPort, DPort, UdpPayload) when is_integer(SPort), is_integer(DPort), is_binary(UdpPayload) -> - ULen = 8 + byte_size(UdpPayload), - RespUdpHeader = pkt:udp(#udp{ - sport = SPort, - dport = DPort, - ulen = ULen, - sum = dns_utils:udp_checksum(SAddr, DAddr, SPort, DPort, UdpPayload) - }), - IpPayload = <>, - - IpPacket0 = #ipv4{ - len = 20 + ULen, - ttl = 64, - off = 0, - mf = 0, - sum = 0, - p = ?UDP_PROTOCOL, - saddr = SAddr, - daddr = DAddr, - opt = <<>> - }, - IpCheckSum = dns_utils:ip_checksum(IpPacket0), - IpHeader = pkt:ipv4(IpPacket0#ipv4{sum = IpCheckSum}), - - <>. - --spec forward_to_upstream(Request :: binary()) -> {ok, Resp :: binary()} | {error, Reason :: any()}. -forward_to_upstream(Request) -> - {DnsIp, DnsPort} = select_dns_server(), - {ok, Socket} = gen_udp:open(0, [binary, {active, true}]), - ok = gen_udp:send(Socket, DnsIp, DnsPort, Request), - logger:debug("[dns_resolver] send to: ~p, packet: ~p", [{DnsIp, DnsPort}, Request]), - receive - {udp, Socket, DnsIp, DnsPort, Resp} -> - {ok, Resp} - after 2000 -> - {error, timeout} - end. - --spec select_dns_server() -> {DnsIp :: inet:ip4_address(), DnsPort :: pos_integer()}. -select_dns_server() -> - {ok, DnsServers} = application:get_env(sdlan, public_dns_servers), - Idx = rand:uniform(length(DnsServers)), - lists:nth(Idx, DnsServers). \ No newline at end of file diff --git a/src/dns_proxy/dns_handler_sup.erl b/src/dns_proxy/dns_handler_sup.erl deleted file mode 100644 index 009395d..0000000 --- a/src/dns_proxy/dns_handler_sup.erl +++ /dev/null @@ -1,66 +0,0 @@ -%%%------------------------------------------------------------------- -%%% @author anlicheng -%%% @copyright (C) 2025, -%%% @doc -%%% -%%% @end -%%% Created : 03. 12月 2025 17:29 -%%%------------------------------------------------------------------- --module(dns_handler_sup). --author("anlicheng"). - --behaviour(supervisor). - -%% API --export([start_link/0]). - -%% Supervisor callbacks --export([init/1]). --export([start_handler/0]). - --define(SERVER, ?MODULE). - -%%%=================================================================== -%%% API functions -%%%=================================================================== - -%% @doc Starts the supervisor --spec(start_link() -> {ok, Pid :: pid()} | ignore | {error, Reason :: term()}). -start_link() -> - supervisor:start_link({local, ?SERVER}, ?MODULE, []). - -%%%=================================================================== -%%% Supervisor callbacks -%%%=================================================================== - -%% @private -%% @doc Whenever a supervisor is started using supervisor:start_link/[2,3], -%% this function is called by the new process to find out about -%% restart strategy, maximum restart frequency and child -%% specifications. -init([]) -> - SupFlags = #{strategy => simple_one_for_one, intensity => 0, period => 1}, - Spec = #{ - id => dns_handler, - start => {'dns_handler', start_link, []}, - restart => temporary, - shutdown => 2000, - type => worker, - modules => ['dns_handler'] - }, - - {ok, {SupFlags, [Spec]}}. - -%%%=================================================================== -%%% Internal functions -%%%=================================================================== - -start_handler() -> - case supervisor:start_child(?MODULE, []) of - {ok, Pid} -> - {ok, Pid}; - {error, {already_started, Pid}} -> - {ok, Pid}; - StartError -> - StartError - end. \ No newline at end of file diff --git a/src/dns_proxy/dns_pending_wheel.erl b/src/dns_proxy/dns_pending_wheel.erl deleted file mode 100644 index ad72903..0000000 --- a/src/dns_proxy/dns_pending_wheel.erl +++ /dev/null @@ -1,64 +0,0 @@ --module(dns_pending_wheel). - --export([start/0, insert/2, lookup/1, delete/1]). - --define(TTL, 5). --define(TICK_MS, 1000). --define(WHEEL_SIZE, (?TTL + 1)). - -%%% ===================================================== -%%% Public API -%%% ===================================================== - -start() -> - ets:new(dns_pending_data, [ordered_set, public, named_table, {read_concurrency, true}, {write_concurrency, true}]), - ets:new(dns_pending_wheel, [bag, public, named_table, {read_concurrency, true}, {write_concurrency, true}]), - start_scanner(). - --spec insert(Key :: any(), Val :: any()) -> ok. -insert(Key, Val) -> - Tick = now_tick(), - Slot = Tick rem ?WHEEL_SIZE, - ets:insert(dns_pending_data, {Key, {Val, Tick}}), - ets:insert(dns_pending_wheel, {Slot, {Key, Tick}}), - ok. - --spec lookup(Key :: any()) -> [term()]. -lookup(Key) -> - ets:lookup(dns_pending_data, Key). - --spec delete(Key :: any()) -> ok. -delete(Key) -> - ets:delete(dns_pending_data, Key), - ok. - -%%% ===================================================== -%%% Internal -%%% ===================================================== - -start_scanner() -> - {ok, spawn_link(fun tick_loop/0)}. - -%% 当前插入数据是在Tick, 而清理是从 Tick + 1 开始的,没有问题 -tick_loop() -> - Tick = now_tick(), - CleanSlot = (Tick + 1) rem ?WHEEL_SIZE, - spawn(fun() -> clean_slot(CleanSlot) end), - timer:sleep(?TICK_MS), - tick_loop(). - -clean_slot(Slot) -> - Items = ets:lookup(dns_pending_wheel, Slot), - true = ets:delete(dns_pending_wheel, Slot), - lists:foreach(fun({_, {Key, InsertTick}}) -> - case ets:lookup(dns_pending_data, Key) of - [{Key, {_Val, InsertTick}}] -> - ets:delete(dns_pending_data, Key); - _ -> - ok - end - end, Items). - --spec now_tick() -> integer(). -now_tick() -> - erlang:system_time(second). \ No newline at end of file diff --git a/src/dns_proxy/dns_resolver.erl b/src/dns_proxy/dns_resolver.erl index 05ca163..7bbada7 100644 --- a/src/dns_proxy/dns_resolver.erl +++ b/src/dns_proxy/dns_resolver.erl @@ -4,138 +4,122 @@ %%% @doc %%% %%% @end -%%% Created : 03. 12月 2025 18:26 +%%% Created : 03. 12月 2025 23:00 %%%------------------------------------------------------------------- -module(dns_resolver). -author("anlicheng"). + -include_lib("dns_erlang/include/dns.hrl"). +-include_lib("pkt/include/pkt.hrl"). +-include("dns_proxy.hrl"). --behaviour(gen_server). +-export([resolve/1]). -%% API --export([start_link/1]). - -%% gen_server callbacks --export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). - --export([forward/5]). - --define(SERVER, ?MODULE). - --record(state, { - socket, - idx :: integer(), - dns_servers = [] -}). +%% 协议部分 +-define(TCP_PROTOCOL, 6). +-define(UDP_PROTOCOL, 17). %%%=================================================================== %%% API %%%=================================================================== -forward(Pid, ReceiverPid, Ref, Request, QueryMsg) -> - gen_server:cast(Pid, {forward, ReceiverPid, Ref, Request, QueryMsg}). - -%% @doc Spawns the server and registers the local name (unique) --spec(start_link(Args :: list()) -> - {ok, Pid :: pid()} | ignore | {error, Reason :: term()}). -start_link(Args) when is_list(Args) -> - gen_server:start_link(?MODULE, [], []). - -%%%=================================================================== -%%% gen_server callbacks -%%%=================================================================== - -%% @private -%% @doc Initializes the server --spec(init(Args :: term()) -> - {ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} | - {stop, Reason :: term()} | ignore). -init([]) -> - {ok, DnsServers} = application:get_env(sdlan, public_dns_servers), - {ok, Sock} = gen_udp:open(0, [binary, {active, true}]), - Idx = erlang:unique_integer([monotonic, positive]), - {ok, #state{socket = Sock, idx = Idx, dns_servers = DnsServers}}. - -%% @private -%% @doc Handling call messages --spec(handle_call(Request :: term(), From :: {pid(), Tag :: term()}, - State :: #state{}) -> - {reply, Reply :: term(), NewState :: #state{}} | - {reply, Reply :: term(), NewState :: #state{}, timeout() | hibernate} | - {noreply, NewState :: #state{}} | - {noreply, NewState :: #state{}, timeout() | hibernate} | - {stop, Reason :: term(), Reply :: term(), NewState :: #state{}} | - {stop, Reason :: term(), NewState :: #state{}}). -handle_call(_Request, _From, State = #state{}) -> - {reply, ok, State}. - -%% @private -%% @doc Handling cast messages --spec(handle_cast(Request :: term(), State :: #state{}) -> - {noreply, NewState :: #state{}} | - {noreply, NewState :: #state{}, timeout() | hibernate} | - {stop, Reason :: term(), NewState :: #state{}}). -handle_cast({forward, ReceiverPid, Ref, Request, #dns_message{id = TxId, questions = [#dns_query{name = QName, type = QType, class = QClass}|_]}}, - State = #state{socket = Socket, idx = Idx, dns_servers = DnsServers}) -> - - lists:foreach(fun({DnsIp, DnsPort}) -> - ok = gen_udp:send(Socket, DnsIp, DnsPort, Request), - Key = {Idx, TxId, DnsIp, DnsPort, QName, QType, QClass}, - logger:debug("[dns_resolver] key: ~p, send to: ~p, packet: ~p", [Key, {DnsIp, DnsPort}, Request]), - dns_pending_wheel:insert(Key, {Ref, ReceiverPid}) - end, DnsServers), - - {noreply, State}. - -%% @private -%% @doc Handling all non call/cast messages --spec(handle_info(Info :: timeout() | term(), State :: #state{}) -> - {noreply, NewState :: #state{}} | - {noreply, NewState :: #state{}, timeout() | hibernate} | - {stop, Reason :: term(), NewState :: #state{}}). -handle_info({udp, Socket, TargetIp, TargetPort, Resp}, State = #state{socket = Socket, idx = Idx}) -> - try dns:decode_message(Resp) of - #dns_message{id = TxId, questions = [#dns_query{name = QName, type = QType, class = QClass}|_]} -> - Key = {Idx, TxId, TargetIp, TargetPort, QName, QType, QClass}, - Records = dns_pending_wheel:lookup(Key), - dns_pending_wheel:delete(Key), - resolver_reply(Records, Resp); - _ -> - ok +-spec resolve(IpPacket :: binary()) -> {ok, RespIpPacket :: binary()} | {error, Reason :: term()}. +resolve(IpPacket) when is_binary(IpPacket) -> + try + {#ipv4{saddr = ReqSAddr, daddr = ReqDAddr, p = Protocol}, ReqIpPayload} = pkt:ipv4(IpPacket), + case Protocol of + ?UDP_PROTOCOL -> + {#udp{sport = ReqSPort, dport = ReqDPort}, UdpPayload} = pkt:udp(ReqIpPayload), + DnsQueryMessage = dns:decode_message(UdpPayload), + case resolve0(DnsQueryMessage) of + {ok, DnsResp} -> + RespIpPacket = build_ip_packet(ReqDAddr, ReqSAddr, ReqDPort, ReqSPort, DnsResp), + {ok, RespIpPacket}; + {error, Reason} -> + {error, Reason} + end; + _ -> + {error, invalid_packet} + end catch error:_ -> - ok - end, - {noreply, State}. - -%% @private -%% @doc This function is called by a gen_server when it is about to -%% terminate. It should be the opposite of Module:init/1 and do any -%% necessary cleaning up. When it returns, the gen_server terminates -%% with Reason. The return value is ignored. --spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()), - State :: #state{}) -> term()). -terminate(_Reason, _State = #state{}) -> - ok. - -%% @private -%% @doc Convert process state when code is changed --spec(code_change(OldVsn :: term() | {down, term()}, State :: #state{}, - Extra :: term()) -> - {ok, NewState :: #state{}} | {error, Reason :: term()}). -code_change(_OldVsn, State = #state{}, _Extra) -> - {ok, State}. + {error, invalid_packet} + end. %%%=================================================================== %%% Internal functions %%%=================================================================== --spec resolver_reply(list(), Resp :: binary()) -> no_return(). -resolver_reply(Records, Resp) when is_binary(Resp) -> - lists:foreach(fun({_, {Ref, ReceiverPid}}) -> - case is_process_alive(ReceiverPid) of - true -> - ReceiverPid ! {dns_resolver_reply, Ref, Resp}; - false -> - ok - end - end, Records). \ No newline at end of file +-spec resolve0(Packet :: binary()) -> {ok, Resp :: binary()} | {error, Reason :: any()}. +resolve0(QueryMsg = #dns_message{qc = 1, questions = [#dns_query{name = QName, type = QType, class = QClass}|_]}) -> + %% 查找是否是内置的域名 + case sdlan_hostname_regedit:lookup(QName) of + {ok, Ip} -> + Answer = #dns_rr { + name = QName, + type = QType, + class = QClass, + ttl = 300, + data = #dns_rrdata_a { + ip = Ip + } + }, + RespMsg = QueryMsg#dns_message{ + qr = true, + ra = true, + anc = 1, + auc = 0, + adc = 0, + answers = [Answer], + authority = [], + additional = [] + }, + logger:debug("[dns_resolver] punchnet inbuilt qnanme: ~p, ip: ~p", [QName, Ip]), + {ok, dns:encode_message(RespMsg)}; + error -> + EmptyDnsResp = dns:encode_message(build_nxdomain_response(QueryMsg)), + {ok, EmptyDnsResp} + end; +resolve0(Error) -> + {error, Error}. + +-spec build_nxdomain_response(QueryMsg :: #dns_message{}) -> EmptyResp :: #dns_message{}. +build_nxdomain_response(QueryMsg) -> + QueryMsg#dns_message{ + qr = true, + aa = true, + ra = true, + rc = ?DNS_RCODE_NXDOMAIN, + anc = 0, + auc = 0, + adc = 0, + answers = [], + authority = [], + additional = [] + }. + +-spec build_ip_packet(SAddr :: inet:ip4_address(), DAddr :: inet:ip4_address(), SPort :: integer(), DPort :: integer(), Payload :: binary()) -> IpPacket :: binary(). +build_ip_packet(SAddr, DAddr, SPort, DPort, UdpPayload) when is_integer(SPort), is_integer(DPort), is_binary(UdpPayload) -> + ULen = 8 + byte_size(UdpPayload), + RespUdpHeader = pkt:udp(#udp{ + sport = SPort, + dport = DPort, + ulen = ULen, + sum = dns_utils:udp_checksum(SAddr, DAddr, SPort, DPort, UdpPayload) + }), + IpPayload = <>, + + IpPacket0 = #ipv4{ + len = 20 + ULen, + ttl = 64, + off = 0, + mf = 0, + sum = 0, + p = ?UDP_PROTOCOL, + saddr = SAddr, + daddr = DAddr, + opt = <<>> + }, + IpCheckSum = dns_utils:ip_checksum(IpPacket0), + IpHeader = pkt:ipv4(IpPacket0#ipv4{sum = IpCheckSum}), + + <>. \ No newline at end of file diff --git a/src/dns_proxy/dns_server.erl b/src/dns_proxy/dns_server.erl index 4c6abfe..ba4eda8 100644 --- a/src/dns_proxy/dns_server.erl +++ b/src/dns_proxy/dns_server.erl @@ -1,19 +1,130 @@ +%%%------------------------------------------------------------------- +%%% @author anlicheng +%%% @copyright (C) 2024, +%%% @doc +%%% +%%% @end +%%% Created : 09. 4月 2024 17:37 +%%%------------------------------------------------------------------- -module(dns_server). --export([start_link/1, init/1]). +-author("anlicheng"). +-include("sdlan.hrl"). +-include("sdlan_pb.hrl"). -start_link(Port) when is_integer(Port) -> - {ok, spawn_link(?MODULE, init, [Port])}. +-behaviour(gen_server). -init(Port) -> - dns_cache:init(), - {ok, Sock} = gen_udp:open(Port, [binary, {active, true}]), - logger:debug("[dns_server] DNS Forwarder started on UDP port ~p~n", [Port]), - loop(Sock). +%% API +-export([start_link/2]). +-export([get_name/1]). -loop(Sock) -> - receive - {udp, Sock, Ip, Port, Packet} -> - logger:debug("[dns_server] ip: ~p, get a packet: ~p", [{Ip, Port}, Packet]), - spawn(fun() -> dns_handler:handle(Sock, Ip, Port, Packet) end), - loop(Sock) - end. \ No newline at end of file +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). + +-define(SERVER, ?MODULE). + +-record(state, { + socket +}). + +%%%=================================================================== +%%% API +%%%=================================================================== + +-spec get_name(Id :: integer()) -> atom(). +get_name(Id) when is_integer(Id) -> + list_to_atom("dns_server:" ++ integer_to_list(Id)). + +%% @doc Spawns the server and registers the local name (unique) +-spec(start_link(Name :: atom(), Port :: integer()) -> + {ok, Pid :: pid()} | ignore | {error, Reason :: term()}). +start_link(Name, Port) when is_atom(Name), is_integer(Port) -> + gen_server:start_link({local, Name}, ?MODULE, [Port], []). + +%%%=================================================================== +%%% gen_server callbacks +%%%=================================================================== + +%% @private +%% @doc Initializes the server +-spec(init(Args :: term()) -> + {ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} | + {stop, Reason :: term()} | ignore). +init([Port]) -> + %% 需要提高进程的调度优先级 + erlang:process_flag(priority, high), + Opts = [ + binary, + {reuseaddr, true}, + {reuseport, true}, + {active, true}, + {recbuf, 5 * 1024 * 1024}, + {sndbuf, 5 * 1024 * 1024} + ], + {ok, Socket} = gen_udp:open(Port, Opts), + inet_udp:controlling_process(Socket, self()), + + logger:debug("[sdlan_stun] start at port: ~p", [Port]), + {ok, #state{socket = Socket}}. + +%% @private +%% @doc Handling call messages +-spec(handle_call(Request :: term(), From :: {pid(), Tag :: term()}, + State :: #state{}) -> + {reply, Reply :: term(), NewState :: #state{}} | + {reply, Reply :: term(), NewState :: #state{}, timeout() | hibernate} | + {noreply, NewState :: #state{}} | + {noreply, NewState :: #state{}, timeout() | hibernate} | + {stop, Reason :: term(), Reply :: term(), NewState :: #state{}} | + {stop, Reason :: term(), NewState :: #state{}}). +handle_call(_Request, _From, State = #state{}) -> + {reply, ok, State}. + +%% @private +%% @doc Handling cast messages +-spec(handle_cast(Request :: term(), State :: #state{}) -> + {noreply, NewState :: #state{}} | + {noreply, NewState :: #state{}, timeout() | hibernate} | + {stop, Reason :: term(), NewState :: #state{}}). +%% 当前node下的转发,基于进程间的通讯 +handle_cast(_Request, State) -> + {noreply, State}. + +%% @private +%% @doc Handling all non call/cast messages +-spec(handle_info(Info :: timeout() | term(), State :: #state{}) -> + {noreply, NewState :: #state{}} | + {noreply, NewState :: #state{}, timeout() | hibernate} | + {stop, Reason :: term(), NewState :: #state{}}). +handle_info({udp, Sock, Ip, Port, IpPacket}, State = #state{socket = Sock}) -> + case dns_resolver:resolve(IpPacket) of + {ok, RespIpPacket} -> + gen_udp:send(Sock, Ip, Port, RespIpPacket); + {error, Reason} -> + logger:debug("[dns_server] resolve dns query error: ~p", [Reason]) + end, + {noreply, State}; +handle_info(Info, State) -> + logger:error("[sdlan_stun] get a unknown message: ~p, channel will closed", [Info]), + {noreply, State}. + +%% @private +%% @doc This function is called by a gen_server when it is about to +%% terminate. It should be the opposite of Module:init/1 and do any +%% necessary cleaning up. When it returns, the gen_server terminates +%% with Reason. The return value is ignored. +-spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()), + State :: #state{}) -> term()). +terminate(_Reason, _State = #state{}) -> + ok. + +%% @private +%% @doc Convert process state when code is changed +-spec(code_change(OldVsn :: term() | {down, term()}, State :: #state{}, + Extra :: term()) -> + {ok, NewState :: #state{}} | {error, Reason :: term()}). +code_change(_OldVsn, State = #state{}, _Extra) -> + {ok, State}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== \ No newline at end of file diff --git a/src/dns_proxy/dns_proxy_sup.erl b/src/dns_proxy/dns_server_sup.erl similarity index 75% rename from src/dns_proxy/dns_proxy_sup.erl rename to src/dns_proxy/dns_server_sup.erl index 9882fb7..1f3f898 100644 --- a/src/dns_proxy/dns_proxy_sup.erl +++ b/src/dns_proxy/dns_server_sup.erl @@ -3,7 +3,7 @@ %% @end %%%------------------------------------------------------------------- --module(dns_proxy_sup). +-module(dns_server_sup). -behaviour(supervisor). @@ -26,25 +26,18 @@ start_link() -> %% modules => modules()} % optional init([]) -> SupFlags = #{strategy => one_for_one, intensity => 1000, period => 3600}, - Port = 15353, - Specs = [ + AcceptorNum = 10, + + Specs = lists:map(fun(Id) -> + Name = dns_server:get_name(Id), #{ - id => dns_handler_sup, - start => {dns_handler_sup, start_link, []}, - restart => permanent, - shutdown => 2000, - type => supervisor, - modules => ['dns_handler_sup'] - }, - #{ - id => dns_server, - start => {dns_server, start_link, [Port]}, + id => Name, + start => {sdlan_stun, start_link, [Name, Port]}, restart => permanent, shutdown => 2000, type => worker, modules => ['dns_server'] } - ], - + end, lists:seq(1, AcceptorNum)), {ok, {SupFlags, Specs}}. \ No newline at end of file diff --git a/src/sdlan_app.erl b/src/sdlan_app.erl index ee34e55..537aae1 100644 --- a/src/sdlan_app.erl +++ b/src/sdlan_app.erl @@ -17,7 +17,6 @@ start(_StartType, _StartArgs) -> %% 启动注册表 sdlan_hostname_regedit:init(), sdlan_domain_regedit:init(), - dns_pending_wheel:start(), %% 权限的数据管理 identity_policy_ets:init(),