You can search for identifiers within the package.
in-package search v0.2.0
Source file dns_forward_resolver.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303(*
* Copyright (C) 2016 David Scott <dave@recoil.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*)letsrc=letsrc=Logs.Src.create"Dns_forward"~doc:"DNS resolution"inLogs.Src.set_levelsrc(SomeLogs.Debug);srcmoduleLog=(valLogs.src_logsrc:Logs.LOG)openLwt.Infixletis_in_domainnamedomain=letname'=List.lengthnameanddomain'=List.lengthdomaininname'>=domain'&&beginletto_remove=name'-domain'inletrectrimnxs=matchn,xswith|0,_->xs|_,[]->invalid_arg"trim"|n,_::xs->trim(n-1)xsinlettrimmed_name=trimto_removenameintrimmed_name=domainendmoduleIntSet=Set.Make(structtypet=intletcompare(a:int)(b:int)=compareabend)letchoose_serversconfigrequest=letopenDns.PacketinletopenDns_forward_configin(* Match the name in the query against the configuration *)beginmatchrequestwith|{questions=[{q_name;_}];_}->letlabels=Dns.Name.to_string_listq_nameinletmatching_servers=List.filter(funserver->Domain.Set.fold(funzoneacc->acc||(is_in_domainlabelszone))server.Server.zonesfalse)configinletall=matchmatching_serverswith|_::_->(* If any of the configured domains match, send to these servers *)matching_servers|[]->(* Otherwise send to all servers *)configin(* Now we order by the order field *)letorders=List.fold_left(funsetserver->IntSet.addserver.Server.orderset)IntSet.emptyallinList.map(funorder->List.filter(funserver->server.Server.order=order)all)(IntSet.elementsorders)|_->[]endletor_fail_msgm=m>>=function|Result.Error`Eof->Lwt.failEnd_of_file|Result.Error(`Msgm)->Lwt.fail(Failurem)|Result.Okx->Lwt.returnxmoduletypeS=Dns_forward_s.RESOLVERmoduleMake(Client:Dns_forward_s.RPC_CLIENT)(Time:Mirage_time_lwt.S)(Clock:Mirage_clock_lwt.MCLOCK)=structmoduleCache=Dns_forward_cache.Make(Time)typeclock=Clock.ttypeaddress=Dns_forward_config.Address.ttypemessage_cb=?src:address->?dst:address->buf:Cstruct.t->unit->unitLwt.ttypeconnection={server:Dns_forward_config.Server.t;client:Client.t;mutablereply_expected_since:int64option;(* if None: we don't expect a reply
if Some t: we haven't heard from the server since time t *)mutablereplies_missing:int;(* the number of requests we've sent which have not been replied to *)mutableonline:bool;(* true if we assume the server is online *)}typet={clock:Clock.t;connections:connectionlist;local_names_cb:(Dns.Packet.question->Dns.Packet.rrlistoptionLwt.t);cache:Cache.t;config:Dns_forward_config.t;}letcreate?(local_names_cb=fun_->Lwt.return_none)~gen_transaction_id?message_cbconfigclock=Lwt_list.map_s(funserver->or_fail_msg@@Client.connect~gen_transaction_id?message_cbserver.Dns_forward_config.Server.address>>=funclient->letreply_expected_since=Noneinletreplies_missing=0inletonline=trueinLwt.return{server;client;reply_expected_since;replies_missing;online})(Dns_forward_config.Server.Set.elementsconfig.Dns_forward_config.servers)>|=funconnections->letcache=Cache.make()in{clock;connections;local_names_cb;cache;config}letdestroyt=Cache.destroyt.cache;Lwt_list.iter_s(func->Client.disconnectc.client)t.connectionsletanswerbuffert=letlen=Cstruct.lenbufferinletbuf=bufferinletopenDns.PacketinmatchDns.Protocol.Server.parse(Cstruct.subbuf0len)with|Some({questions=[question];_}asrequest)->(* Given a set of answers (resource records), synthesize an answer to the
current question. *)letreplyanswers=letid=request.idinletdetail={request.detailwithDns.Packet.qr=Dns.Packet.Response;ra=true}inletquestions=request.questionsinletauthorities=[]andadditionals=[]in{id;detail;questions;answers;authorities;additionals}in(* Look for any local answers to this question *)begint.local_names_cbquestion>>=function|Someanswers->Lwt_result.return(marshal@@replyanswers)|None->(* Ask one server, with caching. Possible results are:
Ok (`Success buf): succesful reply
Ok (`Failure buf): an error like NXDomain
Error (`Msg m): a low-level error or timeout
*)letone_rpcserver=letopenDns_forward_configinletaddress=server.Server.addressin(* Look in the cache *)matchCache.answert.cacheaddressquestionwith|Someanswers->Lwt.return(Ok(`Success(marshal@@replyanswers)))|None->letc=List.find(func->c.server=server)t.connectionsinbeginletnow_ns=Clock.elapsed_nst.clockin(* If no timeout is configured, we will stop listening after
5s to avoid leaking threads if a server is offline *)lettimeout_ns=matchserver.Server.timeout_mswith|None->Duration.of_sec5|Somex->Duration.of_msxin(* If no assume_offline_after_drops is configured then set this
to 5s. *)letassume_offline_after_drops=matcht.config.assume_offline_after_dropswith|Somec->c|None->5in(* Within the overall timeout_ms (configured by the user) we will send
the request at 1s intervals to guard against packet drops. *)letdelays_ns=letrecmakefrom=iffrom>timeout_nsthen[]elsefrom::make(Int64.addfromDuration.(of_sec1))inmake0Linletrequests=List.map(fundelay_ns->Time.sleep_nsdelay_ns>>=fun()->Client.rpcc.clientbuffer)delays_nsinlettimeout=Time.sleep_nstimeout_ns>|=fun()->Error(`Msg"timeout")inLwt.pick(timeout::requests)>>=function|Errorx->ifc.reply_expected_since=Nonethenc.reply_expected_since<-Somenow_ns;c.replies_missing<-c.replies_missing+(List.lengthdelays_ns);ifassume_offline_after_drops<c.replies_missing&&c.onlinethenbeginLog.err(funf->f"Upstream DNS server %s has dropped %d packets in a row: assuming it's offline"(Dns_forward_config.Address.to_stringaddress)c.replies_missing);c.online<-falseend;Lwt.return(Errorx)|Okreply->c.reply_expected_since<-None;c.replies_missing<-0;ifnotc.onlinethenbeginLog.info(funf->f"Upstream DNS server %s is back online"(Dns_forward_config.Address.to_stringaddress));c.online<-true;end;(* Determine whether it's a success or a failure; if a success
then insert the value into the cache. *)letlen=Cstruct.lenreplyinletbuf=replyinbeginmatchDns.Protocol.Server.parse(Cstruct.subbuf0len)with|Some{detail={rcode=NoError;_};answers=((_::_)asanswers);_}->Cache.insertt.cacheaddressquestionanswers;Lwt.return(Ok(`Successreply))|packet->Lwt.return(Ok(`Failure(packet,reply)))endendin(* Ask many servers but first
- Filter the list of servers using any "zone" setting -- this will
prevent queries for private names being leaked to public servers
(if configured).
- Group the servers into lists of equal priorities.
- Send all the requests concurrently. *)letmany_rpcsconnections=letequal_priority_groups=choose_servers(List.map(func->c.server)connections)requestin(* Send all requests in parallel to minimise the chance of hitting a
timeout. Positive replies will be cached, but servers which don't
recognise the name will be queried each time. *)List.map(List.mapone_rpc)equal_priority_groupsinletonline,offline=List.partition(func->c.online)t.connectionsinifonline=[]&&t.connections<>[]thenbeginletopenDns_forward_configinLog.warn(funf->f"There are no online DNS servers configured.");Log.warn(funf->f"DNS servers %s are all marked offline"(String.concat", "(List.map(func->Address.to_string@@c.server.Server.address)offline)))end;(* For all the offline servers, send the requests as a "ping" to see
if they are alive or not. Any response will flip them back to online
but we won't consider their responses until the next RPC *)let_=many_rpcsofflinein(* For all the online servers, send the requests and return the waiting
threads. *)letonline_results=many_rpcsonlinein(* Wait for the best result from a set of equal priority requests *)letrecwaitbest_so_farremaining=ifremaining=[]thenLwt.returnbest_so_farelseLwt.nchoose_splitremaining>>=fun(terminated,remaining)->matchList.fold_left(funbest_so_farnext->matchbest_so_farwith|Ok(`Successresult)->(* No need to wait for any of the rest: one success is good enough *)Ok(`Successresult)|best_so_far->beginmatchbest_so_far,nextwith|_,Ok(`Successresult)->Ok(`Successresult)|Ok(`Failure(a_packet,a_reply)),Ok(`Failure(b_packet,b_reply))->beginmatcha_packet,b_packetwith(* Prefer NXDomain to errors like Refused *)|Some{detail={rcode=NXDomain;_};_},_->Ok(`Failure(a_packet,a_reply))|_,Some{detail={rcode=NXDomain;_};_}->Ok(`Failure(b_packet,b_reply))|_,_->(* other than that, the earlier error is better *)Ok(`Failure(a_packet,a_reply))end|Error_,Ok(`Failure(b_packet,b_reply))->(* prefer a high-level error over a low-level (e.g. socket) error *)Ok(`Failure(b_packet,b_reply))|best_so_far,_->best_so_farend)best_so_farterminatedwith|Ok(`Successresult)->Lwt.return(Ok(`Successresult))|best_so_far->waitbest_so_farremainingin(* Wait for each equal priority group at a time *)Lwt_list.fold_left_s(funbest_so_farnext->matchbest_so_farwith|Ok(`Successresult)->Lwt.return(Ok(`Successresult))|best_so_far->waitbest_so_farnext)(Error(`Msg"no servers configured"))online_results>>=function|Ok(`Successreply)->Lwt_result.returnreply|Ok(`Failure(_,reply))->Lwt_result.returnreply|Errorx->Lwt_result.failxend|Some{questions=_;_}->Lwt_result.fail(`Msg"cannot handle DNS packets where len(questions)<>1")|None->Lwt_result.fail(`Msg"failed to parse request")end