123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234(* (c) 2017, 2018 Hannes Mehnert, all rights reserved *)openDnstyperank=|ZoneFile|ZoneTransfer|AuthoritativeAnswer|AuthoritativeAuthority|ZoneGlue|NonAuthoritativeAnswer|Additionalletcompare_rankab=matcha,bwith|ZoneFile,ZoneFile->0|ZoneFile,_->1|_,ZoneFile->-1|ZoneTransfer,ZoneTransfer->0|ZoneTransfer,_->1|_,ZoneTransfer->-1|AuthoritativeAnswer,AuthoritativeAnswer->0|AuthoritativeAnswer,_->1|_,AuthoritativeAnswer->-1|AuthoritativeAuthority,AuthoritativeAuthority->0|AuthoritativeAuthority,_->1|_,AuthoritativeAuthority->-1|ZoneGlue,ZoneGlue->0|ZoneGlue,_->1|_,ZoneGlue->-1|NonAuthoritativeAnswer,NonAuthoritativeAnswer->0|NonAuthoritativeAnswer,_->1|_,NonAuthoritativeAnswer->-1|Additional,Additional->0letpp_rankppfr=Fmt.stringppf(matchrwith|ZoneFile->"zone file data"|ZoneTransfer->"zone transfer data"|AuthoritativeAnswer->"authoritative answer data"|AuthoritativeAuthority->"authoritative authority data"|ZoneGlue->"zone file glue"|NonAuthoritativeAnswer->"non-authoritative answer"|Additional->"additional data")moduleRRMap=Map.Make(structtypet=Rr_map.kletcompare=Rr_map.comparekend)moduleEntry=structtypemeta=int64*rankletpp_metappf(ts,rank)=Fmt.pfppf"%a created %Lu"pp_rankranktstyperr_map_entry=|EntryofRr_map.b|No_dataof[`raw]Domain_name.t*Soa.t|Serv_failof[`raw]Domain_name.t*Soa.tletpp_map_entryppfentry=matchentrywith|Entryb->Fmt.pfppf"entry %a"Rr_map.pp_bb|No_data(name,soa)->Fmt.pfppf"no data %a SOA %a"Domain_name.ppnameSoa.ppsoa|Serv_fail(name,soa)->Fmt.pfppf"server fail %a SOA %a"Domain_name.ppnameSoa.ppsoaletto_entry=function|Entryb->`Entryb|No_data(name,soa)->`No_data(name,soa)|Serv_fail(name,soa)->`Serv_fail(name,soa)letof_entry=function|`Entryb->Entryb|`No_data(name,soa)->No_data(name,soa)|`Serv_fail(name,soa)->Serv_fail(name,soa)|_->assertfalsetypet=|No_domainofmeta*[`raw]Domain_name.t*Soa.t|Rr_mapof(meta*rr_map_entry)RRMap.t(* Part of the LRU.Weighted interface *)letweight=function|No_domain_->1|Rr_maptm->RRMap.cardinaltmletpp_entryppf(meta,entry)=Fmt.pfppf"e (%a) %a"pp_metametapp_map_entryentryletppppf=function|No_domain(meta,name,soa)->Fmt.pfppf"no domain (%a) %a SOA %a"pp_metametaDomain_name.ppnameSoa.ppsoa|Rr_maprr->Fmt.pfppf"entries: %a"Fmt.(list~sep:(unit";@,")(pairRr_map.ppkpp_entry))(RRMap.bindingsrr)endmoduleKey=structtypet=[`raw]Domain_name.tletequalab=Domain_name.equalablethashrv=Hashtbl.seeded_hashrvendmoduleLRU=Lru.M.MakeSeeded(Key)(Entry)typet=LRU.tletmetrics=letf=function|`Lookup->"lookups"|`Hit->"hits"|`Miss->"misses"|`Drop->"drops"|`Insert->"insertions"inletmetrics=Dns.counter_metrics~f"dns-cache"in(funx->Metrics.addmetrics(funx->x)(fund->dx))letemptysize=LRU.create~random:truesizeletsize=LRU.sizeletcapacity=LRU.capacityletpp=LRU.ppFmt.(pair~sep:(unit": ")Domain_name.ppEntry.pp)moduleN=Domain_name.Setletupdate_ttl~created~nowttl=Int32.subttl(Int32.of_int(Duration.to_sec(Int64.subnowcreated)))typeentry=[|`EntryofRr_map.b|`No_dataof[`raw]Domain_name.t*Soa.t|`No_domainof[`raw]Domain_name.t*Soa.t|`Serv_failof[`raw]Domain_name.t*Soa.t]letpp_entryppfentry=letpp_nsppf(name,soa)=Fmt.pfppf"%a SOA %a"Domain_name.ppnameSoa.ppsoainmatchentrywith|`Entryb->Fmt.pfppf"entry %a"Rr_map.pp_bb|`No_datans->Fmt.(prefix(unit"no data ")pp_ns)ppfns|`No_domainns->Fmt.(prefix(unit"no domain ")pp_ns)ppfns|`Serv_failns->Fmt.(prefix(unit"serv fail ")pp_ns)ppfnsletget_ttl=function|`Entryb->Rr_map.get_ttlb|`No_data(_,soa)->soa.Soa.minimum|`No_domain(_,soa)->soa.Soa.minimum|`Serv_fail(_,soa)->soa.Soa.minimumletwith_ttlttl=function|`Entryb->`Entry(Rr_map.with_ttlbttl)|`No_data(name,soa)->`No_data(name,{soawithSoa.minimum=ttl})|`No_domain(name,soa)->`No_domain(name,{soawithSoa.minimum=ttl})|`Serv_fail(name,soa)->`Serv_fail(name,{soawithSoa.minimum=ttl})letfindcachenamequery_type=matchLRU.findnamecachewith|None->None,Error`Cache_miss|SomeNo_domain(meta,name,soa)->None,Ok(meta,`No_domain(name,soa))|SomeRr_mapresource_records->Someresource_records,matchRRMap.find_opt(Kquery_type)resource_recordswith|Some(meta,entry)->Ok(meta,Entry.to_entryentry)|None->Error`Cache_missletinsertcache?maptsnamequery_typerankentry=letmeta=ts,rankin(matchentrywith|`No_domain(name',soa)->LRU.addname(No_domain(meta,name',soa))cache|`Entry_|`No_data_|`Serv_fail_->letmap=matchmapwithNone->RRMap.empty|Somex->xinletmap'=RRMap.add(Kquery_type)(meta,Entry.of_entryentry)mapinLRU.addname(Rr_mapmap')cache);(* Make sure we are within memory bounds *)LRU.trimcacheletupdate_ttlentry~created~now=letttl=get_ttlentryinletupdated_ttl=update_ttl~created~nowttlinifupdated_ttl<0lthenError`Cache_dropelseOk(with_ttlupdated_ttlentry)letgetcachetsnamequery_type=metrics`Lookup;matchsnd(findcachenamequery_type)with|Errore->metrics`Miss;Errore|Ok((created,_),entry)->matchupdate_ttlentry~created~now:tswith|Okentry'->metrics`Hit;LRU.promotenamecache;Okentry'|Errore->metrics`Drop;Errore(* XXX: we may want to define a minimum as well (5 minutes? 30 minutes?
use SOA expiry?) MS used to use 24 hours in internet explorer
from RFC1034 on this topic:
The idea is that if cached data is known to come from a particular zone,
and if an authoritative copy of the zone's SOA is obtained, and if the
zone's SERIAL has not changed since the data was cached, then the TTL of
the cached data can be reset to the zone MINIMUM value if it is smaller.
This usage is mentioned for planning purposes only, and is not
recommended as yet.
and 2308, Sec 4:
Despite being the original defined meaning, the first of these, the
minimum TTL value of all RRs in a zone, has never in practice been
used and is hereby deprecated.
and 1035 6.2:
The MINIMUM value in the SOA should be used to set a floor on the TTL of data
distributed from a zone. This floor function should be done when the data is
copied into a response. This will allow future dynamic update protocols to
change the SOA MINIMUM field without ambiguous semantics.
*)(* according to RFC1035, section 7.3, a TTL of a week is a good
maximum value! *)letweek=Int32.of_intDuration.(to_sec(of_day7))letclip_ttl_to_weekentry=letttl=get_ttlentryinifttl<weekthenentryelsewith_ttlweekentryletpp_queryppf(name,query_type)=Fmt.pfppf"%a (%a)"Domain_name.ppnamePacket.Question.pp_qtypequery_typeletsetcachetsnamequery_typerankentry=letentry'=clip_ttl_to_weekentryinletcache'map=insertcache?maptsnamequery_typerankentry'inmatchfindcachenamequery_typewith|map,Error_->Logs.debug(funm->m"set: %a nothing found, adding: %a"pp_query(name,`K(Kquery_type))pp_entryentry');metrics`Insert;cache'map|map,Ok((_,rank'),_)->Logs.debug(funm->m"set: %a found rank %a insert rank %a: %d"pp_query(name,`K(Kquery_type))pp_rankrank'pp_rankrank(compare_rankrank'rank));matchcompare_rankrank'rankwith|1->()|_->metrics`Insert;cache'map