package utcp

  1. Overview
  2. Docs

Source file user.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
(* (c) 2019 Hannes Mehnert, all rights reserved *)
open State

let guard p e = if p then Ok () else Error e

let ( let* ) = Result.bind

let src = Logs.Src.create "tcp.user" ~doc:"TCP user"
module Log = (val Logs.src_log src : Logs.LOG)

let connect ~src ?src_port ~dst ~dst_port t now =
  let src_port = match src_port with
    | None -> Randomconv.int16 Mirage_crypto_rng.generate
    | Some p -> p
  in
  let id = src, src_port, dst, dst_port in
  Tracing.debug (fun m -> m "%a [%a] connect" Connection.pp id Mtime.pp now);
  let conn =
    let iss = Sequence.of_int32 (Randomconv.int32 Mirage_crypto_rng.generate) in
    let rcv_wnd = Params.so_rcvbuf in
    let advmss = Subr.tcp_mssopt id in
    let t_rttseg = Some (now, iss) in
    let control_block = {
      initial_cb with
      tt_rexmt = Subr.start_tt_rexmt_syn now 0 false initial_cb.t_rttinf ;
      tt_conn_est = Some (Timers.timer now () Params.tcptv_keep_init) ;
      snd_una = iss ;
      snd_nxt = Sequence.incr iss ;
      snd_max = Sequence.incr iss ;
      iss ;
      rcv_wnd ;
      request_r_scale = Some Params.scale ;
      rcv_adv = Sequence.of_int32 (Int32.of_int rcv_wnd) ; (* rcv_nxt is 0 anyways, this is void *)
      tf_rxwin0sent = (rcv_wnd = 0);
      t_advmss = advmss ;
      t_rttseg
    } in
    conn_state now t.mk_notify ~rcvbufsize:rcv_wnd ~sndbufsize:Params.so_sndbuf Syn_sent control_block
  in
  let _, _, seg = Segment.make_syn conn.control_block id in
  let connections =
    Log.debug (fun m -> m "%a active open %a" Connection.pp id (pp_conn_state now) conn);
    CM.add id conn t.connections
  in
  Stats.incr_active t.stats;
  { t with connections }, id, conn.rcv_notify, (src, dst, seg)

(* shutdown_1 and shutdown_3 *)
let shutdown t now id v =
  Tracing.debug (fun m ->
      let side = match v with
        | `read -> "read"
        | `write -> "write"
        | `read_write -> "readwrite"
      in
      m "%a [%a] shutdown_%s" Connection.pp id Mtime.pp now side);
  match CM.find_opt id t.connections with
  | None -> Error `Not_found
  | Some conn ->
    if conn.tcp_state = Established then
      let write = match v with `write | `read_write -> true | `read -> false
      and read = match v with `read | `read_write -> true | `write -> false
      in
      let cantsndmore = write || conn.cantsndmore
      and cantrcvmore = read || conn.cantrcvmore
      in
      let rcvq = if read then Rope.empty else conn.rcvq in
      let conn' =
        { conn with cantsndmore; cantrcvmore; rcvq }
      in
      let conn', out =
        (* if only shutdown read side, or we already closed something *)
        if conn.cantsndmore || v = `read then
          conn', []
        else
          Segment.tcp_output_perhaps now id conn'
      in
      Ok ({ t with connections = CM.add id conn' t.connections }, out)
    else
      Error (`Msg "not connected")

(* in real, this is shutdown `readwrite (close_2) - and we do this in any state *)
(* there's as well close_3 (the abortive close, i.e. send a RST) -- done when SO_LINGER = 0 *)
let close t now id =
  Tracing.debug (fun m -> m "%a [%a] close" Connection.pp id Mtime.pp now);
  match CM.find_opt id t.connections with
  | None -> Error `Not_found
  | Some conn ->
    (* see above, should deal with all states of conn *)
    let* () =
      guard (behind_established conn.tcp_state) (`Msg "not yet established")
    in
    let conn' =
      let cantsndmore = true and cantrcvmore = true and rcvq = Rope.empty in
      { conn with cantsndmore; cantrcvmore; rcvq }
    in
    (* if we've already been close()d, don't need to output anything *)
    let conn', out =
      if conn.cantsndmore then
        conn', []
      else
        Segment.tcp_output_perhaps now id conn'
    in
    Ok ({ t with connections = CM.add id conn' t.connections }, out)

let send t now id ?(off = 0) ?len buf =
  let len = match len with
    | Some len -> len
    | None -> String.length buf - off
  in
  Tracing.debug (fun m -> m "%a [%a] send %u %s" Connection.pp id Mtime.pp now
                    len (Base64.encode_string (String.sub buf off len)));
  match CM.find_opt id t.connections with
  | None -> Error `Not_found
  | Some conn ->
    let* () =
      guard (behind_established conn.tcp_state) (`Msg "not yet established")
    in
    let* () =
      guard (not conn.cantsndmore) (`Msg "cant write")
    in
    let space = max 0 (conn.sndbufsize - Rope.length conn.sndq) in
    let len = if space < len then space else len in
    let sndq = Rope.append conn.sndq ~off ~len buf in
    let conn' = { conn with sndq } in
    let conn', out = Segment.tcp_output_perhaps now id conn' in
    Ok ({ t with connections = CM.add id conn' t.connections }, len, conn'.snd_notify, out)

let force_enqueue t now id ?(off = 0) ?len buf =
  let len = match len with
    | Some len -> len
    | None -> String.length buf - off
  in
  Tracing.debug (fun m -> m "%a [%a] force enqueue %u %s" Connection.pp id Mtime.pp now
                    len (Base64.encode_string (String.sub buf off len)));
  match CM.find_opt id t.connections with
  | None -> Error `Not_found
  | Some conn ->
    let* () =
      guard (behind_established conn.tcp_state) (`Msg "not yet established")
    in
    let* () =
      guard (not conn.cantsndmore) (`Msg "cant write")
    in
    let space = max 0 (conn.sndbufsize - Rope.length conn.sndq) in
    if space < len then
      Log.warn (fun m -> m "%a enqueuing %u extra bytes" Connection.pp id
                   (len - space));
    let sndq = Rope.append conn.sndq ~off ~len buf in
    let conn' = { conn with sndq } in
    Ok ({ t with connections = CM.add id conn' t.connections })

let recv t now id =
  Tracing.debug (fun m -> m "%a [%a] receive" Connection.pp id Mtime.pp now);
  match CM.find_opt id t.connections with
  | None -> Error `Not_found
  | Some conn ->
    let* () =
      guard (behind_established conn.tcp_state) (`Msg "not yet connected")
    in
    let rcvq = Rope.to_strings conn.rcvq in
    let* () = guard (not (Rope.length conn.rcvq = 0 && conn.cantrcvmore)) `Eof in
    let conn' = { conn with rcvq = Rope.empty } in
    let conn', out = Segment.tcp_output_perhaps now id conn' in
    Ok ({ t with connections = CM.add id conn' t.connections }, rcvq, conn'.rcv_notify, out)