#include "vmlinux-x86.h"
#include "bpf/bpf_helpers.h"
#include "bpf/bpf_tracing.h"

// IP Version
#define AF_INET 2
#define AF_INET6 10

// Protocols
#define TCP     6
#define UDP     17
#define UDPLite 136

#define OUTBOUND 0
#define INBOUND 1

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

// Ring buffer for all connection events
struct {
	__uint(type, BPF_MAP_TYPE_RINGBUF);
	__uint(max_entries, 1 << 24);
} pm_connection_events SEC(".maps");

// Event struct that will be sent to Go on each new connection. (The name should be the same as the go generate command)
struct Event {
	u32 saddr[4];
	u32 daddr[4];
	u16 sport;
	u16 dport;
	u32 pid;
	u8 ipVersion;
	u8 protocol;
	u8 direction;
};
struct Event *unused __attribute__((unused));

// Fentry of tcp_connect will be executed when equivalent kernel function is called.
// In the kernel all IP address and ports should be set before tcp_connect is called. [this-function] -> tcp_connect 
SEC("fentry/tcp_connect")
int BPF_PROG(tcp_connect, struct sock *sk) {
	// Alloc space for the event
	struct Event *tcp_info;
	tcp_info = bpf_ringbuf_reserve(&pm_connection_events, sizeof(struct Event), 0);
	if (!tcp_info) {
		return 0;
	}

	// Read PID (Careful: This is the Thread Group ID in kernel speak!)
	tcp_info->pid = __builtin_bswap32((u32)(bpf_get_current_pid_tgid() >> 32));

	// Set protocol
	tcp_info->protocol = TCP;

	// Set direction
	tcp_info->direction = OUTBOUND;

	// Set src and dist ports
	tcp_info->sport = __builtin_bswap16(sk->__sk_common.skc_num);
	tcp_info->dport = sk->__sk_common.skc_dport;

	// Set src and dist IPs
	if (sk->__sk_common.skc_family == AF_INET) {
		tcp_info->saddr[0] = __builtin_bswap32(sk->__sk_common.skc_rcv_saddr);
		tcp_info->daddr[0] = __builtin_bswap32(sk->__sk_common.skc_daddr);
		// Set IP version
		tcp_info->ipVersion = 4;
	} else if (sk->__sk_common.skc_family == AF_INET6) {
		for(int i = 0; i < 4; i++) {
			tcp_info->saddr[i] = __builtin_bswap32(sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32[i]);
		}
		for(int i = 0; i < 4; i++) {
			tcp_info->daddr[i] = __builtin_bswap32(sk->__sk_common.skc_v6_daddr.in6_u.u6_addr32[i]);
		}
		// Set IP version
		tcp_info->ipVersion = 6;
	}

	// Send event
	bpf_ringbuf_submit(tcp_info, 0);
	return 0;
};

// Fexit(function exit) of `udp_v4_connect` will be executed after the `udp_connect` kernel function is called.
//
// Kernel compatibility note:
// - Linux kernels < 6.13: use `ip4_datagram_connect` function 
//   https://elixir.bootlin.com/linux/v6.12.34/source/net/ipv4/udp.c#L2997
// - Linux kernels >= 6.13: function renamed to `udp_connect` 
//   https://elixir.bootlin.com/linux/v6.13-rc1/source/net/ipv4/udp.c#L3131
//
SEC("fexit/udp_connect") // Note: This attach point name may be overwritten (see worker.go: modifyProgramsAttachPoints())
int BPF_PROG(udp_v4_connect, struct sock *sk) {
	// Ignore everything else then IPv4
	if (sk->__sk_common.skc_family != AF_INET) {
		return 0;
	}

	// ip4_datagram_connect return error
	if (sk->__sk_common.skc_dport == 0) {
		return 0;
	}

	// Allocate space for the event.
	struct Event *udp_info;
	udp_info = bpf_ringbuf_reserve(&pm_connection_events, sizeof(struct Event), 0);
	if (!udp_info) {
		return 0;
	}

	// Read PID (Careful: This is the Thread Group ID in kernel speak!)
	udp_info->pid = __builtin_bswap32((u32)(bpf_get_current_pid_tgid() >> 32));

	// Set src and dst ports
	udp_info->sport = __builtin_bswap16(sk->__sk_common.skc_num);
	udp_info->dport = sk->__sk_common.skc_dport;

	// Set src and dst IPs
	udp_info->saddr[0] = __builtin_bswap32(sk->__sk_common.skc_rcv_saddr);
	udp_info->daddr[0] = __builtin_bswap32(sk->__sk_common.skc_daddr);

	// Set IP version
	udp_info->ipVersion = 4;

	// Set protocol
	if(sk->sk_protocol == IPPROTO_UDPLITE) {
		udp_info->protocol = UDPLite;
	} else {
		udp_info->protocol = UDP;
	}

	// Send event
	bpf_ringbuf_submit(udp_info, 0);
	return 0;
}

// Fentry(function enter) of `udp_v6_connect` will be executed after the `udpv6_connect` kernel function is called.
//
// Kernel compatibility note:
// - Linux kernels < 6.13: use `ip6_datagram_connect` function 
//   https://elixir.bootlin.com/linux/v6.12.34/source/net/ipv4/udp.c#L2997
// - Linux kernels >= 6.13: function renamed to `udpv6_connect` 
//   https://elixir.bootlin.com/linux/v6.13-rc1/source/net/ipv4/udp.c#L3131
//
SEC("fexit/udpv6_connect") // Note: This attach point name may be overwritten (see worker.go: modifyProgramsAttachPoints())
int BPF_PROG(udp_v6_connect, struct sock *sk) {
	// Ignore everything else then IPv6
	if (sk->__sk_common.skc_family != AF_INET6) {
		return 0;
	}

	// ip6_datagram_connect return error
	if (sk->__sk_common.skc_dport == 0) {
		return 0;
	}

	// Make sure its udp6 socket
	struct udp6_sock *us = bpf_skc_to_udp6_sock(sk);
	if (!us) {
		return 0;
	}

	// Allocate space for the event.
	struct Event *udp_info;
	udp_info = bpf_ringbuf_reserve(&pm_connection_events, sizeof(struct Event), 0);
	if (!udp_info) {
		return 0;
	}

	// Read PID (Careful: This is the Thread Group ID in kernel speak!)
	udp_info->pid = __builtin_bswap32((u32)(bpf_get_current_pid_tgid() >> 32));

	// Set src and dst ports
	udp_info->sport = __builtin_bswap16(sk->__sk_common.skc_num);
	udp_info->dport = sk->__sk_common.skc_dport;

	// Set src and dst IPs
	for(int i = 0; i < 4; i++) {
		udp_info->saddr[i] = __builtin_bswap32(sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32[i]);
	}
	for(int i = 0; i < 4; i++) {
		udp_info->daddr[i] = __builtin_bswap32(sk->__sk_common.skc_v6_daddr.in6_u.u6_addr32[i]);
	}

	// IP version
	udp_info->ipVersion = 6;

	// Set protocol
	if(sk->sk_protocol == IPPROTO_UDPLITE) {
		udp_info->protocol = UDPLite;
	} else {
		udp_info->protocol = UDP;
	}

	// Send event
	bpf_ringbuf_submit(udp_info, 0);
	return 0;
}