/* uprobe_ssl.c — Uprobes SSL_read/SSL_set_fd et tracepoints accept4
 *
 * Intercepte les appels OpenSSL pour capturer le trafic déchiffré,
 * et corrige l'association socket ↔ SSL* via les tracepoints syscalls/accept4.
 * Les tracepoints sont plus stables que les kprobes car ils ne dépendent pas
 * du nom manglé __x64_sys_accept4 (variable selon la version du kernel).
 * ============================================================================ */

#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
#include "bpf_types.h"

/* Taille maximale de données SSL à copier par événement */
#define MAX_SSL_DATA 4096

/* ---------------------------------------------------------------------------
 * Map temporaire : pid_tgid → upeer_sockaddr (sauvegardé à l'entrée d'accept4)
 * ---------------------------------------------------------------------------*/
struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __uint(max_entries, 10240);
    __type(key, __u64);
    __type(value, __u64);   /* pointeur userspace vers sockaddr_in */
} accept_args_map SEC(".maps");

/* ---------------------------------------------------------------------------
 * Structs pour les tracepoints syscalls/sys_{enter,exit}_accept4
 *
 * Format vérifié avec : /sys/kernel/tracing/events/syscalls/sys_enter_accept4/format
 * Valable pour les kernels 4.x → 6.x (stable, CO-RE non requis).
 * ---------------------------------------------------------------------------*/
struct sys_enter_accept4_ctx {
    __u16 common_type;
    __u8  common_flags;
    __u8  common_preempt_count;
    __s32 common_pid;
    __s32 __syscall_nr;
    __u32 _pad;
    __s64 listen_fd;
    struct sockaddr *upeer_sockaddr;  /* adresse userspace du client */
    int   *upeer_addrlen;
    __s64 flags;
};

struct sys_exit_accept4_ctx {
    __u16 common_type;
    __u8  common_flags;
    __u8  common_preempt_count;
    __s32 common_pid;
    __s32 __syscall_nr;
    __u32 _pad;
    __s64 ret;   /* fd retourné par accept4, ou < 0 si erreur */
};

/* ===========================================================================
 * uprobe_ssl_set_fd — Intercept SSL_set_fd(SSL *s, int fd)
 *
 * Associe un ssl_ptr à ses informations de connexion (fd, src_ip, src_port)
 * en consultant fd_conn_map. Si fd_conn_map est vide (accept4 non disponible),
 * enregistre quand même l'association ssl_ptr → fd avec IP=0 pour que le
 * Go userspace puisse faire le lookup IP via /proc/<pid>/net/tcp.
 * ===========================================================================*/
SEC("uprobe/SSL_set_fd")
int uprobe_ssl_set_fd(struct pt_regs *ctx)
{
    __u64 ssl_ptr = ((__u64)PT_REGS_PARM1(ctx));
    __u32 fd      = ((__u32)PT_REGS_PARM2(ctx));

    struct ssl_conn_info new_conn = {};
    new_conn.fd = fd;

    /* Tenter de récupérer les infos de connexion via fd_conn_map (accept4) */
    struct ssl_conn_info *conn = bpf_map_lookup_elem(&fd_conn_map, &fd);
    if (conn) {
        new_conn.src_ip   = conn->src_ip;
        new_conn.src_port = conn->src_port;
    }
    /* Sans accept4, src_ip=0 / src_port=0 — le userspace Go fera le lookup /proc */

    bpf_map_update_elem(&ssl_conn_map, &ssl_ptr, &new_conn, BPF_ANY);
    return 0;
}

/* ===========================================================================
 * uprobe_ssl_read_entry — Entrée de SSL_read(SSL *ssl, void *buf, int num)
 *
 * Sauvegarde les arguments pour l'uretprobe correspondant.
 * ===========================================================================*/
SEC("uprobe/SSL_read")
int uprobe_ssl_read_entry(struct pt_regs *ctx)
{
    __u64 pid_tgid = bpf_get_current_pid_tgid();

    struct ssl_read_args args = {};
    args.ssl_ptr = (__u64)PT_REGS_PARM1(ctx);
    args.buf_ptr = (__u64)PT_REGS_PARM2(ctx);
    args.num     = (__u32)PT_REGS_PARM3(ctx);

    bpf_map_update_elem(&ssl_args_map, &pid_tgid, &args, BPF_ANY);
    return 0;
}

/* ===========================================================================
 * uretprobe_ssl_read_exit — Retour de SSL_read
 *
 * Lit le buffer déchiffré et l'émet dans rb_ssl_data.
 * ===========================================================================*/
SEC("uretprobe/SSL_read")
int uretprobe_ssl_read_exit(struct pt_regs *ctx)
{
    __u64 pid_tgid = bpf_get_current_pid_tgid();

    /* Récupérer les arguments sauvegardés à l'entrée */
    struct ssl_read_args *args = bpf_map_lookup_elem(&ssl_args_map, &pid_tgid);
    if (!args)
        return 0;

    /* Vérifier que la lecture a réussi (valeur de retour > 0) */
    long retval = PT_REGS_RC(ctx);
    if (retval <= 0) {
        bpf_map_delete_elem(&ssl_args_map, &pid_tgid);
        return 0;
    }

    /* Allouer un slot dans le ring buffer */
    struct ssl_data_event *evt = bpf_ringbuf_reserve(&rb_ssl_data, sizeof(*evt), 0);
    if (!evt) {
        bpf_map_delete_elem(&ssl_args_map, &pid_tgid);
        return 0;
    }

    evt->pid_tgid    = pid_tgid;
    evt->direction   = 0;  /* lecture = client vers serveur */
    evt->timestamp_ns = bpf_ktime_get_ns();

    /* Limiter la copie à MAX_SSL_DATA octets */
    __u32 data_len = (retval > MAX_SSL_DATA) ? MAX_SSL_DATA : (__u32)retval;
    evt->data_len = data_len;

    /* Copier depuis l'espace utilisateur */
    bpf_probe_read_user(evt->data, data_len & (MAX_SSL_DATA - 1), (void *)args->buf_ptr);

    /* Retrouver les infos de connexion via ssl_ptr */
    struct ssl_conn_info *conn = bpf_map_lookup_elem(&ssl_conn_map, &args->ssl_ptr);
    if (conn) {
        evt->fd       = conn->fd;
        evt->src_ip   = conn->src_ip;
        evt->src_port = conn->src_port;
    } else {
        evt->fd       = 0;
        evt->src_ip   = 0;
        evt->src_port = 0;
    }

    bpf_ringbuf_submit(evt, 0);
    bpf_map_delete_elem(&ssl_args_map, &pid_tgid);

    return 0;
}

/* ===========================================================================
 * kprobe_accept4_entry — Entrée de accept4 via tracepoint syscalls
 *
 * Utilise SEC("tracepoint/syscalls/sys_enter_accept4") au lieu d'un kprobe
 * pour éviter la dépendance au nom manglé __x64_sys_accept4 (kernel 5.1+).
 * Le contexte tracepoint expose directement upeer_sockaddr sans indirection.
 * ===========================================================================*/
SEC("tracepoint/syscalls/sys_enter_accept4")
int kprobe_accept4_entry(struct sys_enter_accept4_ctx *ctx)
{
    __u64 pid_tgid     = bpf_get_current_pid_tgid();
    __u64 sockaddr_ptr = (__u64)ctx->upeer_sockaddr;

    bpf_map_update_elem(&accept_args_map, &pid_tgid, &sockaddr_ptr, BPF_ANY);
    return 0;
}

/* ===========================================================================
 * kretprobe_accept4_exit — Retour de accept4 via tracepoint syscalls
 *
 * Lit la sockaddr_in pour extraire src_ip:src_port du client,
 * peuple accept_map et fd_conn_map, et émet dans rb_accept.
 * ===========================================================================*/
SEC("tracepoint/syscalls/sys_exit_accept4")
int kretprobe_accept4_exit(struct sys_exit_accept4_ctx *ctx)
{
    __u64 pid_tgid = bpf_get_current_pid_tgid();

    /* Vérifier que accept4 a réussi (fd ≥ 0) */
    long new_fd = ctx->ret;
    if (new_fd < 0) {
        bpf_map_delete_elem(&accept_args_map, &pid_tgid);
        return 0;
    }

    /* Récupérer le pointeur vers sockaddr_in */
    __u64 *sockaddr_ptr_p = bpf_map_lookup_elem(&accept_args_map, &pid_tgid);
    if (!sockaddr_ptr_p) {
        return 0;
    }
    __u64 sockaddr_ptr = *sockaddr_ptr_p;
    bpf_map_delete_elem(&accept_args_map, &pid_tgid);

    if (!sockaddr_ptr)
        return 0;

    /* Lire la structure sockaddr_in depuis l'espace utilisateur */
    /* struct sockaddr_in: sin_family(2) + sin_port(2) + sin_addr(4) */
    __u8 sa_buf[8] = {};
    bpf_probe_read_user(sa_buf, sizeof(sa_buf), (void *)sockaddr_ptr);

    /* Extraire port (octets 2-3) et adresse IP (octets 4-7) */
    __u16 sin_port = (__u16)(sa_buf[2] << 8) | sa_buf[3];   /* network byte order */
    __u32 sin_addr = *(__u32 *)(sa_buf + 4);                 /* network byte order */

    __u32 src_ip   = __builtin_bswap32(sin_addr);            /* host byte order */
    __u16 src_port = __builtin_bswap16(sin_port);            /* host byte order */
    __u32 fd       = (__u32)new_fd;

    /* Peupler accept_map[{pid_tgid, fd}] */
    struct accept_key akey = { .pid_tgid = pid_tgid, .fd = fd };
    struct accept_event aevt = {
        .pid_tgid    = pid_tgid,
        .fd          = fd,
        .src_ip      = src_ip,
        .src_port    = src_port,
        .timestamp_ns = bpf_ktime_get_ns(),
    };
    bpf_map_update_elem(&accept_map, &akey, &aevt, BPF_ANY);

    /* Peupler fd_conn_map[fd] pour accès rapide par SSL_set_fd */
    struct ssl_conn_info conn_info = {
        .fd       = fd,
        .src_ip   = src_ip,
        .src_port = src_port,
    };
    bpf_map_update_elem(&fd_conn_map, &fd, &conn_info, BPF_ANY);

    /* Émettre dans rb_accept */
    struct accept_event *out = bpf_ringbuf_reserve(&rb_accept, sizeof(*out), 0);
    if (!out)
        return 0;

    out->pid_tgid    = pid_tgid;
    out->fd          = fd;
    out->src_ip      = src_ip;
    out->src_port    = src_port;
    out->timestamp_ns = aevt.timestamp_ns;

    bpf_ringbuf_submit(out, 0);

    return 0;
}

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

