SCReturnInt(0);
}
+static inline int CompareOverlap(
+ struct StreamTcpSackRecord *lookup, struct StreamTcpSackRecord *intree)
+{
+ if (lookup->re <= intree->le) // entirely before
+ return -1;
+ else if (lookup->re >= intree->le && lookup->le < intree->re) // (some) overlap
+ return 0;
+ else
+ return 1; // entirely after
+}
+
+static struct StreamTcpSackRecord *FindOverlap(
+ struct TCPSACK *head, struct StreamTcpSackRecord *elm)
+{
+ SCLogDebug("looking up le:%u re:%u", elm->le, elm->re);
+
+ struct StreamTcpSackRecord *tmp = RB_ROOT(head);
+ struct StreamTcpSackRecord *res = NULL;
+ while (tmp) {
+ SCLogDebug("compare with le:%u re:%u", tmp->le, tmp->re);
+ const int comp = CompareOverlap(elm, tmp);
+ SCLogDebug("compare result: %d", comp);
+ if (comp < 0) {
+ res = tmp;
+ tmp = RB_LEFT(tmp, rb);
+ } else if (comp > 0) {
+ tmp = RB_RIGHT(tmp, rb);
+ } else {
+ return tmp;
+ }
+ }
+ return res;
+}
+
+bool StreamTcpSackPacketIsOutdated(TcpStream *stream, Packet *p)
+{
+ const int records = TCP_GET_SACK_CNT(p);
+ const uint8_t *data = TCP_GET_SACK_PTR(p);
+ if (records > 0 && data != NULL) {
+ int sack_outdated = 0;
+ TCPOptSackRecord rec[records], *sack_rec = rec;
+ memcpy(&rec, data, sizeof(TCPOptSackRecord) * records);
+ for (int record = 0; record < records; record++) {
+ const uint32_t le = SCNtohl(sack_rec->le);
+ const uint32_t re = SCNtohl(sack_rec->re);
+ SCLogDebug("%p last_ack %u, left edge %u, right edge %u", sack_rec, stream->last_ack,
+ le, re);
+
+ struct StreamTcpSackRecord lookup = { .le = le, .re = re };
+ struct StreamTcpSackRecord *res = FindOverlap(&stream->sack_tree, &lookup);
+ SCLogDebug("res %p", res);
+ if (res) {
+ if (le >= res->le && re <= res->re) {
+ SCLogDebug("SACK rec le:%u re:%u eclipsed by in tree le:%u re:%u", le, re,
+ res->le, res->re);
+ sack_outdated++;
+ } else {
+ SCLogDebug("SACK rec le:%u re:%u SACKs new DATA vs in tree le:%u re:%u", le, re,
+ res->le, res->re);
+ }
+ } else {
+ SCLogDebug("SACK rec le:%u re:%u SACKs new DATA. No match in tree", le, re);
+ }
+ sack_rec++;
+ }
+#ifdef DEBUG
+ StreamTcpSackPrintList(stream);
+#endif
+ if (records != sack_outdated) {
+ // SACK tree needs updating
+ return false;
+ } else {
+ // SACK list is packet is completely outdated
+ return true;
+ }
+ }
+ return false;
+}
+
void StreamTcpSackPruneList(TcpStream *stream)
{
SCEnter();
}
int StreamTcpSackUpdatePacket(TcpStream *, Packet *);
+bool StreamTcpSackPacketIsOutdated(TcpStream *stream, Packet *p);
void StreamTcpSackPruneList(TcpStream *);
void StreamTcpSackFreeList(TcpStream *);
void StreamTcpSackRegisterTests (void);
SCReturnUInt(ack);
}
+/** \internal
+ * \brief check if a ACK packet is outdated so processing can be fast tracked
+ *
+ * Consider a packet outdated ack if:
+ * - state is >= ESTABLISHED
+ * - ACK < last_ACK
+ * - SACK acks nothing new
+ * - packet has no data
+ * - SEQ == next_SEQ
+ * - flags has ACK set but don't contain SYN/FIN/RST
+ *
+ * \todo the most likely explanation for this packet is that we already
+ * accepted a "newer" ACK. We will not consider an outdated timestamp
+ * option an issue for this packet, but we should probably still
+ * check if the ts isn't too far off.
+ */
+static bool StreamTcpPacketIsOutdatedAck(TcpSession *ssn, Packet *p)
+{
+ if (ssn->state < TCP_ESTABLISHED)
+ return false;
+ if (p->payload_len != 0)
+ return false;
+ if ((p->tcph->th_flags & (TH_ACK | TH_SYN | TH_FIN | TH_RST)) != TH_ACK)
+ return false;
+
+ /* lets see if this is a packet that is entirely eclipsed by earlier ACKs */
+ if (PKT_IS_TOSERVER(p)) {
+ if (SEQ_EQ(TCP_GET_SEQ(p), ssn->client.next_seq) &&
+ SEQ_LT(TCP_GET_ACK(p), ssn->server.last_ack)) {
+ if (!TCP_HAS_SACK(p)) {
+ SCLogDebug("outdated ACK (no SACK, SEQ %u vs next_seq %u)", TCP_GET_SEQ(p),
+ ssn->client.next_seq);
+ return true;
+ }
+
+ if (StreamTcpSackPacketIsOutdated(&ssn->server, p)) {
+ SCLogDebug("outdated ACK (have SACK, SEQ %u vs next_seq %u)", TCP_GET_SEQ(p),
+ ssn->client.next_seq);
+ return true;
+ }
+ }
+ } else {
+ if (SEQ_EQ(TCP_GET_SEQ(p), ssn->server.next_seq) &&
+ SEQ_LT(TCP_GET_ACK(p), ssn->client.last_ack)) {
+ if (!TCP_HAS_SACK(p)) {
+ SCLogDebug("outdated ACK (no SACK, SEQ %u vs next_seq %u)", TCP_GET_SEQ(p),
+ ssn->client.next_seq);
+ return true;
+ }
+
+ if (StreamTcpSackPacketIsOutdated(&ssn->client, p)) {
+ SCLogDebug("outdated ACK (have SACK, SEQ %u vs next_seq %u)", TCP_GET_SEQ(p),
+ ssn->client.next_seq);
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
/**
* \brief Function to handle the TCP_ESTABLISHED state. The function handles
* ACK, FIN, RST packets and correspondingly changes the connection
/* if packet is not a valid window update, check if it is perhaps
* a bad window update that we should ignore (and alert on) */
- if (StreamTcpPacketIsFinShutdownAck(ssn, p) == 0)
- if (StreamTcpPacketIsWindowUpdate(ssn, p) == 0)
+ if (StreamTcpPacketIsFinShutdownAck(ssn, p) == 0) {
+ if (StreamTcpPacketIsWindowUpdate(ssn, p) == 0) {
if (StreamTcpPacketIsBadWindowUpdate(ssn,p))
goto skip;
+ if (StreamTcpPacketIsOutdatedAck(ssn, p))
+ goto skip;
+ }
+ }
/* handle the per 'state' logic */
if (StreamTcpStateDispatch(tv, p, stt, ssn, &stt->pseudo_queue, ssn->state) < 0)