Skip to content

Commit 7093825

Browse files
committed
add more linux-specific TCP sockopts
1 parent 515344f commit 7093825

File tree

5 files changed

+158
-0
lines changed

5 files changed

+158
-0
lines changed

lib_eio_linux/eio_stubs.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <fcntl.h>
1616
#include <signal.h>
1717
#include <unistd.h>
18+
#include <string.h>
1819
#include <sys/socket.h>
1920
#include <netinet/tcp.h>
2021

@@ -258,3 +259,26 @@ CAMLprim value caml_eio_sockopt_int_get(value v_fd, value v_level, value v_optio
258259
if (ret == -1) uerror("getsockopt", Nothing);
259260
CAMLreturn(Val_int(val));
260261
}
262+
263+
/* Socket option stubs for string options (e.g. TCP_CONGESTION) */
264+
CAMLprim value caml_eio_sockopt_string_set(value v_fd, value v_level, value v_option, value v_val) {
265+
CAMLparam4(v_fd, v_level, v_option, v_val);
266+
const char *str = String_val(v_val);
267+
int ret = setsockopt(Int_val(v_fd), Int_val(v_level), Int_val(v_option),
268+
str, strlen(str) + 1);
269+
if (ret == -1) uerror("setsockopt", Nothing);
270+
CAMLreturn(Val_unit);
271+
}
272+
273+
CAMLprim value caml_eio_sockopt_string_get(value v_fd, value v_level, value v_option) {
274+
CAMLparam3(v_fd, v_level, v_option);
275+
CAMLlocal1(v_result);
276+
char buffer[256];
277+
socklen_t len = sizeof(buffer);
278+
int ret = getsockopt(Int_val(v_fd), Int_val(v_level), Int_val(v_option),
279+
buffer, &len);
280+
if (ret == -1) uerror("getsockopt", Nothing);
281+
buffer[len < sizeof(buffer) ? len : sizeof(buffer) - 1] = '\0';
282+
v_result = caml_copy_string(buffer);
283+
CAMLreturn(v_result);
284+
}

lib_eio_linux/low_level.ml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,15 +629,21 @@ module Process = struct
629629
end
630630

631631
module Sockopt = struct
632+
let tcp_maxseg = 2 (* TCP_MAXSEG from netinet/tcp.h *)
632633
let tcp_cork = 3 (* TCP_CORK from netinet/tcp.h *)
633634
let tcp_keepidle = 4 (* TCP_KEEPIDLE *)
634635
let tcp_keepintvl = 5 (* TCP_KEEPINTVL *)
635636
let tcp_keepcnt = 6 (* TCP_KEEPCNT *)
637+
let tcp_linger2 = 8 (* TCP_LINGER2 *)
638+
let tcp_defer_accept = 9 (* TCP_DEFER_ACCEPT *)
639+
let tcp_congestion = 13 (* TCP_CONGESTION *)
636640
let tcp_user_timeout = 18 (* TCP_USER_TIMEOUT *)
637641
let ipproto_tcp = 6 (* IPPROTO_TCP *)
638642

639643
external setsockopt_int : Unix.file_descr -> int -> int -> int -> unit = "caml_eio_sockopt_int_set"
640644
external getsockopt_int : Unix.file_descr -> int -> int -> int = "caml_eio_sockopt_int_get"
645+
external setsockopt_string : Unix.file_descr -> int -> int -> string -> unit = "caml_eio_sockopt_string_set"
646+
external getsockopt_string : Unix.file_descr -> int -> int -> string = "caml_eio_sockopt_string_get"
641647

642648
(* Define Linux-specific socket options as extensions of Eio.Net.Sockopt.t *)
643649
type _ Eio.Net.Sockopt.t +=
@@ -646,6 +652,10 @@ module Sockopt = struct
646652
| TCP_KEEPINTVL : int Eio.Net.Sockopt.t
647653
| TCP_KEEPCNT : int Eio.Net.Sockopt.t
648654
| TCP_USER_TIMEOUT : int Eio.Net.Sockopt.t
655+
| TCP_MAXSEG : int Eio.Net.Sockopt.t
656+
| TCP_LINGER2 : int Eio.Net.Sockopt.t
657+
| TCP_DEFER_ACCEPT : int Eio.Net.Sockopt.t
658+
| TCP_CONGESTION : string Eio.Net.Sockopt.t
649659

650660
let set : type a. Fd.t -> a Eio.Net.Sockopt.t -> a -> unit = fun fd opt v ->
651661
match opt with
@@ -664,6 +674,18 @@ module Sockopt = struct
664674
| TCP_USER_TIMEOUT ->
665675
Fd.use_exn "setsockopt" fd (fun unix_fd ->
666676
setsockopt_int unix_fd ipproto_tcp tcp_user_timeout v)
677+
| TCP_MAXSEG ->
678+
Fd.use_exn "setsockopt" fd (fun unix_fd ->
679+
setsockopt_int unix_fd ipproto_tcp tcp_maxseg v)
680+
| TCP_LINGER2 ->
681+
Fd.use_exn "setsockopt" fd (fun unix_fd ->
682+
setsockopt_int unix_fd ipproto_tcp tcp_linger2 v)
683+
| TCP_DEFER_ACCEPT ->
684+
Fd.use_exn "setsockopt" fd (fun unix_fd ->
685+
setsockopt_int unix_fd ipproto_tcp tcp_defer_accept v)
686+
| TCP_CONGESTION ->
687+
Fd.use_exn "setsockopt" fd (fun unix_fd ->
688+
setsockopt_string unix_fd ipproto_tcp tcp_congestion v)
667689
| _ -> Eio_unix.Net.Sockopt.set fd opt v
668690

669691
let get : type a. Fd.t -> a Eio.Net.Sockopt.t -> a = fun fd opt ->
@@ -683,5 +705,17 @@ module Sockopt = struct
683705
| TCP_USER_TIMEOUT ->
684706
Fd.use_exn "getsockopt" fd (fun unix_fd ->
685707
getsockopt_int unix_fd ipproto_tcp tcp_user_timeout)
708+
| TCP_MAXSEG ->
709+
Fd.use_exn "getsockopt" fd (fun unix_fd ->
710+
getsockopt_int unix_fd ipproto_tcp tcp_maxseg)
711+
| TCP_LINGER2 ->
712+
Fd.use_exn "getsockopt" fd (fun unix_fd ->
713+
getsockopt_int unix_fd ipproto_tcp tcp_linger2)
714+
| TCP_DEFER_ACCEPT ->
715+
Fd.use_exn "getsockopt" fd (fun unix_fd ->
716+
getsockopt_int unix_fd ipproto_tcp tcp_defer_accept)
717+
| TCP_CONGESTION ->
718+
Fd.use_exn "getsockopt" fd (fun unix_fd ->
719+
getsockopt_string unix_fd ipproto_tcp tcp_congestion)
686720
| _ -> Eio_unix.Net.Sockopt.get fd opt
687721
end

lib_eio_linux/low_level.mli

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,18 @@ module Sockopt : sig
216216
| TCP_USER_TIMEOUT : int Eio.Net.Sockopt.t
217217
(** Maximum time (in milliseconds) that transmitted data may remain unacknowledged
218218
before TCP will forcibly close the connection. *)
219+
| TCP_MAXSEG : int Eio.Net.Sockopt.t
220+
(** The maximum segment size for outgoing TCP packets. If set before connection
221+
establishment, it also changes the MSS value announced to the other end. *)
222+
| TCP_LINGER2 : int Eio.Net.Sockopt.t
223+
(** The lifetime of orphaned FIN_WAIT2 state sockets (in seconds).
224+
Not to be confused with SO_LINGER. *)
225+
| TCP_DEFER_ACCEPT : int Eio.Net.Sockopt.t
226+
(** Allow a listener to be awakened only when data arrives on the socket.
227+
Value is the maximum time in seconds to wait for data. *)
228+
| TCP_CONGESTION : string Eio.Net.Sockopt.t
229+
(** Set the TCP congestion control algorithm to be used (e.g., "cubic", "reno").
230+
Unprivileged processes are restricted to algorithms in tcp_allowed_congestion_control. *)
219231

220232
val set : fd -> 'a Eio.Net.Sockopt.t -> 'a -> unit
221233
(** [set fd opt v] sets socket option [opt] to value [v] on file descriptor [fd]. *)

lib_eio_linux/tests/network.md

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Linux-specific networking tests
2+
3+
```ocaml
4+
# #require "eio_linux";;
5+
# #require "eio.unix";;
6+
# open Eio.Std;;
7+
# let addr = `Tcp (Eio.Net.Ipaddr.V4.loopback, 0);;
8+
val addr : [> `Tcp of [> `V4 ] Eio.Net.Ipaddr.t * int ] =
9+
`Tcp ("\127\000\000\001", 0)
10+
```
11+
12+
## Linux-specific TCP socket options
13+
14+
Test Linux-specific TCP socket options:
15+
16+
```ocaml
17+
# Eio_linux.run @@ fun env ->
18+
Switch.run @@ fun sw ->
19+
let net = env#net in
20+
let listen_sock = Eio.Net.listen net ~sw ~reuse_addr:true ~backlog:5 addr in
21+
let listen_fd = Eio_unix.Net.fd listen_sock in
22+
let listening_addr = Eio.Net.listening_addr listen_sock in
23+
let module ELS = Eio_linux.Low_level.Sockopt in
24+
(* Test TCP_DEFER_ACCEPT on listening socket *)
25+
ELS.set listen_fd ELS.TCP_DEFER_ACCEPT 5;
26+
let defer = ELS.get listen_fd ELS.TCP_DEFER_ACCEPT in
27+
traceln "TCP_DEFER_ACCEPT on listening socket: %s" (if defer > 0 then "enabled" else "disabled");
28+
29+
(* Create a TCP connection for other tests *)
30+
Eio.Fiber.both
31+
(fun () ->
32+
let client = Eio.Net.connect ~sw net listening_addr in
33+
let fd = Eio_unix.Net.fd client in
34+
35+
(* Test TCP_CORK *)
36+
ELS.set fd ELS.TCP_CORK true;
37+
let cork = ELS.get fd ELS.TCP_CORK in
38+
traceln "TCP_CORK enabled: %b" cork;
39+
ELS.set fd ELS.TCP_CORK false;
40+
let cork = ELS.get fd ELS.TCP_CORK in
41+
traceln "TCP_CORK disabled: %b" cork;
42+
43+
(* Test TCP_KEEPIDLE, TCP_KEEPINTVL, TCP_KEEPCNT *)
44+
ELS.set fd ELS.TCP_KEEPIDLE 60;
45+
let keepidle = ELS.get fd ELS.TCP_KEEPIDLE in
46+
traceln "TCP_KEEPIDLE: %d seconds" keepidle;
47+
48+
ELS.set fd ELS.TCP_KEEPINTVL 10;
49+
let keepintvl = ELS.get fd ELS.TCP_KEEPINTVL in
50+
traceln "TCP_KEEPINTVL: %d seconds" keepintvl;
51+
52+
ELS.set fd ELS.TCP_KEEPCNT 5;
53+
let keepcnt = ELS.get fd ELS.TCP_KEEPCNT in
54+
traceln "TCP_KEEPCNT: %d probes" keepcnt;
55+
56+
(* Test TCP_LINGER2 *)
57+
ELS.set fd ELS.TCP_LINGER2 110;
58+
let linger2 = ELS.get fd ELS.TCP_LINGER2 in
59+
traceln "TCP_LINGER2: %d seconds" linger2;
60+
61+
(* Test TCP_CONGESTION *)
62+
let congestion = ELS.get fd ELS.TCP_CONGESTION in
63+
traceln "Current TCP_CONGESTION algorithm: %s" congestion;
64+
65+
(* Try to set cubic if available - may fail based on system config *)
66+
(try
67+
ELS.set fd ELS.TCP_CONGESTION "cubic";
68+
let new_congestion = ELS.get fd ELS.TCP_CONGESTION in
69+
traceln "Successfully set TCP_CONGESTION to: %s" new_congestion
70+
with _ ->
71+
traceln "Could not change TCP_CONGESTION (normal for unprivileged)");
72+
73+
Eio.Flow.close client)
74+
(fun () ->
75+
let conn, _addr = Eio.Net.accept ~sw listen_sock in
76+
Eio.Flow.close conn);;
77+
+TCP_DEFER_ACCEPT on listening socket: enabled
78+
+TCP_CORK enabled: true
79+
+TCP_CORK disabled: false
80+
+TCP_KEEPIDLE: 60 seconds
81+
+TCP_KEEPINTVL: 10 seconds
82+
+TCP_KEEPCNT: 5 probes
83+
+TCP_LINGER2: 110 seconds
84+
+Current TCP_CONGESTION algorithm: cubic
85+
+Successfully set TCP_CONGESTION to: cubic
86+
- : unit = ()
87+
```

tests/network.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,7 @@ Error handling for read-only options:
652652
- : unit = ()
653653
```
654654

655+
655656
## Getaddrinfo
656657

657658
```ocaml

0 commit comments

Comments
 (0)