diff --git a/apps/sdlan/include/dns_proxy.hrl b/apps/sdlan/include/dns_proxy.hrl new file mode 100644 index 0000000..6ff16e7 --- /dev/null +++ b/apps/sdlan/include/dns_proxy.hrl @@ -0,0 +1,21 @@ +%%%------------------------------------------------------------------- +%%% @author anlicheng +%%% @copyright (C) 2025, +%%% @doc +%%% +%%% @end +%%% Created : 04. 12月 2025 11:41 +%%%------------------------------------------------------------------- +-author("anlicheng"). + +-record(dns_cache, { + %% {Qname, QType, QClass} + key, + answers = [], + authority = [], + additional = [], + rc :: integer(), + flags = #{}, + % unix time + expire_at :: integer() +}). \ No newline at end of file diff --git a/apps/sdlan/src/dns_proxy/dns_cache.erl b/apps/sdlan/src/dns_proxy/dns_cache.erl new file mode 100644 index 0000000..da597c0 --- /dev/null +++ b/apps/sdlan/src/dns_proxy/dns_cache.erl @@ -0,0 +1,59 @@ +-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, + lager: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/apps/sdlan/src/dns_proxy/dns_handler.erl b/apps/sdlan/src/dns_proxy/dns_handler.erl new file mode 100644 index 0000000..a984c9a --- /dev/null +++ b/apps/sdlan/src/dns_proxy/dns_handler.erl @@ -0,0 +1,240 @@ +%%%------------------------------------------------------------------- +%%% @author anlicheng +%%% @copyright (C) 2025, +%%% @doc +%%% +%%% @end +%%% Created : 03. 12月 2025 23:00 +%%%------------------------------------------------------------------- +-module(dns_handler). +-author("anlicheng"). + +-behaviour(gen_server). + +-include_lib("dns_erlang/include/dns.hrl"). +-include_lib("pkt/include/pkt.hrl"). +-include("dns_proxy.hrl"). + +%% 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([handle_ip_packet/5]). + +-define(SERVER, ?MODULE). +-define(RESOLVER_POOL, dns_resolver_pool). + +%% 协议部分 +-define(TCP_PROTOCOL, 6). +-define(UDP_PROTOCOL, 17). + +-record(state, {}). + +%%%=================================================================== +%%% API +%%%=================================================================== + +start_link() -> + gen_server:start_link(?MODULE, [], []). + +handle_ip_packet(Pid, Sock, SrcIp, SrcPort, Packet) when is_pid(Pid) -> + gen_server:cast(Pid, {handle_ip_packet, Sock, SrcIp, SrcPort, Packet}). + +%%%=================================================================== +%%% 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, #state{}}. + +%% @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({handle_ip_packet, Sock, SrcIp, SrcPort, IpPacket}, State) -> + {#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), + lager:debug("[dns_handler] ip packet: ~p", [RespIpPacket]), + gen_udp:send(Sock, SrcIp, SrcPort, RespIpPacket); + {error, Reason} -> + lager:debug("[dns_handler] resolver get error: ~p", [Reason]) + end; + false -> + lager:debug("[dns_handler] resolver invalid protocol: ~p", [Protocol]) + end, + {stop, normal, 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(_Info, State) -> + {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 +%%%=================================================================== + +-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_dns_resolver:resolve(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 = [] + }, + lager:debug("[dns_handler] inbuilt qnanme: ~p, ip: ~p", [QName, Ip]), + {ok, dns:encode_message(RespMsg)}; + error -> + case dns_cache:lookup(Question) of + {hit, Cache} -> + lager:debug("[dns_handler] question: ~p, hit cache answers: ~p", [Question, Cache#dns_cache.answers]), + RespMsg = build_response(QueryMsg, Cache), + {ok, dns:encode_message(RespMsg)}; + miss -> + lager:debug("[dns_handler] cache is miss"), + Ref = make_ref(), + forward_to_upstream(Ref, Packet, QueryMsg), + receive + {dns_resolver_reply, Ref, Resp} -> + case dns:decode_message(Resp) of + RespMsg = #dns_message{answers = Answers} -> + lager:debug("[dns_handler] get a response answers: ~p", [Answers]), + dns_cache:insert(Question, RespMsg), + {ok, Resp}; + Error -> + lager:debug("[dns_handler] parse reply get error: ~p", [Error]), + {error, Error} + end + after 5000 -> + {error, timeout} + end + end + end; +resolver0(_, Error) -> + lager:warning("[dns_handler] decode dns query get error: ~p", [Error]), + {error, Error}. + +-spec forward_to_upstream(Ref :: reference(), Request :: binary(), QueryMsg :: #dns_message{}) -> no_return(). +forward_to_upstream(Ref, Request, QueryMsg) -> + ReceiverPid = self(), + poolboy:transaction(?RESOLVER_POOL, fun(Pid) -> dns_resolver:forward(Pid, ReceiverPid, Ref, Request, QueryMsg) end). + +-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_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/apps/sdlan/src/dns_proxy/dns_handler_sup.erl b/apps/sdlan/src/dns_proxy/dns_handler_sup.erl new file mode 100644 index 0000000..009395d --- /dev/null +++ b/apps/sdlan/src/dns_proxy/dns_handler_sup.erl @@ -0,0 +1,66 @@ +%%%------------------------------------------------------------------- +%%% @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/apps/sdlan/src/dns_proxy/dns_proxy_sup.erl b/apps/sdlan/src/dns_proxy/dns_proxy_sup.erl new file mode 100644 index 0000000..9882fb7 --- /dev/null +++ b/apps/sdlan/src/dns_proxy/dns_proxy_sup.erl @@ -0,0 +1,50 @@ +%%%------------------------------------------------------------------- +%% @doc dns_proxy top level supervisor. +%% @end +%%%------------------------------------------------------------------- + +-module(dns_proxy_sup). + +-behaviour(supervisor). + +-export([start_link/0]). +-export([init/1]). + +-define(SERVER, ?MODULE). + +start_link() -> + supervisor:start_link({local, ?SERVER}, ?MODULE, []). + +%% sup_flags() = #{strategy => strategy(), % optional +%% intensity => non_neg_integer(), % optional +%% period => pos_integer()} % optional +%% child_spec() = #{id => child_id(), % mandatory +%% start => mfargs(), % mandatory +%% restart => restart(), % optional +%% shutdown => shutdown(), % optional +%% type => worker(), % optional +%% modules => modules()} % optional +init([]) -> + SupFlags = #{strategy => one_for_one, intensity => 1000, period => 3600}, + + Port = 15353, + Specs = [ + #{ + 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]}, + restart => permanent, + shutdown => 2000, + type => worker, + modules => ['dns_server'] + } + ], + + {ok, {SupFlags, Specs}}. \ No newline at end of file diff --git a/apps/sdlan/src/dns_proxy/dns_resolver.erl b/apps/sdlan/src/dns_proxy/dns_resolver.erl new file mode 100644 index 0000000..7dc2956 --- /dev/null +++ b/apps/sdlan/src/dns_proxy/dns_resolver.erl @@ -0,0 +1,150 @@ +%%%------------------------------------------------------------------- +%%% @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/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). +-define(REQUEST_TTL, 5000). + +-record(state, { + socket, + tid, + dns_servers = [] +}). + +%%%=================================================================== +%%% 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(dns_proxy, public_dns_servers), + + {ok, Sock} = gen_udp:open(0, [binary, {active, true}]), + %% 通过ets来保存映射关系 + Tid = ets:new(random_table(), [set, {read_concurrency, true}, {write_concurrency, true}, private]), + + {ok, #state{socket = Sock, tid = Tid, 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, tid = Tid, dns_servers = DnsServers}) -> + Keys = lists:foldl(fun({DnsIp, DnsPort}, Acc) -> + ok = gen_udp:send(Socket, DnsIp, DnsPort, Request), + Key = {TxId, DnsIp, DnsPort, QName, QType, QClass}, + lager:debug("[dns_resolver] key: ~p, send to: ~p, packet: ~p", [Key, {DnsIp, DnsPort}, Request]), + true = ets:insert(Tid, {Key, Ref, ReceiverPid}), + [Key|Acc] + end, [], DnsServers), + erlang:start_timer(?REQUEST_TTL, self(), {clean_ticker, Keys}), + + {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, type = QType, class = QClass}|_]} -> + Key = {TxId, TargetIp, TargetPort, QName, QType, QClass}, + Records = ets:take(Tid, Key), + resolver_reply(Records, Resp); + _ -> + ok + end, + {noreply, State}; + +handle_info({timeout, _, {clean_ticker, Keys}}, State = #state{tid = Tid}) -> + lists:foreach(fun(Key) -> ets:delete(Tid, Key) end, Keys), + {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 +%%%=================================================================== + +-spec random_table() -> atom(). +random_table() -> + list_to_atom("udp_ets:" ++ integer_to_list(erlang:unique_integer([monotonic, positive]))). + +-spec resolver_reply(list(), Resp :: binary()) -> no_return(). +resolver_reply([{_, Ref, ReceiverPid}], Resp) when is_binary(Resp) -> + case is_process_alive(ReceiverPid) of + true -> + ReceiverPid ! {dns_resolver_reply, Ref, Resp}; + false -> + ok + end; +resolver_reply(_, _) -> + ok. \ No newline at end of file diff --git a/apps/sdlan/src/dns_proxy/dns_server.erl b/apps/sdlan/src/dns_proxy/dns_server.erl new file mode 100644 index 0000000..ba870ac --- /dev/null +++ b/apps/sdlan/src/dns_proxy/dns_server.erl @@ -0,0 +1,24 @@ +-module(dns_server). +-export([start_link/1, init/1]). + +start_link(Port) when is_integer(Port) -> + {ok, spawn_link(?MODULE, init, [Port])}. + +init(Port) -> + dns_cache:init(), + {ok, Sock} = gen_udp:open(Port, [binary, {active, true}]), + io:format("DNS Forwarder started on UDP port ~p~n", [Port]), + loop(Sock). + +loop(Sock) -> + receive + {udp, Sock, Ip, Port, Packet} -> + lager:debug("[dns_server] ip: ~p, get a packet: ~p", [{Ip, Port}, Packet]), + case dns_handler_sup:start_handler() of + {ok, HandlerPid} -> + dns_handler:handle_ip_packet(HandlerPid, Sock, Ip, Port, Packet); + Error -> + lager:debug("[dns_server] start handler get error: ~p", [Error]) + end, + loop(Sock) + end. \ No newline at end of file diff --git a/apps/sdlan/src/dns_proxy/dns_utils.erl b/apps/sdlan/src/dns_proxy/dns_utils.erl new file mode 100644 index 0000000..2d07524 --- /dev/null +++ b/apps/sdlan/src/dns_proxy/dns_utils.erl @@ -0,0 +1,131 @@ +%%%------------------------------------------------------------------- +%%% @author anlicheng +%%% @copyright (C) 2025, +%%% @doc +%%% +%%% @end +%%% Created : 05. 12月 2025 18:06 +%%%------------------------------------------------------------------- +-module(dns_utils). +-author("anlicheng"). + +-include_lib("pkt/include/pkt.hrl"). + +%% API +-export([ends_with/2, parse_address/1, checksum/1, udp_checksum/5, ip_checksum/1]). +-export([test/0]). + +-spec ends_with(Bin :: binary(), Suffix :: binary()) -> boolean(). +ends_with(Bin, Suffix) when is_binary(Bin), is_binary(Suffix) -> + case binary:match(Bin, Suffix) of + {Pos, Len} -> + Pos + Len =:= byte_size(Bin); + nomatch -> + false + end. + +-spec parse_address(Ip :: any()) -> {ok, IpAddress :: inet:ip4_address()} | {error, Reason :: any()}. +parse_address(Ip = {Ip0, Ip1, Ip2, Ip3}) when is_integer(Ip0), is_integer(Ip1), is_integer(Ip2), is_integer(Ip3) -> + {ok, Ip}; +parse_address(Bin) when is_binary(Bin) -> + inet:parse_address(binary_to_list(Bin)). + + +%%-------------------------------------------------------------------- +%% @doc +%% Calculate 16-bit one's-complement checksum. +%% +%% Input: +%% Bin :: binary() +%% +%% Output: +%% Checksum :: 0..16#FFFF +%% +%% Usage: +%% Checksum = checksum(Bin). +%% +%% Notes: +%% - Bin is treated as big-endian 16-bit words +%% - If Bin length is odd, a zero byte is padded +%%-------------------------------------------------------------------- +-spec checksum(binary()) -> non_neg_integer(). +checksum(Bin) when is_binary(Bin) -> + Sum = checksum_sum(Bin, 0), + %% fold carry bits + Folded = fold16(Sum), + %% one's complement + (bnot Folded) band 16#FFFF. +checksum_sum(<<>>, Acc) -> + Acc; +checksum_sum(<>, Acc) -> + checksum_sum(Rest, Acc + Word); +checksum_sum(<>, Acc) -> + %% odd length: pad low byte with zero + checksum_sum(<<>>, Acc + (Byte bsl 8)). + +fold16(S) when S > 16#FFFF -> + fold16((S band 16#FFFF) + (S bsr 16)); +fold16(S) -> + S. + +-spec udp_checksum(SAddr :: inet:ip4_address(), DAddr :: inet:ip4_address(), SPort :: integer(), DPort :: integer(), UDPPayload :: binary()) -> non_neg_integer(). +udp_checksum({SA1, SA2, SA3, SA4}, {DA1, DA2, DA3, DA4}, SPort, DPort, UDPPayload) when is_integer(SPort), is_integer(DPort), is_binary(UDPPayload) -> + ULen = 8 + byte_size(UDPPayload), + PseudoHeader = <>, + UDPHeader = <>, + CheckSum = checksum(<>), + case CheckSum of + 0 -> + 16#FFFF; + _ -> + CheckSum + end. + +-spec ip_checksum(Ipv4 :: #ipv4{}) -> non_neg_integer(). +ip_checksum(#ipv4{hl = HL, tos = ToS, len = Len, + id = Id, df = DF, mf = MF, + off = Off, ttl = TTL, p = P, + saddr = {SA1, SA2, SA3, SA4}, + daddr = {DA1, DA2, DA3, DA4}, + opt = Opt}) -> + + IPBinForChecksum = + <<4:4, HL:4, %% Version=4 + IHL + ToS:8, %% Type of Service + Len:16/big, %% Total Length + Id:16/big, %% Identification + DF:1, MF:1, Off:14, %% Flags + Fragment offset + TTL:8, %% TTL + P:8, %% Protocol + 0:16, %% checksum field set to 0 for calculation + SA1:8, SA2:8, SA3:8, SA4:8, %% Source IP + DA1:8, DA2:8, DA3:8, DA4:8, %% Dest IP + Opt/binary>>, %% Options (可选) + CheckSum = checksum(IPBinForChecksum), + case CheckSum of + 0 -> + 16#FFFF; + _ -> + CheckSum + end. + +test() -> + %Bin = <<69,0,0,77,48,179,0,0,64,17,28,168,100,123,0,2,100,100,100,100,252,230,0,53,0,57,6,92,152,24,1,0,0,1,0,0,0,0,0,0,2,100,98,7,95,100,110,115,45,115,100,4,95,117,100,112,8,112,117,110,99,104,110,101,116,2,116,115,3,110,101,116,0,0,12,0,1>>, + Bin = <<69,0,0,93,0,0,0,0,64,6,77,86,100,100,100,100,100,123,0,2,0,53,196,102,0,73,39,7,215,192,129,128,0,1,0,1,0,0,0,0,2,108,98,7,95,100,110,115,45,115,100,4,95,117,100,112,8,112,117,110,99,104,110,101,116,2,116,115,3,110,101,116,0,0,12,0,1,192,12,0,12,0,1,0,0,1,44,0,4,192,168,1,101>>, + + {IPPacket = #ipv4{ + saddr = SAddr, + daddr = DAddr, + sum = IpSum + }, UdpPacket} = pkt:ipv4(Bin), + + {UDP = #udp{sport = SPort, dport = DPort, sum = CheckSum}, UDPPayload} = pkt:udp(UdpPacket), + + X = udp_checksum(SAddr, DAddr, SPort, DPort, UDPPayload), + + lager:debug("ip_sum: ~p, =: ~p, udp: ~p, checkSum: ~p, =: ~p", [IpSum, ip_checksum(IPPacket), UDP, CheckSum, X]), + + dns:decode_message(UDPPayload). \ No newline at end of file diff --git a/apps/sdlan/src/sdlan.app.src b/apps/sdlan/src/sdlan.app.src index 114507c..be8ab20 100644 --- a/apps/sdlan/src/sdlan.app.src +++ b/apps/sdlan/src/sdlan.app.src @@ -14,8 +14,9 @@ jiffy, hackney, gpb, - dns_proxy, throttle, + dns_erlang, + pkt, parse_trans, mnesia, erts, diff --git a/apps/sdlan/src/sdlan_app.erl b/apps/sdlan/src/sdlan_app.erl index c0ff69f..2617106 100644 --- a/apps/sdlan/src/sdlan_app.erl +++ b/apps/sdlan/src/sdlan_app.erl @@ -16,7 +16,6 @@ start(_StartType, _StartArgs) -> %% 加速内存的回收 erlang:system_flag(fullsweep_after, 16), - start_dns_resolver(), start_http_server(), start_tcp_server(), sdlan_sup:start_link(). @@ -74,8 +73,4 @@ start_tcp_server() -> }, {ok, _} = ranch:start_listener('sdlan/tcp_server', ranch_tcp, TransOpts, sdlan_channel, []), - lager:debug("[sdlan_app] the tcp server start at: ~p", [Port]). - -%% 启动dns的解析服务 -start_dns_resolver() -> - ok = dns_proxy:start_proxy(15353, {sdlan_dns_resolver, resolve, []}). \ No newline at end of file + lager:debug("[sdlan_app] the tcp server start at: ~p", [Port]). \ No newline at end of file diff --git a/config/sys-dev.config b/config/sys-dev.config index 01c832a..d6cee1f 100644 --- a/config/sys-dev.config +++ b/config/sys-dev.config @@ -23,6 +23,12 @@ % {stun_servers, [{'sdlan_stun:2:1', 1265}, {'sdlan_stun:2:2', 1266}]}, + %% 公共的dns域名解析服务 + {public_dns_servers, [ + {{114, 114, 114, 114}, 53}, + {{8,8,8,8}, 53} + ]}, + {pools, [ %% mysql连接池配置 {mysql_sdlan, @@ -37,6 +43,11 @@ {database, "sdlan"}, {queries, [<<"set names utf8">>]} ] + }, + + {dns_resolver_pool, + [{size, 20}, {max_overflow, 100}, {worker_module, dns_resolver}], + [] } ]}, @@ -49,22 +60,6 @@ {access_context, sync_transaction} ]}, - {dns_proxy, [ - - %% 公共的dns域名解析服务 - {public_dns_servers, [ - {{114, 114, 114, 114}, 53}, - {{8,8,8,8}, 53} - ]}, - - {dns_resolver_pool, [ - {size, 20}, - {max_overflow, 100}, - {worker_module, dns_resolver} - ]} - - ]}, - %% 系统日志配置,系统日志为lager, 支持日志按日期自动分割 {lager, [ {colored, true}, diff --git a/rebar.config b/rebar.config index eb5f28a..19b784d 100644 --- a/rebar.config +++ b/rebar.config @@ -2,13 +2,14 @@ {deps, [ {poolboy, ".*", {git, "https://github.com/devinus/poolboy.git", {tag, "1.5.1"}}}, {hackney, ".*", {git, "https://github.com/benoitc/hackney.git", {tag, "1.16.0"}}}, - {sync, ".*", {git, "https://github.com/rustyio/sync.git", {branch, "master"}}}, {jiffy, ".*", {git, "https://github.com/davisp/jiffy.git", {tag, "1.1.1"}}}, {cowboy, ".*", {git, "https://github.com/ninenines/cowboy.git", {tag, "2.12.0"}}}, {mysql, ".*", {git, "https://github.com/mysql-otp/mysql-otp", {tag, "1.8.0"}}}, {gpb, ".*", {git, "https://github.com/tomas-abrahamsson/gpb.git", {tag, "4.21.1"}}}, - {dns_proxy, ".*", {git, "https://gitea.s5s8.com/anlicheng/dns_proxy.git", {branch, "main"}}}, {throttle, ".*", {git, "https://github.com/lambdaclass/throttle.git", {tag, "0.3.0"}}}, + {dns_erlang, ".*", {git, "https://github.com/dnsimple/dns_erlang.git", {tag, "v4.4.0"}}}, + {pkt, ".*", {git, "https://github.com/msantos/pkt.git", {tag, "0.6.0"}}}, + {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"}}} ]}.