RTSP connectiontracking

I spent most of last week trying to figure out how connection tracking and NAT works in the 2.6 kernel. There is a patch for connectiontracking of RTSP in patch-o-matig-ng, but it didn't compile without Full NAT enabled. So I had to fix it, and I did.

Here are the notes I made while figuring out how everything fits together:


net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c:
ipv4_conntrack_in() and ipv4_conntrack_local() get registered into PREROUTING

ipv4_conntrack_in() and ipv4_conntrack_local() call nf_conntrack_in()

net/netfilter/nf_conntrack_core.c:
nf_conntrack_in()

# if ((*pskb)->nfct) {
# NF_CT_STAT_INC(ignore);
# return NF_ACCEPT;
# }

previously seen or untracked packets are ignored
"The association between a packet and a conntrack is established by means of a pointer"
nfct is that pointer (struct nf_conntrack *nfct; in struct sk_buff, include/linux/skbuff.h)

# l3proto = __nf_ct_l3proto_find((u_int16_t)pf);
# if ((ret = l3proto->prepare(pskb, hooknum, &dataoff, &protonum)) <= 0) {
# DEBUGP("not prepared to track yet or error occured\n");
# return -ret;
# }

__nf_ct_l3proto_find() looks up a protocol number and returns a struct nf_conntrack_l3proto *.
This contains pointers to functions like invert_tuple and others.
layer3 is IP -> ipv4, ipv6, ...
e.g. net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c calls nf_conntrack_l3proto_register()
to register these functions for ipv4

prepare() fills in some variables for some packet.
dataoff = the offset into the skbuf where data can be found (after the IP header)
protonum = layer 4 protocol number of the packet

# proto = __nf_ct_proto_find((u_int16_t)pf, protonum);

__nf_ct_proto_find() looks up the layer4 protocol number and returns a struct nf_conntrack_protocol *.
This structure also contains pointers to functions
layer4 -> tcp, udp, icmp
e.g. net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c calls nf_conntrack_protocol_register()

# if (proto->error != NULL &&
# (ret = proto->error(*pskb, dataoff, &ctinfo, pf, hooknum)) <= 0) {
# NF_CT_STAT_INC(error);
# NF_CT_STAT_INC(invalid);
# return -ret;
# }

the error() hook, e.g. tcp_error() in net/netfilter/nf_conntrack_proto_tcp.c does some checking:
"Protect conntrack agaist broken packets. Code taken from ipt_unclean.c."

# ct = resolve_normal_ct(*pskb, dataoff, pf, protonum, l3proto, proto,
# &set_reply, &ctinfo);

"The subsystem tries to look up a conntrack that matches with the packet received.
If no conntrack is found, it will be created. This mechanism is implemented in the
function resolve_normal_ct."

resolve_normal_ct() - creates a tuple from the connection
- the tuple is looked up (to find the conntrack)
- if it doesn't exist, one is created and returned with
init_conntrack(), which calls expectfn()
"< Gandalf__> it's a functionpointer that helpers can set
if they want to be notified that the expectation was used"
- if the packet is a reply, set the ctinfo to established and
mark as a reply. set_reply is 1
- otherwise (meaning packets have gone both ways), categorize
the packet and set ctinfo to either established, related or
new, set_reply = 0
- nfct in the packet is no longer NULL
- nfctinfo in the packet is set to ctinfo
# if (!ct) {
# /* Not valid part of a connection */
# NF_CT_STAT_INC(invalid);
# return NF_ACCEPT;
# }
#
# if (IS_ERR(ct)) {
# /* Too stressed to deal. */
# NF_CT_STAT_INC(drop);
# return NF_DROP;
# }
#
# NF_CT_ASSERT((*pskb)->nfct);

the returned conntrack is checked.

# ret = proto->packet(ct, *pskb, dataoff, ctinfo, pf, hooknum);

"/* Returns verdict for packet, or -1 for invalid. */"
checks whether packet is a valid packet in this connection (packet in window etc)

# if (ret < 0) {
# /* Invalid: inverse of the return code tells
# * the netfilter core what to do */
# DEBUGP("nf_conntrack_in: Can't track with proto module\n");
# nf_conntrack_put((*pskb)->nfct);
# (*pskb)->nfct = NULL;
# NF_CT_STAT_INC(invalid);
# return -ret;
# }

nf_conntrack_put() calls destroy on the conntrack
"/* Called when a conntrack entry has already been removed from the hashes
* and is about to be deleted from memory */"

# if (set_reply && !test_and_set_bit(IPS_SEEN_REPLY_BIT, &ct->status))
# nf_conntrack_event_cache(IPCT_STATUS, *pskb);

if this is the first reply (?), call nf_conntrack_event_cache() # FIXME: what does event cache do ???

net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c:
ipv4_conntrack_help() gets registered into LOCAL INPUT and POSTROUTING

ipv4_conntrack_help()
matches the packet against a conntrack
calls nfct_help() on the conntrack to get a helper function
calls the helper function

net/ipv4/netfilter/ip_conntrack_core.c:
ip_conntrack_helper_register() is used to register helper functions



ip_nat_*.c mostly just set a hook at init()


info on expectfn:
ip_nat_irc.c:
/* When you see the packet, we need to NAT it the same as the
* this one. */
exp->expectfn = ip_nat_follow_master;




probable flow:
- new connection comes into prerouting. no conntrack exists so one is created with
init_conntrack() and expectfn() is called if this is a connection we were expecting
and the helper wants to be notified.
- on other packets going out on postrouting or local input, the helper is called,
which checks if an expectation should be launched.
- in this function, also the nat hook is called, which can mangle the packet (like FTP port stuff)
and launches the expectation



The article about netfilter's connectiontracking by Pablo Neira Ayuso was very helpful, as was the kernel newbies website.