diff --git a/apps/dns_proxy/src/dns_handler.erl b/apps/dns_proxy/src/dns_handler.erl index 51e12a5..80833dc 100644 --- a/apps/dns_proxy/src/dns_handler.erl +++ b/apps/dns_proxy/src/dns_handler.erl @@ -13,22 +13,27 @@ -include_lib("dns_erlang/include/dns_records.hrl"). -include_lib("dns_erlang/include/dns_terms.hrl"). --export([handle_request/4]). +-export([start_link/4, init/4]). -handle_request(Sock, Ip, Port, Packet) -> +start_link(Sock, Ip, Port, Packet) -> + {ok, proc_lib:spawn(?MODULE, init, [Sock, Ip, Port, Packet])}. + +init(Sock, Ip, Port, Packet) -> case dns:decode_message(Packet) of - {ok, Msg} -> - %Qname = (Msg#dns_message.questions)#dns_question.qname, - Qname = <<"www.baidu.com">>, + Msg = #dns_message{qc = 1, questions = [Question|_]} -> + Qname = Question#dns_query.name, + lager:debug("[dns_handler] qname: ~p", [Qname]), case dns_cache:lookup(Qname) of {hit, R} -> Resp = build_response(Msg, R), gen_udp:send(Sock, Ip, Port, dns:encode_message(Resp)); miss -> + lager:debug("[dns_handler] cache is miss"), forward_to_upstream(Sock, Ip, Port, Packet) end; - _ -> - ok + Other -> + lager:warning("decode msg get error: ~p", [Other]), + exit(normal) end. forward_to_upstream(Sock, Ip, Port, Packet) -> diff --git a/apps/dns_proxy/src/dns_handler_sup.erl b/apps/dns_proxy/src/dns_handler_sup.erl index cead1e8..6fad53a 100644 --- a/apps/dns_proxy/src/dns_handler_sup.erl +++ b/apps/dns_proxy/src/dns_handler_sup.erl @@ -47,7 +47,7 @@ init([]) -> SupFlags = #{strategy => simple_one_for_one, intensity => 0, period => 1}, Spec = #{ id => dns_handler, - start => {'dns_handler', dns_handler, []}, + start => {'dns_handler', start_link, []}, restart => temporary, shutdown => 2000, type => worker, diff --git a/apps/dns_proxy/src/dns_proxy.app.src b/apps/dns_proxy/src/dns_proxy.app.src index 9b811bd..b529991 100644 --- a/apps/dns_proxy/src/dns_proxy.app.src +++ b/apps/dns_proxy/src/dns_proxy.app.src @@ -8,6 +8,7 @@ sync, lager, dns_erlang, + poolboy, kernel, stdlib ]}, diff --git a/apps/dns_proxy/src/dns_proxy_sup.erl b/apps/dns_proxy/src/dns_proxy_sup.erl index 0d75827..d01427f 100644 --- a/apps/dns_proxy/src/dns_proxy_sup.erl +++ b/apps/dns_proxy/src/dns_proxy_sup.erl @@ -27,6 +27,10 @@ start_link() -> %% modules => modules()} % optional init([]) -> SupFlags = #{strategy => one_for_one, intensity => 1000, period => 3600}, + + {ok, PoolArgs} = application:get_env(dns_proxy, dns_resolver_pool), + ResolverPoolSpec = poolboy:child_spec(dns_resolver_pool, [{name, {local, dns_resolver_pool}}|PoolArgs], []), + ChildSpecs = [ #{ id => dns_handler_sup, @@ -45,6 +49,6 @@ init([]) -> modules => ['dns_server'] } ], - {ok, {SupFlags, ChildSpecs}}. + {ok, {SupFlags, [ResolverPoolSpec|ChildSpecs]}}. %% internal functions diff --git a/apps/dns_proxy/src/dns_resolver.erl b/apps/dns_proxy/src/dns_resolver.erl new file mode 100644 index 0000000..fcdb442 --- /dev/null +++ b/apps/dns_proxy/src/dns_resolver.erl @@ -0,0 +1,130 @@ +%%%------------------------------------------------------------------- +%%% @author anlicheng +%%% @copyright (C) 2025, +%%% @doc +%%% +%%% @end +%%% Created : 03. 12月 2025 18:26 +%%%------------------------------------------------------------------- +-module(dns_resolver). +-author("anlicheng"). +-include_lib("dns_erlang/include/dns.hrl"). + +-behaviour(gen_server). + +%% API +-export([start_link/0]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). + +-export([forward/6]). + +-define(SERVER, ?MODULE). + +-record(state, { + socket, + tid +}). + +%%%=================================================================== +%%% API +%%%=================================================================== + +forward(Pid, ReceiverPid, Ref, TargetIp, TargetPort, Request) -> + gen_server:cast(Pid, {forward, ReceiverPid, Ref, TargetIp, TargetPort, Request}). + +%% @doc Spawns the server and registers the local name (unique) +-spec(start_link() -> + {ok, Pid :: pid()} | ignore | {error, Reason :: term()}). +start_link() -> + gen_server:start_link({local, ?SERVER}, ?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, Sock} = gen_udp:open(0, [binary, {active, true}]), + %% 通过ets来保存映射关系 + Table = list_to_atom("udp_ets:" ++ erlang:unique_integer([monotonic, positive])), + Tid = ets:new(Table, [set, {read_concurrency, true}, {write_concurrency, true}, private]), + + {ok, #state{socket = Sock, tid = Tid}}. + +%% @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, TargetIp, TargetPort, Request}, State = #state{socket = Socket, tid = Tid}) -> + case dns:decode_message(Request) of + #dns_message{id = TxId, questions = [#dns_query{name = QName}|_]} -> + ok = gen_udp:send(Socket, TargetIp, TargetPort, Request), + ok = ets:insert(Tid, {{TxId, TargetIp, TargetPort, QName}, {Ref, ReceiverPid}}); + _ -> + ok + end, + {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{tid = Tid, socket = Socket}) -> + case dns:decode_message(Resp) of + #dns_message{id = TxId, questions = [#dns_query{name = QName}|_]} -> + Key = {TxId, TargetIp, TargetPort, QName}, + case ets:take(Tid, Key) of + [{_, {Ref, ReceiverPid}}] -> + ReceiverPid ! {xyz, Ref, Resp}; + [] -> + ok + end; + _ -> + 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}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== diff --git a/apps/dns_proxy/src/dns_server.erl b/apps/dns_proxy/src/dns_server.erl index aedf48c..afb3785 100644 --- a/apps/dns_proxy/src/dns_server.erl +++ b/apps/dns_proxy/src/dns_server.erl @@ -24,6 +24,7 @@ init() -> loop(Sock) -> receive {udp, Sock, Ip, Port, Packet} -> - dns_handler_sup:start_handler(Sock, Ip, Port, Packet), + Res = dns_handler_sup:start_handler(Sock, Ip, Port, Packet), + lager:debug("ip: ~p, get a packet: ~p, handler res: ~p", [{Ip, Port}, Packet, Res]), loop(Sock) end. \ No newline at end of file diff --git a/config/sys.config b/config/sys.config index f8b9adf..20685e4 100644 --- a/config/sys.config +++ b/config/sys.config @@ -4,7 +4,13 @@ %% 公共的dns域名解析服务 {public_dns_servers, [ {{8,8,8,8}, 53}, - {{1,1,1,1}, 53} + {{8,8,8,8}, 53} + ]}, + + {dns_resolver_pool, [ + {size, 200}, + {max_overflow, 1000}, + {worker_module, dns_resolver} ]} ]}, diff --git a/rebar.config b/rebar.config index 6074325..78a8e2c 100644 --- a/rebar.config +++ b/rebar.config @@ -1,6 +1,7 @@ {erl_opts, [debug_info]}. {deps, [ {dns_erlang, ".*", {git, "https://github.com/dnsimple/dns_erlang.git", {tag, "v4.4.0"}}}, + {poolboy, ".*", {git, "https://github.com/devinus/poolboy.git", {tag, "1.5.1"}}}, {sync, ".*", {git, "https://github.com/rustyio/sync.git", {branch, "master"}}}, {parse_trans, ".*", {git, "https://github.com/uwiger/parse_trans", {tag, "3.0.0"}}}, {lager, ".*", {git,"https://github.com/erlang-lager/lager.git", {tag, "3.9.2"}}}