Skip to content

Commit

Permalink
Allow datagram_socket to be created without address (#360)
Browse files Browse the repository at this point in the history
* net: allow to create udp socket without address

* udp: add udp test

* udp: allow Eio.Net.Sockaddr.t in datagram_socket() without casting

* udp: add reuse_addr and reuse_port to datagram_socket

* Simplify tests

Removed the last IPv6 test since it was identical to an earlier test.

* Simplify luv code

* Move socket options out of Eio_unix

* Simplify eio_linux code

Fix problem that UDP sockets were not created close-on-exec.

Co-authored-by: Thomas Leonard <talex5@gmail.com>
  • Loading branch information
bikallem and talex5 authored Nov 4, 2022
1 parent 5c1eae4 commit a70865e
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 22 deletions.
8 changes: 6 additions & 2 deletions lib_eio/mock/net.ml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,12 @@ let make label =
Switch.on_release sw (fun () -> Eio.Flow.close socket);
socket

method datagram_socket ~sw addr =
traceln "%s: datagram_socket %a" label Eio.Net.Sockaddr.pp addr;
method datagram_socket ~reuse_addr:_ ~reuse_port:_ ~sw addr =
(match addr with
| `Udp _ as saddr -> traceln "%s: datagram_socket %a" label Eio.Net.Sockaddr.pp saddr
| `UdpV4 -> traceln "%s: datagram_socket UDPv4" label
| `UdpV6 -> traceln "%s: datagram_socket UDPv6" label
);
let socket = Handler.run on_datagram_socket in
Switch.on_release sw (fun () -> Eio.Flow.close socket);
socket
Expand Down
12 changes: 10 additions & 2 deletions lib_eio/net.ml
Original file line number Diff line number Diff line change
Expand Up @@ -180,15 +180,23 @@ let recv (t:#datagram_socket) = t#recv
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 datagram_socket :
reuse_addr:bool
-> reuse_port:bool
-> sw:Switch.t
-> [Sockaddr.datagram | `UdpV4 | `UdpV6]
-> <datagram_socket; Flow.close>

method virtual getaddrinfo : service:string -> string -> Sockaddr.t list
method virtual getnameinfo : Sockaddr.t -> (string * string)
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 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

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

Expand Down
28 changes: 24 additions & 4 deletions lib_eio/net.mli
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,13 @@ end
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 datagram_socket :
reuse_addr:bool
-> reuse_port:bool
-> sw:Switch.t
-> [Sockaddr.datagram | `UdpV4 | `UdpV6]
-> <datagram_socket; Flow.close>

method virtual getaddrinfo : service:string -> string -> Sockaddr.t list
method virtual getnameinfo : Sockaddr.t -> (string * string)
end
Expand Down Expand Up @@ -187,9 +193,23 @@ val accept_sub :

(** {2 Datagram Sockets} *)

val datagram_socket : sw:Switch.t -> #t -> Sockaddr.datagram -> <datagram_socket; Flow.close>
(** [datagram_socket ~sw t addr] creates a new datagram socket that data can be sent to
and received from. The new socket will be closed when [sw] finishes. *)
val datagram_socket :
?reuse_addr:bool
-> ?reuse_port:bool
-> sw:Switch.t
-> #t
-> [< Sockaddr.datagram | `UdpV4 | `UdpV6]
-> <datagram_socket; Flow.close>
(** [datagram_socket ~sw t addr] creates a new datagram socket bound to [addr]. The new
socket will be closed when [sw] finishes.
[`UdpV4] and [`UdpV6] represents IPv4 and IPv6
datagram client sockets where the OS assigns the next available socket address and port
automatically. [`Udp ..] can be used to create both listening server socket and client
socket.
@param reuse_addr Set the {!Unix.SO_REUSEADDR} socket option.
@param reuse_port Set the {!Unix.SO_REUSEPORT} socket option. *)

val send : #datagram_socket -> Sockaddr.datagram -> Cstruct.t -> unit
(** [send sock addr buf] sends the data in [buf] to the address [addr] using the
Expand Down
22 changes: 14 additions & 8 deletions lib_eio_linux/eio_linux.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1129,6 +1129,8 @@ end

let socket_domain_of = function
| `Unix _ -> Unix.PF_UNIX
| `UdpV4 -> Unix.PF_INET
| `UdpV6 -> Unix.PF_INET6
| `Udp (host, _)
| `Tcp (host, _) ->
Eio.Net.Ipaddr.fold host
Expand Down Expand Up @@ -1183,17 +1185,21 @@ let net = object
Low_level.connect sock addr;
(flow sock :> <Eio.Flow.two_way; Eio.Flow.close>)

method datagram_socket ~sw saddr =
match saddr with
method datagram_socket ~reuse_addr ~reuse_port ~sw saddr =
let sock_unix = Unix.socket ~cloexec:true (socket_domain_of saddr) Unix.SOCK_DGRAM 0 in
let sock = FD.of_unix ~sw ~seekable:false ~close_unix:true sock_unix in
begin match saddr with
| `Udp (host, port) ->
let host = Eio_unix.Ipaddr.to_unix host in
let addr = Unix.ADDR_INET (host, port) in
let sock_unix = Unix.socket (socket_domain_of saddr) Unix.SOCK_DGRAM 0 in
Unix.setsockopt sock_unix Unix.SO_REUSEADDR true;
Unix.setsockopt sock_unix Unix.SO_REUSEPORT true;
let sock = FD.of_unix ~sw ~seekable:false ~close_unix:true sock_unix in
Unix.bind sock_unix addr;
udp_socket sock
if reuse_addr then
Unix.setsockopt sock_unix Unix.SO_REUSEADDR true;
if reuse_port then
Unix.setsockopt sock_unix Unix.SO_REUSEPORT true;
Unix.bind sock_unix addr
| `UdpV4 | `UdpV6 -> ()
end;
udp_socket sock

method getaddrinfo = Low_level.getaddrinfo

Expand Down
26 changes: 20 additions & 6 deletions lib_eio_luv/eio_luv.ml
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,15 @@ let listening_unix_socket ~backlog sock = object
`Unix (Luv.Pipe.getpeername (Handle.get "get_client_addr" c) |> or_raise)
end

let socket_domain_of = function
| `UdpV4 -> `INET
| `UdpV6 -> `INET6
| `Udp (host, _)
| `Tcp (host, _) ->
Eio.Net.Ipaddr.fold host
~v4:(fun _ -> `INET)
~v6:(fun _ -> `INET6)

let net = object
inherit Eio.Net.t

Expand Down Expand Up @@ -861,14 +870,19 @@ let net = object
let sock = Stream.connect_pipe ~sw path in
(socket sock :> < Eio.Flow.two_way; Eio.Flow.close >)

method datagram_socket ~sw = function
method datagram_socket ~reuse_addr ~reuse_port ~sw saddr =
let domain = socket_domain_of saddr in
let sock = Luv.UDP.init ~domain ~loop:(get_loop ()) () |> or_raise in
let dg_sock = Handle.of_luv ~sw sock in
begin match saddr with
| `Udp (host, port) ->
let domain = Eio.Net.Ipaddr.fold ~v4:(fun _ -> `INET) ~v6:(fun _ -> `INET6) host in
let sock = Luv.UDP.init ~domain ~loop:(get_loop ()) () |> or_raise in
let dg_sock = Handle.of_luv ~sw sock in
let addr = luv_addr_of_eio host port in
Luv.UDP.bind sock addr |> or_raise;
udp_socket dg_sock
luv_reuse_addr sock reuse_addr;
luv_reuse_port sock reuse_port;
Luv.UDP.bind sock addr |> or_raise
| `UdpV4 | `UdpV6 -> ()
end;
udp_socket dg_sock

method getaddrinfo = Low_level.getaddrinfo

Expand Down
43 changes: 43 additions & 0 deletions tests/network.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,49 @@ Handling one UDP packet using IPv6:
- : unit = ()
```

Now test host-assigned addresses.
`run_dgram2` is like `run_dgram` above, but doesn't print the sender address
since it will be different in each run:

```ocaml
let run_dgram2 ~e1 addr ~net sw =
let server_addr = `Udp (addr, 8082) in
let listening_socket = Eio.Net.datagram_socket ~sw net server_addr in
Fiber.both
(fun () ->
let buf = Cstruct.create 20 in
traceln "Waiting to receive data on %a" Eio.Net.Sockaddr.pp server_addr;
let addr, recv = Eio.Net.recv listening_socket buf in
traceln "Received message %s" (Cstruct.(to_string (sub buf 0 recv)))
)
(fun () ->
let e = Eio.Net.datagram_socket ~sw net e1 in
traceln "Sending data to %a" Eio.Net.Sockaddr.pp server_addr;
Eio.Net.send e server_addr (Cstruct.of_string "UDP Message"));;
```

Handling one UDP packet using IPv4:

```ocaml
# let addr = Eio.Net.Ipaddr.V4.loopback in
run @@ run_dgram2 addr ~e1:`UdpV4;;
+Waiting to receive data on udp:127.0.0.1:8082
+Sending data to udp:127.0.0.1:8082
+Received message UDP Message
- : unit = ()
```

Handling one UDP packet using IPv6:

```ocaml
# let addr = Eio.Net.Ipaddr.V6.loopback in
run @@ run_dgram2 addr ~e1:`UdpV6;;
+Waiting to receive data on udp:[::1]:8082
+Sending data to udp:[::1]:8082
+Received message UDP Message
- : unit = ()
```

## Unix interop

Extracting file descriptors from Eio objects:
Expand Down

0 comments on commit a70865e

Please sign in to comment.