blob: 64d64eb064d742528b2adda0554af01f27ab8e14 [file] [log] [blame] [raw]
/*
* tcp_raw.c
*
* Copyright (c) 2000 Dug Song <dugsong@monkey.org>
*
* $Id: tcp_raw.c,v 1.10 2001/03/15 08:33:04 dugsong Exp $
*/
#include "config.h"
#include <sys/types.h>
#include <sys/uio.h>
#include <netinet/in.h>
#include <time.h>
#include <err.h>
#include <libnet.h>
#include "options.h"
#include "tcp_raw.h"
struct tha {
in_addr_t src;
in_addr_t dst;
u_short port;
};
struct tcp_seg {
u_int32_t seq;
u_char *data;
int len;
};
struct tcp_conn {
struct tha tha;
time_t mtime;
struct tcp_seg *seg;
int segcnt;
int segmax;
struct tcp_conn *next;
};
#define TCP_TIMEOUT 60
#define TCP_HASHSIZE 919
static struct tcp_conn conntab[TCP_HASHSIZE];
static int
tcp_seg_compare(const void *a, const void *b)
{
struct tcp_seg *sa, *sb;
sa = (struct tcp_seg *) a;
sb = (struct tcp_seg *) b;
if (sa->seq < sb->seq)
return (-1);
else if (sa->seq > sb->seq)
return (1);
else return (0);
}
static void
tcp_raw_delete(struct tcp_conn *conn)
{
struct tcp_conn *hold;
int i;
if (conn->next != NULL) {
for (i = 0; i < conn->segcnt; i++) {
if (conn->seg[i].data)
free(conn->seg[i].data);
}
free(conn->seg);
conn->seg = NULL;
conn->segcnt = conn->segmax = 0;
if (conn->next->next != NULL) {
hold = conn->next;
*conn = *conn->next;
free(hold);
}
else {
free(conn->next);
conn->next = NULL;
}
}
}
static struct iovec *
tcp_raw_reassemble(struct tcp_conn *conn, int minlen)
{
struct iovec *iov;
int i, len;
len = 0;
for (i = 0; i < conn->segcnt; i++)
len += conn->seg[i].len;
if (len < minlen)
return (NULL);
if ((iov = (struct iovec *) malloc(sizeof(*iov))) == NULL)
err(1, "tcp_raw_reassemble: malloc");
if ((iov->iov_base = (u_char *) malloc(len)) == NULL)
err(1, "tcp_raw_reassemble: malloc");
iov->iov_len = 0;
qsort(conn->seg, conn->segcnt, sizeof(*conn->seg), tcp_seg_compare);
for (i = 0; i < conn->segcnt; i++) {
len = conn->seg[i].len;
memcpy(iov->iov_base + iov->iov_len, conn->seg[i].data, len);
iov->iov_len += len;
}
return (iov);
}
struct iovec *
tcp_raw_input(struct libnet_ipv4_hdr *ip, struct libnet_tcp_hdr *tcp, int len)
{
struct tha tha;
struct tcp_conn *conn;
struct tcp_seg seg;
struct iovec *iov;
u_short cksum;
u_char *buf;
int tcp_hl = tcp->th_off * 4;
/* Verify TCP checksum. */
cksum = tcp->th_sum;
libnet_do_checksum(NULL, (u_char *) ip, IPPROTO_TCP, len);
if (cksum != tcp->th_sum)
return (NULL);
tha.src = ip->ip_src.s_addr;
tha.dst = ip->ip_dst.s_addr;
tha.port = ntohs(tcp->th_sport) << 16 | ntohs(tcp->th_dport);
buf = (u_char *)tcp + tcp_hl;
len -= tcp_hl;
iov = NULL;
/* Find half-duplex stream associated with this segment. */
for (conn = &conntab[tha.port % TCP_HASHSIZE];
conn->next != NULL; conn = conn->next) {
if (memcmp((char *)&tha, (char *)&conn->tha, sizeof(tha)) == 0)
break;
}
/* Process by TCP flags. */
if (conn->next == NULL) {
if (tcp->th_flags & TH_SYN) {
if (conn->next == NULL &&
(conn->next = (struct tcp_conn *)
calloc(1, sizeof(*conn))) == NULL) {
err(1, "tcp_raw_input: calloc");
}
conn->tha = tha;
if (conn->seg == NULL &&
(conn->seg = (struct tcp_seg *)
malloc(sizeof(seg) * 128)) == NULL) {
err(1, "tcp_raw_input: malloc");
}
conn->segmax = 128;
}
}
else if (tcp->th_flags & TH_FIN || tcp->th_flags & TH_RST) {
iov = tcp_raw_reassemble(conn, 1);
}
else if (tcp->th_flags & TH_ACK && len > 0) {
seg.seq = ntohl(tcp->th_seq);
if (bsearch(&seg, conn->seg, conn->segcnt,
sizeof(seg), tcp_seg_compare) == NULL) {
if ((seg.data = (u_char *) malloc(len)) == NULL)
err(1, "tcp_raw_input: malloc");
memcpy(seg.data, buf, len);
seg.len = len;
if (conn->segcnt == conn->segmax) {
if ((conn->seg = (struct tcp_seg *)
realloc(conn->seg, (conn->segmax * 2) *
sizeof(seg))) == NULL)
err(1, "tcp_raw_input: realloc");
conn->segmax *= 2;
}
conn->seg[conn->segcnt++] = seg;
iov = tcp_raw_reassemble(conn, Opt_snaplen);
}
}
conn->mtime = time(NULL);
/* If we successfully reassembled the stream, delete its entry. */
if (iov != NULL) {
tcp_raw_delete(conn);
}
return (iov);
}
void
tcp_raw_timeout(int timeout, tcp_raw_callback_t callback)
{
struct tcp_conn *conn;
struct iovec *iov;
time_t now;
int i;
now = time(NULL);
for (i = 0; i < TCP_HASHSIZE; i++) {
for (conn = &conntab[i]; conn != NULL && conn->next != NULL;
conn = conn->next) {
if (now - conn->mtime > timeout) {
iov = tcp_raw_reassemble(conn, 1);
if (iov != NULL) {
callback(conn->tha.src, conn->tha.dst,
conn->tha.port >> 16,
conn->tha.port & 0xffff,
iov->iov_base, iov->iov_len);
free(iov->iov_base);
free(iov);
}
tcp_raw_delete(conn);
}
}
}
}