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

Expose getaddrinfo errors. Was: Return empty list on EAI_ errors for getaddrinfo@luv (#351) #352

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions lib_eio/net.ml
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,41 @@ let datagram_socket ?(reuse_addr=false) ?(reuse_port=false) ~sw (t:#t) addr =
let addr = (addr :> [Sockaddr.datagram | `UdpV4 | `UdpV6]) in
t#datagram_socket ~reuse_addr ~reuse_port ~sw addr

(* keep in sync with C stubs *)
type getaddrinfo_error =
| EAI_ADDRFAMILY
| EAI_AGAIN
| EAI_BADFLAGS
| EAI_BADHINTS
| EAI_FAIL
| EAI_FAMILY
| EAI_MEMORY
| EAI_NODATA
| EAI_NONAME
| EAI_OVERFLOW
| EAI_PROTOCOL
| EAI_SERVICE
| EAI_SOCKTYPE
| EAI_SYSTEM

exception Getaddrinfo_error of getaddrinfo_error

let getaddrinfo_error_to_string = function
| EAI_ADDRFAMILY -> "address family for name not supported"
| EAI_AGAIN -> "temporary failure in name resolution"
| EAI_BADFLAGS -> "invalid value for ai_flags"
| EAI_BADHINTS -> "invalid value for hints"
| EAI_FAIL -> "non-recoverable failure in name resolution"
| EAI_FAMILY -> "ai_family not supported"
| EAI_MEMORY -> "memory allocation failure"
| EAI_NODATA -> "no address associated with name"
| EAI_NONAME -> "name or service is not known"
| EAI_OVERFLOW -> "argument buffer overflow"
| EAI_PROTOCOL -> "resolved protocol is unknown"
| EAI_SERVICE -> "service not supported for ai_socktype"
| EAI_SOCKTYPE -> "ai_socktype not supported"
| EAI_SYSTEM -> "system error"

let getaddrinfo ?(service="") (t:#t) hostname = t#getaddrinfo ~service hostname

let getaddrinfo_stream ?service t hostname =
Expand Down
37 changes: 34 additions & 3 deletions lib_eio/net.mli
Original file line number Diff line number Diff line change
Expand Up @@ -220,22 +220,53 @@ 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} *)
(** {2 Getaddrinfo queries}

Note that unlike {!Unix.getaddrinfo}, EIO's [getaddrinfo] family
of functions raise an exception {!Getaddrinfo_error} with an error
code instead of returning an empty list. *)

(* keep in sync with C stubs *)
type getaddrinfo_error =
| EAI_ADDRFAMILY
| EAI_AGAIN
| EAI_BADFLAGS
| EAI_BADHINTS
| EAI_FAIL
| EAI_FAMILY
| EAI_MEMORY
| EAI_NODATA
| EAI_NONAME
| EAI_OVERFLOW
| EAI_PROTOCOL
| EAI_SERVICE
| EAI_SOCKTYPE
| EAI_SYSTEM
(** Possible errors raised by getaddrinfo functions, check
getaddrinfo(3) and gai_strerror(3) for more information. *)

exception Getaddrinfo_error of getaddrinfo_error

val getaddrinfo_error_to_string : getaddrinfo_error -> string
(** [getaddrinfo_error_to_string e] returns a string representation of [e], like gai_strerror(3). *)

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.
May raise {!Getaddrinfo_error} and never returns an empty list.

@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}. *)

val getaddrinfo_stream: ?service:string -> #t -> string -> Sockaddr.stream list
(** [getaddrinfo_stream] is like {!getaddrinfo}, but filters out non-stream protocols. *)
(** [getaddrinfo_stream] is like {!getaddrinfo}, but filters out non-stream protocols.
May raise {!Getaddrinfo_error} and never returns an empty list. *)

val getaddrinfo_datagram: ?service:string -> #t -> string -> Sockaddr.datagram list
(** [getaddrinfo_datagram] is like {!getaddrinfo}, but filters out non-datagram protocols. *)
(** [getaddrinfo_datagram] is like {!getaddrinfo}, but filters out non-datagram protocols.
May raise {!Getaddrinfo_error} and never returns an empty list. *)

val getnameinfo : #t -> Sockaddr.t -> (string * string)
(** [getnameinfo t sockaddr] is [(hostname, service)] corresponding to [sockaddr]. [hostname] is the
Expand Down
3 changes: 3 additions & 0 deletions lib_eio_linux/dune
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@
(language c)
(flags :standard -D_LARGEFILE64_SOURCE)
(names eio_stubs))
(foreign_stubs
(language c)
(names getaddrinfo_stubs))
(libraries eio eio.utils eio.unix uring logs fmt))
8 changes: 7 additions & 1 deletion lib_eio_linux/eio_linux.ml
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,10 @@ module Low_level = struct

external eio_getdents : Unix.file_descr -> string list = "caml_eio_getdents"

external eio_getaddrinfo : string -> string -> Unix.getaddrinfo_option list ->
(Unix.addr_info list, Eio.Net.getaddrinfo_error) result
= "caml_eio_getaddrinfo"

let getrandom { Cstruct.buffer; off; len } =
let rec loop n =
if n = len then
Expand Down Expand Up @@ -954,7 +958,9 @@ module Low_level = struct
| _ -> None
in
Eio_unix.run_in_systhread @@ fun () ->
Unix.getaddrinfo node service []
(match (eio_getaddrinfo node service []) with
| Ok l -> l
| Error e -> raise (Eio.Net.Getaddrinfo_error e))
|> List.filter_map to_eio_sockaddr_t
end

Expand Down
3 changes: 3 additions & 0 deletions lib_eio_linux/eio_linux.mli
Original file line number Diff line number Diff line change
Expand Up @@ -251,4 +251,7 @@ module Low_level : sig
(** [getaddrinfo host] returns a list of IP addresses for [host]. [host] is either a domain name or
an ipaddress. *)

val eio_getaddrinfo : string -> string -> Unix.getaddrinfo_option list ->
(Unix.addr_info list, Eio.Net.getaddrinfo_error) result

end
181 changes: 181 additions & 0 deletions lib_eio_linux/getaddrinfo_stubs.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
/**************************************************************************/
/* */
/* OCaml */
/* */
/* Xavier Leroy, projet Cristal, INRIA Rocquencourt, */
/* Christiano Haesbaert, Tarides */
/* Copyright 2004 Institut National de Recherche en Informatique et */
/* en Automatique. */
/* Copyright 2022 Tarides */
/* */
/* All rights reserved. This file is distributed under the terms of */
/* the GNU Lesser General Public License version 2.1, with the */
/* special exception on linking described in the file LICENSE. */
Comment on lines +11 to +13
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we want to avoid LGPL code in Eio (it's currently BSD and ISC only). Though they might relicense this bit if asked.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gasche @xavierleroy (sorry for the ping).
I've copied significant part of otherlibs/unix/getaddrinfo.c into EIO (https://github.com/ocaml-multicore/eio/blob/61b737d5be683b9be611844b16c7e73b5bece09a/lib_eio_linux/getaddrinfo_stubs.c). The original file is LGPLed and we would like to relicense our new file to ISC.
Do you permit us to relicense the bits we copied out from LGPL to ISC in EIO ?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. I only use copyleft licenses for my free software. Please consider using the LGPL with OCaml linking exception for your library, or do not reuse any of my code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. I only use copyleft licenses for my free software. Please consider using the LGPL with OCaml linking exception for your library, or do not reuse any of my code.

Thank you, will do one of the two as suggested.

/* */
/**************************************************************************/

#include <sys/types.h>
#include <sys/socket.h>

#include <errno.h>
#include <netdb.h>

#include <caml/mlvalues.h>
#include <caml/memory.h>
#include <caml/alloc.h>
#include <caml/unixsupport.h>
#include <caml/socketaddr.h>

extern value caml_unix_cst_to_constr(int, int *, int, int);
extern int caml_unix_socket_domain_table[]; /* from socket.c */
extern int caml_unix_socket_type_table[]; /* from socket.c */

static value convert_addrinfo(struct addrinfo * a)
{
CAMLparam0();
CAMLlocal3(vres,vaddr,vcanonname);
union sock_addr_union sa;
socklen_param_type len;

len = a->ai_addrlen;
if (len > sizeof(sa)) len = sizeof(sa);
memcpy(&sa.s_gen, a->ai_addr, len);
vaddr = caml_unix_alloc_sockaddr(&sa, len, -1);
vcanonname = caml_copy_string(a->ai_canonname == NULL ? "" : a->ai_canonname);
vres = caml_alloc_small(5, 0);
Field(vres, 0) =
caml_unix_cst_to_constr(a->ai_family, caml_unix_socket_domain_table, 3, 0);
Field(vres, 1) =
caml_unix_cst_to_constr(a->ai_socktype, caml_unix_socket_type_table, 4, 0);
Field(vres, 2) = Val_int(a->ai_protocol);
Field(vres, 3) = vaddr;
Field(vres, 4) = vcanonname;
CAMLreturn(vres);
}

/* glibc doesn't define a bunch of EAI_, so fake one since code gets copied around */

#ifndef EAI_ADDRFAMILY
#define EAI_ADDRFAMILY (-3000)
#endif /* EAI_ADDRFAMILY */

#ifndef EAI_BADHINTS
#define EAI_BADHINTS (-3013)
#endif /* EAI_BADHINTS */

#ifndef EAI_NODATA
#define EAI_NODATA (-3007)
#endif /* EAI_NODATA */

#ifndef EAI_OVERFLOW
#define EAI_OVERFLOW (-3009)
#endif /* EAI_OVERFLOW */

#ifndef EAI_PROTOCOL
#define EAI_PROTOCOL (-3014)
#endif /* EAI_PROTOCOL */

static int gai_errors[] = {
EAI_ADDRFAMILY,
EAI_AGAIN,
EAI_BADFLAGS,
EAI_BADHINTS,
EAI_FAIL,
EAI_FAMILY,
EAI_MEMORY,
EAI_NODATA,
EAI_NONAME,
EAI_OVERFLOW,
EAI_PROTOCOL,
EAI_SERVICE,
EAI_SOCKTYPE,
EAI_SYSTEM /* NOTE: must be last */
};

#define nmemb_gai_errors (sizeof(gai_errors) / sizeof(int))

CAMLprim value caml_eio_getaddrinfo(value vnode, value vserv, value vopts)
{
CAMLparam3(vnode, vserv, vopts);
CAMLlocal4(vres, v, e, vret);
char * node, * serv;
struct addrinfo hints;
struct addrinfo * res, * r;
int retcode, i;

if (! (caml_string_is_c_safe(vnode) && caml_string_is_c_safe(vserv)))
CAMLreturn (Val_emptylist);

/* Extract "node" parameter */
if (caml_string_length(vnode) == 0) {
node = NULL;
} else {
node = caml_stat_strdup(String_val(vnode));
}
/* Extract "service" parameter */
if (caml_string_length(vserv) == 0) {
serv = NULL;
} else {
serv = caml_stat_strdup(String_val(vserv));
}
/* Parse options, set hints */
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
for (/*nothing*/; vopts != Val_emptylist; vopts = Field(vopts, 1)) {
v = Field(vopts, 0);
if (Is_block(v))
switch (Tag_val(v)) {
case 0: /* AI_FAMILY of socket_domain */
hints.ai_family = caml_unix_socket_domain_table[Int_val(Field(v, 0))];
break;
case 1: /* AI_SOCKTYPE of socket_type */
hints.ai_socktype = caml_unix_socket_type_table[Int_val(Field(v, 0))];
break;
case 2: /* AI_PROTOCOL of int */
hints.ai_protocol = Int_val(Field(v, 0));
break;
}
else
switch (Int_val(v)) {
case 0: /* AI_NUMERICHOST */
hints.ai_flags |= AI_NUMERICHOST; break;
case 1: /* AI_CANONNAME */
hints.ai_flags |= AI_CANONNAME; break;
case 2: /* AI_PASSIVE */
hints.ai_flags |= AI_PASSIVE; break;
}
}
/* Do the call */
caml_enter_blocking_section();
retcode = getaddrinfo(node, serv, &hints, &res);
caml_leave_blocking_section();
if (node != NULL) caml_stat_free(node);
if (serv != NULL) caml_stat_free(serv);
/* Convert result */
vres = Val_emptylist;
if (retcode == 0) {
for (r = res; r != NULL; r = r->ai_next) {
e = convert_addrinfo(r);
v = caml_alloc_small(2, Tag_cons);
Field(v, 0) = e;
Field(v, 1) = vres;
vres = v;
}
vret = caml_alloc_small(1, 0); /* 0 = Ok */
Field(vret, 0) = vres;
freeaddrinfo(res);
} else {
for (i = 0; i < nmemb_gai_errors; i++)
if (gai_errors[i] == retcode)
break;
/* Paranoia keeps the world spinning */
if (i == nmemb_gai_errors) {
errno = EINVAL;
i = gai_errors[nmemb_gai_errors - 1]; /* EAI_SYSTEM */
}
vret = caml_alloc_small(1, 1); /* 1 = Error */
Field(vret, 0) = Val_int(i);
}

CAMLreturn(vret);
}
24 changes: 20 additions & 4 deletions lib_eio_luv/eio_luv.ml
Original file line number Diff line number Diff line change
Expand Up @@ -654,10 +654,26 @@ module Low_level = struct
| _ -> 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

let r e = raise (Eio.Net.Getaddrinfo_error e) in
match (await_with_cancel ~request
(fun loop -> Luv.DNS.getaddrinfo ~loop ~request ~service ~node ()))
with
| Ok nl -> List.filter_map to_eio_sockaddr_t nl
| Error `EAI_ADDRFAMILY -> r EAI_ADDRFAMILY
| Error `EAI_AGAIN -> r EAI_AGAIN
| Error `EAI_BADFLAGS -> r EAI_BADFLAGS
| Error `EAI_BADHINTS -> r EAI_BADHINTS
| Error `EAI_CANCELED -> r EAI_FAIL (* note *)
| Error `EAI_FAIL -> r EAI_FAIL
| Error `EAI_FAMILY -> r EAI_FAMILY
| Error `EAI_MEMORY -> r EAI_MEMORY
| Error `EAI_NODATA -> r EAI_NODATA
| Error `EAI_NONAME -> r EAI_NONAME
| Error `EAI_OVERFLOW -> r EAI_OVERFLOW
| Error `EAI_PROTOCOL -> r EAI_PROTOCOL
| Error `EAI_SERVICE -> r EAI_SERVICE
| Error `EAI_SOCKTYPE -> r EAI_SOCKTYPE
| Error e -> raise (Luv_error e)
end

open Low_level
Expand Down
Loading