Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for getaddrinfo #278

Merged
merged 1 commit into from
Aug 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib_eio/mock/eio_mock.mli
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ module Net : sig
on_listen : Eio.Net.listening_socket Handler.t;
on_connect : <Eio.Net.stream_socket; Eio.Flow.close> Handler.t;
on_datagram_socket : <Eio.Net.datagram_socket; Eio.Flow.close> Handler.t;
on_getaddrinfo : Eio.Net.Sockaddr.t list Handler.t;
>

type listening_socket = <
Expand All @@ -140,6 +141,8 @@ module Net : sig
val on_datagram_socket : t -> <Eio.Net.datagram_socket; Eio.Flow.close; ..> 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. *)

Expand Down
9 changes: 9 additions & 0 deletions lib_eio/mock/net.ml
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,21 @@ type t = <
on_listen : Eio.Net.listening_socket Handler.t;
on_connect : <Eio.Net.stream_socket; Eio.Flow.close> Handler.t;
on_datagram_socket : <Eio.Net.datagram_socket; Eio.Flow.close> 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;
Expand All @@ -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 =
Expand All @@ -49,6 +56,8 @@ let on_datagram_socket (t:t) actions =
let as_socket x = (x :> <Eio.Net.datagram_socket; Eio.Flow.close>) 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;
Expand Down
3 changes: 3 additions & 0 deletions lib_eio/net.ml
Original file line number Diff line number Diff line change
Expand Up @@ -180,11 +180,14 @@ 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 -> <stream_socket; Flow.close>
method virtual datagram_socket : sw:Switch.t -> Sockaddr.datagram -> <datagram_socket; Flow.close>
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
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
12 changes: 12 additions & 0 deletions lib_eio/net.mli
Original file line number Diff line number Diff line change
Expand Up @@ -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 -> <stream_socket; Flow.close>
method virtual datagram_socket : sw:Switch.t -> Sockaddr.datagram -> <datagram_socket; Flow.close>
method virtual getaddrinfo : service:string -> string -> Sockaddr.t list
end

(** {2 Out-bound Connections} *)
Expand Down Expand Up @@ -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 <https://man7.org/linux/man-pages/man3/getaddrinfo.3.html> getaddrinfo *)

(** {2 Closing} *)
val close : <close: unit; ..> -> unit
(** [close t] marks the socket as closed. It can no longer be used after this. *)
19 changes: 19 additions & 0 deletions lib_eio_linux/eio_linux.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 = <
Expand Down
7 changes: 7 additions & 0 deletions lib_eio_linux/eio_linux.mli
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion lib_eio_luv/dune
Original file line number Diff line number Diff line change
@@ -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))
24 changes: 24 additions & 0 deletions lib_eio_luv/eio_luv.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 =
Expand Down
6 changes: 6 additions & 0 deletions lib_eio_luv/eio_luv.mli
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
42 changes: 42 additions & 0 deletions tests/network.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
```

<!-- $MDX non-deterministic=output -->
```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)]
```

<!-- $MDX non-deterministic=output -->
```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)]
```

<!-- $MDX non-deterministic=output -->
```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)]
```