childflow 0.4.0

A per-command-tree network sandbox for Linux
// Copyright (c) 2026 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.

use std::net::{Ipv4Addr, Ipv6Addr};

use clap::ValueEnum;

#[derive(Copy, Clone, Debug, Eq, PartialEq, ValueEnum)]
pub enum NetworkBackend {
    Rootful,
    RootlessInternal,
}

#[derive(Clone, Debug)]
pub struct NetworkPlan {
    pub(crate) host_veth: String,
    pub(crate) child_veth: String,
    pub(crate) host_ipv4: Ipv4Addr,
    pub(crate) child_ipv4: Ipv4Addr,
    pub(crate) subnet_v4_cidr: String,
    pub(crate) host_ipv6: Ipv6Addr,
    pub(crate) child_ipv6: Ipv6Addr,
    pub(crate) subnet_v6_cidr: String,
    pub(crate) route_table: u32,
    pub(crate) tproxy_table: u32,
    pub(crate) route_priority: u32,
    pub(crate) tproxy_priority: u32,
    pub(crate) route_mark: u32,
    pub(crate) tproxy_mark: u32,
    pub(crate) divert_chain: String,
    pub(crate) tproxy_chain: String,
}

impl NetworkPlan {
    pub fn new() -> Self {
        let entropy = crate::util::run_entropy();
        let (host_ipv4, child_ipv4, subnet_v4_cidr) = allocate_ipv4_subnet(entropy);
        let (host_ipv6, child_ipv6, subnet_v6_cidr) = allocate_ipv6_subnet(entropy);
        let suffix = format!("{:06x}", entropy & 0x00ff_ffff);

        Self {
            host_veth: format!("cfh{}", &suffix[..6]),
            child_veth: format!("cfc{}", &suffix[..6]),
            host_ipv4,
            child_ipv4,
            subnet_v4_cidr,
            host_ipv6,
            child_ipv6,
            subnet_v6_cidr,
            route_table: 10_000 + (entropy % 10_000),
            tproxy_table: 10_001 + (entropy % 10_000),
            route_priority: 10_000 + (entropy % 1_000),
            tproxy_priority: 10_001 + (entropy % 1_000),
            route_mark: 0x10000 | (entropy & 0x0fff),
            tproxy_mark: 0x20000 | (entropy & 0x0fff),
            divert_chain: format!("CFD{}", &suffix[..6]),
            tproxy_chain: format!("CFT{}", &suffix[..6]),
        }
    }

    pub fn host_ipv4(&self) -> Ipv4Addr {
        self.host_ipv4
    }

    pub fn host_ipv6(&self) -> Ipv6Addr {
        self.host_ipv6
    }
}

fn allocate_ipv4_subnet(entropy: u32) -> (Ipv4Addr, Ipv4Addr, String) {
    let octet3 = ((entropy >> 8) & 0xff) as u8;
    let block = ((entropy & 0x3f) as u8) * 4;
    let host_ip = Ipv4Addr::new(10, 240, octet3, block + 1);
    let child_ip = Ipv4Addr::new(10, 240, octet3, block + 2);
    let subnet_cidr = format!("10.240.{octet3}.{block}/30");
    (host_ip, child_ip, subnet_cidr)
}

fn allocate_ipv6_subnet(entropy: u32) -> (Ipv6Addr, Ipv6Addr, String) {
    let upper = ((entropy >> 16) & 0xffff) as u16;
    let lower = (entropy & 0xffff) as u16;
    let subnet = format!("fd42:{upper:04x}:{lower:04x}::/64");
    let host_ip = Ipv6Addr::new(0xfd42, upper, lower, 0, 0, 0, 0, 1);
    let child_ip = Ipv6Addr::new(0xfd42, upper, lower, 0, 0, 0, 0, 2);
    (host_ip, child_ip, subnet)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn allocate_ipv4_subnet_uses_a_private_slash30() {
        let (host, child, cidr) = allocate_ipv4_subnet(0x1234_5678);

        assert_eq!(host, Ipv4Addr::new(10, 240, 0x56, 0xe0 + 1));
        assert_eq!(child, Ipv4Addr::new(10, 240, 0x56, 0xe0 + 2));
        assert_eq!(cidr, "10.240.86.224/30");
    }

    #[test]
    fn allocate_ipv6_subnet_uses_a_unique_ula_prefix() {
        let (host, child, cidr) = allocate_ipv6_subnet(0x1234_5678);

        assert_eq!(host, "fd42:1234:5678::1".parse::<Ipv6Addr>().unwrap());
        assert_eq!(child, "fd42:1234:5678::2".parse::<Ipv6Addr>().unwrap());
        assert_eq!(cidr, "fd42:1234:5678::/64");
    }
}