(* Lightweight thread library for Objective Caml * http://www.ocsigen.org/lwt * Program Relay * Copyright (C) 2011 Jérémie Dimino * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, with linking exceptions; * either version 2.1 of the License, or (at your option) any later * version. See COPYING file for details. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. *) (* Relay data from an address to another. *) open Lwt (* +-----------------------------------------------------------------+ | Relaying | +-----------------------------------------------------------------+ *) (* Write exactly [len] bytes from [buf] at [ofs]. *) let rec write_exactly fd buf ofs len = lwt n = Lwt_bytes.write fd buf ofs len in if n = len then (* Everything has been written, do nothing. *) return () else (* Write remaining data. *) write_exactly fd buf (ofs + n) (len - n) (* Copy continously data from [in_fd] to [out_fd]. *) let relay in_fd out_fd = (* Queue of data received but not yet written. *) let queue = Queue.create () in (* Condition used to signal the writer that some data are available. *) let cond = Lwt_condition.create () in (* Boolean which tells whether the input socket has been closed. *) let end_of_input = ref false in (* Write continously data received to [out_fd]. *) let rec loop_write () = if Queue.is_empty queue then if !end_of_input then (* End of input reached, exit. *) return () else (* There is no data pending, wait for some. *) lwt () = Lwt_condition.wait cond in loop_write () else let (buf, len) = Queue.take queue in lwt () = write_exactly out_fd buf 0 len in loop_write () in (* Start the writer. *) let writer = loop_write () in (* Read continously from [in_fd]. *) let rec loop_read () = let buf = Lwt_bytes.create 8192 in match_lwt Lwt_bytes.read in_fd buf 0 8192 with | 0 -> (* If we read nothing, this means that the connection has been closed. *) (* Mark the end of input has reached. *) end_of_input := true; (* Singal the writer in case it is waiting for data. *) Lwt_condition.signal cond (); (* Wait for it to terminate. *) writer | n -> (* Otherwise, send data to the writer. *) Queue.add (buf, n) queue; (* Singal the writer in case it is waiting for data. *) Lwt_condition.signal cond (); loop_read () in (* Wait for either the reader to terminate or the writer to fail. *) pick [writer; loop_read ()] (* +-----------------------------------------------------------------+ | Entry point | +-----------------------------------------------------------------+ *) let usage () = prerr_endline "usage: relay : :"; exit 2 (* Convert a string of the form ":" to an internet address object. *) let addr_of_string str = (* Split the host and the port. *) let idx = try String.index str ':' with Not_found -> usage () in let host = String.sub str 0 idx and port = String.sub str (idx + 1) (String.length str - idx - 1) in (* Parse the port. *) let port = try int_of_string port with Failure _ -> usage () in (* Request the address of the host. *) lwt entry = Lwt_unix.gethostbyname host in if Array.length entry.Unix.h_addr_list = 0 then begin Printf.eprintf "no address found for host %S\n" host; exit 1 end; return (Unix.ADDR_INET (entry.Unix.h_addr_list.(0), port)) lwt () = if Array.length Sys.argv <> 3 then usage (); try_lwt (* Resolve addresses. *) lwt src_addr = addr_of_string Sys.argv.(1) and dst_addr = addr_of_string Sys.argv.(2) in (* Initialize the listening address. *) let sock = Lwt_unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in Lwt_unix.setsockopt sock Unix.SO_REUSEADDR true; Lwt_unix.bind sock src_addr; Lwt_unix.listen sock 1024; ignore (Lwt_log.notice "waiting for connection"); (* Wait for a connection. *) lwt fd1, _ = Lwt_unix.accept sock in ignore (Lwt_log.notice "connection received, start relayling"); (* Closes the no-more used listening socket. *) lwt () = Lwt_unix.close sock in (* Connect to the destination port. *) let fd2 = Lwt_unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in lwt () = Lwt_unix.connect fd2 dst_addr in (* Start relaying. *) lwt () = pick [relay fd1 fd2; relay fd2 fd1] in ignore (Lwt_log.notice "done relayling"); return () with exn -> ignore (Lwt_log.error ~exn "something went wrong"); exit 1