123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501(*
* Copyright (c) 2005-2006 Tim Deegan <tjd@phlegethon.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.
*
* dnsquery.ml -- map DNS query-response mechanism onto trie database
*
*)openOperatorsopenRRopenTrieopenPrintfmoduleH=Hashcons(* We answer a query with RCODE, AA, ANSWERS, AUTHORITY and ADDITIONAL *)typeanswer={rcode:Packet.rcode;aa:bool;answer:Packet.rrlist;authority:Packet.rrlist;additional:Packet.rrlist;}typefilter=Name.t->RR.rrset->RR.rrsettypeflush=Name.t->Packet.rdata->boolletresponse_of_answer?(mdns=false)queryanswer=(*let edns_rec =
try
List.find (fun rr -> ) query.additionals
with Not_found -> []
in *)letdetail={Packet.qr=Packet.Response;opcode=Packet.Standard;aa=answer.aa;tc=false;rd=(ifmdnsthenfalseelsePacket.(query.detail.rd));(* rfc6762 s18.6_p1_c1 *)ra=false;rcode=answer.rcode}inPacket.({id=(ifmdnsthen0elsequery.id);detail;(* mDNS does not echo questions in the response *)questions=(ifmdnsthen[]elsequery.questions);answers=answer.answer;authorities=answer.authority;additionals=answer.additional;})letanswer_of_response?(preserve_aa=false)({Packet.detail={Packet.rcode;aa;_};answers;authorities;additionals;_})={rcode;aa=ifpreserve_aathenaaelsefalse;answer=answers;authority=authorities;additional=additionals;}letcreate?(dnssec=false)~idq_classq_typeq_name=letopenPacketinletdetail={qr=Query;opcode=Standard;aa=false;tc=false;rd=true;ra=false;rcode=NoError;}inletadditionals=ifdnssecthen[({name=Name.empty;cls=RR_IN;flush=false;ttl=0l;rdata=(EDNS0(1500,0,true,[]));})]else[]inletquestion={q_name;q_type;q_class;q_unicast=Q_Normal}in{id;detail;questions=[question];answers=[];authorities=[];additionals;}letnull_filter_ownerrrset=rrsetletflush_false_owner_rdata=falseletanswer_multiple?(dnssec=false)?(mdns=false)?(filter=null_filter)?(flush=flush_false)questionstrie=letaa_flag=reftrueinletans_rrs=ref[]inletauth_rrs=ref[]inletadd_rrs=ref[]inletaddqueue=ref[]inletrrlog=ref[]in(* We must avoid repeating RRSets in the response. To do this, we
keep two lists: one of RRSets that are already included, and one of
RRSets we plan to put in the additional records section. When we
add an RRSet to the answer or authority section we strip it from
the additionals queue, and when we enqueue an additional RRSet we
make sure it's not already been included.
N.B. (1) We only log those types that might turn up more than once.
N.B. (2) We can use "==" and "!=" because owners are unique:
they are either the owner field of a dnsnode from the
trie, or they are the qname, which only happens if it
does not have any RRSets of its own and matched a wildcard.*)letlog_rrsetownerrrtype=addqueue:=List.filter(fun(n,_q,t)->rrtype!=t||owner!=n.owner.H.node)!addqueue;rrlog:=(owner,rrtype)::!rrloginletin_logownerrrtype=tryList.assqowner!rrlog==rrtypewithNot_found->falseinletenqueue_additionaldnsnodeqtyperrtype=ifnot(in_logdnsnode.owner.H.noderrtype)thenaddqueue:=(dnsnode,qtype,rrtype)::!addqueueinletadd_rrsetownerttlrdatasection=letaddrrrr=letrr=Packet.({name=owner;cls=Packet.RR_IN;flush=flushownerrr;ttl=ttl;rdata=rr})inmatchsectionwith|`Answer->ans_rrs:=rr::!ans_rrs|`Authority->auth_rrs:=rr::!auth_rrs|`Additional->add_rrs:=rr::!add_rrsin(* having extracted record from trie, partially marshal it *)matchrdatawith|RR.Al->log_rrsetownerPacket.RR_A;List.iter(funip->addrr(Packet.Aip))l|RR.AAAAl->log_rrsetownerPacket.RR_AAAA;List.iter(funip->addrr(Packet.AAAAip))l|RR.NSl->log_rrsetownerPacket.RR_NS;List.iter(fund->enqueue_additionaldPacket.Q_APacket.RR_A;enqueue_additionaldPacket.Q_AAAAPacket.RR_AAAA;addrr(Packet.NSd.owner.H.node))l|RR.CNAMEl->List.iter(fund->addrr(Packet.CNAMEd.owner.H.node))l|RR.SOAl->log_rrsetownerPacket.RR_SOA;List.iter(fun(prim,admin,serial,refresh,retry,expiry,minttl)->addrr(Packet.SOA(prim.owner.H.node,admin.owner.H.node,serial,refresh,retry,expiry,minttl)))l|RR.MBl->List.iter(fund->enqueue_additionaldPacket.Q_APacket.RR_A;enqueue_additionaldPacket.Q_AAAAPacket.RR_AAAA;addrr(Packet.MBd.owner.H.node))l|RR.MGl->List.iter(fund->addrr(Packet.MGd.owner.H.node))l|RR.MRl->List.iter(fund->addrr(Packet.MRd.owner.H.node))l|RR.WKSl->List.iter(fun(address,protocol,bitmap)->addrr(Packet.WKS(address,protocol,bitmap.H.node)))l|RR.PTRl->List.iter(fund->ifmdnsthenbegin(* RFC 6763 section 12.1 *)enqueue_additionaldPacket.Q_SRVPacket.RR_SRV;enqueue_additionaldPacket.Q_TXTPacket.RR_TXT;end;addrr(Packet.PTRd.owner.H.node))l|RR.HINFOl->List.iter(fun(cpu,os)->addrr(Packet.HINFO(cpu.H.node,os.H.node)))l|RR.MINFOl->List.iter(fun(rm,em)->addrr(Packet.MINFO(rm.owner.H.node,em.owner.H.node)))l|RR.MXl->List.iter(fun(preference,d)->enqueue_additionaldPacket.Q_APacket.RR_A;enqueue_additionaldPacket.Q_AAAAPacket.RR_AAAA;addrr(Packet.MX(preference,d.owner.H.node)))l|RR.TXTl->List.iter(funsl->(* XXX handle multiple TXT cstrings properly *)letdata=List.map(funx->x.H.node)slinaddrr(Packet.TXTdata))l|RR.RPl->List.iter(fun(mbox,txt)->addrr(Packet.RP(mbox.owner.H.node,txt.owner.H.node)))l|RR.AFSDBl->List.iter(fun(t,d)->enqueue_additionaldPacket.Q_APacket.RR_A;enqueue_additionaldPacket.Q_AAAAPacket.RR_AAAA;addrr(Packet.AFSDB(t,d.owner.H.node)))l|RR.X25l->log_rrsetownerPacket.RR_X25;List.iter(funs->addrr(Packet.X25s.H.node))l|RR.ISDNl->log_rrsetownerPacket.RR_ISDN;List.iter(fun(a,sa)->letsa=matchsawithNone->None|Somesa->Somesa.H.nodeinaddrr(Packet.ISDN(a.H.node,sa)))l(*
(function (* XXX handle multiple cstrings properly *)
| (addr, None)
-> addrr (`ISDN addr.H.node)
| (addr, Some sa) (* XXX Handle multiple charstrings properly *)
-> addrr (`ISDN (addr.H.node ^ sa.H.node))) l
*)|RR.RTl->List.iter(fun(preference,d)->enqueue_additionaldPacket.Q_APacket.RR_A;enqueue_additionaldPacket.Q_AAAAPacket.RR_AAAA;enqueue_additionaldPacket.Q_X25Packet.RR_X25;enqueue_additionaldPacket.Q_ISDNPacket.RR_ISDN;addrr(Packet.RT(preference,d.owner.H.node)))l|RR.SRVl->List.iter(fun(priority,weight,port,d)->enqueue_additionaldPacket.Q_APacket.RR_A;enqueue_additionaldPacket.Q_AAAAPacket.RR_AAAA;addrr(Packet.SRV(priority,weight,port,d.owner.H.node)))l|RR.DSl->List.iter(fun(tag,alg,digest,k)->addrr(Packet.DS(tag,alg,digest,k.H.node)))l(* | RR.UNSPEC l -> *)(* List.iter (fun s -> addrr (Packet.UNSPEC s.H.node)) l *)|RR.DNSKEYl->List.iter(fun(fl,t,k)->lettt=Packet.int_to_dnssec_algtinmatchttwith|None->failwith(sprintf"bad dnssec alg type t:%d"t)|Somett->addrr(Packet.DNSKEY(fl,tt,k.H.node)))l|RR.RRSIGl->beginList.iter(fun{rrsig_type=typ;rrsig_alg=alg;rrsig_labels=lbl;rrsig_ttl=ttl;rrsig_expiry=exp_ts;rrsig_incept=inc_ts;rrsig_keytag=tag;rrsig_name=name;rrsig_sig=sign;}->addrr(Packet.RRSIG(typ,alg,lbl,ttl,exp_ts,inc_ts,tag,name,sign)))lend|RR.Unknown(t,l)->lets=l||>(funx->x.H.node)|>String.concat""inaddrr(Packet.UNKNOWN(t,s))in(* Extract relevant RRSets given a query type, a list of RRSets and a flag to
say whether to return Cnames too *)letget_rrsetsqtypesetscnames_ok=letsome_rrsetset=(* eprintf "MATCH q:%s r:%s\n%!" *)(* (Packet.q_type_to_string qtype) (RR.rdata_to_string set.rdata); *)(* TODO: where does this map belong? *)match(qtype,set.rdata)with|(Packet.Q_A,A_)|(Packet.Q_NS,NS_)|(Packet.Q_CNAME,CNAME_)|(Packet.Q_SOA,SOA_)|(Packet.Q_MB,MB_)|(Packet.Q_MG,MG_)|(Packet.Q_MR,MR_)|(Packet.Q_WKS,WKS_)|(Packet.Q_PTR,PTR_)|(Packet.Q_HINFO,HINFO_)|(Packet.Q_MINFO,MINFO_)|(Packet.Q_MX,MX_)|(Packet.Q_TXT,TXT_)|(Packet.Q_RP,RP_)|(Packet.Q_AFSDB,AFSDB_)|(Packet.Q_X25,X25_)|(Packet.Q_ISDN,ISDN_)|(Packet.Q_RT,RT_)|(Packet.Q_SRV,SRV_)|(Packet.Q_AAAA,AAAA_)|(Packet.Q_DS,DS_)|(Packet.Q_DNSKEY,DNSKEY_)|(Packet.Q_RRSIG,RRSIG_)(* | (Packet.Q_UNSPEC, UNSPEC _) -> true *)|(Packet.Q_MAILB,MB_)|(Packet.Q_MAILB,MG_)|(Packet.Q_MAILB,MR_)|(Packet.Q_ANY_TYP,_)->Someset|(_,CNAME_)whencnames_ok->Someset|(_,RRSIGrrsigl)whendnssec->Some({setwithrdata=RRSIG(List.filter(fun{rrsig_type;_}->Packet.q_type_matches_rr_typeqtyperrsig_type)rrsigl)})|(_,_)->NoneinList.fold_right(funsetsets->matchsome_rrsetsetwith|Someset->set::sets|_->sets)sets[]in(* Get an RRSet, which may not exist *)letadd_opt_rrsetnodeqtyperrtypesection=ifnot(in_lognode.owner.H.noderrtype)thenleta=get_rrsetsqtypenode.rrsetsfalseinList.iter(funs->add_rrsetnode.owner.H.nodes.ttls.rdatasection)ain(* Get an RRSet, which must exist *)letadd_req_rrsetnodeqtyperrtypesection=ifnot(in_lognode.owner.H.noderrtype)thenleta=get_rrsetsqtypenode.rrsetsfalseinifa=[]thenraiseTrieCorrupt;List.iter(funs->add_rrsetnode.owner.H.nodes.ttls.rdatasection)ain(* Get the SOA RRSet for a negative response *)letadd_negative_soa_rrset=ifmdnsthenfun_node->()elsefunnode->(* Don't need to check if it's already there *)leta=get_rrsetsPacket.Q_SOAnode.rrsetsfalseinifa=[]thenraiseTrieCorrupt;(* RFC 2308: The TTL of the SOA RRset in a negative response must be set
to the minimum of its own TTL and the "minimum" field of the SOA
itself *)List.iter(funs->matchs.rdatawithSOA((_,_,_,_,_,_,ttl)::_)->add_rrsetnode.owner.H.node(mins.ttlttl)s.rdata`Authority|_->raiseTrieCorrupt)ain(* Fill in the ANSWER section *)letrecadd_answer_rrsetsowner?(lc=5)rrsetsqtype=letadd_answer_rrsets=matchswith|{rdata=CNAME(d::_);_}->(* Only follow the first CNAME in a set *)ifnot(lc<1||qtype=Packet.Q_CNAME)thenbeginadd_answer_rrsetsd.owner.H.node~lc:(lc-1)d.rrsetsqtypeend;add_rrsetowners.ttls.rdata`Answer|_->add_rrsetowners.ttls.rdata`Answerinleta=get_rrsetsqtyperrsetstrueinletf1=List.map(filterowner)ainletf2=List.filter(funrrset->rrset.RR.ttl<>0l)f1inList.iteradd_answer_rrsetf2in(* Call the trie lookup and assemble the RRs for a response *)letmain_lookupqnameqtypetrie=letkey=Name.to_keyqnameinmatchlookupkeytrie~mdnswith|`Found(_sec,node,zonehead)->(* Name has RRs, and we own it. *)add_answer_rrsetsnode.owner.H.nodenode.rrsetsqtype;add_opt_rrsetzoneheadPacket.Q_NSPacket.RR_NS`Authority;Packet.NoError|`NoError(zonehead)->(* Name "exists", but has no RRs. *)add_negative_soa_rrsetzonehead;Packet.NoError|`NoErrorNSEC(zonehead,_nsec)->add_negative_soa_rrsetzonehead;(* add_opt_rrset nsec `NSEC `Authority; *)Packet.NoError|`Delegated(_sec,cutpoint)->(* Name is delegated. *)add_req_rrsetcutpointPacket.Q_NSPacket.RR_NS`Authority;aa_flag:=false;(* DNSSEC child zone keys *)Packet.NoError|`Wildcard(source,zonehead)->(* Name is matched by a wildcard. *)add_answer_rrsetsqnamesource.rrsetsqtype;add_opt_rrsetzoneheadPacket.Q_NSPacket.RR_NS`Authority;Packet.NoError|`WildcardNSEC(source,zonehead,_nsec)->add_answer_rrsetsqnamesource.rrsetsqtype;add_opt_rrsetzoneheadPacket.Q_NSPacket.RR_NS`Authority;(* add_opt_rrset nsec `NSEC `Authority; *)Packet.NoError|`NXDomain(zonehead)->(* Name doesn't exist. *)add_negative_soa_rrsetzonehead;Packet.NXDomain|`NXDomainNSEC(zonehead,_nsec1,_nsec2)->add_negative_soa_rrsetzonehead;(* add_opt_rrset nsec1 `NSEC `Authority; *)(* add_opt_rrset nsec2 `NSEC `Authority; *)Packet.NXDomaininletreclookup_multipleqstrierc=letopenPacketinmatchqswith|[]->rc|hd::tl->letnext_rc=(* main_lookup only supports RR_IN *)matchhd.q_classwith|Q_IN|Q_ANY_CLS->main_lookuphd.q_namehd.q_typetrie|Q_CS|Q_CH|Q_HS|Q_NONE->NXDomaininmatchnext_rcwith(* If all questions result in NXDomain then return NXDomain,
or if any question results in another kind of error then abort,
else return NoError *)|NoError->lookup_multipletltrieNoError|NXDomain->lookup_multipletltrierc|_->next_rcintryletrc=lookup_multiplequestionstriePacket.NXDomaininList.iter(fun(o,q,t)->add_opt_rrsetoqt`Additional)!addqueue;let_=if(dnssec)thenletrr=Packet.({name=Name.empty;cls=RR_IN;flush=false;ttl=0x00008000l;rdata=EDNS0(1500,0,true,[])})inadd_rrs:=!add_rrs@[(rr)]in{rcode=rc;aa=!aa_flag;answer=!ans_rrs;authority=!auth_rrs;additional=!add_rrs}with|Name.BadDomainName_->{rcode=Packet.FormErr;aa=false;answer=[];authority=[];additional=[];}|TrieCorrupt->{rcode=Packet.ServFail;aa=false;answer=[];authority=[];additional=[];}letanswer?(dnssec=false)?(mdns=false)?(filter=null_filter)?flush:_qnameqtypetrie=answer_multiple~dnssec~mdns~filter[{Packet.q_name=qname;Packet.q_type=qtype;Packet.q_class=Packet.Q_IN;Packet.q_unicast=Packet.Q_Normal}]trie