123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467(*
* Copyright (c) 2015 Luke Dunstan <LukeDunstan81@gmail.com>
*
* 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.
*)openLwt.InfixmoduleDR=Dns.RRmoduleDP=Dns.PacketmoduleDS=Dns.Protocol.ServermoduleDQ=Dns.QuerymoduleH=HashconsmoduleProbe=Dns.Probetypeip_endpoint=Ipaddr.V4.t*intmoduletypeTRANSPORT=sigvalalloc:unit->Cstruct.tvalwrite:ip_endpoint->Cstruct.t->unitLwt.tvalsleep:float->unitLwt.tendletlabelstr=MProf.Trace.label("Mdns_responder:"^str)letmulticast_ip=Ipaddr.V4.of_string_exn"224.0.0.251"letsentinel=DR.Unknown(0,[])letfilter_out_knownrrknown=match(rr,known)with|(DR.Al,DP.Ak)->letlf=List.filter(funip->k<>ip)liniflf<>[]thenDR.Alfelsesentinel|(DR.AAAAl,DP.AAAAk)->letlf=List.filter(funip->k<>ip)liniflf<>[]thenDR.AAAAlfelsesentinel|(DR.CNAMEl,DP.CNAMEk)->letlf=List.filter(fund->d.DR.owner.H.node<>k)liniflf<>[]thenDR.CNAMElfelsesentinel|(DR.MBl,DP.MBk)->letlf=List.filter(fund->d.DR.owner.H.node<>k)liniflf<>[]thenDR.MBlfelsesentinel|(DR.MGl,DP.MBk)->letlf=List.filter(fund->d.DR.owner.H.node<>k)liniflf<>[]thenDR.MGlfelsesentinel|(DR.MRl,DP.MRk)->letlf=List.filter(fund->d.DR.owner.H.node<>k)liniflf<>[]thenDR.MRlfelsesentinel|(DR.NSl,DP.NSk)->letlf=List.filter(fund->d.DR.owner.H.node<>k)liniflf<>[]thenDR.NSlfelsesentinel(* SOA not relevant *)|(DR.WKSl,DP.WKS(ka,kp,kb))->letlf=List.filter(fun(address,protocol,bitmap)->address<>ka||protocol<>kp||bitmap.H.node<>kb)liniflf<>[]thenDR.WKSlfelsesentinel|(DR.PTRl,DP.PTRk)->letlf=List.filter(fund->d.DR.owner.H.node<>k)liniflf<>[]thenDR.PTRlfelsesentinel|(DR.HINFOl,DP.HINFO(kcpu,kos))->letlf=List.filter(fun(cpu,os)->cpu.H.node<>kcpu||os.H.node<>kos)liniflf<>[]thenDR.HINFOlfelsesentinel|(DR.MINFOl,DP.MINFO(krm,kem))->letlf=List.filter(fun(rm,em)->rm.DR.owner.H.node<>krm||em.DR.owner.H.node<>kem)liniflf<>[]thenDR.MINFOlfelsesentinel|(DR.MXl,DP.MX(kp,kn))->letlf=List.filter(fun(preference,d)->preference<>kp||d.DR.owner.H.node<>kn)liniflf<>[]thenDR.MXlfelsesentinel|(DR.TXT_ll,DP.TXT_kl)->sentinel(* TODO *)|(DR.RPl,DP.RP(kmbox,ktxt))->letlf=List.filter(fun(mbox,txt)->mbox.DR.owner.H.node<>kmbox||txt.DR.owner.H.node<>ktxt)liniflf<>[]thenDR.RPlfelsesentinel|(DR.AFSDBl,DP.AFSDB(kt,kn))->letlf=List.filter(fun(t,d)->t<>kt||d.DR.owner.H.node<>kn)liniflf<>[]thenDR.AFSDBlfelsesentinel|(DR.X25l,DP.X25k)->letlf=List.filter(funs->s.H.node<>k)liniflf<>[]thenDR.X25lfelsesentinel|(DR.ISDNl,DP.ISDN(ka,ksa))->letlf=List.filter(fun(a,sa)->letsa=matchsawithNone->None|Somesa->Somesa.H.nodeina.H.node<>ka||sa<>ksa)liniflf<>[]thenDR.ISDNlfelsesentinel|(DR.RTl,DP.RT(kp,kn))->letlf=List.filter(fun(preference,d)->preference<>kp||d.DR.owner.H.node<>kn)liniflf<>[]thenDR.RTlfelsesentinel|(DR.SRVl,DP.SRV(kprio,kw,kport,kn))->letlf=List.filter(fun(priority,weight,port,d)->priority<>kprio||weight<>kw||port<>kport||d.DR.owner.H.node<>kn)liniflf<>[]thenDR.SRVlfelsesentinel|(DR.DSl,DP.DS(kt,ka,kd,kn))->letlf=List.filter(fun(tag,alg,digest,k)->tag<>kt||alg<>ka||digest<>kd||k.H.node<>kn)liniflf<>[]thenDR.DSlfelsesentinel|(DR.DNSKEYl,DP.DNSKEY(kfl,ktt,kk))->letlf=List.filter(fun(fl,t,k)->lettt=DP.int_to_dnssec_algtinmatchttwith|None->false|Somett->fl<>kfl||tt<>ktt||k.H.node<>kk)liniflf<>[]thenDR.DNSKEYlfelsesentinel|(DR.RRSIGl,DP.RRSIG(ktyp,kalg,klbl,kttl,kexp_ts,kinc_ts,ktag,kname,ksign))->letlf=List.filterDR.(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;}->typ<>ktyp||alg<>kalg||lbl<>klbl||ttl<>kttl||exp_ts<>kexp_ts||inc_ts<>kinc_ts||tag<>ktag||name<>kname||sign<>ksign)liniflf<>[]thenDR.RRSIGlfelsesentinel|(DR.Unknown_,_)->sentinel|_,_->rrletrecfilter_known_listrrknownl=matchknownlwith|[]->rr|known::tl->beginletfrr=filter_out_knownrrknown.DP.rdatainmatchfrrwithDR.Unknown_->frr|_->filter_known_listfrrtlendmoduleMake(Transport:TRANSPORT)=structtypet={db:Dns.Loader.db;dnstrie:Dns.Trie.dnstrie;probe_condition:unitLwt_condition.t;mutableprobe_forever:unitLwt.t;mutableprobe:Probe.state;}letof_dbdb=letdnstrie=db.Dns.Loader.triein{db;dnstrie;probe_condition=Lwt_condition.create();probe_forever=Lwt.return_unit;probe=Probe.new_statedb;}letof_zonebufszonebufs=letdb=List.fold_left(fundb->Dns.Zone.load~db[])(Dns.Loader.new_db())zonebufsinof_dbdbletof_zonebufzonebuf=of_zonebufs[zonebuf]letadd_unique_hostnametname?(ttl=120_l)ip=(* TODO: support IPv6 with AAAA *)(* Add it to the trie *)Dns.Loader.add_a_rripttlnamet.db;(* Add an entry to our own table of unique records *)t.probe<-Probe.add_namet.probename(* This predicate controls the cache-flush bit *)letis_confirmed_uniquetowner_rdata=Probe.is_confirmedt.probeownerletrecprobe_forevertactionfirstfirst_wakener=letsend_actionpacketipport=matchDns.Protocol.contain_exc"marshal"(fun()->DP.marshal~alloc:Transport.allocpacket)with|None->Lwt.return_unit|Somebuf->Transport.write(ip,port)bufinmatchactionwith|Probe.Nothing->label"Nothing";if(Probe.is_first_completet.probe)&&!firstthenbegin(* Only once, because a thread can only be woken once *)first:=false;Lwt.wakeupfirst_wakener()end;Lwt_condition.waitt.probe_condition>>=fun()->probe_forevertProbe.Continuefirstfirst_wakener|Probe.ToSend(packet,ip,port)->label"ToSend";(* t.probe is also modified in process_response *)send_actionpacketipport>>=fun()->letstate,next_action=Probe.on_send_completet.probeint.probe<-state;probe_forevertnext_actionfirstfirst_wakener|Probe.Delaydelay->label"Delay";(* The condition allows the sleep to be interrupted *)(* t.probe is also modified in process_response *)Lwt.pick[Transport.sleepdelay;Lwt_condition.waitt.probe_condition]>>=fun()->letstate,next_action=Probe.on_delay_completet.probeint.probe<-state;probe_forevertnext_actionfirstfirst_wakener|Probe.Continue->label"Continue";letstate,next_action=Probe.do_probet.probeint.probe<-state;probe_forevertnext_actionfirstfirst_wakener|Probe.NotReady->label"NotReady";(* This is a bug. There's not much we can do but return. *)Lwt.return_unit|Probe.Stop->label"Stop";Lwt.return_unitletfirst_probet=label"first_probe";(* Random delay of 0-250 ms *)Transport.sleep(Random.float0.25)>>=fun()->letfirst=reftrueinletfirst_wait,first_wakener=Lwt.wait()int.probe_forever<-probe_forevertProbe.Continuefirstfirst_wakener;(* The caller may wait for the first complete probe cycle *)first_waitletannouncet~repeat=label"announce";letquestions=ref[]inletbuild_questionsnode=letq=DP.({q_name=node.DR.owner.H.node;q_type=Q_ANY_TYP;q_class=Q_IN;q_unicast=Q_Normal;})inquestions:=q::!questionsinletdedup_answeranswer=(* Delete duplicate RRs from the response *)(* FIXME: O(N*N) *)(* TODO: Dns.Query shouldn't generate duplicate RRs *)letrr_eqrr1rr2=rr1.DP.name=rr2.DP.name&&DP.compare_rdatarr1.DP.rdatarr2.DP.rdata=0inletrecdedupl=matchlwith|[]->l|hd::tl->ifList.exists(rr_eqhd)tlthentlelsehd::deduptlin{answerwithDQ.answer=dedupanswer.DQ.answer;DQ.additional=[]}inletrecwrite_repeatdestobufrepeatsleept=(* RFC 6762 section 11 - TODO: send with IP TTL = 255 *)Transport.writedestobuf>>=fun()->ifrepeat=1thenLwt.return_unitelseTransport.sleepsleept>>=fun()->write_repeatdestobuf(repeat-1)(sleept*.2.0)inDns.Trie.iterbuild_questionst.dnstrie;(* TODO: if the data for a shared record has changed, we should send 'goodbye'.
See RFC 6762 section 8.4 *)letanswer=DQ.answer_multiple~dnssec:false~mdns:true~flush:(is_confirmed_uniquet)!questionst.dnstrieinletanswer=dedup_answeranswerinletdest_host=multicast_ipinletdest_port=5353in(* TODO: refactor Dns.Query to avoid the need for this fake query *)letfake_detail=DP.({qr=Query;opcode=Standard;aa=false;tc=false;rd=false;ra=false;rcode=NoError})inletfake_query=DP.({id=0;detail=fake_detail;questions=!questions;answers=[];authorities=[];additionals=[];})inletresponse=DQ.response_of_answer~mdns:truefake_queryanswerinifresponse.DP.answers=[]thenLwt.return_unitelse(* TODO: limit the response packet size *)matchDS.marshal~alloc:Transport.allocfake_queryresponsewith|None->Lwt.return_unit|Someobuf->write_repeat(dest_host,dest_port)obufrepeat1.0letget_answertquery=letfilternamerrset=(* RFC 6762 section 7.1 - Known Answer Suppression *)(* First match on owner name and check TTL *)letrelevant_known=List.filter(funknown->(name=known.DP.name)&&(known.DP.ttl>=Int32.divrrset.DR.ttl2l))query.DP.answersin(* Now suppress known records based on RR type *)letrdata=filter_known_listrrset.DR.rdatarelevant_knownin{DR.ttl=(matchrdatawithDR.Unknown_->0l|_->rrset.DR.ttl);DR.rdata=rdata;}in(* DNSSEC disabled for testing *)DQ.answer_multiple~dnssec:false~mdns:true~filter~flush:(is_confirmed_uniquet)query.DP.questionst.dnstrieletprocess_querytsrc_dstquery=letget_delaylegacyresponse=iflegacythen(* No delay for legacy mode *)Lwt.return_unitelseifList.exists(funa->a.DP.flush)response.DP.answersthen(* No delay for records that have been verified as unique *)(* TODO: send separate unique and non-unique responses if applicable *)Lwt.return_unitelse(* Delay response for 20-120 ms *)Transport.sleep(0.02+.Random.float0.1)in(* rfc6762 s6.7_p2_c1 - legacy TTL must be <= 10 sec *)letlimit_rrs_ttl~limitrrs=List.map(funrr->{rrwithDP.ttl=(minrr.DP.ttllimit)})rrsinletlimit_answer_ttl~limitanswer={answerwithDQ.answer=limit_rrs_ttl~limitanswer.DQ.answer;DQ.authority=limit_rrs_ttl~limitanswer.DQ.authority;DQ.additional=limit_rrs_ttl~limitanswer.DQ.additional;}inmatchDns.Protocol.contain_exc"answer"(fun()->get_answertquery)with|None->Lwt.return_unit|Someanswerwhenanswer.DQ.answer=[]->Lwt.return_unit|Someanswer->letsrc_host,src_port=srcinletlegacy=(src_port!=5353)inletunicast=(* True if all of the questions have the unicast response bit set *)(* TODO: split into separate unicast and multicast responses if applicable *)iflegacythenfalseelseList.for_all(funq->q.DP.q_unicast=DP.Q_mDNS_Unicast)query.DP.questionsinletreply_host=iflegacy||unicastthensrc_hostelsemulticast_ipinletreply_port=src_portin(* rfc6762 s6.7_p2_c1 - legacy TTL must be <= 10 sec *)letanswer=iflegacythenlimit_answer_ttl~limit:10_lanswerelseanswerin(* RFC 6762 section 18.5 - TODO: check tc bit *)(* NOTE: echoing of questions is still required for legacy mode *)letresponse=DQ.response_of_answer~mdns:(notlegacy)queryanswerinletresponse,new_state,conflict=Probe.on_query_receivedt.probequeryresponseint.probe<-new_state;ifconflict=Probe.ConflictRestartthenLwt_condition.signalt.probe_condition();ifresponse.DP.answers=[]thenLwt.return_unitelsebegin(* Possible delay before responding *)get_delaylegacyresponse>>=fun()->(* TODO: limit the response packet size *)matchDS.marshal~alloc:Transport.allocqueryresponsewith|None->Lwt.return_unit|Someobuf->(* RFC 6762 section 11 - TODO: send with IP TTL = 255 *)Transport.write(reply_host,reply_port)obufendletprocess_responsetresponse=letstate,conflict=Probe.on_response_receivedt.proberesponseint.probe<-state;ifconflict=Probe.ConflictRestartthenLwt_condition.signalt.probe_condition();(* RFC 6762 section 10.5 - TODO: passive observation of failures *)Lwt.return_unitletprocesst~src~dstibuf=label"mDNS process";letopenDPinmatchDS.parseibufwith|None->Lwt.return_unit|Somedpwhendp.detail.opcode!=Standard->(* RFC 6762 section 18.3 *)Lwt.return_unit|Somedpwhendp.detail.rcode!=NoError->(* RFC 6762 section 18.11 *)Lwt.return_unit|Somedpwhendp.detail.qr=Query->process_querytsrcdstdp|Somedp->process_responsetdpletstop_probet=(* TODO: send 'goodbye' for all names *)t.probe<-Probe.stopt.probe;Lwt_condition.signalt.probe_condition();t.probe_foreverlettriet=t.dnstrieend