#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sched.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/xfrm.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <sys/wait.h>

static int pa[2],pb[2];

static void nl_send(int s,int t,int f,void*d,int l){
    char hdr[16]={0};
    *(unsigned int*)(hdr+0)=16+l;
    *(unsigned short*)(hdr+4)=t;
    *(unsigned short*)(hdr+6)=f;
    *(unsigned int*)(hdr+8)=1;
    *(unsigned int*)(hdr+12)=getpid();
    struct iovec iov[2]={{hdr,16},{d,l}};
    struct msghdr m={0};
    m.msg_iov=iov;m.msg_iovlen=2;
    sendmsg(s,&m,0);
}
static int nl_err(int s){
    char buf[512];
    alarm(3);int r=recv(s,buf,sizeof(buf),0);alarm(0);
    if(r<20)return -999;
    if(*(unsigned short*)(buf+4)==2)return *(int*)(buf+16);
    return 0;
}
int main(void){
    pipe(pa);pipe(pb);
    pid_t child=fork();
    if(child==0){
        close(pa[0]);close(pb[1]);
        if(unshare(CLONE_NEWUSER|CLONE_NEWNET)<0){perror("unshare");_exit(1);}
        write(pa[1],"1",1);close(pa[1]);
        char c;read(pb[0],&c,1);close(pb[0]);
        if(c!='1')_exit(1);

        printf("uid=%d\n",getuid());fflush(stdout);

        int xs=socket(AF_NETLINK,SOCK_RAW,NETLINK_XFRM);
        struct sockaddr_nl nl={.nl_family=AF_NETLINK};
        bind(xs,(void*)&nl,sizeof(nl));

        struct xfrm_usersa_info sa={0};
        // dst = fe80::2
        sa.id.daddr.a6[0]=__builtin_bswap32(0xfe800000);
        sa.id.daddr.a6[3]=__builtin_bswap32(0x00000002);
        sa.id.spi=__builtin_bswap32(0x12345678);
        sa.id.proto=IPPROTO_ESP;
        // src = fe80::1
        sa.saddr.a6[0]=__builtin_bswap32(0xfe800000);
        sa.saddr.a6[3]=__builtin_bswap32(0x00000001);
        // lifetime: unlimited
        sa.lft.soft_byte_limit=sa.lft.hard_byte_limit=~0ULL;
        sa.lft.soft_packet_limit=sa.lft.hard_packet_limit=~0ULL;
        sa.lft.soft_add_expires_seconds=sa.lft.hard_add_expires_seconds=~0ULL;
        sa.lft.soft_use_expires_seconds=sa.lft.hard_use_expires_seconds=~0ULL;
        sa.reqid=1;
        sa.family=AF_INET6;
        sa.mode=XFRM_MODE_TRANSPORT;
        sa.replay_window=32;
        printf("sizeof sa=%zu\n",sizeof(sa));fflush(stdout);

        // Build AEAD nlattr
        struct {unsigned short len,type;} nla_hdr;
        struct xfrm_algo_aead aead;
        memset(&aead,0,sizeof(aead));
        strncpy(aead.alg_name,"rfc4106(gcm(aes))",64);
        aead.alg_key_len=160;
        aead.alg_icv_len=128;
        // 20 bytes key (zero)
        char key[20]={0};
        // Full AEAD attr: header + xfrm_algo_aead header + key
        int aead_data_len=sizeof(aead)+sizeof(key);
        char aead_buf[256]={0};
        *(unsigned short*)(aead_buf+0)=4+aead_data_len;
        *(unsigned short*)(aead_buf+2)=XFRMA_ALG_AEAD;
        memcpy(aead_buf+4,&aead,sizeof(aead));
        memcpy(aead_buf+4+sizeof(aead),key,sizeof(key));
        int aead_attr_len=4+aead_data_len;
        // pad to 4 bytes
        while(aead_attr_len%4)aead_attr_len++;

        printf("sizeof xfrm_algo_aead=%zu\n",sizeof(aead));fflush(stdout);

        // Build full payload
        char buf[1024]={0};
        memcpy(buf,&sa,sizeof(sa));
        memcpy(buf+sizeof(sa),aead_buf,aead_attr_len);
        int total=sizeof(sa)+aead_attr_len;

        printf("sending total=%d\n",total);fflush(stdout);
        nl_send(xs,XFRM_MSG_NEWSA,0x605,buf,total);
        int e=nl_err(xs);
        printf("NEWSA errno=%d\n",e);fflush(stdout);
        close(xs);
        _exit(0);
    }
    close(pa[1]);close(pb[0]);
    char c;read(pa[0],&c,1);close(pa[0]);
    char path[256],buf[64];
    snprintf(path,256,"/proc/%d/uid_map",child);
    snprintf(buf,64,"0 %d 1\n",getuid());
    int fd=open(path,O_WRONLY);if(fd>=0){write(fd,buf,strlen(buf));close(fd);}
    snprintf(path,256,"/proc/%d/setgroups",child);
    fd=open(path,O_WRONLY);if(fd>=0){write(fd,"deny",4);close(fd);}
    snprintf(path,256,"/proc/%d/gid_map",child);
    snprintf(buf,64,"0 %d 1\n",getgid());
    fd=open(path,O_WRONLY);if(fd>=0){write(fd,buf,strlen(buf));close(fd);}
    write(pb[1],"1",1);close(pb[1]);
    int st;waitpid(child,&st,0);
    return 0;
}
