import os,socket,struct,ctypes,sys
libc=ctypes.CDLL("libc.so.6",use_errno=True)
CLONE_NEWUSER=0x10000000
CLONE_NEWNET=0x40000000
AF_NETLINK=16;NETLINK_XFRM=6;AF_INET6=10;IPPROTO_ESP=50

# Correct XFRM message types (NLMSG_MIN_TYPE=0x10=16)
XFRM_MSG_NEWSA=16
XFRM_MSG_NEWPOLICY=19
NLM_F_REQUEST=1;NLM_F_ACK=4;NLM_F_CREATE=0x400

def nlattr(t,d):
    l=4+len(d);pad=(4-(len(d)%4))%4
    return struct.pack("HH",l,t)+d+b"\x00"*pad

def send_recv(s,mt,flags,payload):
    h=struct.pack("IHHII",16+len(payload),mt,flags,1,os.getpid())
    s.send(h+payload)
    s.settimeout(3)
    try:
        r=s.recv(4096)
        if len(r)>=20:
            _,rt,_,_,_=struct.unpack_from("IHHII",r,0)
            if rt==2:return struct.unpack_from("<i",r,16)[0]
            return 0
    except:pass
    return -999

pa_r,pa_w=os.pipe();pb_r,pb_w=os.pipe()
pid=os.fork()
if pid==0:
    os.close(pa_r);os.close(pb_w)
    libc.unshare(CLONE_NEWUSER|CLONE_NEWNET)
    os.write(pa_w,b"1");os.close(pa_w)
    c=os.read(pb_r,1);os.close(pb_r)
    if c!=b"1":os._exit(1)

    results=["uid=%d"%os.getuid()]
    xs=socket.socket(AF_NETLINK,socket.SOCK_RAW,NETLINK_XFRM)
    xs.bind((0,0))

    src6=b"\xfe\x80"+b"\x00"*13+b"\x01"
    dst6=b"\xfe\x80"+b"\x00"*13+b"\x02"

    # Correct xfrm_usersa_info: 224 bytes
    sel=b"\x00"*56
    xfrm_id=dst6+struct.pack(">I",0xdeadbeef)+bytes([IPPROTO_ESP])+b"\x00"*3  # 24
    lft=b"\xff"*64   # xfrm_lifetime_cfg: 8 x u64
    curlft=b"\x00"*32  # xfrm_lifetime_cur: 4 x u64
    stats=b"\x00"*12   # xfrm_stats: 3 x u32
    tail=struct.pack("<IIHBBB",0,1,AF_INET6,0,32,0)+b"\x00"*7  # 13+7=20
    sa_info=sel+xfrm_id+src6+lft+curlft+stats+tail
    results.append("sa_info_len=%d"%len(sa_info))  # should be 224

    # AEAD nlattr (XFRMA_ALG_AEAD=9)
    aes_key=b"\x00"*20
    alg_name=b"rfc4106(gcm(aes))\x00"+b"\x00"*46  # 64
    aead_data=alg_name+struct.pack("<II",160,128)+aes_key  # 64+4+4+20=92
    aead_attr=nlattr(9,aead_data)

    e=send_recv(xs,XFRM_MSG_NEWSA,NLM_F_REQUEST|NLM_F_ACK|NLM_F_CREATE,sa_info+aead_attr)
    results.append("NEWSA_type16: errno=%d"%e)

    if e==0:
        # Setup policy
        sel_p=b"\x00"*56
        lft_p=b"\xff"*32+b"\x00"*32  # soft+hard lft + curlft
        tail_p=struct.pack("<IIBBBB",0,0,1,0,0,0)+b"\x00"*12  # 4+4+1+1+1+1+12=24
        # Actually xfrm_userpolicy_info: sel(56)+lft(64)+curlft(32)+priority(4)+index(4)+dir(1)+action(1)+flags(1)+share(1)+pad(12)=176
        lft64_p=b"\xff"*64
        curlft32_p=b"\x00"*32
        policy_tail=struct.pack("<IIBBBB",0,0,1,0,0,0)+b"\x00"*12
        policy_info=sel_p+lft64_p+curlft32_p+policy_tail
        results.append("policy_len=%d"%len(policy_info))

        tmpl=dst6+struct.pack(">I",0xdeadbeef)+bytes([IPPROTO_ESP])+b"\x00\x00\x00"
        tmpl+=struct.pack("<H",AF_INET6)+src6
        tmpl+=struct.pack("<IBBB",1,0,0,0)+b"\x00"
        tmpl+=struct.pack("<III",0xffffffff,0xffffffff,0xffffffff)
        tmpl+=struct.pack("<HH",AF_INET6,0)
        tmpl_attr=nlattr(4,tmpl)

        e2=send_recv(xs,XFRM_MSG_NEWPOLICY,NLM_F_REQUEST|NLM_F_ACK|NLM_F_CREATE,policy_info+tmpl_attr)
        results.append("NEWPOLICY_type19: errno=%d"%e2)

    xs.close()
    print("\n".join(results))
    sys.stdout.flush()
    os._exit(0)

os.close(pa_w);os.close(pb_r)
c=os.read(pa_r,1);os.close(pa_r)
if c==b"1":
    try:open("/proc/%d/uid_map"%pid,"w").write("0 %d 1\n"%os.getuid())
    except:pass
    try:open("/proc/%d/setgroups"%pid,"w").write("deny")
    except:pass
    try:open("/proc/%d/gid_map"%pid,"w").write("0 %d 1\n"%os.getgid())
    except:pass
    os.write(pb_w,b"1")
os.close(pb_w)
os.waitpid(pid,0)
