mDNS/DNS-SD service discovery for OCaml (RFC 6762/6763)
at main 104 lines 3.5 kB view raw
1(*--------------------------------------------------------------------------- 2 Copyright (c) 2025 Thomas Gazagnaire. All rights reserved. 3 SPDX-License-Identifier: MIT 4 ---------------------------------------------------------------------------*) 5 6(** mdns-query - mDNS service discovery CLI tool *) 7 8let pp_domain ppf name = Fmt.pf ppf "%s" (Domain_name.to_string name) 9 10let pp_response ppf (r : Mdns.response) = 11 List.iter 12 (fun (service, instance) -> 13 Fmt.pf ppf "@[<h>PTR %a -> %a@]@." pp_domain service pp_domain instance) 14 r.ptrs; 15 List.iter 16 (fun (instance, port, target) -> 17 Fmt.pf ppf "@[<h>SRV %a -> %a:%d@]@." pp_domain instance pp_domain target 18 port) 19 r.srvs; 20 List.iter 21 (fun (instance, txts) -> 22 Fmt.pf ppf "@[<h>TXT %a -> %a@]@." pp_domain instance 23 Fmt.(list ~sep:(any " ") string) 24 txts) 25 r.txts; 26 List.iter 27 (fun (name, ip) -> 28 Fmt.pf ppf "@[<h>A %a -> %a@]@." pp_domain name Ipaddr.V4.pp ip) 29 r.addrs; 30 List.iter 31 (fun (name, ip) -> 32 Fmt.pf ppf "@[<h>AAAA %a -> %a@]@." pp_domain name Ipaddr.V6.pp ip) 33 r.addrs6 34 35let is_empty (r : Mdns.response) = 36 r.ptrs = [] && r.srvs = [] && r.txts = [] && r.addrs = [] && r.addrs6 = [] 37 38let run service timeout quiet = 39 Eio_main.run @@ fun env -> 40 Eio.Switch.run @@ fun sw -> 41 let net = Eio.Stdenv.net env in 42 let clock = Eio.Stdenv.clock env in 43 let name = 44 try Domain_name.of_string_exn service 45 with Invalid_argument msg -> 46 Fmt.epr "Error: invalid service name %S: %s@." service msg; 47 exit 1 48 in 49 if not quiet then Fmt.pr "Querying %s (timeout: %.1fs)...@.@." service timeout; 50 let responses = Mdns.query ~sw ~net ~clock ~timeout name in 51 let merged = Mdns.merge responses in 52 if is_empty merged then begin 53 if not quiet then Fmt.pr "No responses received.@."; 54 exit 1 55 end 56 else begin 57 if not quiet then Fmt.pr "Results:@."; 58 pp_response Fmt.stdout merged 59 end 60 61(* Cmdliner interface *) 62open Cmdliner 63 64let service = 65 let doc = 66 "Service type to query. Use DNS-SD format: $(b,_service._proto.local) \ 67 (e.g., $(b,_http._tcp.local), $(b,_hap._tcp.local))." 68 in 69 Arg.(required & pos 0 (some string) None & info [] ~docv:"SERVICE" ~doc) 70 71let timeout = 72 let doc = "Timeout in seconds to wait for responses." in 73 Arg.(value & opt float 3.0 & info [ "t"; "timeout" ] ~docv:"SECONDS" ~doc) 74 75let quiet = 76 let doc = "Suppress informational messages; only print results." in 77 Arg.(value & flag & info [ "q"; "quiet" ] ~doc) 78 79let cmd = 80 let doc = "Query mDNS services on the local network" in 81 let man = 82 [ 83 `S Manpage.s_description; 84 `P 85 "$(tname) sends an mDNS query for the specified service type and \ 86 displays all responses received within the timeout period."; 87 `P "Exit status is 0 if at least one response was received, 1 otherwise."; 88 `S Manpage.s_examples; 89 `P "Query for HomeKit accessories:"; 90 `Pre " $(tname) _hap._tcp.local"; 91 `P "Query for Matter devices:"; 92 `Pre " $(tname) _matter._tcp.local"; 93 `P "Query for HTTP servers with longer timeout:"; 94 `Pre " $(tname) -t 5 _http._tcp.local"; 95 `P "Quiet mode for scripting:"; 96 `Pre " $(tname) -q _hap._tcp.local"; 97 `S Manpage.s_see_also; 98 `P "$(b,dns-sd)(1), RFC 6762 (mDNS), RFC 6763 (DNS-SD)"; 99 ] 100 in 101 let info = Cmd.info "mdns-query" ~version:"%%VERSION%%" ~doc ~man in 102 Cmd.v info Term.(const run $ service $ timeout $ quiet) 103 104let () = exit (Cmd.eval cmd)