It all looks so easy, fill in a structure of the packets you want to see:
memset(&helper, 0, sizeof(struct nf_conntrack_helper)); /* Information about this conntrack module. */ helper.name = "skinny"; helper.me = THIS_MODULE; helper.help = skinny_conntrack_helper; helper.max_expected = 1; helper.timeout = 0; /* Send all Skinny packets to this conntrack module. */ helper.tuple.src.l3num = AF_INET; helper.tuple.dst.protonum = IPPROTO_TCP; helper.tuple.dst.u.tcp.port = htons(2000);
and then kick it off
ret = nf_conntrack_helper_register(&helper);
Now skinny_conntrack_helper() should be called for each TCP packet on port 2000. But it's not, it's called for almost anything but. The registration comes with a mask option, so I use that too, even though little of the kernel conntrack modules use masks (if so, how do they work? Do they work?)
helper.mask.dst.protonum = 0xff; helper.mask.src.l3num = 0xffff; helper.mask.dst.u.tcp.port = __constant_htons(0xffff);
No difference. Sigh. I'd love some up-to-date documentation at this point. But that appears to be too much to hope for. "Use the code Luke" is just confusion, since practice there seems contradictory. And I've wasted a whole evening over this, sigh.