github.com/ttpreport/gvisor-ligolo@v0.0.0-20240123134145-a858404967ba/pkg/tcpip/transport/tcpconntrack/tcp_conntrack.go (about) 1 // Copyright 2018 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package tcpconntrack implements a TCP connection tracking object. It allows 16 // users with access to a segment stream to figure out when a connection is 17 // established, reset, and closed (and in the last case, who closed first). 18 package tcpconntrack 19 20 import ( 21 "github.com/ttpreport/gvisor-ligolo/pkg/tcpip/header" 22 "github.com/ttpreport/gvisor-ligolo/pkg/tcpip/seqnum" 23 ) 24 25 // Result is returned when the state of a TCB is updated in response to a 26 // segment. 27 type Result int 28 29 const ( 30 // ResultDrop indicates that the segment should be dropped. 31 ResultDrop Result = iota 32 33 // ResultConnecting indicates that the connection remains in a 34 // connecting state. 35 ResultConnecting 36 37 // ResultAlive indicates that the connection remains alive (connected). 38 ResultAlive 39 40 // ResultReset indicates that the connection was reset. 41 ResultReset 42 43 // ResultClosedByResponder indicates that the connection was gracefully 44 // closed, and the reply stream was closed first. 45 ResultClosedByResponder 46 47 // ResultClosedByOriginator indicates that the connection was gracefully 48 // closed, and the original stream was closed first. 49 ResultClosedByOriginator 50 ) 51 52 // maxWindowShift is the maximum shift value of the per the windows scale 53 // option defined by RFC 1323. 54 const maxWindowShift = 14 55 56 // TCB is a TCP Control Block. It holds state necessary to keep track of a TCP 57 // connection and inform the caller when the connection has been closed. 58 type TCB struct { 59 reply stream 60 original stream 61 62 // State handlers. hdr is not guaranteed to contain bytes beyond the TCP 63 // header itself, i.e. it may not contain the payload. 64 handlerReply func(tcb *TCB, hdr header.TCP, dataLen int) Result 65 handlerOriginal func(tcb *TCB, hdr header.TCP, dataLen int) Result 66 67 // firstFin holds a pointer to the first stream to send a FIN. 68 firstFin *stream 69 70 // state is the current state of the stream. 71 state Result 72 } 73 74 // Init initializes the state of the TCB according to the initial SYN. 75 func (t *TCB) Init(initialSyn header.TCP, dataLen int) Result { 76 t.handlerReply = synSentStateReply 77 t.handlerOriginal = synSentStateOriginal 78 79 iss := seqnum.Value(initialSyn.SequenceNumber()) 80 t.original.una = iss 81 t.original.nxt = iss.Add(logicalLenSyn(initialSyn, dataLen)) 82 t.original.end = t.original.nxt 83 // TODO(gvisor.dev/issue/6734): Cache TCP options instead of re-parsing them. 84 // Because original and reply are streams, scale applies to the reply; it is 85 // the receive window in the reply direction. 86 t.reply.shiftCnt = header.ParseSynOptions(initialSyn.Options(), false /* isAck */).WS 87 88 // Even though "end" is a sequence number, we don't know the initial 89 // receive sequence number yet, so we store the window size until we get 90 // a SYN from the server. 91 t.reply.una = 0 92 t.reply.nxt = 0 93 t.reply.end = seqnum.Value(initialSyn.WindowSize()) 94 t.state = ResultConnecting 95 return t.state 96 } 97 98 // UpdateStateReply updates the state of the TCB based on the supplied reply 99 // segment. 100 func (t *TCB) UpdateStateReply(tcp header.TCP, dataLen int) Result { 101 st := t.handlerReply(t, tcp, dataLen) 102 if st != ResultDrop { 103 t.state = st 104 } 105 return st 106 } 107 108 // UpdateStateOriginal updates the state of the TCB based on the supplied 109 // original segment. 110 func (t *TCB) UpdateStateOriginal(tcp header.TCP, dataLen int) Result { 111 st := t.handlerOriginal(t, tcp, dataLen) 112 if st != ResultDrop { 113 t.state = st 114 } 115 return st 116 } 117 118 // State returns the current state of the TCB. 119 func (t *TCB) State() Result { 120 return t.state 121 } 122 123 // IsAlive returns true as long as the connection is established(Alive) 124 // or connecting state. 125 func (t *TCB) IsAlive() bool { 126 return !t.reply.rstSeen && !t.original.rstSeen && (!t.reply.closed() || !t.original.closed()) 127 } 128 129 // OriginalSendSequenceNumber returns the snd.NXT for the original stream. 130 func (t *TCB) OriginalSendSequenceNumber() seqnum.Value { 131 return t.original.nxt 132 } 133 134 // ReplySendSequenceNumber returns the snd.NXT for the reply stream. 135 func (t *TCB) ReplySendSequenceNumber() seqnum.Value { 136 return t.reply.nxt 137 } 138 139 // adapResult modifies the supplied "Result" according to the state of the TCB; 140 // if r is anything other than "Alive", or if one of the streams isn't closed 141 // yet, it is returned unmodified. Otherwise it's converted to either 142 // ClosedByOriginator or ClosedByResponder depending on which stream was closed 143 // first. 144 func (t *TCB) adaptResult(r Result) Result { 145 // Check the unmodified case. 146 if r != ResultAlive || !t.reply.closed() || !t.original.closed() { 147 return r 148 } 149 150 // Find out which was closed first. 151 if t.firstFin == &t.original { 152 return ResultClosedByOriginator 153 } 154 155 return ResultClosedByResponder 156 } 157 158 // synSentStateReply is the state handler for reply segments when the 159 // connection is in SYN-SENT state. 160 func synSentStateReply(t *TCB, tcp header.TCP, dataLen int) Result { 161 flags := tcp.Flags() 162 ackPresent := flags&header.TCPFlagAck != 0 163 ack := seqnum.Value(tcp.AckNumber()) 164 165 // Ignore segment if ack is present but not acceptable. 166 if ackPresent && !(ack-1).InRange(t.original.una, t.original.nxt) { 167 return ResultConnecting 168 } 169 170 // If reset is specified, we will let the packet through no matter what 171 // but we will also destroy the connection if the ACK is present (and 172 // implicitly acceptable). 173 if flags&header.TCPFlagRst != 0 { 174 if ackPresent { 175 t.reply.rstSeen = true 176 return ResultReset 177 } 178 return ResultConnecting 179 } 180 181 // Ignore segment if SYN is not set. 182 if flags&header.TCPFlagSyn == 0 { 183 return ResultConnecting 184 } 185 186 // TODO(gvisor.dev/issue/6734): Cache TCP options instead of re-parsing them. 187 // Because original and reply are streams, scale applies to the reply; it is 188 // the receive window in the original direction. 189 t.original.shiftCnt = header.ParseSynOptions(tcp.Options(), ackPresent).WS 190 191 // Window scaling works only when both ends use the scale option. 192 if t.original.shiftCnt != -1 && t.reply.shiftCnt != -1 { 193 // Per RFC 1323 section 2.3: 194 // 195 // "If a Window Scale option is received with a shift.cnt value exceeding 196 // 14, the TCP should log the error but use 14 instead of the specified 197 // value." 198 if t.original.shiftCnt > maxWindowShift { 199 t.original.shiftCnt = maxWindowShift 200 } 201 if t.reply.shiftCnt > maxWindowShift { 202 t.original.shiftCnt = maxWindowShift 203 } 204 } else { 205 t.original.shiftCnt = 0 206 t.reply.shiftCnt = 0 207 } 208 // Update state informed by this SYN. 209 irs := seqnum.Value(tcp.SequenceNumber()) 210 t.reply.una = irs 211 t.reply.nxt = irs.Add(logicalLen(tcp, dataLen, seqnum.Size(t.reply.end) /* end currently holds the receive window size */)) 212 t.reply.end <<= t.reply.shiftCnt 213 t.reply.end.UpdateForward(seqnum.Size(irs)) 214 215 windowSize := t.original.windowSize(tcp) 216 t.original.end = t.original.una.Add(windowSize) 217 218 // If the ACK was set (it is acceptable), update our unacknowledgement 219 // tracking. 220 if ackPresent { 221 // Advance the "una" and "end" indices of the original stream. 222 if t.original.una.LessThan(ack) { 223 t.original.una = ack 224 } 225 226 if end := ack.Add(seqnum.Size(windowSize)); t.original.end.LessThan(end) { 227 t.original.end = end 228 } 229 } 230 231 // Update handlers so that new calls will be handled by new state. 232 t.handlerReply = allOtherReply 233 t.handlerOriginal = allOtherOriginal 234 235 return ResultAlive 236 } 237 238 // synSentStateOriginal is the state handler for original segments when the 239 // connection is in SYN-SENT state. 240 func synSentStateOriginal(t *TCB, tcp header.TCP, _ int) Result { 241 // Drop original segments that aren't retransmits of the original one. 242 if tcp.Flags() != header.TCPFlagSyn || tcp.SequenceNumber() != uint32(t.original.una) { 243 return ResultDrop 244 } 245 246 // Update the receive window. We only remember the largest value seen. 247 if wnd := seqnum.Value(tcp.WindowSize()); wnd > t.reply.end { 248 t.reply.end = wnd 249 } 250 251 return ResultConnecting 252 } 253 254 // update updates the state of reply and original streams, given the supplied 255 // reply segment. For original segments, this same function can be called with 256 // swapped reply/original streams. 257 func update(tcp header.TCP, reply, original *stream, firstFin **stream, dataLen int) Result { 258 // Ignore segments out of the window. 259 s := seqnum.Value(tcp.SequenceNumber()) 260 if !reply.acceptable(s, seqnum.Size(dataLen)) { 261 return ResultAlive 262 } 263 264 flags := tcp.Flags() 265 if flags&header.TCPFlagRst != 0 { 266 reply.rstSeen = true 267 return ResultReset 268 } 269 270 // Ignore segments that don't have the ACK flag, and those with the SYN 271 // flag. 272 if flags&header.TCPFlagAck == 0 || flags&header.TCPFlagSyn != 0 { 273 return ResultAlive 274 } 275 276 // Ignore segments that acknowledge not yet sent data. 277 ack := seqnum.Value(tcp.AckNumber()) 278 if original.nxt.LessThan(ack) { 279 return ResultAlive 280 } 281 282 // Advance the "una" and "end" indices of the original stream. 283 if original.una.LessThan(ack) { 284 original.una = ack 285 } 286 287 if end := ack.Add(original.windowSize(tcp)); original.end.LessThan(end) { 288 original.end = end 289 } 290 291 // Advance the "nxt" index of the reply stream. 292 end := s.Add(logicalLen(tcp, dataLen, reply.rwndSize())) 293 if reply.nxt.LessThan(end) { 294 reply.nxt = end 295 } 296 297 // Note the index of the FIN segment. And stash away a pointer to the 298 // first stream to see a FIN. 299 if flags&header.TCPFlagFin != 0 && !reply.finSeen { 300 reply.finSeen = true 301 reply.fin = end - 1 302 303 if *firstFin == nil { 304 *firstFin = reply 305 } 306 } 307 308 return ResultAlive 309 } 310 311 // allOtherReply is the state handler for reply segments in all states 312 // except SYN-SENT. 313 func allOtherReply(t *TCB, tcp header.TCP, dataLen int) Result { 314 return t.adaptResult(update(tcp, &t.reply, &t.original, &t.firstFin, dataLen)) 315 } 316 317 // allOtherOriginal is the state handler for original segments in all states 318 // except SYN-SENT. 319 func allOtherOriginal(t *TCB, tcp header.TCP, dataLen int) Result { 320 return t.adaptResult(update(tcp, &t.original, &t.reply, &t.firstFin, dataLen)) 321 } 322 323 // streams holds the state of a TCP unidirectional stream. 324 type stream struct { 325 // The interval [una, end) is the allowed interval as defined by the 326 // receiver, i.e., anything less than una has already been acknowledged 327 // and anything greater than or equal to end is beyond the receiver 328 // window. The interval [una, nxt) is the acknowledgable range, whose 329 // right edge indicates the sequence number of the next byte to be sent 330 // by the sender, i.e., anything greater than or equal to nxt hasn't 331 // been sent yet. 332 una seqnum.Value 333 nxt seqnum.Value 334 end seqnum.Value 335 336 // finSeen indicates if a FIN has already been sent on this stream. 337 finSeen bool 338 339 // fin is the sequence number of the FIN. It is only valid after finSeen 340 // is set to true. 341 fin seqnum.Value 342 343 // rstSeen indicates if a RST has already been sent on this stream. 344 rstSeen bool 345 346 // shiftCnt is the shift of the window scale of the receiver of the stream, 347 // i.e. in a stream from A to B it is B's receive window scale. It cannot be 348 // greater than maxWindowScale. 349 shiftCnt int 350 } 351 352 // acceptable determines if the segment with the given sequence number and data 353 // length is acceptable, i.e., if it's within the [una, end) window or, in case 354 // the window is zero, if it's a packet with no payload and sequence number 355 // equal to una. 356 func (s *stream) acceptable(segSeq seqnum.Value, segLen seqnum.Size) bool { 357 return header.Acceptable(segSeq, segLen, s.una, s.end) 358 } 359 360 // closed determines if the stream has already been closed. This happens when 361 // a FIN has been set by the sender and acknowledged by the receiver. 362 func (s *stream) closed() bool { 363 return s.finSeen && s.fin.LessThan(s.una) 364 } 365 366 // rwndSize returns the stream's receive window size. 367 func (s *stream) rwndSize() seqnum.Size { 368 return s.una.Size(s.end) 369 } 370 371 // windowSize returns the stream's window size accounting for scale. 372 func (s *stream) windowSize(tcp header.TCP) seqnum.Size { 373 return seqnum.Size(tcp.WindowSize()) << s.shiftCnt 374 } 375 376 // logicalLenSyn calculates the logical length of a SYN (without ACK) segment. 377 // It is similar to logicalLen, but does not impose a window size requirement 378 // because of the SYN. 379 func logicalLenSyn(tcp header.TCP, dataLen int) seqnum.Size { 380 length := seqnum.Size(dataLen) 381 flags := tcp.Flags() 382 if flags&header.TCPFlagSyn != 0 { 383 length++ 384 } 385 if flags&header.TCPFlagFin != 0 { 386 length++ 387 } 388 return length 389 } 390 391 // logicalLen calculates the logical length of the TCP segment. 392 func logicalLen(tcp header.TCP, dataLen int, windowSize seqnum.Size) seqnum.Size { 393 // If the segment is too large, TCP trims the payload per RFC 793 page 70. 394 length := logicalLenSyn(tcp, dataLen) 395 if length > windowSize { 396 length = windowSize 397 } 398 return length 399 } 400 401 // IsEmpty returns true if tcb is not initialized. 402 func (t *TCB) IsEmpty() bool { 403 if t.reply != (stream{}) || t.original != (stream{}) { 404 return false 405 } 406 407 if t.firstFin != nil || t.state != ResultDrop { 408 return false 409 } 410 411 return true 412 }