123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390(*
* Copyright (C) 2017 Docker Inc <dave.scott@docker.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.
*
*)letsrc=letsrc=Logs.Src.create"dnssd"~doc:"DNS-SD interface"inLogs.Src.set_levelsrc(SomeLogs.Info);srcmoduleLog=(valLogs.src_logsrc:Logs.LOG)[@@@warning"-37"]typekDNSServiceType=|A|NS|MD|MF|CNAME|SOA|MB|MG|MR|NULL|WKS|PTR|HINFO|MINFO|MX|TXT|RP|AFSDB|X25|ISDN|RT|NSAP|NSAP_PTR|SIG|KEY|PX|GPOS|AAAA|LOC|NXT|EID|NIMLOC|SRV|ATMA|NAPTR|KX|CERT|A6|DNAME|SINK|OPT|APL|DS|SSHFP|IPSECKEY|RRSIG|NSEC|DNSKEY|DHCID|NSEC3|NSEC3PARAM|HIP|SPF|UINFO|UID|GID|UNSPEC|TKEY|TSIG|IXFR|AXFR|MAILB|MAILA|ANY[@@@warning"+37"]externalint_of_DNSServiceType:kDNSServiceType->int="stub_int_of_DNSServiceType"letkDNSServiceType_of_q_type=function|Dns.Packet.Q_A->OkA|Dns.Packet.Q_NS->OkNS|Dns.Packet.Q_MD->OkMD|Dns.Packet.Q_MF->OkMF|Dns.Packet.Q_CNAME->OkCNAME|Dns.Packet.Q_SOA->OkSOA|Dns.Packet.Q_MB->OkMB|Dns.Packet.Q_MG->OkMG|Dns.Packet.Q_MR->OkMR|Dns.Packet.Q_NULL->OkNULL|Dns.Packet.Q_WKS->OkWKS|Dns.Packet.Q_PTR->OkPTR|Dns.Packet.Q_HINFO->OkHINFO|Dns.Packet.Q_MINFO->OkMINFO|Dns.Packet.Q_MX->OkMX|Dns.Packet.Q_TXT->OkTXT|Dns.Packet.Q_RP->OkRP|Dns.Packet.Q_AFSDB->OkAFSDB|Dns.Packet.Q_X25->OkX25|Dns.Packet.Q_ISDN->OkISDN|Dns.Packet.Q_RT->OkRT|Dns.Packet.Q_NSAP->OkNSAP|Dns.Packet.Q_NSAPPTR->Error(`Msg"NSAPPTR query type not supported")|Dns.Packet.Q_SIG->OkSIG|Dns.Packet.Q_KEY->OkKEY|Dns.Packet.Q_PX->OkPX|Dns.Packet.Q_GPOS->OkGPOS|Dns.Packet.Q_AAAA->OkAAAA|Dns.Packet.Q_LOC->OkLOC|Dns.Packet.Q_NXT->OkNXT|Dns.Packet.Q_EID->OkEID|Dns.Packet.Q_NIMLOC->OkNIMLOC|Dns.Packet.Q_SRV->OkSRV|Dns.Packet.Q_ATMA->OkATMA|Dns.Packet.Q_NAPTR->OkNAPTR|Dns.Packet.Q_KM->Error(`Msg"KM query type not supported")|Dns.Packet.Q_CERT->OkCERT|Dns.Packet.Q_A6->OkA6|Dns.Packet.Q_DNAME->OkDNAME|Dns.Packet.Q_SINK->OkSINK|Dns.Packet.Q_OPT->OkOPT|Dns.Packet.Q_APL->OkAPL|Dns.Packet.Q_DS->OkDS|Dns.Packet.Q_SSHFP->OkSSHFP|Dns.Packet.Q_IPSECKEY->OkIPSECKEY|Dns.Packet.Q_RRSIG->OkRRSIG|Dns.Packet.Q_NSEC->OkNSEC|Dns.Packet.Q_DNSKEY->OkDNSKEY|Dns.Packet.Q_NSEC3->OkNSEC3|Dns.Packet.Q_NSEC3PARAM->OkNSEC3PARAM|Dns.Packet.Q_SPF->OkSPF|Dns.Packet.Q_UINFO->OkUINFO|Dns.Packet.Q_UID->OkUID|Dns.Packet.Q_GID->OkGID|Dns.Packet.Q_UNSPEC->OkUNSPEC|Dns.Packet.Q_AXFR->OkAXFR|Dns.Packet.Q_MAILB->OkMAILB|Dns.Packet.Q_MAILA->OkMAILA|Dns.Packet.Q_ANY_TYP->Error(`Msg"ANY_TYP query type not supported")|Dns.Packet.Q_TA->Error(`Msg"TA query type not supported")|Dns.Packet.Q_DLV->Error(`Msg"DLV query type not supported")|Dns.Packet.Q_UNKNOWNx->Error(`Msg(Printf.sprintf"Unknown query type %d"x))typeerror=|Unknown|NoSuchName|NoMemory|BadParam|BadReference|BadState|BadFlags|Unsupported|NotInitialized|AlreadyRegistered|NameConflict|Invalid|Firewall|Incompatible|BadInterfaceIndex|Refused|NoSuchRecord|NoAuth|NoSuchKey|NATTraversal|DoubleNAT|BadTime|BadSig|BadKey|Transient|ServiceNotRunning|NATPortMappingUnsupported|NATPortMappingDisabled|NoRouter|PollingMode|Timeoutletstring_of_error=function|Unknown->"Unknown"|NoSuchName->"NoSuchName"|NoMemory->"NoMemory"|BadParam->"BadParam"|BadReference->"BadReference"|BadState->"BadState"|BadFlags->"BadFlags"|Unsupported->"Unsupported"|NotInitialized->"NotInitialized"|AlreadyRegistered->"AlreadyRegistered"|NameConflict->"NameConflict"|Invalid->"Invalid"|Firewall->"Firewall"|Incompatible->"Incompatible"|BadInterfaceIndex->"BadInterfaceIndex"|Refused->"Refused"|NoSuchRecord->"NoSuchRecord"|NoAuth->"NoAuth"|NoSuchKey->"NoSuchKey"|NATTraversal->"NATTraversal"|DoubleNAT->"DoubleNAT"|BadTime->"BadTime"|BadSig->"BadSig"|BadKey->"BadKey"|Transient->"Transient"|ServiceNotRunning->"ServiceNotRunning"|NATPortMappingUnsupported->"NATPortMappingUnsupported"|NATPortMappingDisabled->"NATPortMappingDisabled"|NoRouter->"NoRouter"|PollingMode->"PollingMode"|Timeout->"Timeout"(* Low-level, unsafe APIs *)typedNSServiceRefletnext_token=leti=ref0infun()->letthis=!iinincri;this(* The callback fires once per result *)typecb_result={cb_fullname:string;cb_rrtype:int;cb_rrclass:int;cb_rrdata:Bytes.t;cb_ttl:int;}(* Accumulate the results here *)letin_progress_calls=Hashtbl.create7typetoken=intexternalquery_record:string->int->token->dNSServiceRef="stub_query_record"externalquery_fd:dNSServiceRef->Unix.file_descr="stub_query_fd"externalquery_process:dNSServiceRef->unit="stub_query_process"externalquery_deallocate:dNSServiceRef->unit="stub_query_deallocate"externalis_supported_on_this_platform:unit->bool="stub_is_supported_on_this_platform"letcommon_callbacktokenresult=matchresultwith|Errorerr->Hashtbl.replacein_progress_callstoken(Errorerr)|Okthis->letbuf=Cstruct.create(Bytes.lengththis.cb_rrdata)inCstruct.blit_from_bytesthis.cb_rrdata0buf0(Bytes.lengththis.cb_rrdata);letrr=matchDns.Packet.int_to_rr_typethis.cb_rrtypewith|None->None|Somerrtype->begintryletrdata=Dns.Packet.parse_rdata(Hashtbl.create1)0rrtypethis.cb_rrclass(Int32.of_intthis.cb_ttl)bufinletname=Dns.Name.of_stringthis.cb_fullnameinletcls=Dns.Packet.RR_INinletttl=Int32.of_intthis.cb_ttlinifthis.cb_rrclass=1thenSome{Dns.Packet.name;cls;flush=false;ttl;rdata}elseNonewithDns.Packet.Not_implemented->Noneendinbeginmatchrrwith|None->Log.warn(funf->letbuffer=Buffer.create128inCstruct.hexdump_to_bufferbufferbuf;f"Failed to parse resource record: fullname = %s; rrtype = %d; rrclass = %d; rrdata(%d) = %s; ttl = %d"this.cb_fullnamethis.cb_rrtypethis.cb_rrclass(Bytes.lengththis.cb_rrdata)(Buffer.contentsbuffer)this.cb_ttl)|Somerr->ifHashtbl.memin_progress_callstokenthenbeginmatchHashtbl.findin_progress_callstokenwith|Error_->()(* keep the error *)|Okexisting->(* [1] list is accumulated backwards, see below [2] *)Hashtbl.replacein_progress_callstoken(Ok(rr::existing))endelseHashtbl.replacein_progress_callstoken(Ok[rr])endletquery_onenamety=matchkDNSServiceType_of_q_typetywith|Error(`Msgm)->failwithm|Okty'->letty''=int_of_DNSServiceTypety'inifty''<0thenfailwith"Unrecognised query type";lettoken=next_token()inletq=query_recordnamety''tokeninquery_processq;(* [2] list is accumulated backwards, see above [1] *)letresult=matchHashtbl.findin_progress_callstokenwith|Errore->Errore|Okxs->Ok(List.revxs)inHashtbl.removein_progress_callstoken;query_deallocateq;resultletqueryrequested_namety=(* The DNSServiceRef API will return CNAMEs first, without resolving to
A/AAAA/... This function recursively resolves the CNAMES while avoiding
returning duplicate records. *)(* NB we only return NoSuchRecord if we find no records. This is because it
is possible to query a CNAME which exists, but which points to a non-existent
record. *)letrecloopaccnamety=letname'=Dns.Name.to_stringnameinmatchquery_onename'tywith(* When we're recursing, ignore the NoSuchRecord error *)|ErrorNoSuchRecordwhenname'<>requested_name->Okacc|Errore->Errore|Okrrs->letnot_seen_before=List.filter(funx->not(List.memxacc))rrsin(* If there are any CNAMEs, resolve these too *)letcnames=List.rev@@List.fold_left(funaccrr->matchrr.Dns.Packet.rdatawith|CNAMEname->name::acc|_->acc)[]not_seen_beforeinList.fold_left(funaccname->matchaccwith|Errore->Errore|Okacc->loopaccnamety)(Ok(acc@not_seen_before))cnamesinloop[](Dns.Name.of_stringrequested_name)tymoduleLowLevel=structtypequery={query:dNSServiceRef;token:int;mutablecancelled:bool;}letquerynamety=matchkDNSServiceType_of_q_typetywith|Error(`Msgm)->failwithm|Okty'->letty''=int_of_DNSServiceTypety'inifty''<0thenfailwith"Unrecognised query type";lettoken=next_token()inletquery=query_recordnamety''tokeninletcancelled=falsein{query;token;cancelled}exceptionCancelledletsocket{query;cancelled;_}=ifcancelledthenraiseCancelled;query_fdqueryletresponse{query;token;cancelled}=ifcancelledthenraiseCancelled;query_processquery;(* [2] list is accumulated backwards, see above [1] *)letresult=matchHashtbl.findin_progress_callstokenwith|Errore->Errore|Okxs->Ok(List.revxs)inHashtbl.removein_progress_callstoken;query_deallocatequery;resultletcancelq=q.cancelled<-true;query_deallocateq.queryendlet()=Callback.register"ocaml-osx-dnssd"common_callback