//
// Syd: rock-solid application kernel
// src/syd-tor.rs: Syd's SOCKS Proxy Forwarder
//
// Copyright (c) 2024, 2025 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

use std::{
    collections::HashMap,
    env,
    io::stderr,
    net::{IpAddr, Ipv4Addr, SocketAddr},
    os::{
        fd::{AsFd, AsRawFd, FromRawFd, OwnedFd, RawFd},
        unix::net::UnixStream,
    },
    process::{exit, ExitCode},
};

use ahash::RandomState;
use nix::{
    errno::Errno,
    fcntl::{splice, OFlag, SpliceFFlags},
    poll::PollTimeout,
    sched::{unshare, CloneFlags},
    sys::{
        epoll::{Epoll, EpollCreateFlags, EpollEvent, EpollFlags},
        prctl::set_pdeathsig,
        resource::{getrlimit, setrlimit, Resource},
        signal::Signal,
        socket::{
            accept4, connect, listen, socket, AddressFamily, Backlog, SockFlag, SockType,
            SockaddrIn, SockaddrIn6, SockaddrLike,
        },
    },
    unistd::{chdir, chroot, close, pipe2, write},
};
use sendfd::RecvWithFd;
use syd::{
    compat::epoll_ctl_safe,
    config::PIPE_BUF,
    err::{SydError, SydResult},
    fs::set_pipemax,
    libseccomp::{ScmpAction, ScmpFilterContext, ScmpSyscall},
    path::XPathBuf,
    proc::proc_pipemax,
    scmp_cmp, seccomp_native_has_socketcall,
};

// Pong reply to Syd for debugging.
const PONG: &[u8] =
    b"{\"ctx\":\"recv_proxy_fd\",\"tor\":\"pong\",\"msg\":\"Action brings good fortune.\"}\n\0";

#[allow(clippy::disallowed_methods)]
fn main() -> SydResult<ExitCode> {
    // Configure syd::proc.
    syd::config::proc_init()?;

    // 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()),
    };

    // Start listening on the bound fd.
    // Note, Syd already set this fd non-blocking.
    listen(&proxy, Backlog::MAXCONN)?;

    // Set socket options (tcp fastopen et al.)
    set_socket_options(&proxy, 0, Some(nix::libc::SOMAXCONN));

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

    // Initialize client manager.
    let client_manager = ClientManager::new();

    // Confine.
    // Print rules if SYD_TOR_RULES is set in the environment.
    let print = env::var_os("SYD_TOR_RULES").is_some();
    confine(&*addr, debug, print)?;

    // Run the proxy server.
    run_proxy_server(client_manager, &epoll, &fpid, &proxy, addr, pmax, debug).expect("TOR");

    Ok(ExitCode::SUCCESS)
}

/// Run the proxy server
fn run_proxy_server<F: AsFd>(
    mut client_manager: ClientManager,
    epoll: &Epoll,
    pid_fd: &F,
    listening_fd: &F,
    external_addr: Box<dyn SockaddrLike>,
    pipe_max: usize,
    debug: bool,
) -> SydResult<()> {
    // 1. Add PIDFd to epoll (becomes readable when process terminates).
    let event = libc::epoll_event {
        events: (EpollFlags::EPOLLET
            | EpollFlags::EPOLLIN
            | EpollFlags::EPOLLRDHUP
            | EpollFlags::EPOLLONESHOT)
            .bits() as u32,
        u64: pid_fd.as_fd().as_raw_fd() as u64,
    };
    epoll_ctl_safe(&epoll.0, pid_fd.as_fd().as_raw_fd(), Some(event))?;

    // 2. Add listening socket to epoll (not necessary to set EPOLL{ERR,HUP}).
    let event = libc::epoll_event {
        events: (EpollFlags::EPOLLET | EpollFlags::EPOLLIN | EpollFlags::EPOLLRDHUP).bits() as u32,
        u64: listening_fd.as_fd().as_raw_fd() as u64,
    };
    epoll_ctl_safe(&epoll.0, listening_fd.as_fd().as_raw_fd(), Some(event))?;

    if debug {
        // Reply to Syd to indicate start of traffic forwarding.
        let _ = write(stderr(), PONG);
    }

    // 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 = HashMap<RawFd, RawFd, RandomState>;
type CliMap = HashMap<RawFd, Client, RandomState>;

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 = libc::epoll_event {
            events: (EpollFlags::EPOLLET
                | EpollFlags::EPOLLIN
                | EpollFlags::EPOLLOUT
                | EpollFlags::EPOLLHUP
                | EpollFlags::EPOLLRDHUP)
                .bits() as u32,
            u64: fd_cli.as_raw_fd() as u64,
        };
        epoll_ctl_safe(&epoll.0, fd_cli.as_raw_fd(), Some(event))?;

        let event = libc::epoll_event {
            events: (EpollFlags::EPOLLET
                | EpollFlags::EPOLLIN
                | EpollFlags::EPOLLOUT
                | EpollFlags::EPOLLHUP
                | EpollFlags::EPOLLRDHUP)
                .bits() as u32,
            u64: fd_ext.as_raw_fd() as u64,
        };
        epoll_ctl_safe(&epoll.0, fd_ext.as_raw_fd(), 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, Some(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,
        )?;

        // Set socket options on the external fd.
        set_socket_options(&ext_fd, pipe_max_0, 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.0, self.fd_cli.as_raw_fd(), None);
        let _ = epoll_ctl_safe(&epoll.0, self.fd_ext.as_raw_fd(), 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, print_rules: 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 port: u16 = match family {
        AddressFamily::Inet => {
            // SAFETY: We ensure that addr points to a valid sockaddr_in and length is correct.
            let sa_in = unsafe {
                SockaddrIn::from_raw(addr.as_ptr(), Some(addr.len()))
                    .ok_or::<SydError>(Errno::EAFNOSUPPORT.into())?
            };
            sa_in.port()
        }
        AddressFamily::Inet6 => {
            // SAFETY: We ensure that addr points to a valid sockaddr_in6 and length is correct.
            let sa_in6 = unsafe {
                SockaddrIn6::from_raw(addr.as_ptr(), Some(addr.len()))
                    .ok_or::<SydError>(Errno::EAFNOSUPPORT.into())?
            };
            sa_in6.port()
        }
        _ => 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)],
        )?;
    }

    // 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 parent-death signal to SIGKILL.
        // We do not want to outlive the caller Syd process.
        // Since we do have a pidfd to the Syd process,
        // and this is the second layer, we ignore errors
        // and move on.
        let _ = set_pdeathsig(Some(Signal::SIGKILL));

        // 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:
        // 1. Disallow all filesystem access.
        // 2. Allow only connect to TOR port.
        // Ignore errors as Landlock may not be supported.
        let abi = syd::landlock::ABI::new_current();
        let _ = syd::landlock_operation(abi, &[], &[], &[], &[(port..=port)], true, true);

        // 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 print_rules {
        // Dump filter to standard error.
        eprintln!("# syd-tor rules 1");
        let _ = filter2.export_pfc(std::io::stderr());
        eprintln!("# syd-tor rules 2");
        let _ = filter1.export_pfc(std::io::stderr());
    }

    if !dry_run {
        // 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(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)> {
    use lexopt::prelude::*;

    // Parse CLI options.
    let mut opt_addr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
    let mut opt_port = 9050;
    let mut opt_pmax = None;
    let mut opt_sock = None;
    let mut opt_fpid = None;

    // Skip confinement if SYD_TOR_DEBUG environment variable is set.
    // Another way to achieve the same is the `-d` CLI option.
    let mut opt_debug = env::var_os("SYD_TOR_DEBUG").is_some();

    let mut parser = lexopt::Parser::from_env();
    while let Some(arg) = parser.next()? {
        match arg {
            Short('h') => {
                help();
                exit(0);
            }
            Short('d') => opt_debug = true,
            Short('b') => opt_pmax = Some(parser.value()?.parse::<String>()?.parse::<usize>()?),
            Short('p') => opt_fpid = Some(parser.value()?.parse::<String>()?),
            Short('i') => opt_sock = Some(parser.value()?.parse::<String>()?),
            Short('o') => {
                let value = parser.value()?.parse::<String>()?;

                // Split address and port.
                let (addr, port) = value
                    .rsplit_once(':')
                    .ok_or::<SydError>(Errno::EAFNOSUPPORT.into())?;

                // Parse the address and the external port.
                opt_addr = addr.parse::<IpAddr>()?;
                opt_port = port.parse::<u16>()?;
            }
            _ => return Err(arg.unexpected().into()),
        }
    }

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

        // Validate file descriptor.
        let mut pfd = XPathBuf::from("/proc/self/fd");
        pfd.push_fd(fpid);

        if !pfd.exists(true) {
            return Err(Errno::ENOENT.into());
        }

        // 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) = opt_sock {
        // Parse file descriptor.
        let sock = sock.parse::<RawFd>()?;
        if sock < 0 {
            return Err(Errno::EBADF.into());
        }

        // Validate file descriptor.
        let mut pfd = XPathBuf::from("/proc/self/fd");
        pfd.push_fd(sock);

        if !pfd.exists(true) {
            return Err(Errno::ENOENT.into());
        }

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

    // Validate socket address and convert.
    let addr = SocketAddr::new(opt_addr, opt_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) = opt_pmax {
        std::cmp::max(pmax, PIPE_BUF)
    } else {
        proc_pipemax().unwrap_or(PIPE_BUF as nix::libc::c_int) as usize
    };

    // syd::proc is no longer necessary,
    // close the file descriptors so we
    // dont accidentally leak them.
    syd::config::proc_close();

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

// Set common socket options for Proxy sandboxing.
fn set_socket_options<F: AsFd>(fd: &F, buf_size: usize, backlog: Option<nix::libc::c_int>) {
    // 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 let Some(backlog) = backlog {
        if backlog != 0 {
            // TCP_FASTOPEN enables Fast Open (RFC 7413) on the listener
            // socket. The value specifies the maximum length of pending SYNs
            // (similar to the backlog argument in listen(2)). Once enabled,
            // the listener socket grants the TCP Fast Open cookie on
            // incoming SYN with TCP Fast Open option.
            let _ = set_socket_option(fd, nix::libc::IPPROTO_TCP, nix::libc::TCP_FASTOPEN, backlog);
        }
    } else {
        // TCP_FASTOPEN_CONNECT enables an alternative way to perform Fast
        // Open on the active side (client).
        let _ = set_socket_option(
            fd,
            nix::libc::IPPROTO_TCP,
            nix::libc::TCP_FASTOPEN_CONNECT,
            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.");
}
