Skip to content

Commit

Permalink
hostnet: convert Uwt errors to Unix_errors, pretty-print "expected" c…
Browse files Browse the repository at this point in the history
…ases

Previously we lacked a function to convert from Uwt.error to Unix.error.
Happily uwt.0.0.3 contains such a useful error converting function, so
we use it.

When a port forward is requested, use `Lwt.catch` to handle the exception
at the top-level and pretty-print the error message for the expected cases

- EADDRINUSE: the port was already 'allocated'
- EADDRNOTAVAIL: the interface IP was not found (probably it exists in a
  VM but not on the host)
- EPERM: insufficient privileges to bind a privileged port

The pretty-printing is intended to match the output of `docker` on
regular Linux.

This is related to [docker/compose#3277]

Signed-off-by: David Scott <dave.scott@docker.com>
  • Loading branch information
djs55 committed Jul 12, 2016
1 parent 74d95ce commit 81137c8
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 32 deletions.
73 changes: 49 additions & 24 deletions src/hostnet/lib/forward.ml
Expand Up @@ -305,30 +305,55 @@ let start_udp_proxy description vsock_path_var remote_port server =
let start vsock_path_var t =
match t.local with
| `Tcp (local_ip, local_port) ->
check_bind_allowed (Ipaddr.V4 local_ip)
>>= fun () ->
Socket.Stream.Tcp.bind (local_ip, local_port)
>>= fun server ->
t.server <- Some (`Tcp server);
(* Resolve the local port yet (the fds are already bound) *)
t.local <- ( match t.local with
| `Tcp (local_ip, 0) ->
let _, port = Socket.Stream.Tcp.getsockname server in
`Tcp (local_ip, port)
| _ ->
t.local );
start_tcp_proxy (to_string t) vsock_path_var t.remote_port server
>>= fun () ->
Lwt.return (Result.Ok t)
| `Udp (local_ip, local_port) ->
check_bind_allowed local_ip
>>= fun () ->
Socket.Datagram.Udp.bind (local_ip, local_port)
>>= fun server ->
t.server <- Some (`Udp server);
start_udp_proxy (to_string t) vsock_path_var t.remote_port server
>>= fun () ->
Lwt.return (Result.Ok t)
Lwt.catch
(fun () ->
check_bind_allowed (Ipaddr.V4 local_ip)
>>= fun () ->
Socket.Stream.Tcp.bind (local_ip, local_port)
>>= fun server ->
t.server <- Some (`Tcp server);
(* Resolve the local port yet (the fds are already bound) *)
t.local <- ( match t.local with
| `Tcp (local_ip, 0) ->
let _, port = Socket.Stream.Tcp.getsockname server in
`Tcp (local_ip, port)
| _ ->
t.local );
start_tcp_proxy (to_string t) vsock_path_var t.remote_port server
>>= fun () ->
Lwt.return (Result.Ok t)
) (function
| Unix.Unix_error(Unix.EADDRINUSE, _, _) ->
Lwt.return (Result.Error (`Msg (Printf.sprintf "Bind for %s:%d failed: port is already allocated" (Ipaddr.V4.to_string local_ip) local_port)))
| Unix.Unix_error(Unix.EADDRNOTAVAIL, _, _) ->
Lwt.return (Result.Error (`Msg (Printf.sprintf "listen tcp %s:%d: bind: cannot assign requested address" (Ipaddr.V4.to_string local_ip) local_port)))
| Unix.Unix_error(Unix.EPERM, _, _) ->
Lwt.return (Result.Error (`Msg (Printf.sprintf "Bind for %s:%d failed: permission denied" (Ipaddr.V4.to_string local_ip) local_port)))
| e ->
Lwt.return (Result.Error (`Msg (Printf.sprintf "Bind for %s:%d: unexpected error %s" (Ipaddr.V4.to_string local_ip) local_port (Printexc.to_string e))))
)
| `Udp (local_ip, local_port) ->
Lwt.catch
(fun () ->
check_bind_allowed local_ip
>>= fun () ->
Socket.Datagram.Udp.bind (local_ip, local_port)
>>= fun server ->
t.server <- Some (`Udp server);
start_udp_proxy (to_string t) vsock_path_var t.remote_port server
>>= fun () ->
Lwt.return (Result.Ok t)
) (function
| Unix.Unix_error(Unix.EADDRINUSE, _, _) ->
Lwt.return (Result.Error (`Msg (Printf.sprintf "Bind for %s:%d failed: port is already allocated" (Ipaddr.to_string local_ip) local_port)))
| Unix.Unix_error(Unix.EADDRNOTAVAIL, _, _) ->
Lwt.return (Result.Error (`Msg (Printf.sprintf "listen udp %s:%d: bind: cannot assign requested address" (Ipaddr.to_string local_ip) local_port)))
| Unix.Unix_error(Unix.EPERM, _, _) ->
Lwt.return (Result.Error (`Msg (Printf.sprintf "Bind for %s:%d failed: permission denied" (Ipaddr.to_string local_ip) local_port)))
| e ->
Lwt.return (Result.Error (`Msg (Printf.sprintf "Bind for %s:%d: unexpected error %s" (Ipaddr.to_string local_ip) local_port (Printexc.to_string e))))
)


let stop t =
Log.debug (fun f -> f "%s: closing listening socket" (to_string t));
Expand Down
19 changes: 11 additions & 8 deletions src/hostnet/lib/host_uwt.ml
Expand Up @@ -170,8 +170,9 @@ module Sockets = struct
let fd = Uwt.Udp.init () in
let result = Uwt.Udp.bind ~mode:[ Uwt.Udp.Reuse_addr ] fd ~addr:sockaddr () in
if not(Uwt.Int_result.is_ok result) then begin
Log.err (fun f -> f "Socket.Datagram.bind returned %s" (Uwt.strerror (Uwt.Int_result.to_error result)));
Lwt.fail (Uwt.Int_result.to_exn result)
let error = Uwt.Int_result.to_error result in
Log.err (fun f -> f "Socket.Udp.bind(%s, %d): %s" (Ipaddr.to_string ip) port (Uwt.strerror error));
Lwt.fail (Unix.Unix_error(Uwt.to_unix_error error, "bind", ""))
end else Lwt.return { fd; closed = false }

let of_bound_fd fd =
Expand Down Expand Up @@ -351,21 +352,23 @@ module Sockets = struct
let error = Uwt.Int_result.to_error result in
let msg = Printf.sprintf "Socket.Tcp.bind(%s, %d): %s" (Ipaddr.to_string ip) port (Uwt.strerror error) in
Log.err (fun f -> f "%s" msg);
if error = Uwt.EADDRINUSE
then Lwt.fail (Unix.Unix_error(Unix.EADDRINUSE, "bind", ""))
else Lwt.fail (Failure msg)
Lwt.fail (Unix.Unix_error(Uwt.to_unix_error error, "bind", ""))
end else Lwt.return fd

let bind (ip, port) =
bind_one (Ipaddr.V4 ip, port)
>>= fun fd ->
let local_port = match Uwt.Tcp.getsockname fd with
( match Uwt.Tcp.getsockname fd with
| Uwt.Ok sockaddr ->
begin match ip_port_of_sockaddr sockaddr with
| Some (_, local_port) -> local_port
| Some (_, local_port) -> Lwt.return local_port
| _ -> assert false
end
| _ -> assert false in
| Uwt.Error error ->
let msg = Printf.sprintf "Socket.Tcp.bind(%s, %d): %s" (Ipaddr.V4.to_string ip) port (Uwt.strerror error) in
Log.err (fun f -> f "%s" msg);
Lwt.fail (Unix.Unix_error(Uwt.to_unix_error error, "bind", "")) )
>>= fun local_port ->
(* On some systems localhost will resolve to ::1 first and this can
cause performance problems (particularly on Windows). Perform a
best-effort bind to the ::1 address. *)
Expand Down

0 comments on commit 81137c8

Please sign in to comment.