···3939let is_empty (r : Mdns.response) =
4040 r.ptrs = [] && r.srvs = [] && r.txts = [] && r.addrs = [] && r.addrs6 = []
41414242+let parse_service service =
4343+ try Domain_name.of_string_exn service
4444+ with Invalid_argument msg ->
4545+ Log.err (fun m -> m "Invalid service name %S: %s" service msg);
4646+ exit 1
4747+4848+let print_results merged =
4949+ if is_empty merged then (
5050+ Log.info (fun m -> m "No responses received.");
5151+ exit 1);
5252+ Log.info (fun m -> m "Results:");
5353+ pp_response Fmt.stdout merged
5454+4255let run () service timeout =
5656+ let name = parse_service service in
4357 Eio_main.run @@ fun env ->
4458 Eio.Switch.run @@ fun sw ->
4559 let net = Eio.Stdenv.net env in
4660 let clock = Eio.Stdenv.clock env in
4747- let name =
4848- try Domain_name.of_string_exn service
4949- with Invalid_argument msg ->
5050- Log.err (fun m -> m "Invalid service name %S: %s" service msg);
5151- exit 1
5252- in
5361 Log.info (fun m -> m "Querying %s (timeout: %.1fs)..." service timeout);
5454- let responses = Mdns.query ~sw ~net ~clock ~timeout name in
5555- let merged = Mdns.merge responses in
5656- if is_empty merged then begin
5757- Log.info (fun m -> m "No responses received.");
5858- exit 1
5959- end
6060- else begin
6161- Log.info (fun m -> m "Results:");
6262- pp_response Fmt.stdout merged
6363- end
6262+ print_results (Mdns.merge (Mdns.query ~sw ~net ~clock ~timeout name))
64636564(* Cmdliner interface *)
6665open Cmdliner
+32-38
lib/mdns.ml
···9292 let pkt = query name in
9393 fst (Dns.Packet.encode `Udp pkt)
94949595+(* Parse a single resource record, advancing [off] past it *)
9696+let parse_rr buf ~len ~off ~ptrs ~srvs ~txts ~addrs ~addrs6 =
9797+ if !off >= len then ()
9898+ else
9999+ let name = domain_of_labels (collect_labels buf !off) in
100100+ let off' = skip_name buf !off in
101101+ if off' + 10 > len then off := len
102102+ else
103103+ let typ = u16 buf off' in
104104+ let rdlen = u16 buf (off' + 8) in
105105+ let rdata = off' + 10 in
106106+ off := rdata + rdlen;
107107+ if rdata + rdlen <= len then
108108+ match typ with
109109+ | 1 when rdlen = 4 ->
110110+ let ip = Ipaddr.V4.of_int32 (String.get_int32_be buf rdata) in
111111+ addrs := (name, ip) :: !addrs
112112+ | 12 ->
113113+ ptrs := (name, domain_of_labels (collect_labels buf rdata)) :: !ptrs
114114+ | 16 -> txts := (name, parse_txt buf rdata (rdata + rdlen)) :: !txts
115115+ | 28 when rdlen = 16 ->
116116+ let hi = String.get_int64_be buf rdata in
117117+ let lo = String.get_int64_be buf (rdata + 8) in
118118+ addrs6 := (name, Ipaddr.V6.of_int64 (hi, lo)) :: !addrs6
119119+ | 33 when rdlen >= 6 ->
120120+ let port = u16 buf (rdata + 4) in
121121+ let target = domain_of_labels (collect_labels buf (rdata + 6)) in
122122+ srvs := (name, port, target) :: !srvs
123123+ | _ -> ()
124124+95125(* Parse mDNS response *)
96126let parse buf =
97127 let len = String.length buf in
···100130 let qdcount = u16 buf 4 in
101131 let rr_count = u16 buf 6 + u16 buf 8 + u16 buf 10 in
102132 let off = ref 12 in
103103- (* Skip question section with bounds checking *)
104133 for _ = 1 to qdcount do
105134 let off' = skip_name buf !off in
106106- (* Need 4 more bytes for qtype/qclass after name *)
107107- if off' + 4 <= len then off := off' + 4
108108- else off := len (* Invalid, skip to end *)
135135+ if off' + 4 <= len then off := off' + 4 else off := len
109136 done;
110137 let ptrs = ref [] and srvs = ref [] and txts = ref [] in
111138 let addrs = ref [] and addrs6 = ref [] in
112139 for _ = 1 to rr_count do
113113- (* Check we have space for name + 10 byte RR header *)
114114- if !off < len then begin
115115- let name = domain_of_labels (collect_labels buf !off) in
116116- let off' = skip_name buf !off in
117117- (* Verify off' + 10 <= len before reading RR header *)
118118- if off' + 10 <= len then begin
119119- let typ = u16 buf off' in
120120- let rdlen = u16 buf (off' + 8) in
121121- let rdata = off' + 10 in
122122- off := rdata + rdlen;
123123- (* Verify rdata region is within bounds *)
124124- if rdata + rdlen <= len then
125125- match typ with
126126- | 1 when rdlen = 4 ->
127127- let ip = Ipaddr.V4.of_int32 (String.get_int32_be buf rdata) in
128128- addrs := (name, ip) :: !addrs
129129- | 12 ->
130130- ptrs :=
131131- (name, domain_of_labels (collect_labels buf rdata)) :: !ptrs
132132- | 16 -> txts := (name, parse_txt buf rdata (rdata + rdlen)) :: !txts
133133- | 28 when rdlen = 16 ->
134134- let hi = String.get_int64_be buf rdata in
135135- let lo = String.get_int64_be buf (rdata + 8) in
136136- addrs6 := (name, Ipaddr.V6.of_int64 (hi, lo)) :: !addrs6
137137- | 33 when rdlen >= 6 ->
138138- let port = u16 buf (rdata + 4) in
139139- let target =
140140- domain_of_labels (collect_labels buf (rdata + 6))
141141- in
142142- srvs := (name, port, target) :: !srvs
143143- | _ -> ()
144144- end
145145- else off := len (* Invalid RR header, abort *)
146146- end
140140+ parse_rr buf ~len ~off ~ptrs ~srvs ~txts ~addrs ~addrs6
147141 done;
148142 Some
149143 {
+3-2
lib/mdns.mli
···5050 [ `raw ] Domain_name.t ->
5151 response list
5252(** [query ~sw ~net ~clock ~timeout name] sends an mDNS query and collects
5353- responses until [timeout] seconds have elapsed.
5353+ responses until [timeout] seconds have elapsed. Returns a list of all
5454+ responses received.
54555556 @param sw Eio switch for resource management.
5657 @param net Eio network capability.
5758 @param clock Eio clock for timeout.
5859 @param timeout Maximum time to wait for responses in seconds.
5959- @param name Service type to query (e.g., "_http._tcp.local") *)
6060+ @param name Service type to query (e.g., "_http._tcp.local"). *)
60616162val merge : response list -> response
6263(** [merge responses] combines multiple responses into a single response. *)