Search code examples
clinux-kernelnetfilterconntrack

How to remove conntrack entry from within Linux kernel?


I need to delete a conntrack entry in the kernel. There are numerous functions like nf_ct_delete and nf_ct_put. At the moment I use both on a skb->nfct (block on every tracked skb). It seems to work fine at first, but after some seconds the kernel just crashes.

My current code is as follows:

struct nf_conntrack *con = skb->nfct;
nf_conntrack_get(sub_conntrack);
//... do some stuff 
nf_ct_delete((struct nf_conn *) con, 0, 0);
nf_ct_put((struct nf_conn *) con);

Directly after execution of the delete and put statement, I check the entries with conntrack -L conntrack and they are gone (as expected). But after one minute or so, the system freezes. I guess conntrack starts some timers that crash when fired.

So my question is: How can I immediately delete everything for a specific connection? Including stopping any timers and cleaning up all state.


Solution

  • This is adapted from the netfilter code

    int delete_nat_entry(u32 loc_ip, u32 rem_ip, u16 loc_port, u16 rem_port) {
            struct nf_conntrack_tuple_hash *h;
            struct nf_conntrack_tuple tuple;
            struct nf_conn *ct;
    
            memset(&tuple, 0, sizeof(tuple));
    
            //layer 3
            tuple.src.l3num = PF_INET;
            tuple.src.u3.ip = htonl(loc_ip);
            tuple.dst.u3.ip = htonl(rem_ip);
    
            //layer 4
            tuple.dst.protonum = IPPROTO_TCP;
            tuple.dst.dir = IP_CT_DIR_ORIGINAL;
            tuple.src.u.tcp.port = htons(loc_port);
            tuple.dst.u.tcp.port = htons(rem_port);
    
            h = nf_conntrack_find_get(&init_net, NF_CT_DEFAULT_ZONE, &tuple);
            if (!h) {
                    debug_msg(KERN_INFO "cannot find conntrack tuple\n");
                    return -ENOENT;
            }
            ct = nf_ct_tuplehash_to_ctrack(h);
    
            if (del_timer(&ct->timeout))
                    nf_ct_delete(ct, 0, 0);
    
            nf_ct_put(ct);
    
            return 0;
    }