#define _GNU_SOURCE #include "conntrack.h" #include "darkmagic.h" #include #include #include "params.h" #include "lua.h" #undef uthash_nonfatal_oom #define uthash_nonfatal_oom(elt) ut_oom_recover(elt) static bool oom = false; static void ut_oom_recover(void *elem) { oom = true; } static const char *connstate_s[] = { "SYN","ESTABLISHED","FIN" }; static void connswap(const t_conn *c, t_conn *c2) { memset(c2, 0, sizeof(*c2)); c2->l3proto = c->l3proto; c2->l4proto = c->l4proto; c2->src = c->dst; c2->dst = c->src; c2->sport = c->dport; c2->dport = c->sport; } void ConntrackClearHostname(t_ctrack *track) { free(track->hostname); track->hostname = NULL; track->hostname_is_ip = false; } static void ConntrackClearTrack(t_ctrack *track) { ConntrackClearHostname(track); ReasmClear(&track->reasm_client); rawpacket_queue_destroy(&track->delayed); luaL_unref(params.L, LUA_REGISTRYINDEX, track->lua_state); luaL_unref(params.L, LUA_REGISTRYINDEX, track->lua_instance_cutoff); } static void ConntrackFreeElem(t_conntrack_pool *elem) { ConntrackClearTrack(&elem->track); free(elem); } static void ConntrackPoolDestroyPool(t_conntrack_pool **pp) { t_conntrack_pool *elem, *tmp; HASH_ITER(hh, *pp, elem, tmp) { HASH_DEL(*pp, elem); ConntrackFreeElem(elem); } } void ConntrackPoolDestroy(t_conntrack *p) { ConntrackPoolDestroyPool(&p->pool); } void ConntrackPoolInit(t_conntrack *p, time_t purge_interval, uint32_t timeout_syn, uint32_t timeout_established, uint32_t timeout_fin, uint32_t timeout_udp) { p->timeout_syn = timeout_syn; p->timeout_established = timeout_established; p->timeout_fin = timeout_fin; p->timeout_udp = timeout_udp; p->t_purge_interval = purge_interval; p->t_last_purge = boottime(); p->pool = NULL; } bool ConntrackExtractConn(t_conn *c, bool bReverse, const struct dissect *dis) { memset(c, 0, sizeof(*c)); if (dis->ip) { c->l3proto = IPPROTO_IP; c->dst.ip = bReverse ? dis->ip->ip_src : dis->ip->ip_dst; c->src.ip = bReverse ? dis->ip->ip_dst : dis->ip->ip_src; } else if (dis->ip6) { c->l3proto = IPPROTO_IPV6; c->dst.ip6 = bReverse ? dis->ip6->ip6_src : dis->ip6->ip6_dst; c->src.ip6 = bReverse ? dis->ip6->ip6_dst : dis->ip6->ip6_src; } else return false; extract_ports(dis->tcp, dis->udp, &c->l4proto, bReverse ? &c->dport : &c->sport, bReverse ? &c->sport : &c->dport); return c->l4proto!=IPPROTO_NONE; } static t_conntrack_pool *ConntrackPoolSearch(t_conntrack_pool *p, const t_conn *c) { t_conntrack_pool *t; HASH_FIND(hh, p, c, sizeof(*c), t); return t; } static void ConntrackInitTrack(t_ctrack *t) { memset(t, 0, sizeof(*t)); t->l7proto = L7_UNKNOWN; t->pos.client.scale = t->pos.server.scale = 0; rawpacket_queue_init(&t->delayed); lua_newtable(params.L); t->lua_state = luaL_ref(params.L, LUA_REGISTRYINDEX); lua_newtable(params.L); t->lua_instance_cutoff = luaL_ref(params.L, LUA_REGISTRYINDEX); } static void ConntrackReInitTrack(t_ctrack *t) { ConntrackClearTrack(t); ConntrackInitTrack(t); } static t_conntrack_pool *ConntrackNew(t_conntrack_pool **pp, const t_conn *c) { t_conntrack_pool *ctnew; if (!(ctnew = malloc(sizeof(*ctnew)))) return NULL; ctnew->conn = *c; oom = false; HASH_ADD(hh, *pp, conn, sizeof(*c), ctnew); if (oom) { free(ctnew); return NULL; } ConntrackInitTrack(&ctnew->track); return ctnew; } static void ConntrackApplyPos(t_ctrack *t, bool bReverse, const struct dissect *dis) { uint8_t scale; uint16_t mss; t_ctrack_position *direct, *reverse; direct = bReverse ? &t->pos.server : &t->pos.client; reverse = bReverse ? &t->pos.client : &t->pos.server; if (dis->ip6) direct->ip6flow = ntohl(dis->ip6->ip6_ctlun.ip6_un1.ip6_un1_flow); direct->winsize_calc = direct->winsize = ntohs(dis->tcp->th_win); if (t->pos.state == SYN) { // scale and mss only valid in syn packets scale = tcp_find_scale_factor(dis->tcp); if (scale != SCALE_NONE) direct->scale = scale; direct->mss = tcp_find_mss(dis->tcp); } else // apply scale only outside of the SYN stage direct->winsize_calc <<= direct->scale; direct->seq_last = ntohl(dis->tcp->th_seq); direct->pos = direct->seq_last + dis->len_payload; reverse->pos = reverse->seq_last = ntohl(dis->tcp->th_ack); if (t->pos.state == SYN) direct->uppos_prev = direct->uppos = direct->pos; else if (dis->len_payload) { direct->uppos_prev = direct->uppos; if (!((direct->pos - direct->uppos) & 0x80000000)) direct->uppos = direct->pos; } if (!direct->rseq_over_2G && ((direct->seq_last - direct->seq0) & 0x80000000)) direct->rseq_over_2G = true; if (!reverse->rseq_over_2G && ((reverse->seq_last - reverse->seq0) & 0x80000000)) reverse->rseq_over_2G = true; } static void ConntrackFeedPacket(t_ctrack *t, bool bReverse, const struct dissect *dis) { if (bReverse) { t->pos.server.pcounter++; t->pos.server.pdcounter += !!dis->len_payload; t->pos.server.pbcounter += dis->len_payload; } else { t->pos.client.pcounter++; t->pos.client.pdcounter += !!dis->len_payload; t->pos.client.pbcounter += dis->len_payload; } if (dis->tcp) { if (tcp_syn_segment(dis->tcp)) { if (t->pos.state != SYN) ConntrackReInitTrack(t); // erase current entry t->pos.client.seq0 = ntohl(dis->tcp->th_seq); } else if (tcp_synack_segment(dis->tcp)) { // ignore SA dups uint32_t seq0 = ntohl(dis->tcp->th_ack) - 1; if (t->pos.state != SYN && t->pos.client.seq0 != seq0) ConntrackReInitTrack(t); // erase current entry if (!t->pos.client.seq0) t->pos.client.seq0 = seq0; t->pos.server.seq0 = ntohl(dis->tcp->th_seq); } else if (dis->tcp->th_flags & (TH_FIN | TH_RST)) { t->pos.state = FIN; } else { if (t->pos.state == SYN) { t->pos.state = ESTABLISHED; if (!bReverse && !t->pos.server.seq0) t->pos.server.seq0 = ntohl(dis->tcp->th_ack) - 1; } } ConntrackApplyPos(t, bReverse, dis); } clock_gettime(CLOCK_BOOT_OR_UPTIME, &t->pos.t_last); // make sure t_start gets exactly the same value as first t_last if (!t->t_start.tv_sec) t->t_start = t->pos.t_last; } static bool ConntrackPoolDoubleSearchPool(t_conntrack_pool **pp, const struct dissect *dis, t_ctrack **ctrack, bool *bReverse) { t_conn conn, connswp; t_conntrack_pool *ctr; if (!ConntrackExtractConn(&conn, false, dis)) return false; if ((ctr = ConntrackPoolSearch(*pp, &conn))) { if (bReverse) *bReverse = false; if (ctrack) *ctrack = &ctr->track; return true; } else { connswap(&conn, &connswp); if ((ctr = ConntrackPoolSearch(*pp, &connswp))) { if (bReverse) *bReverse = true; if (ctrack) *ctrack = &ctr->track; return true; } } return false; } bool ConntrackPoolDoubleSearch(t_conntrack *p, const struct dissect *dis, t_ctrack **ctrack, bool *bReverse) { return ConntrackPoolDoubleSearchPool(&p->pool, dis, ctrack, bReverse); } static bool ConntrackPoolFeedPool(t_conntrack_pool **pp, const struct dissect *dis, t_ctrack **ctrack, bool *bReverse) { t_conn conn, connswp; t_conntrack_pool *ctr; bool b_rev; uint8_t proto = dis->tcp ? IPPROTO_TCP : dis->udp ? IPPROTO_UDP : IPPROTO_NONE; if (!ConntrackExtractConn(&conn, false, dis)) return false; if ((ctr = ConntrackPoolSearch(*pp, &conn))) { ConntrackFeedPacket(&ctr->track, (b_rev = false), dis); goto ok; } else { connswap(&conn, &connswp); if ((ctr = ConntrackPoolSearch(*pp, &connswp))) { ConntrackFeedPacket(&ctr->track, (b_rev = true), dis); goto ok; } } b_rev = dis->tcp && tcp_synack_segment(dis->tcp); if ((dis->tcp && tcp_syn_segment(dis->tcp)) || b_rev || dis->udp) { if ((ctr = ConntrackNew(pp, b_rev ? &connswp : &conn))) { ConntrackFeedPacket(&ctr->track, b_rev, dis); goto ok; } } return false; ok: ctr->track.pos.ipproto = proto; if (ctrack) *ctrack = &ctr->track; if (bReverse) *bReverse = b_rev; return true; } bool ConntrackPoolFeed(t_conntrack *p, const struct dissect *dis, t_ctrack **ctrack, bool *bReverse) { return ConntrackPoolFeedPool(&p->pool, dis, ctrack, bReverse); } static bool ConntrackPoolDropPool(t_conntrack_pool **pp, const struct dissect *dis) { t_conn conn, connswp; t_conntrack_pool *t; if (!ConntrackExtractConn(&conn, false, dis)) return false; if (!(t = ConntrackPoolSearch(*pp, &conn))) { connswap(&conn, &connswp); t = ConntrackPoolSearch(*pp, &connswp); } if (!t) return false; HASH_DEL(*pp, t); ConntrackFreeElem(t); return true; } bool ConntrackPoolDrop(t_conntrack *p, const struct dissect *dis) { return ConntrackPoolDropPool(&p->pool, dis); } void ConntrackPoolPurge(t_conntrack *p) { time_t tidle; time_t tnow; t_conntrack_pool *t, *tmp; if (!(tnow=boottime())) return; if ((tnow - p->t_last_purge) >= p->t_purge_interval) { HASH_ITER(hh, p->pool, t, tmp) { tidle = tnow - t->track.pos.t_last.tv_sec; if (t->track.b_cutoff || (t->conn.l4proto == IPPROTO_TCP && ( (t->track.pos.state == SYN && tidle >= p->timeout_syn) || (t->track.pos.state == ESTABLISHED && tidle >= p->timeout_established) || (t->track.pos.state == FIN && tidle >= p->timeout_fin)) ) || (t->conn.l4proto == IPPROTO_UDP && tidle >= p->timeout_udp) ) { HASH_DEL(p->pool, t); ConntrackFreeElem(t); } } p->t_last_purge = tnow; } } static void taddr2str(uint8_t l3proto, const t_addr *a, char *buf, size_t bufsize) { if (!inet_ntop(family_from_proto(l3proto), a, buf, bufsize) && bufsize) *buf = 0; } void ConntrackPoolDump(const t_conntrack *p) { t_conntrack_pool *t, *tmp; time_t tnow; char sa1[INET6_ADDRSTRLEN], sa2[INET6_ADDRSTRLEN]; if (!(tnow=boottime())) return; HASH_ITER(hh, p->pool, t, tmp) { taddr2str(t->conn.l3proto, &t->conn.src, sa1, sizeof(sa1)); taddr2str(t->conn.l3proto, &t->conn.dst, sa2, sizeof(sa2)); printf("%s [%s]:%u => [%s]:%u : %s : t0=%llu last=t0+%llu now=last+%llu client=d%llu/n%llu/b%llu server=d%llu/n%llu/b%llu ", proto_name(t->conn.l4proto), sa1, t->conn.sport, sa2, t->conn.dport, t->conn.l4proto == IPPROTO_TCP ? connstate_s[t->track.pos.state] : "-", (unsigned long long)t->track.t_start.tv_sec, (unsigned long long)(t->track.pos.t_last.tv_sec - t->track.t_start.tv_sec), (unsigned long long)(tnow - t->track.pos.t_last.tv_sec), (unsigned long long)t->track.pos.client.pdcounter, (unsigned long long)t->track.pos.client.pcounter, (unsigned long long)t->track.pos.client.pbcounter, (unsigned long long)t->track.pos.server.pdcounter, (unsigned long long)t->track.pos.server.pcounter, (unsigned long long)t->track.pos.server.pbcounter); if (t->conn.l4proto == IPPROTO_TCP) printf("seq0=%u rseq=%u client.pos=%u ack0=%u rack=%u server.pos=%u client.mss=%u server.mss=%u client.wsize=%u:%d server.wsize=%u:%d", t->track.pos.client.seq0, t->track.pos.client.seq_last - t->track.pos.client.seq0, t->track.pos.client.pos - t->track.pos.client.seq0, t->track.pos.server.seq0, t->track.pos.server.seq_last - t->track.pos.server.seq0, t->track.pos.server.pos - t->track.pos.server.seq0, t->track.pos.client.mss, t->track.pos.server.mss, t->track.pos.client.winsize, t->track.pos.client.scale, t->track.pos.server.winsize, t->track.pos.server.scale); else printf("rseq=%u client.pos=%u rack=%u server.pos=%u", t->track.pos.client.seq_last, t->track.pos.client.pos, t->track.pos.server.seq_last, t->track.pos.server.pos); printf(" req_retrans=%u cutoff=%u lua_in_cutoff=%u lua_out_cutoff=%u hostname=%s l7proto=%s\n", t->track.req_retrans_counter, t->track.b_cutoff, t->track.b_lua_in_cutoff, t->track.b_lua_out_cutoff, t->track.hostname ? t->track.hostname : "", l7proto_str(t->track.l7proto)); }; } void ReasmClear(t_reassemble *reasm) { free(reasm->packet); reasm->packet = NULL; reasm->size = reasm->size_present = 0; } bool ReasmInit(t_reassemble *reasm, size_t size_requested, uint32_t seq_start) { reasm->packet = malloc(size_requested); if (!reasm->packet) return false; reasm->size = size_requested; reasm->size_present = 0; reasm->seq = seq_start; return true; } bool ReasmResize(t_reassemble *reasm, size_t new_size) { uint8_t *p = realloc(reasm->packet, new_size); if (!p) return false; reasm->packet = p; reasm->size = new_size; if (reasm->size_present > new_size) reasm->size_present = new_size; return true; } #define REASM_MAX_NEG 0x100000 bool ReasmFeed(t_reassemble *reasm, uint32_t seq, const void *payload, size_t len) { uint32_t dseq = seq - reasm->seq; if (dseq && (dseq < REASM_MAX_NEG)) return false; // fail session if a gap about to appear uint32_t neg_overlap = reasm->seq - seq; if (neg_overlap > REASM_MAX_NEG) return false; // too big minus size_t szcopy, szignore; szignore = (neg_overlap > reasm->size_present) ? neg_overlap - reasm->size_present : 0; if (szignore>=len) return true; // everyting is before the starting pos szcopy = len - szignore; neg_overlap -= szignore; if ((reasm->size_present - neg_overlap + szcopy) > reasm->size) return false; // buffer overflow // in case of seq overlap new data replaces old - unix behavior memcpy(reasm->packet + reasm->size_present - neg_overlap, (const uint8_t*)payload + szignore, szcopy); if (szcopy>neg_overlap) { reasm->size_present += szcopy - neg_overlap; reasm->seq += (uint32_t)szcopy - neg_overlap; } return true; } bool ReasmHasSpace(t_reassemble *reasm, size_t len) { return (reasm->size_present + len) <= reasm->size; }