Mnet.TCPTCP connections for unikernels.
This module provides a socket-like API for TCP connections, backed by the pure utcp implementation. It supports both client (outgoing) and server (incoming) connections over IPv4 and IPv6.
Errors are reported via exceptions rather than result types, which makes the API closer to Unix. The three exceptions Net_unreach, Closed_by_peer, and Connection_refused cover the main failure modes.
Two reading strategies are available:
get returns data directly from the Ethernet frame. This is the fast path when you can process data in-place.read copies data into a user-supplied buffer. An internal buffer handles overflow when the frame payload exceeds the buffer size.TCP runs a background daemon (see create) that processes incoming segments, retransmissions, and timers. Most operations (read, accept, connect) may suspend the current Miou task. Some operations (close, write_without_interruption) are uninterruptible: they complete without yielding to the scheduler, making them safe for use in finalizers.
let listen = Mnet.TCP.listen tcp 9000 in
let flow = Mnet.TCP.accept tcp listen in
let buf = Bytes.create 4096 in
let rec loop () =
let len = Mnet.TCP.read flow buf in
if len > 0 then begin
Mnet.TCP.write flow (Bytes.sub_string buf 0 len);
loop ()
end
in
(try loop () with Mnet.TCP.Closed_by_peer -> ());
Mnet.TCP.close flowlet flow = Mnet.TCP.connect tcp (Ipaddr.V4 server, 9000) in
Mnet.TCP.write flow "Hello!";
Mnet.TCP.shutdown flow `write;
let buf = Bytes.create 4096 in
let len = Mnet.TCP.read flow buf in
(* process response *)
Mnet.TCP.close flowRaised when the destination network is unreachable (no route found or ARPv4/NDPv6 resolution failed).
Raised on write operations when the remote end has closed its side of the connection.
Raised by connect or write operations when the remote end actively refuses the connection (e.g. RST packet received).
A background task that manages TCP timers and incoming segment processing. Must be terminated with kill when no longer needed.
handler state src dst payload processes an incoming TCP segment. This function is installed by Mnet.stack as the protocol handler for TCP segments (protocol number 6) received by the IPv4 and IPv6 layers. Users normally do not need to call this directly.
val kill : daemon -> unitkill daemon allows you to terminate the background task launched by create.
connect state ipaddr port is a Solo5 friendly Unix.connect.
get flow allows reading from a given flow without involving a temporary buffer. In other words, the data returned is that from the Ethernet frame.
If data exists in the internal buffer, get flushes it and prepends this content to what we obtain from the Ethernet frames.
val read : flow -> ?off:int -> ?len:int -> bytes -> intread flow buf ~off ~len reads up to len bytes (defaults to Bytes.length buf - off from the given connection flow, storing them in byte sequence buf, starting at position off in buf (defaults to 0). It returns the actual number of characters read, between 0 and len (inclusive).
NOTE: In order to be able to deliver data without loss despite the fixed size of the given buffer buf, an internal buffer is used to store the overflow and ensure that it is delivered to the next read call. In other words, read is buffered, which involves copying. If, for performance reasons, you would like to avoid copying, we recommend using get.
val really_read : flow -> ?off:int -> ?len:int -> bytes -> unitreally_read flow buf ~off ~len reads len bytes (defaults to Bytes.length buf - off) from the given connection flow, storing them in byte sequence buf, starting at position off in buf (defaults to 0). If len = 0, really_read does nothing.
val write : flow -> ?off:int -> ?len:int -> string -> unitwrite fd str ~off ~len writes len bytes (defaults to String.length str - off) from byte sequence buf, starting at offset off (defaults to 0), to the given connection flow.
NOTE: This function can potentially emit one or more effects and, by extension, give Miou the opportunity to reschedule. Furthermore, it is not advisable to use this function with finalisers such as those required by the Miou.Ownership.create function. For this, it is preferable to use write_directly.
val write_without_interruption : flow -> ?off:int -> ?len:int -> string -> unitwrite_without_interruption writes len bytes (defaults to String.length str - off) from byte sequence buf, starting at offset off (defaults to 0), to the given connection flow.
NOTE: This function does not perform any effects and can not be interrupted. If the user wants to emit something from an abnormal termination, this function can be useful.
val close : flow -> unitclose flow closes properly the given flow.
NOTE: close has the particularity of being effective and uninterrupted. That is to say, this function does not give Miou the opportunity to perform another task. Furthermore, it is possible to use close in the finalisation of a resource (with Miou.Ownership.create).
let handler flow =
let finally = Mnet.TCP.close in
let r = Miou.Ownership.create ~finally flow in
Miou.Ownership.own r;
...
Mnet.TCP.close flow;
Miou.Ownership.disown rval shutdown : flow -> [ `read | `write | `read_write ] -> unitshutdown flow mode shutdowns a TCP connection. `write as second argument causes reads on the other end of the connection to return an end-of-file condition. `read causes writes on the other end of the connection to return a Closed_by_peer.
peers flow returns (local, remote) where each element is an (address, port) pair identifying the two endpoints of the connection. This is analogous to Unix.getsockname and Unix.getpeername.
val tags : flow -> Logs.Tag.settags flow returns logging tags for the given flow. These tags contain connection metadata (local and remote addresses/ports) and can be attached to Logs messages for structured debugging output.
listen state port prepares port for receiving incoming TCP connection requests. This is analogous to Unix.listen. The returned handle is passed to accept to wait for clients.
accept state listen blocks the current Miou task until a client connects to the port associated with listen, then returns a flow connected to that client. This is analogous to Unix.accept.
To handle multiple clients concurrently, spawn each accepted flow in a separate Miou task:
let clean_up orphans = match Miou.care orphans with
| None | Some None -> ()
| Some (Some prm) -> Miou.await_exn prm; clean_up orphans
let listen = Mnet.TCP.listen tcp 9000 in
let rec loop orphans =
clean_up orphans;
let flow = Mnet.TCP.accept tcp listen in
let _ = Miou.async ~orphans @@ fun () -> handler flow in
loop orphans
in
loop (Miou.orphans ())