//
// Syd: rock-solid unikernel
// src/syd-tor.rs: Syd's SOCKS Proxy Forwarder
//
// Copyright (c) 2024 Ali Polatel <alip@chesswob.org>
// Based in part upon socksns crate which is:
//     Copyright (c) 2020 Steven Engler
//     SPDX-License-Identifier: MIT
//
// SPDX-License-Identifier: GPL-3.0-or-later

use std::{
    env,
    ffi::CString,
    net::{IpAddr, Ipv4Addr, SocketAddr},
    os::{
        fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd},
        unix::net::UnixStream,
    },
    path::Path,
    process::{exit, ExitCode},
};

use getargs::{Opt, Options};
use libseccomp::{scmp_cmp, ScmpAction, ScmpFilterContext, ScmpSyscall};
use nix::{
    errno::Errno,
    fcntl::{splice, OFlag, SpliceFFlags},
    poll::PollTimeout,
    sched::{unshare, CloneFlags},
    sys::{
        epoll::{Epoll, EpollCreateFlags, EpollEvent, EpollFlags},
        resource::{getrlimit, setrlimit, Resource},
        socket::{
            accept4, connect, socket, AddressFamily, SockFlag, SockType, SockaddrIn, SockaddrIn6,
            SockaddrLike,
        },
    },
    unistd::{chdir, chroot, close, pipe2, write},
};
use rustc_hash::FxHashMap;
use sendfd::RecvWithFd;
use syd::{
    compat::epoll_ctl_safe,
    config::PIPE_BUF,
    err::{SydError, SydResult},
    fs::{path_exists, set_nonblock, set_pipemax, SmallPathBuf},
    proc::proc_pipemax,
    seccomp_native_has_socketcall,
};

// Set global allocator to mimalloc.
#[global_allocator]
static GLOBAL: mimalloc2_rust::GlobalMiMalloc = mimalloc2_rust::GlobalMiMalloc;

#[allow(clippy::disallowed_methods)]
fn main() -> SydResult<ExitCode> {
    // Configure mimalloc.
    syd::configure_mimalloc();

    // Parse options.
    let (fpid, sock, addr, pmax, debug) = parse_options()?;

    // Ignore all signals except SIG{KILL,STOP,PIPE,CHLD,Core}.
    // This is used to ensure we can deny {rt_,}sigreturn(2) to mitigate SROP.
    syd::ignore_signals()?;

    // Close all file descriptors, except:
    // 1. Standard input, output, and error which are set to /dev/null by Syd.
    // 2. The PID fd and the socket fd passed by the Syd process.
    let max_fd = std::cmp::max(fpid.as_raw_fd(), sock.as_raw_fd());
    for fd in 3..=max_fd {
        if fd != fpid.as_raw_fd() && fd != sock.as_raw_fd() {
            let _ = close(fd);
        }
    }
    // SAFETY: nix does not have a close_range wrapper yet.
    let _ = unsafe {
        nix::libc::syscall(
            nix::libc::SYS_close_range,
            max_fd + 1,
            nix::libc::c_int::MAX,
            0,
        )
    };

    // Attempt to set file-max to hard limit overriding the soft limit.
    if let Ok((soft_limit, hard_limit)) = getrlimit(Resource::RLIMIT_NOFILE) {
        if soft_limit < hard_limit {
            let _ = setrlimit(Resource::RLIMIT_NOFILE, hard_limit, hard_limit);
        }
    }

    // Receive the socket bound inside the namespace.
    let mut buf = [0u8; 1];
    let mut lfd = [-1; 1];
    let proxy = match sock.recv_with_fd(&mut buf, &mut lfd) {
        Ok((_, 1)) if lfd[0] >= 0 => {
            // Socket no longer necessary.
            drop(sock);
            // SAFETY: recvmsg received a valid FD.
            unsafe { OwnedFd::from_raw_fd(lfd[0]) }
        }
        Ok(_) => return Err(Errno::EBADF.into()),
        Err(error) => return Err(error.into()),
    };

    // Create epoll instance.
    let epoll = Epoll::new(EpollCreateFlags::EPOLL_CLOEXEC)?;

    // Confine.
    confine(&*addr, debug)?;

    let debug_fd =
        unsafe { BorrowedFd::borrow_raw(if debug { nix::libc::STDERR_FILENO } else { -42 }) };

    // Run the proxy server.
    match run_proxy_server(&epoll, &fpid, &proxy, addr, pmax) {
        Ok(_) => Ok(ExitCode::SUCCESS),
        Err(error) => {
            let error = format!("syd-tor: {error}\n");
            let error = CString::new(error).unwrap();
            let _ = write(debug_fd, error.as_bytes_with_nul());
            Ok(ExitCode::FAILURE)
        }
    }
}

/// Run the proxy server
pub fn run_proxy_server<F: AsFd>(
    epoll: &Epoll,
    pid_fd: &F,
    listening_fd: &F,
    external_addr: Box<dyn SockaddrLike>,
    pipe_max: usize,
) -> SydResult<()> {
    // Set socket options.
    set_socket_options(listening_fd, 0);

    // Set listening fd non blocking.
    set_nonblock(&listening_fd.as_fd(), true)?;

    // Add PIDFd to epoll (becomes readable when process terminates).
    let event = EpollEvent::new(
        EpollFlags::EPOLLET | EpollFlags::EPOLLIN | EpollFlags::EPOLLRDHUP,
        pid_fd.as_fd().as_raw_fd() as u64,
    );
    epoll_ctl_safe(epoll, pid_fd, Some(event))?;

    // Add listening socket to epoll (not necessary to set EPOLL{ERR,HUP}).
    let event = EpollEvent::new(
        EpollFlags::EPOLLET | EpollFlags::EPOLLIN | EpollFlags::EPOLLRDHUP,
        listening_fd.as_fd().as_raw_fd() as u64,
    );
    epoll_ctl_safe(epoll, listening_fd, Some(event))?;

    let mut client_manager = ClientManager::new();

    // TODO: MAX_EVENTS=1024 move to config.rs
    let mut events = vec![EpollEvent::empty(); 1024];
    loop {
        // Wait for events and handle EINTR.
        let n = match epoll.wait(&mut events, PollTimeout::NONE) {
            Ok(n) => n,
            Err(Errno::EINTR) => continue, // Retry if interrupted by a signal.
            Err(errno) => return Err(errno.into()),
        };

        for event in events.iter().take(n) {
            let fd = event.data() as RawFd;
            let event_flags = event.events();

            if fd == pid_fd.as_fd().as_raw_fd() {
                // Syd exited, exit gracefully.
                return Ok(());
            } else if fd == listening_fd.as_fd().as_raw_fd() {
                if event_flags.intersects(
                    EpollFlags::EPOLLHUP | EpollFlags::EPOLLRDHUP | EpollFlags::EPOLLERR,
                ) {
                    // The listening socket's other end has been closed
                    // or an error occurred, exit gracefully.
                    return Ok(());
                }

                // Accept new connection
                handle_new_connection(
                    listening_fd,
                    &*external_addr,
                    epoll,
                    pipe_max,
                    &mut client_manager,
                )?;
            } else {
                // Handle events for existing connections
                handle_existing_connection(fd, event_flags, epoll, &mut client_manager)?;
            }
        }
    }
}

fn handle_existing_connection(
    fd: RawFd,
    event_flags: EpollFlags,
    epoll: &Epoll,
    client_manager: &mut ClientManager,
) -> SydResult<()> {
    if let Some(client) = client_manager.get_client_mut(fd) {
        let result = if event_flags.contains(EpollFlags::EPOLLIN) {
            // Handle readable events
            if fd == client.fd_cli.as_raw_fd() {
                client.handle_splice(false)
            } else if fd == client.fd_ext.as_raw_fd() {
                client.handle_splice(true)
            } else {
                // SAFETY: This cannot happen.
                unreachable!();
            }
        } else if event_flags.contains(EpollFlags::EPOLLOUT) {
            // Handle writable events
            if fd == client.fd_cli.as_raw_fd() {
                client.attempt_write(false)
            } else if fd == client.fd_ext.as_raw_fd() {
                client.attempt_write(true)
            } else {
                // SAFETY: This cannot happen.
                unreachable!();
            }
        } else {
            // Handle disconnection events.
            Err(Errno::EPIPE)
        };

        // Handle errors and close clients if needed.
        if let Err(e) = result {
            if e != Errno::EAGAIN {
                if let Some(client) = client_manager.remove_client(fd) {
                    client.close(epoll);
                }
            }
        }
    }

    Ok(())
}

fn handle_new_connection<F: AsFd>(
    listening_fd: &F,
    external_addr: &dyn SockaddrLike,
    epoll: &Epoll,
    pipe_max: usize,
    client_manager: &mut ClientManager,
) -> SydResult<()> {
    // Quoting accept(2):
    // Linux accept() (and accept4()) passes already-pending network
    // errors on the new socket as an error code from accept(). This
    // behavior differs from other BSD socket implementations. For
    // reliable operation the application should detect the network
    // errors defined for the protocol after accept() and treat them
    // like EAGAIN by retrying. In the case of TCP/IP, these are
    // ENETDOWN, EPROTO, ENOPROTOOPT, EHOSTDOWN, ENONET, EHOSTUNREACH,
    // EOPNOTSUPP, and ENETUNREACH.
    #[allow(unreachable_patterns)]
    let fd_cli = match accept4(
        listening_fd.as_fd().as_raw_fd(),
        SockFlag::SOCK_NONBLOCK | SockFlag::SOCK_CLOEXEC,
    ) {
        Ok(fd) => unsafe { OwnedFd::from_raw_fd(fd) },
        Err(
            Errno::EAGAIN
            | Errno::EHOSTDOWN
            | Errno::EHOSTUNREACH
            | Errno::ENETDOWN
            | Errno::ENETUNREACH
            | Errno::ENONET
            | Errno::ENOPROTOOPT
            | Errno::EOPNOTSUPP
            | Errno::EPROTO
            | Errno::EWOULDBLOCK,
        ) => return Ok(()), // No more connections to accept
        Err(errno) => return Err(errno.into()),
    };
    let client = Client::new(fd_cli, external_addr, pipe_max)?;
    client_manager.add_client(epoll, client)?;
    Ok(())
}

type FDMap = FxHashMap<RawFd, RawFd>;
type CliMap = FxHashMap<RawFd, Client>;

struct ClientManager {
    clients: CliMap,
    fd_index: FDMap,
}

impl ClientManager {
    fn new() -> Self {
        ClientManager {
            clients: CliMap::default(),
            fd_index: FDMap::default(),
        }
    }

    fn add_client(&mut self, epoll: &Epoll, client: Client) -> SydResult<()> {
        let fd_cli = client.fd_cli.as_fd();
        let fd_ext = client.fd_ext.as_fd();

        let event = EpollEvent::new(
            EpollFlags::EPOLLET
                | EpollFlags::EPOLLIN
                | EpollFlags::EPOLLOUT
                | EpollFlags::EPOLLHUP
                | EpollFlags::EPOLLRDHUP,
            fd_cli.as_raw_fd() as u64,
        );
        epoll_ctl_safe(epoll, &fd_cli, Some(event))?;

        let event = EpollEvent::new(
            EpollFlags::EPOLLET
                | EpollFlags::EPOLLIN
                | EpollFlags::EPOLLOUT
                | EpollFlags::EPOLLHUP
                | EpollFlags::EPOLLRDHUP,
            fd_ext.as_raw_fd() as u64,
        );
        epoll_ctl_safe(epoll, &fd_ext, Some(event))?;

        let fd_cli = fd_cli.as_raw_fd();
        let fd_ext = fd_ext.as_raw_fd();
        self.fd_index.insert(fd_cli, fd_cli);
        self.fd_index.insert(fd_ext, fd_cli);
        self.clients.insert(fd_cli, client);

        Ok(())
    }

    fn remove_client(&mut self, fd: RawFd) -> Option<Client> {
        if let Some(fd_cli) = self.fd_index.remove(&fd) {
            self.fd_index
                .remove(&self.clients[&fd_cli].fd_ext.as_raw_fd());
            return self.clients.remove(&fd_cli);
        }
        None
    }

    fn get_client_mut(&mut self, fd: RawFd) -> Option<&mut Client> {
        if let Some(&fd_cli) = self.fd_index.get(&fd) {
            self.clients.get_mut(&fd_cli)
        } else {
            None
        }
    }
}

struct Client {
    fd_cli: OwnedFd,
    fd_ext: OwnedFd,

    pipe_cli: (OwnedFd, OwnedFd),
    pipe_ext: (OwnedFd, OwnedFd),

    pipe_max: (usize, usize),
}

impl Client {
    /// Create a new client
    fn new(
        client_fd: OwnedFd,
        external_addr: &dyn SockaddrLike,
        pipe_max: usize,
    ) -> SydResult<Self> {
        let (pipe_in_from_client, pipe_out_to_client) =
            pipe2(OFlag::O_NONBLOCK | OFlag::O_CLOEXEC)?;
        let (pipe_in_from_ext, pipe_out_to_ext) = pipe2(OFlag::O_NONBLOCK | OFlag::O_CLOEXEC)?;

        // Set pipe max size for efficient transmission.
        // Careful, this may return EPERM mid-fly so
        // use the safest maximum as necessary.
        let (pipe_max_0, pipe_max_1) = if pipe_max <= *PIPE_BUF {
            (*PIPE_BUF, *PIPE_BUF)
        } else {
            (
                set_pipemax(&pipe_in_from_client, pipe_max as nix::libc::c_int)
                    .unwrap_or(*PIPE_BUF),
                set_pipemax(&pipe_in_from_ext, pipe_max as nix::libc::c_int).unwrap_or(*PIPE_BUF),
            )
        };

        // Set socket options on the client fd.
        set_socket_options(&client_fd, pipe_max_0);

        let family = external_addr
            .family()
            .ok_or::<SydError>(Errno::EAFNOSUPPORT.into())?;
        let ext_fd = socket(
            family,
            SockType::Stream,
            SockFlag::SOCK_NONBLOCK | SockFlag::SOCK_CLOEXEC,
            None,
        )?;

        connect(ext_fd.as_raw_fd(), external_addr).or_else(|e| {
            if e == Errno::EINPROGRESS {
                Ok(())
            } else {
                Err(e)
            }
        })?;

        Ok(Client {
            fd_cli: client_fd,
            fd_ext: ext_fd,

            pipe_cli: (pipe_in_from_client, pipe_out_to_client),
            pipe_ext: (pipe_in_from_ext, pipe_out_to_ext),

            pipe_max: (pipe_max_0, pipe_max_1),
        })
    }

    /// Close client connection and clean up resources
    fn close(self, epoll: &Epoll) {
        let _ = epoll_ctl_safe(epoll, &self.fd_cli, None);
        let _ = epoll_ctl_safe(epoll, &self.fd_ext, None);
        drop(self); // Closes all the OwnedFds.
    }

    fn handle_splice(&self, ext: bool) -> std::result::Result<(), Errno> {
        let (src_fd, dst_fd, pipe_in_fd, pipe_out_fd, pipe_max) = if ext {
            (
                self.fd_ext.as_fd(),
                self.fd_cli.as_fd(),
                self.pipe_cli.0.as_fd(),
                self.pipe_cli.1.as_fd(),
                self.pipe_max.0,
            )
        } else {
            (
                self.fd_cli.as_fd(),
                self.fd_ext.as_fd(),
                self.pipe_cli.0.as_fd(),
                self.pipe_cli.1.as_fd(),
                self.pipe_max.1,
            )
        };

        loop {
            match Self::splice_data(&src_fd, &pipe_out_fd, pipe_max) {
                Ok(_) => while Self::splice_data(&pipe_in_fd, &dst_fd, pipe_max).is_ok() {},
                Err(error) => break Err(error),
            }
        }
    }

    fn attempt_write(&self, ext: bool) -> std::result::Result<(), Errno> {
        let (src_fd, dst_fd, pipe_max) = if ext {
            (
                self.pipe_cli.0.as_fd(),
                self.fd_ext.as_fd(),
                self.pipe_max.0,
            )
        } else {
            (
                self.pipe_ext.0.as_fd(),
                self.fd_cli.as_fd(),
                self.pipe_max.1,
            )
        };

        loop {
            match Self::splice_data(&src_fd, &dst_fd, pipe_max) {
                Ok(_) => {}
                Err(error) => break Err(error),
            }
        }
    }

    /// Splice data from source to destination
    fn splice_data<F: AsFd>(
        src_fd: &F,
        dst_fd: &F,
        pipe_max: usize,
    ) -> std::result::Result<(), Errno> {
        if splice(
            src_fd,
            None,
            dst_fd,
            None,
            pipe_max,
            SpliceFFlags::SPLICE_F_NONBLOCK | SpliceFFlags::SPLICE_F_MORE,
        )? > 0
        {
            Ok(())
        } else {
            // Handle EOF.
            Err(Errno::EPIPE)
        }
    }
}

/// Transit this process to a confined state.
fn confine(addr: &dyn SockaddrLike, dry_run: bool) -> SydResult<()> {
    let family = addr
        .family()
        .ok_or::<SydError>(Errno::EAFNOSUPPORT.into())?;
    let domain = match family {
        AddressFamily::Inet => nix::libc::AF_INET,
        AddressFamily::Inet6 => nix::libc::AF_INET6,
        _ => return Err(Errno::EAFNOSUPPORT.into()),
    };
    let addr_ptr = addr.as_ptr() as u64;
    let addr_len = addr.len() as usize;

    // We add two seccomp filters:
    // 1. General filter that allows required syscalls.
    // 2. Mprotect filter that protects the whole memory region of the
    //    address pointer.
    let mut filter1 = new_filter(ScmpAction::KillProcess)?;
    let mut filter2 = new_filter(ScmpAction::Allow)?;

    let allow_call = [
        // can exit.
        "exit",
        "exit_group",
        // can {{dr}e,}allocate memory.
        // mmap{,2} and mprotect are further confined.
        "brk",
        "madvise",
        "mremap",
        "munmap",
        "getrandom",
        // can handle signals.
        // can not return from signal handlers (mitigate SROP).
        "sigaction",
        "sigaltstack",
        "sigpending",
        "sigprocmask",
        "sigsuspend",
        //"sigreturn",
        "rt_sigaction",
        "rt_sigpending",
        "rt_sigprocmask",
        "rt_sigqueueinfo",
        //"rt_sigreturn",
        "rt_sigtimedwait",
        "rt_sigtimedwait_time64",
        // can set file flags.
        "fcntl",
        "fcntl64",
        // can close files.
        "close",
        // can do I/O with pipes.
        "pipe2",
        "splice",
        // can forward network.
        // socket and connect are further confined as necessary.
        "accept4",
        "setsockopt",
        // can use EPoll API,
        // can not create new EPoll FDs.
        "epoll_ctl",
        "epoll_wait",
        "epoll_pwait",
        "epoll_pwait2",
    ];

    // Default allowlist.
    for name in allow_call {
        if let Ok(syscall) = ScmpSyscall::from_name(name) {
            filter1.add_rule(ScmpAction::Allow, syscall)?;
        }
    }

    // Socket filtering only works if there's no multiplexing socketcall.
    if seccomp_native_has_socketcall() {
        for sysname in ["socket", "connect"] {
            #[allow(clippy::disallowed_methods)]
            filter1.add_rule(ScmpAction::Allow, ScmpSyscall::from_name(sysname).unwrap())?;
        }
    } else {
        // Restrict socket to the given domain, type and protocol.
        let sock_domain = domain as u64;
        let sock_type =
            (nix::libc::SOCK_STREAM | nix::libc::SOCK_NONBLOCK | nix::libc::SOCK_CLOEXEC) as u64;
        let sock_protocol = nix::libc::IPPROTO_IP as u64;
        #[allow(clippy::disallowed_methods)]
        filter1.add_rule_conditional(
            ScmpAction::Allow,
            ScmpSyscall::from_name("socket").unwrap(),
            &[
                scmp_cmp!($arg0 == sock_domain),
                scmp_cmp!($arg1 == sock_type),
                scmp_cmp!($arg2 == sock_protocol),
            ],
        )?;

        // Restrict connect to a single safe pointer.
        #[allow(clippy::disallowed_methods)]
        filter1.add_rule_conditional(
            ScmpAction::Allow,
            ScmpSyscall::from_name("connect").unwrap(),
            &[scmp_cmp!($arg1 == addr_ptr)],
        )?;
    }

    // Prevent executable memory.
    const PROT_EXEC: u64 = nix::libc::PROT_EXEC as u64;
    for name in ["mmap", "mmap2", "mprotect"] {
        #[allow(clippy::disallowed_methods)]
        let syscall = ScmpSyscall::from_name(name).unwrap();
        filter1.add_rule_conditional(
            ScmpAction::Allow,
            syscall,
            &[scmp_cmp!($arg2 & PROT_EXEC == 0)],
        )?;
    }

    // Allow write(2) to -42 for debugging.
    const MEANING_OF_LIFE: u64 = -42i64 as u64;
    #[allow(clippy::disallowed_methods)]
    filter1.add_rule_conditional(
        ScmpAction::Allow,
        ScmpSyscall::from_name("write").unwrap(),
        &[scmp_cmp!($arg0 == MEANING_OF_LIFE)],
    )?;

    // Protect the memory area of address pointer,
    // from unwanted modifications.
    for offset in 0..=addr_len {
        let addr_ptr = addr_ptr.saturating_add(offset as u64);
        for name in ["mprotect", "munmap"] {
            #[allow(clippy::disallowed_methods)]
            filter2.add_rule_conditional(
                ScmpAction::KillProcess,
                ScmpSyscall::from_name(name).unwrap(),
                &[scmp_cmp!($arg0 == addr_ptr)],
            )?;
        }
    }

    if !dry_run {
        // Set up namespace isolation for all except NET.
        // Ignore errors as unprivileged userns may not be supported.
        if unshare(
            CloneFlags::CLONE_NEWUSER
                | CloneFlags::CLONE_NEWCGROUP
                | CloneFlags::CLONE_NEWIPC
                | CloneFlags::CLONE_NEWNS
                | CloneFlags::CLONE_NEWPID
                | CloneFlags::CLONE_NEWUTS,
        )
        .is_ok()
        {
            // /var/empty does not exist on Ubuntu...
            chroot("/proc/self/fdinfo")?;
            chdir("/")?;
        }

        // Set up a Landlock sandbox to disallow all access.
        // Ignore errors as Landlock may not be supported.
        let p = vec![];
        let _ = syd::landlock_operation(&p, &p, syd::config::LANDLOCK_ABI);

        // Set up Memory-Deny-Write-Execute protections.
        // Ignore errors as PR_SET_MDWE may not be supported.
        const PR_SET_MDWE: nix::libc::c_int = 65;
        const PR_MDWE_REFUSE_EXEC_GAIN: nix::libc::c_ulong = 1;
        // SAFETY: nix has no wrapper for PR_SET_MDWE.
        let _ = unsafe { nix::libc::prctl(PR_SET_MDWE, PR_MDWE_REFUSE_EXEC_GAIN, 0, 0, 0) };

        // Set the process dumpable attribute to not-dumpable.
        // SAFETY: Our nix version does not have a wrapper for set_dumpable yet.
        let _ = unsafe { nix::libc::prctl(nix::libc::PR_SET_DUMPABLE, 0, 0, 0, 0) };

        // Deny reading the timestamp counter (x86 only).
        // SAFETY: Our nix version does not have a wrapper for SET_TSC yet.
        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
        let _ = unsafe { nix::libc::prctl(nix::libc::PR_SET_TSC, nix::libc::PR_TSC_SIGSEGV) };
    }

    if dry_run {
        // Dump filter and return without loading.
        let _ = filter2.export_pfc(&mut std::io::stderr());
        let _ = filter1.export_pfc(&mut std::io::stderr());
    } else {
        // All done, load seccomp filter and begin confinement.
        // Careful, we have to load the mprotect filter first,
        // as the first general filter is more restrictive.
        filter2.load()?;
        filter1.load()?;
    }

    Ok(())
}

fn new_filter(action: ScmpAction) -> SydResult<ScmpFilterContext> {
    let mut filter = ScmpFilterContext::new_filter(action)?;

    // Enforce the NO_NEW_PRIVS functionality before
    // loading the seccomp filter into the kernel.
    filter.set_ctl_nnp(true)?;

    // Kill process for bad arch.
    filter.set_act_badarch(ScmpAction::KillProcess)?;

    // Use a binary tree sorted by syscall number, if possible.
    let _ = filter.set_ctl_optimize(2);

    Ok(filter)
}

// Parse command line options.
#[allow(clippy::type_complexity)]
fn parse_options() -> SydResult<(OwnedFd, UnixStream, Box<dyn SockaddrLike>, usize, bool)> {
    let args = env::args().skip(1).collect::<Vec<_>>();
    let mut opts = Options::new(args.iter().map(String::as_str));
    let mut addr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
    let mut port = 9050;
    let mut pmax: Option<usize> = None;
    let mut sock: Option<&str> = None;
    let mut fpid: Option<&str> = None;
    let mut debug = false; // If enabled, skips confinement.

    #[allow(clippy::disallowed_methods)]
    while let Some(opt) = opts.next_opt().expect("next opt") {
        match opt {
            Opt::Short('h') => {
                help();
                exit(0);
            }
            Opt::Short('d') => {
                debug = true;
            }
            Opt::Short('b') => {
                pmax = if let Ok(value) = opts.value() {
                    Some(value.parse::<usize>()?)
                } else {
                    eprintln!("Error: -b requires an argument.");
                    help();
                    exit(1);
                };
            }
            Opt::Short('p') => {
                fpid = opts.value().ok();
            }
            Opt::Short('i') => {
                sock = opts.value().ok();
            }
            Opt::Short('o') => {
                (addr, port) = match opts.value() {
                    Ok(value) => {
                        // Split address and port.
                        let (addr, port) = value
                            .rsplit_once(':')
                            .ok_or::<SydError>(Errno::EAFNOSUPPORT.into())?;

                        // Parse the address.
                        let addr: IpAddr = addr.parse()?;

                        // Parse the external port.
                        let port: u16 = port.parse()?;

                        (addr, port)
                    }
                    Err(_) => {
                        eprintln!("Error: -o requires an argument.");
                        help();
                        exit(1);
                    }
                };
            }
            Opt::Short(opt) => {
                eprintln!("Unknown option: -{opt}!");
                help();
                exit(1);
            }
            Opt::Long(opt) => {
                eprintln!("Unknown option: --{opt}!");
                help();
                exit(1);
            }
        }
    }

    let fpid = if let Some(fpid) = fpid {
        // Parse file descriptor.
        let fpid = fpid.parse::<RawFd>()?;
        if fpid < 0 {
            return Err(Errno::EBADF.into());
        }

        // Validate file descriptor.
        let mut buf = itoa::Buffer::new();
        let mut pfd = SmallPathBuf::new();
        pfd.push(Path::new("/proc/self/fd"));
        pfd.push(Path::new(buf.format(fpid)));
        path_exists(&pfd)?;

        // SAFETY: Syd passes a valid PID FD to syd-tor.
        // Any other usecase is unsupported.
        unsafe { OwnedFd::from_raw_fd(fpid) }
    } else {
        eprintln!("Error: -p is required.");
        help();
        exit(1);
    };

    let sock = if let Some(sock) = sock {
        // Parse file descriptor.
        let sock = sock.parse::<RawFd>()?;
        if sock < 0 {
            return Err(Errno::EBADF.into());
        }

        // Validate file descriptor.
        let mut buf = itoa::Buffer::new();
        let mut pfd = SmallPathBuf::new();
        pfd.push(Path::new("/proc/self/fd"));
        pfd.push(Path::new(buf.format(sock)));
        path_exists(&pfd)?;

        // SAFETY: Syd passes a valid socket FD to syd-tor.
        // Any other usecase is unsupported.
        unsafe { UnixStream::from_raw_fd(sock) }
    } else {
        eprintln!("Error: -i is required.");
        help();
        exit(1);
    };

    // Validate socket address and convert.
    let addr = SocketAddr::new(addr, port);
    let addr: Box<dyn SockaddrLike> = match addr {
        SocketAddr::V4(addr) => Box::new(SockaddrIn::from(addr)),
        SocketAddr::V6(addr) => Box::new(SockaddrIn6::from(addr)),
    };

    let pmax = if let Some(pmax) = pmax {
        std::cmp::max(pmax, *PIPE_BUF)
    } else {
        proc_pipemax().unwrap_or(*PIPE_BUF as nix::libc::c_int) as usize
    };

    Ok((fpid, sock, addr, pmax, debug))
}

fn set_socket_options<F: AsFd>(fd: &F, buf_size: usize) {
    // Helper to set socket option
    fn set_socket_option<T>(
        fd: RawFd,
        level: nix::libc::c_int,
        optname: nix::libc::c_int,
        optval: T,
    ) -> Result<(), Errno> {
        // SAFETY: nix lacks some socket options we want to set.
        let ret = unsafe {
            nix::libc::setsockopt(
                fd,
                level,
                optname,
                &optval as *const _ as *const nix::libc::c_void,
                std::mem::size_of::<T>() as nix::libc::socklen_t,
            )
        };
        if ret == -1 {
            Err(Errno::last())
        } else {
            Ok(())
        }
    }

    // Convert Fd to RawFd.
    let fd = fd.as_fd().as_raw_fd();

    // TCP_NODELAY disables Nagle's algorithm, which improves the
    // latency of small packets by sending them immediately instead of
    // waiting to combine them with other packets.
    let _ = set_socket_option(fd, nix::libc::IPPROTO_TCP, nix::libc::TCP_NODELAY, 1);

    // SO_KEEPALIVE ensures that connections are checked periodically to
    // detect broken connections. This helps in maintaining long-lived
    // connections by detecting and closing broken ones.
    let _ = set_socket_option(fd, nix::libc::SOL_SOCKET, nix::libc::SO_KEEPALIVE, 1);

    // TCP_QUICKACK ensures that ACKs (acknowledgments) are sent
    // immediately, reducing the latency for connections that rely on
    // timely acknowledgment of received packets.
    let _ = set_socket_option(fd, nix::libc::IPPROTO_TCP, nix::libc::TCP_QUICKACK, 1);

    if buf_size != 0 {
        // SO_RCVBUF sets the receive buffer size for the socket.
        // Matching this with PIPE_BUF ensures efficient data transfer,
        // as the buffer sizes are aligned for optimal performance.
        let _ = set_socket_option(
            fd,
            nix::libc::SOL_SOCKET,
            nix::libc::SO_RCVBUF,
            buf_size as nix::libc::c_int,
        );

        // SO_SNDBUF sets the send buffer size for the socket. Matching
        // this with PIPE_BUF ensures efficient data transfer, as the
        // buffer sizes are aligned for optimal performance.
        let _ = set_socket_option(
            fd,
            nix::libc::SOL_SOCKET,
            nix::libc::SO_SNDBUF,
            buf_size as nix::libc::c_int,
        );
    }
}

fn help() {
    let pipe_max = proc_pipemax().unwrap_or(*PIPE_BUF as nix::libc::c_int);

    println!("Usage: syd-tor [-dh] [-b bufsiz] -p <pid-fd> -i <socket-fd> [-o addr:port]");
    println!("Syd's SOCKS Proxy Forwarder");
    println!("Receives listening socket from fd and forwards traffic to addr:port.");
    println!("External address must be an IPv4 or IPv6 address.");
    println!("PID file descriptor is used to track the exit of Syd process.");
    println!("  -h             Print this help message and exit.");
    println!("  -d             Run in debug mode without confinement.");
    println!("  -b <bufsiz>    Set the pipe buffer size.");
    println!("                 Defaults to the value specified in the file:");
    println!("                 /proc/sys/fs/pipe-max-size ({pipe_max} bytes)");
    println!("  -p <pid-fd>    PID file descriptor of Syd process.");
    println!("  -i <socket-fd> Socket file descriptor to receive the listening socket from.");
    println!("  -o <addr:port> Specify external address to forward traffic to.");
    println!("                 Defaults to 127.0.0.1:9050.");
}
