From f3d70518fa4268390807db7c8ce48ae80e72fa2d Mon Sep 17 00:00:00 2001 From: Patrick Ferris Date: Fri, 5 May 2023 00:24:26 +0100 Subject: [PATCH] Add more windows tests and fix network code --- lib_eio_windows/domain_mgr.ml | 3 +- lib_eio_windows/low_level.ml | 6 -- lib_eio_windows/net.ml | 12 ++-- lib_eio_windows/test/test.ml | 56 ++++----------- lib_eio_windows/test/test_net.ml | 118 +++++++++++++++++++++++++++++++ 5 files changed, 139 insertions(+), 56 deletions(-) create mode 100755 lib_eio_windows/test/test_net.ml diff --git a/lib_eio_windows/domain_mgr.ml b/lib_eio_windows/domain_mgr.ml index 1c054d83e..f6c2df733 100755 --- a/lib_eio_windows/domain_mgr.ml +++ b/lib_eio_windows/domain_mgr.ml @@ -30,7 +30,8 @@ let run_event_loop fn x = | Eio_unix.Private.Get_monotonic_clock -> Some (fun k -> continue k (Time.mono_clock : Eio.Time.Mono.t)) | Eio_unix.Private.Socket_of_fd (sw, close_unix, unix_fd) -> Some (fun k -> let fd = Fd.of_unix ~sw ~blocking:false ~close_unix unix_fd in - Unix.set_nonblock unix_fd; + (* TODO: On Windows, if the FD from Unix.pipe () is passed this will fail *) + (try Unix.set_nonblock unix_fd with Unix.Unix_error (Unix.ENOTSOCK, _, _) -> ()); continue k (Flow.of_fd fd :> Eio_unix.socket) ) | Eio_unix.Private.Socketpair (sw, domain, ty, protocol) -> Some (fun k -> diff --git a/lib_eio_windows/low_level.ml b/lib_eio_windows/low_level.ml index 7e35d7b88..e52bbb20f 100755 --- a/lib_eio_windows/low_level.ml +++ b/lib_eio_windows/low_level.ml @@ -162,12 +162,6 @@ module Open_flags = struct let cloexec = Config.o_noinherit let creat = Config.o_creat let excl = Config.o_excl - (* let directory = Config.o_directory - let dsync = Config.o_dsync - let noctty = Config.o_noctty - let nofollow = Config.o_nofollow *) - (* let nonblock = Config.o_nonblock *) - (* let sync = Config.o_sync *) let trunc = Config.o_trunc let empty = 0 diff --git a/lib_eio_windows/net.ml b/lib_eio_windows/net.ml index 27921307d..4f771ef4f 100755 --- a/lib_eio_windows/net.ml +++ b/lib_eio_windows/net.ml @@ -82,7 +82,7 @@ let getaddrinfo ~service node = aux () let listen ~reuse_addr ~reuse_port ~backlog ~sw (listen_addr : Eio.Net.Sockaddr.stream) = - let socket_type, addr = + let socket_type, addr, is_unix_socket = match listen_addr with | `Unix path -> if reuse_addr then ( @@ -92,10 +92,10 @@ let listen ~reuse_addr ~reuse_port ~backlog ~sw (listen_addr : Eio.Net.Sockaddr. | exception Unix.Unix_error (Unix.ENOENT, _, _) -> () | exception Unix.Unix_error (code, name, arg) -> raise @@ Err.wrap code name arg ); - Unix.SOCK_STREAM, Unix.ADDR_UNIX path + Unix.SOCK_STREAM, Unix.ADDR_UNIX path, true | `Tcp (host, port) -> let host = Eio_unix.Ipaddr.to_unix host in - Unix.SOCK_STREAM, Unix.ADDR_INET (host, port) + Unix.SOCK_STREAM, Unix.ADDR_INET (host, port), false in let sock = Low_level.socket ~sw (socket_domain_of listen_addr) socket_type 0 in (* For Unix domain sockets, remove the path when done (except for abstract sockets). *) @@ -107,12 +107,14 @@ let listen ~reuse_addr ~reuse_port ~backlog ~sw (listen_addr : Eio.Net.Sockaddr. Switch.null_hook in Fd.use_exn "listen" sock (fun fd -> - if reuse_addr then + (* REUSEADDR cannot be set on a Windows UNIX domain socket, + otherwise the Unix.bind will fail! *) + if not is_unix_socket && reuse_addr then Unix.setsockopt fd Unix.SO_REUSEADDR true; if reuse_port then Unix.setsockopt fd Unix.SO_REUSEPORT true; Unix.bind fd addr; - Unix.listen fd backlog; + Unix.listen fd backlog ); listening_socket ~hook sock diff --git a/lib_eio_windows/test/test.ml b/lib_eio_windows/test/test.ml index eac63960a..db699ab61 100755 --- a/lib_eio_windows/test/test.ml +++ b/lib_eio_windows/test/test.ml @@ -12,56 +12,24 @@ module Timeout = struct ] end -module Net = struct - open Eio.Std - - let read_all flow = - let b = Buffer.create 100 in - Eio.Flow.copy flow (Eio.Flow.buffer_sink b); - Buffer.contents b - - let run_client ~sw ~net ~addr = - traceln "Connecting to server..."; - let flow = Eio.Net.connect ~sw net addr in - Eio.traceln "connected"; - Eio.Flow.copy_string "Hello from client" flow; - Eio.Flow.shutdown flow `Send; - let msg = read_all flow in - msg - - let run_server ~sw msg socket = - Eio.Net.accept_fork socket ~sw (fun flow _addr -> - traceln "Server accepted connection from client"; - Fun.protect (fun () -> - let msg = read_all flow in - traceln "Server received: %S" msg - ) ~finally:(fun () -> Eio.Flow.copy_string msg flow) - ) - ~on_error:(function - | ex -> traceln "Error handling connection: %s" (Printexc.to_string ex) - ) - - let test_client_server env () = - Eio.Switch.run @@ fun sw -> - let addr = `Tcp (Eio.Net.Ipaddr.V4.loopback, 8081) in - let server = Eio.Net.listen env#net ~sw ~reuse_addr:true ~backlog:5 addr in - let msg = "From the server" in - Fiber.both - (fun () -> run_server ~sw msg server) - (fun () -> - let client_msg = run_client ~sw ~net:env#net ~addr in - Alcotest.(check string) "same message" msg client_msg - ) - +module Random = struct + let test_random env () = + let src = Eio.Stdenv.secure_random env in + let b1 = Cstruct.create 8 in + let b2 = Cstruct.create 8 in + Eio.Flow.read_exact src b1; + Eio.Flow.read_exact src b2; + Alcotest.(check bool) "different random" (not (Cstruct.equal b1 b2)) true let tests env = [ - "server-client", `Quick, test_client_server env + "different", `Quick, test_random env ] end let () = Eio_windows.run @@ fun env -> Alcotest.run "eio_windows" [ - "net", Net.tests env; - "timeout", Timeout.tests env + "net", Test_net.tests env; + "timeout", Timeout.tests env; + "random", Random.tests env ] \ No newline at end of file diff --git a/lib_eio_windows/test/test_net.ml b/lib_eio_windows/test/test_net.ml new file mode 100755 index 000000000..0877c9f0a --- /dev/null +++ b/lib_eio_windows/test/test_net.ml @@ -0,0 +1,118 @@ +open Eio.Std + +let read_all flow = + let b = Buffer.create 100 in + Eio.Flow.copy flow (Eio.Flow.buffer_sink b); + Buffer.contents b + +let run_client ~sw ~net ~addr = + traceln "Connecting to server..."; + let flow = Eio.Net.connect ~sw net addr in + Eio.traceln "connected"; + Eio.Flow.copy_string "Hello from client" flow; + Eio.Flow.shutdown flow `Send; + let msg = read_all flow in + msg + +let run_server ~sw msg socket = + Eio.Net.accept_fork socket ~sw (fun flow _addr -> + traceln "Server accepted connection from client"; + Fun.protect (fun () -> + let msg = read_all flow in + traceln "Server received: %S" msg + ) ~finally:(fun () -> Eio.Flow.copy_string msg flow) + ) + ~on_error:(function + | ex -> traceln "Error handling connection: %s" (Printexc.to_string ex) + ) + +let test_client_server env addr () = + Eio.Switch.run @@ fun sw -> + let server = Eio.Net.listen env#net ~sw ~reuse_addr:true ~backlog:5 addr in + let msg = "From the server" in + Fiber.both + (fun () -> run_server ~sw msg server) + (fun () -> + let client_msg = run_client ~sw ~net:env#net ~addr in + Alcotest.(check string) "same message" msg client_msg + ) + +let run_dgram addr ~net sw msg = + let e1 = `Udp (addr, 8081) in + let e2 = `Udp (addr, 8082) in + let listening_socket = Eio.Net.datagram_socket ~sw net e2 in + Fiber.both + (fun () -> + let buf = Cstruct.create 20 in + traceln "Waiting to receive data on %a" Eio.Net.Sockaddr.pp e2; + let addr, recv = Eio.Net.recv listening_socket buf in + traceln "Received message from %a" + Eio.Net.Sockaddr.pp addr; + Alcotest.(check string) "same udp msg" msg (Cstruct.(to_string (sub buf 0 recv))) + ) + (fun () -> + let e = Eio.Net.datagram_socket ~sw net e1 in + traceln "Sending data from %a to %a" Eio.Net.Sockaddr.pp e1 Eio.Net.Sockaddr.pp e2; + Eio.Net.send e e2 (Cstruct.of_string msg)) + +let test_udp env addr () = + Eio.Switch.run @@ fun sw -> + run_dgram addr ~net:env#net sw "UDP on Windows" + +let test_fd env addr () = + Eio.Switch.run @@ fun sw -> + let addr = `Tcp (addr, 8081) in + let server = Eio.Net.listen env#net ~sw ~reuse_addr:true ~backlog:5 addr in + Alcotest.(check bool) "Listening socket has Unix FD" (Eio_unix.Resource.fd_opt server <> None) true; + let have_client, have_server = + Fiber.pair + (fun () -> + let flow = Eio.Net.connect ~sw env#net addr in + (Eio_unix.Resource.fd_opt flow <> None) + ) + (fun () -> + let flow, _addr = Eio.Net.accept ~sw server in + (Eio_unix.Resource.fd_opt flow <> None) + ) + in + Alcotest.(check bool) "Client-side socket has Unix FD" have_client true; + Alcotest.(check bool) "Server-side socket has Unix FD" have_server true + +let test_wrap_socket pipe_or_socketpair () = + Switch.run @@ fun sw -> + let r, w = + match pipe_or_socketpair with + | `Pipe -> Unix.pipe () + | `Socketpair -> Unix.socketpair Unix.PF_UNIX Unix.SOCK_STREAM 0 + in + let source = (Eio_unix.import_socket_stream ~sw ~close_unix:true r :> Eio.Flow.source) in + let sink = (Eio_unix.import_socket_stream ~sw ~close_unix:true w :> Eio.Flow.sink) in + let msg = "Hello" in + Fiber.both + (fun () -> Eio.Flow.copy_string (msg ^ "\n") sink) + (fun () -> + let b = Eio.Buf_read.of_flow source ~max_size:1000 in + Alcotest.(check string) "same message" (Eio.Buf_read.line b) msg + ) + +let test_eio_socketpair () = + Switch.run @@ fun sw -> + let a, b = Eio_unix.socketpair ~sw () in + ignore (Eio_unix.Resource.fd a : Eio_unix.Fd.t); + ignore (Eio_unix.Resource.fd b : Eio_unix.Fd.t); + Eio.Flow.copy_string "foo" a; + Eio.Flow.close a; + let msg = Eio.Buf_read.of_flow b ~max_size:10 |> Eio.Buf_read.take_all in + Alcotest.(check string) "same messagw" "foo" msg + +let tests env = [ + "tcp-ip4", `Quick, test_client_server env (`Tcp (Eio.Net.Ipaddr.V4.loopback, 8081)); + "tcp-ip6", `Quick, test_client_server env (`Tcp (Eio.Net.Ipaddr.V6.loopback, 8081)); + "unix", `Quick, test_client_server env (`Unix "eio-test.sock"); + "udp-ip4", `Quick, test_udp env Eio.Net.Ipaddr.V4.loopback; + "udp-ip6", `Quick, test_udp env Eio.Net.Ipaddr.V6.loopback; + "fds", `Quick, test_fd env Eio.Net.Ipaddr.V4.loopback; + "wrap-pipe", `Quick, test_wrap_socket `Pipe; + "wrap-socketpair", `Quick, test_wrap_socket `Socketpair; + "eio-socketpair", `Quick, test_eio_socketpair +] \ No newline at end of file