From 9cc5e1ee6a931467f142a51570504c622decca5f Mon Sep 17 00:00:00 2001 From: Bikal Lem Date: Wed, 10 Aug 2022 19:02:04 +0100 Subject: [PATCH] Add Eio.Net.getaddrinfo --- lib_eio/mock/eio_mock.mli | 3 +++ lib_eio/mock/net.ml | 9 ++++++++ lib_eio/net.ml | 3 +++ lib_eio/net.mli | 12 +++++++++++ lib_eio_linux/eio_linux.ml | 19 +++++++++++++++++ lib_eio_linux/eio_linux.mli | 7 +++++++ lib_eio_luv/dune | 2 +- lib_eio_luv/eio_luv.ml | 24 +++++++++++++++++++++ lib_eio_luv/eio_luv.mli | 6 ++++++ tests/network.md | 42 +++++++++++++++++++++++++++++++++++++ 10 files changed, 126 insertions(+), 1 deletion(-) diff --git a/lib_eio/mock/eio_mock.mli b/lib_eio/mock/eio_mock.mli index 2a47dbf6a..8d986755d 100644 --- a/lib_eio/mock/eio_mock.mli +++ b/lib_eio/mock/eio_mock.mli @@ -121,6 +121,7 @@ module Net : sig on_listen : Eio.Net.listening_socket Handler.t; on_connect : Handler.t; on_datagram_socket : Handler.t; + on_getaddrinfo : Eio.Net.Sockaddr.t list Handler.t; > type listening_socket = < @@ -140,6 +141,8 @@ module Net : sig val on_datagram_socket : t -> Handler.actions -> unit (** [on_datagram_socket t actions] configures how to create datagram sockets. *) + val on_getaddrinfo : t -> Eio.Net.Sockaddr.t list Handler.actions -> unit + val listening_socket : string -> listening_socket (** [listening_socket label] can be configured to provide mock connections. *) diff --git a/lib_eio/mock/net.ml b/lib_eio/mock/net.ml index 4ad4e8c9c..3329609ed 100644 --- a/lib_eio/mock/net.ml +++ b/lib_eio/mock/net.ml @@ -5,18 +5,21 @@ type t = < on_listen : Eio.Net.listening_socket Handler.t; on_connect : Handler.t; on_datagram_socket : Handler.t; + on_getaddrinfo : Eio.Net.Sockaddr.t list Handler.t; > let make label = let on_listen = Handler.make (`Raise (Failure "Mock listen handler not configured")) in let on_connect = Handler.make (`Raise (Failure "Mock connect handler not configured")) in let on_datagram_socket = Handler.make (`Raise (Failure "Mock datagram_socket handler not configured")) in + let on_getaddrinfo = Handler.make (`Raise (Failure "Mock getaddrinfo handler not configured")) in object inherit Eio.Net.t method on_listen = on_listen method on_connect = on_connect method on_datagram_socket = on_datagram_socket + method on_getaddrinfo = on_getaddrinfo method listen ~reuse_addr:_ ~reuse_port:_ ~backlog:_ ~sw addr = traceln "%s: listen on %a" label Eio.Net.Sockaddr.pp addr; @@ -35,6 +38,10 @@ let make label = let socket = Handler.run on_datagram_socket in Switch.on_release sw (fun () -> Eio.Flow.close socket); socket + + method getaddrinfo ~service node = + traceln "%s: getaddrinfo ~service:%s %s" label service node; + Handler.run on_getaddrinfo end let on_connect (t:t) actions = @@ -49,6 +56,8 @@ let on_datagram_socket (t:t) actions = let as_socket x = (x :> ) in Handler.seq t#on_datagram_socket (List.map (Action.map as_socket) actions) +let on_getaddrinfo (t:t) actions = Handler.seq t#on_getaddrinfo actions + type listening_socket = < Eio.Net.listening_socket; on_accept : (Flow.t * Eio.Net.Sockaddr.stream) Handler.t; diff --git a/lib_eio/net.ml b/lib_eio/net.ml index ec4a747ac..6d4db3245 100644 --- a/lib_eio/net.ml +++ b/lib_eio/net.ml @@ -180,6 +180,7 @@ class virtual t = object method virtual listen : reuse_addr:bool -> reuse_port:bool -> backlog:int -> sw:Switch.t -> Sockaddr.stream -> listening_socket method virtual connect : sw:Switch.t -> Sockaddr.stream -> method virtual datagram_socket : sw:Switch.t -> Sockaddr.datagram -> + method virtual getaddrinfo : service:string -> string -> Sockaddr.t list end let listen ?(reuse_addr=false) ?(reuse_port=false) ~backlog ~sw (t:#t) = t#listen ~reuse_addr ~reuse_port ~backlog ~sw @@ -187,4 +188,6 @@ let connect ~sw (t:#t) = t#connect ~sw let datagram_socket ~sw (t:#t) = t#datagram_socket ~sw +let getaddrinfo ?(service="") (t:#t) hostname = t#getaddrinfo ~service hostname + let close = Flow.close diff --git a/lib_eio/net.mli b/lib_eio/net.mli index 13d86634f..bc2e25bcd 100644 --- a/lib_eio/net.mli +++ b/lib_eio/net.mli @@ -104,6 +104,7 @@ class virtual t : object method virtual listen : reuse_addr:bool -> reuse_port:bool -> backlog:int -> sw:Switch.t -> Sockaddr.stream -> listening_socket method virtual connect : sw:Switch.t -> Sockaddr.stream -> method virtual datagram_socket : sw:Switch.t -> Sockaddr.datagram -> + method virtual getaddrinfo : service:string -> string -> Sockaddr.t list end (** {2 Out-bound Connections} *) @@ -171,6 +172,17 @@ val recv : #datagram_socket -> Cstruct.t -> Sockaddr.datagram * int returned along with the sender address and port. If the [buf] is too small then excess bytes may be discarded depending on the type of the socket the message is received from. *) +(** {2 DNS queries} *) + +val getaddrinfo: ?service:string -> #t -> string -> Sockaddr.t list +(** [getaddrinfo ?service t node] returns a list of IP addresses for [node]. [node] is either a domain name or + an IP address. + + @param service is a human friendly textual name for internet services assigned by IANA., eg. + 'http', 'https', 'ftp', etc. + + For a more thorough treatment, @see getaddrinfo *) + (** {2 Closing} *) val close : -> unit (** [close t] marks the socket as closed. It can no longer be used after this. *) diff --git a/lib_eio_linux/eio_linux.ml b/lib_eio_linux/eio_linux.ml index c3a779de6..3f0ff7526 100644 --- a/lib_eio_linux/eio_linux.ml +++ b/lib_eio_linux/eio_linux.ml @@ -892,6 +892,23 @@ module Low_level = struct read_all (files @ acc) fd in Eio_unix.run_in_systhread (fun () -> read_all [] fd) + + (* https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml *) + let getaddrinfo ~service node = + let to_eio_sockaddr_t {Unix.ai_family; ai_addr; ai_socktype; ai_protocol; _ } = + match ai_family, ai_socktype, ai_addr with + | (Unix.PF_INET | PF_INET6), + (Unix.SOCK_STREAM | SOCK_DGRAM), + Unix.ADDR_INET (inet_addr,port) -> ( + match ai_protocol with + | 6 -> Some (`Tcp (Eio_unix.Ipaddr.of_unix inet_addr, port)) + | 17 -> Some (`Udp (Eio_unix.Ipaddr.of_unix inet_addr, port)) + | _ -> None) + | _ -> None + in + Eio_unix.run_in_systhread @@ fun () -> + Unix.getaddrinfo node service [] + |> List.filter_map to_eio_sockaddr_t end external eio_eventfd : int -> Unix.file_descr = "caml_eio_eventfd" @@ -1120,6 +1137,8 @@ let net = object let sock = FD.of_unix ~sw ~seekable:false ~close_unix:true sock_unix in Unix.bind sock_unix addr; udp_socket sock + + method getaddrinfo = Low_level.getaddrinfo end type stdenv = < diff --git a/lib_eio_linux/eio_linux.mli b/lib_eio_linux/eio_linux.mli index ca52024f1..70dca0de4 100644 --- a/lib_eio_linux/eio_linux.mli +++ b/lib_eio_linux/eio_linux.mli @@ -247,4 +247,11 @@ module Low_level : sig It uses Linux's [getrandom] call, which is like reading from /dev/urandom except that it will block (the whole domain) if used at early boot when the random system hasn't been initialised yet. *) + + (** {1 DNS functions} *) + + val getaddrinfo : service:string -> string -> Eio.Net.Sockaddr.t list + (** [getaddrinfo host] returns a list of IP addresses for [host]. [host] is either a domain name or + an ipaddress. *) + end diff --git a/lib_eio_luv/dune b/lib_eio_luv/dune index 1b4e1c560..324f60110 100644 --- a/lib_eio_luv/dune +++ b/lib_eio_luv/dune @@ -1,4 +1,4 @@ (library (name eio_luv) (public_name eio_luv) - (libraries eio.unix luv luv_unix eio.utils logs fmt)) + (libraries eio eio.unix luv luv_unix eio.utils logs fmt)) diff --git a/lib_eio_luv/eio_luv.ml b/lib_eio_luv/eio_luv.ml index 1b086d30a..c2b91c2ea 100644 --- a/lib_eio_luv/eio_luv.ml +++ b/lib_eio_luv/eio_luv.ml @@ -407,6 +407,28 @@ module Low_level = struct Luv.Timer.start timer delay (fun () -> if Fiber_context.clear_cancel_fn k.fiber then enqueue_thread st k () ) |> or_raise + + (* https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml *) + let getaddrinfo ~service node = + let ( let* ) o f = Option.bind o f in + let to_eio_sockaddr_t {Luv.DNS.Addr_info.family; addr; socktype; protocol; _ } = + match family, socktype with + | (`INET | `INET6), + (`STREAM | `DGRAM) -> ( + let* host = Luv.Sockaddr.to_string addr in + let* port = Luv.Sockaddr.port addr in + let ipaddr = Unix.inet_addr_of_string host |> Eio_unix.Ipaddr.of_unix in + match protocol with + | 6 -> Some (`Tcp (ipaddr, port)) + | 17 -> Some (`Udp (ipaddr, port)) + | _ -> None) + | _ -> None + in + let request = Luv.DNS.Addr_info.Request.make () in + await_with_cancel ~request (fun loop -> Luv.DNS.getaddrinfo ~loop ~request ~service ~node ()) + |> or_raise + |> List.filter_map to_eio_sockaddr_t + end open Low_level @@ -647,6 +669,8 @@ let net = object let addr = luv_addr_of_eio host port in Luv.UDP.bind sock addr |> or_raise; udp_socket dg_sock + + method getaddrinfo = Low_level.getaddrinfo end let secure_random = diff --git a/lib_eio_luv/eio_luv.mli b/lib_eio_luv/eio_luv.mli index 326cce9ff..923547ec1 100644 --- a/lib_eio_luv/eio_luv.mli +++ b/lib_eio_luv/eio_luv.mli @@ -26,6 +26,12 @@ module Low_level : sig val sleep_until : float -> unit (** [sleep_until time] blocks until the current time is [time]. *) + (** {DNS functions} *) + + val getaddrinfo : service:string -> string -> Eio.Net.Sockaddr.t list + (** [getaddrinfo ~service host] returns a list of IP addresses for [host]. [host] is either a domain name or + an ipaddress. *) + (** {1 Low-level wrappers for Luv functions} *) module File : sig diff --git a/tests/network.md b/tests/network.md index 69a98e6a8..b8e4037df 100644 --- a/tests/network.md +++ b/tests/network.md @@ -485,3 +485,45 @@ EPIPE: (fun () -> Eio.Flow.shutdown a `Receive);; - : unit = () ``` + +## Getaddrinfo + +```ocaml +# Eio_main.run @@ fun env -> + Eio.Net.getaddrinfo env#net "127.0.0.1";; +- : Eio.Net.Sockaddr.t list = +[`Tcp ("\127\000\000\001", 0); `Udp ("\127\000\000\001", 0)] +``` + +```ocaml +# Eio_main.run @@ fun env -> + Eio.Net.getaddrinfo env#net "127.0.0.1" ~service:"80";; +- : Eio.Net.Sockaddr.t list = +[`Tcp ("\127\000\000\001", 80); `Udp ("\127\000\000\001", 80)] +``` + + +```ocaml +# Eio_main.run @@ fun env -> + Eio.Net.getaddrinfo ~service:"http" env#net "127.0.0.1";; +- : Eio.Net.Sockaddr.t list = +[`Tcp ("\127\000\000\001", 80); `Udp ("\127\000\000\001", 80)] +``` + + +```ocaml +# Eio_main.run @@ fun env -> + Eio.Net.getaddrinfo ~service:"ftp" env#net "127.0.0.1";; +- : Eio.Net.Sockaddr.t list = +[`Tcp ("\127\000\000\001", 21); `Udp ("\127\000\000\001", 21)] +``` + + +```ocaml +# Eio_main.run @@ fun env -> + Eio.Net.getaddrinfo ~service:"https" env#net "google.com";; +- : Eio.Net.Sockaddr.t list = +[`Tcp ("Ø:ÔÎ", 443); `Udp ("Ø:ÔÎ", 443); + `Tcp ("*\000\020P@\t\b \000\000\000\000\000\000 \014", 443); + `Udp ("*\000\020P@\t\b \000\000\000\000\000\000 \014", 443)] +```