/* ack_recogniser: An eBPF program that correctly recognises modern TCP ACKs,
   with tcp option fields.
 */

#include "bpf_api.h"
#include "uapi/linux/if_ether.h"
#include "uapi/linux/ip.h"
#include "uapi/linux/in.h"
// #include "uapi/linux/ipv6.h" // iproute2 doesn't import this header
#include "uapi/linux/tcp.h"

/* Idealized syntax example to move acks into a priority queue:

tc qdisc del dev $IFACE root 2> /dev/null
tc qdisc add dev $IFACE root handle 1: prio bands 3 priomap 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
tc qdisc add dev $IFACE parent 1:1 handle 10:1 fq_codel
tc qdisc add dev $IFACE parent 1:2 handle 20:1 fq_codel
tc qdisc add dev $IFACE parent 1:3 handle 30:1 fq_codel

tc filter add dev $IFACE  prio 1 bpf object-file ack_recognise.o da ack_match flowid 10:1 # 1:1?

*/

/* A pure ack contains the ip header, the tcp header + options, flags with the
 * ack field set, and no additional payload. That last bit is what every prior
 * ack filter gets wrong, they typically assume an obsolete 64 bytes.
 */

__section_cls_entry
int ack_match(struct __sk_buff *skb)
{
	void *data = (void *)(long)skb->data;
	void *data_end = (void *)(long)skb->data_end;
	struct ethhdr *eth = data;
	struct iphdr *iph = data + sizeof(*eth);
	struct tcphdr *tcp;
	int len, isize;

	if (data + sizeof(*eth) + sizeof(*iph) + sizeof(*tcp) > data_end)
		return 0;

	if (eth->h_proto != htons(ETH_P_IP))
		return 0;

	if (iph->version == 4) {
		if(iph->protocol != IPPROTO_TCP)
			return 0;
		len = iph->tot_len; // htons?
		if(iph->ihl > 4) {
			isize = (iph->ihl+1) * 4 - sizeof(*iph);
			tcp = data + sizeof(*eth) + isize;
		} else {
			return 0;
		}
/* grump: iproute2 does not export ip6hdrs
	} else  if (iph->version == 6) {
		struct ipv6hdr iph6 = (struct ipv6hdr *) iph;
// FIXME: walk ip6hdrs appropriately
		if(iph6->nexthdr != IPPROTO_TCP)
			return 0;
		len = iph6->payload_len;
		isize = sizeof(*iph6) + ??;
		tcp = data + sizeof(*eth) + isize;
		*/
	} else
		return 0;

	if (!tcp->ack) return 0;

	if (isize + sizeof(*tcp) + tcp->doff*4 == len)
		return -1; // We matched, return TC_ACT_RECLASSIFY?

	return 0;
}

char __license[] __section("license") = "GPL";
