#include <libpcapng/reassembly_tcp.h>
#include <libpcapng/protocols/flow.h>
#include <stdlib.h>
#include <string.h>
#define HOLD_MAX 16
#define BUF_CAP_MAX (4u << 20)
typedef struct { uint32_t seq; int len; uint8_t *data; } hold_t;
typedef struct {
int active;
uint32_t src_ip, dst_ip;
uint16_t src_port, dst_port;
uint32_t next_seq;
int started;
uint8_t *buf;
int buf_len, buf_cap;
hold_t holds[HOLD_MAX];
int nholds;
} half_t;
typedef struct {
int used;
uint32_t a_ip, b_ip;
uint16_t a_port, b_port;
half_t half[2];
} flow_t;
struct pcapng_tcp_reasm {
flow_t *flows;
int n, cap;
};
pcapng_tcp_reasm_t *pcapng_tcp_reasm_new(void)
{
return calloc(1, sizeof(pcapng_tcp_reasm_t));
}
static void half_free(half_t *h)
{
int i;
free(h->buf);
for (i = 0; i < h->nholds; i++) free(h->holds[i].data);
}
void pcapng_tcp_reasm_free(pcapng_tcp_reasm_t *r)
{
int i;
if (!r) return;
for (i = 0; i < r->n; i++) {
half_free(&r->flows[i].half[0]);
half_free(&r->flows[i].half[1]);
}
free(r->flows);
free(r);
}
static flow_t *flow_find(pcapng_tcp_reasm_t *r, uint32_t a_ip, uint16_t a_port,
uint32_t b_ip, uint16_t b_port)
{
int i;
for (i = 0; i < r->n; i++) {
flow_t *f = &r->flows[i];
if (f->used && f->a_ip == a_ip && f->a_port == a_port &&
f->b_ip == b_ip && f->b_port == b_port)
return f;
}
if (r->n == r->cap) {
int nc = r->cap ? r->cap * 2 : 64;
flow_t *nf = realloc(r->flows, (size_t)nc * sizeof *nf);
if (!nf) return NULL;
memset(nf + r->cap, 0, (size_t)(nc - r->cap) * sizeof *nf);
r->flows = nf;
r->cap = nc;
}
{
flow_t *f = &r->flows[r->n++];
memset(f, 0, sizeof *f);
f->used = 1;
f->a_ip = a_ip; f->a_port = a_port; f->b_ip = b_ip; f->b_port = b_port;
return f;
}
}
static void buf_append(half_t *h, const uint8_t *d, int n)
{
if (n <= 0) return;
if (h->buf_len + n > h->buf_cap) {
int nc = h->buf_cap ? h->buf_cap : 4096;
while (nc < h->buf_len + n && (unsigned)nc < BUF_CAP_MAX) nc *= 2;
if ((unsigned)nc > BUF_CAP_MAX) nc = BUF_CAP_MAX;
if (nc > h->buf_cap) {
uint8_t *nb = realloc(h->buf, (size_t)nc);
if (!nb) return;
h->buf = nb; h->buf_cap = nc;
}
}
if (h->buf_len + n > h->buf_cap) n = h->buf_cap - h->buf_len;
if (n <= 0) return;
memcpy(h->buf + h->buf_len, d, (size_t)n);
h->buf_len += n;
}
static void drain_holds(half_t *h, pcapng_tcp_stream_cb cb, void *ud, int dir)
{
int progressed = 1;
while (progressed) {
int i;
progressed = 0;
for (i = 0; i < h->nholds; i++) {
hold_t *hd = &h->holds[i];
if (hd->seq == h->next_seq) {
buf_append(h, hd->data, hd->len);
h->next_seq += (uint32_t)hd->len;
if (cb)
cb(ud, h->src_ip, h->src_port, h->dst_ip, h->dst_port,
dir, hd->data, (size_t)hd->len, h->buf, (size_t)h->buf_len);
free(hd->data);
h->holds[i] = h->holds[--h->nholds];
progressed = 1;
break;
}
if ((int32_t)(hd->seq + hd->len - h->next_seq) <= 0) {
free(hd->data);
h->holds[i] = h->holds[--h->nholds];
progressed = 1;
break;
}
}
}
}
static void hold_add(half_t *h, uint32_t seq, const uint8_t *d, int n)
{
hold_t *hd;
int i;
for (i = 0; i < h->nholds; i++) if (h->holds[i].seq == seq) return;
if (h->nholds == HOLD_MAX) {
int worst = 0;
for (i = 1; i < h->nholds; i++)
if ((int32_t)(h->holds[i].seq - h->holds[worst].seq) > 0) worst = i;
free(h->holds[worst].data);
h->holds[worst] = h->holds[--h->nholds];
}
hd = &h->holds[h->nholds++];
hd->seq = seq;
hd->len = n;
hd->data = malloc((size_t)n);
if (hd->data) memcpy(hd->data, d, (size_t)n);
else h->nholds--;
}
void pcapng_tcp_reasm_add(pcapng_tcp_reasm_t *r,
uint32_t src_ip, uint32_t dst_ip,
uint16_t src_port, uint16_t dst_port,
uint32_t seq, uint8_t tcp_flags,
const uint8_t *payload, size_t payload_len,
pcapng_tcp_stream_cb cb, void *userdata)
{
uint32_t a_ip, b_ip;
uint16_t a_port, b_port;
uint8_t from_a;
flow_t *f;
half_t *h;
int dir, paylen = (int)payload_len;
if (!r) return;
from_a = libpcapng_normalize_flow_direction(src_ip, dst_ip, src_port, dst_port,
&a_ip, &b_ip, &a_port, &b_port);
f = flow_find(r, a_ip, a_port, b_ip, b_port);
if (!f) return;
dir = from_a ? 0 : 1;
h = &f->half[dir];
if (!h->active) {
h->active = 1;
h->src_ip = src_ip; h->dst_ip = dst_ip;
h->src_port = src_port; h->dst_port = dst_port;
}
if (!h->started) {
h->started = 1;
h->next_seq = seq + ((tcp_flags & LIBPCAPNG_TCP_SYN) ? 1u : 0u);
}
if (paylen <= 0) return;
if (seq == h->next_seq) {
buf_append(h, payload, paylen);
h->next_seq += (uint32_t)paylen;
if (cb) cb(userdata, h->src_ip, h->src_port, h->dst_ip, h->dst_port,
dir, payload, (size_t)paylen, h->buf, (size_t)h->buf_len);
drain_holds(h, cb, userdata, dir);
} else if ((int32_t)(seq - h->next_seq) > 0) {
hold_add(h, seq, payload, paylen);
} else {
uint32_t already = h->next_seq - seq;
if ((int)already < paylen) {
const uint8_t *tail = payload + already;
int tlen = paylen - (int)already;
buf_append(h, tail, tlen);
h->next_seq += (uint32_t)tlen;
if (cb) cb(userdata, h->src_ip, h->src_port, h->dst_ip, h->dst_port,
dir, tail, (size_t)tlen, h->buf, (size_t)h->buf_len);
drain_holds(h, cb, userdata, dir);
}
}
}