packrat-tui 0.3.2

A Wireshark-style terminal packet analyzer, reverse engineering, and security research tool with live capture, IDS, port scanner, packet crafter, and PCAP replay
/// Simulated packet generator for demo / testing without a real interface.
use rand::Rng;
use crate::net::packet::Packet;

const LOCAL_IPS: &[&str] = &[
    "192.168.1.1", "192.168.1.42", "192.168.1.100",
    "10.0.0.1", "10.0.0.23", "172.16.0.5",
];
const REMOTE_IPS: &[&str] = &[
    "8.8.8.8", "1.1.1.1", "151.101.64.81",
    "142.250.80.46", "104.21.55.33", "172.217.14.206",
    "13.107.42.14", "52.84.17.200", "34.120.208.123",
];
const OT_IPS: &[&str] = &[
    "192.168.0.10", "192.168.0.20", "192.168.100.1",
    "10.10.1.5", "10.10.1.10", "10.10.1.100",
];
const DNS_NAMES: &[&str] = &[
    "google.com", "github.com", "api.stripe.com",
    "fonts.googleapis.com", "cdn.cloudflare.com",
    "s3.amazonaws.com", "app.slack.com", "discord.com",
];
const MQTT_TOPICS: &[&str] = &[
    "sensors/temperature", "sensors/pressure", "sensors/humidity",
    "actuators/valve", "actuators/pump", "actuators/relay",
    "plant/line1/status", "plant/line2/alarm", "device/plc01/health",
];
const PROTOS: &[&str] = &[
    "TCP", "UDP", "DNS", "HTTP", "HTTPS", "TLS", "ARP", "ICMP", "DHCP",
    "Modbus", "MQTT", "CoAP", "BACnet", "DNP3", "OPC-UA", "S7comm", "EtherNet/IP",
    "NTP", "PTP", "SIP", "FTP", "BGP", "WireGuard", "VXLAN", "GRE", "IGMP",
    "SMB", "RDP", "Kafka", "AMQP", "NATS", "Kerberos",
];
const WEIGHTS: &[u32] = &[26, 13, 18, 7, 10, 7, 3, 3, 1, 3, 3, 2, 1, 1, 1, 1, 1, 3, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];

static START: std::sync::OnceLock<std::time::Instant> = std::sync::OnceLock::new();

pub fn generate_packet(counter: u64) -> Packet {
    let start = START.get_or_init(std::time::Instant::now);
    let ts = start.elapsed().as_secs_f64();
    let mut rng = rand::thread_rng();
    let proto = weighted_pick(PROTOS, WEIGHTS, &mut rng);

    let length: u16 = match proto {
        "ARP"  => 42,
        "ICMP" => rng.gen_range(60..=128),
        _      => rng.gen_range(60..=1460),
    };

    let bytes: Vec<u8> = (0..length).map(|i| {
        let b: u8 = rng.r#gen();
        if i < 54 { b }
        else if b % 3 == 0 { rng.gen_range(32..127) }
        else { b }
    }).collect();

    let (src, dst, src_port, dst_port, info) = match proto {
        "ARP" => {
            let src = rand_ip(LOCAL_IPS, &mut rng).to_string();
            let tgt = rand_ip(LOCAL_IPS, &mut rng);
            (src.clone(), "ff:ff:ff:ff:ff:ff".to_string(), None, None,
             format!("Who has {}? Tell {}", tgt, src))
        }
        "ICMP" => {
            let src = rand_ip(LOCAL_IPS, &mut rng).to_string();
            let dst = rand_ip(REMOTE_IPS, &mut rng).to_string();
            let id: u16 = rng.r#gen();
            let seq: u16 = rng.gen_range(1..=100);
            (src, dst, None, None,
             format!("Echo request id=0x{:04x} seq={}", id, seq))
        }
        "DNS" => {
            let src = rand_ip(LOCAL_IPS, &mut rng).to_string();
            let dst = if rng.gen_bool(0.7) { "8.8.8.8" } else { "1.1.1.1" }.to_string();
            let name = DNS_NAMES[rng.gen_range(0..DNS_NAMES.len())];
            let tid: u16 = rng.r#gen();
            let info = if rng.gen_bool(0.5) {
                format!("Query 0x{:04x} A {}", tid, name)
            } else {
                format!("Response A {}.{}.{}.{}",
                    rng.gen_range(1..=254u8), rng.r#gen::<u8>(),
                    rng.r#gen::<u8>(), rng.gen_range(1..=254u8))
            };
            let sp: u16 = rng.gen_range(1024..=65535);
            (src, dst, Some(sp), Some(53u16), info)
        }
        "DHCP" => {
            let msg = ["Discover", "Request", "Offer", "ACK"][rng.gen_range(0..4)];
            let tid: u32 = rng.r#gen();
            ("0.0.0.0".into(), "255.255.255.255".into(),
             Some(68u16), Some(67u16),
             format!("DHCP {} TID=0x{:08x}", msg, tid))
        }
        "HTTP" => {
            let src = rand_ip(LOCAL_IPS, &mut rng).to_string();
            let dst = rand_ip(REMOTE_IPS, &mut rng).to_string();
            let methods = ["GET", "POST", "PUT", "DELETE", "HEAD"];
            let paths = ["/api/v1/users", "/index.html", "/assets/main.js", "/api/data"];
            let m = methods[rng.gen_range(0..methods.len())];
            let p = paths[rng.gen_range(0..paths.len())];
            let sp: u16 = rng.gen_range(1024..=65535);
            (src, dst, Some(sp), Some(80u16), format!("{} {} HTTP/1.1", m, p))
        }
        "HTTPS" | "TLS" => {
            let src = rand_ip(LOCAL_IPS, &mut rng).to_string();
            let dst = rand_ip(REMOTE_IPS, &mut rng).to_string();
            let hs = ["Client Hello", "Server Hello", "Certificate", "Finished", "Application Data"];
            let sp: u16 = rng.gen_range(1024..=65535);
            (src, dst, Some(sp), Some(443u16),
             format!("TLS {}", hs[rng.gen_range(0..hs.len())]))
        }
        "Modbus" => {
            let src = rand_ip(OT_IPS, &mut rng).to_string();
            let dst = rand_ip(OT_IPS, &mut rng).to_string();
            let fn_codes = [
                (1u8, "Read Coils"), (2, "Read Discrete Inputs"),
                (3, "Read Holding Registers"), (4, "Read Input Registers"),
                (5, "Write Single Coil"), (6, "Write Single Register"),
                (15, "Write Multiple Coils"), (16, "Write Multiple Registers"),
            ];
            let (fc, fc_name) = fn_codes[rng.gen_range(0..fn_codes.len())];
            let unit: u8 = rng.gen_range(1..=10);
            let addr: u16 = rng.gen_range(0..=1000);
            (src, dst, Some(rng.gen_range(1024u16..=65535)), Some(502),
             format!("FC{} {} unit={} addr=0x{:04x}", fc, fc_name, unit, addr))
        }
        "MQTT" => {
            let src = rand_ip(OT_IPS, &mut rng).to_string();
            let dst = rand_ip(OT_IPS, &mut rng).to_string();
            let topic = MQTT_TOPICS[rng.gen_range(0..MQTT_TOPICS.len())];
            let msg_types = [
                "CONNECT client=sensor_01", "CONNACK rc=0",
                "SUBSCRIBE", "SUBACK",
            ];
            let info = if rng.gen_bool(0.6) {
                format!("PUBLISH topic={} qos={} len={}", topic, rng.gen_range(0u8..=2), rng.gen_range(4u16..=64))
            } else {
                msg_types[rng.gen_range(0..msg_types.len())].to_string()
            };
            (src, dst, Some(rng.gen_range(1024u16..=65535)), Some(1883), info)
        }
        "CoAP" => {
            let src = rand_ip(OT_IPS, &mut rng).to_string();
            let dst = rand_ip(OT_IPS, &mut rng).to_string();
            let resources = ["/sensors/temperature", "/sensors/pressure", "/actuators/relay", "/status"];
            let methods = ["GET", "PUT", "POST", "DELETE"];
            let types = ["CON", "NON", "ACK"];
            let res = resources[rng.gen_range(0..resources.len())];
            let m = methods[rng.gen_range(0..methods.len())];
            let t = types[rng.gen_range(0..types.len())];
            let mid: u16 = rng.r#gen();
            (src, dst, Some(rng.gen_range(1024u16..=65535)), Some(5683),
             format!("{} {} {} mid=0x{:04x}", t, m, res, mid))
        }
        "BACnet" => {
            let src = rand_ip(OT_IPS, &mut rng).to_string();
            let dst = rand_ip(OT_IPS, &mut rng).to_string();
            let objs = ["AI:1", "AI:2", "AO:1", "BI:1", "AV:10", "BV:5"];
            let props = ["present-value", "object-name", "description", "units", "status-flags"];
            let obj = objs[rng.gen_range(0..objs.len())];
            let prop = props[rng.gen_range(0..props.len())];
            (src, dst, Some(rng.gen_range(1024u16..=65535)), Some(47808),
             format!("ReadProperty objectId={} propId={}", obj, prop))
        }
        "DNP3" => {
            let src = rand_ip(OT_IPS, &mut rng).to_string();
            let dst = rand_ip(OT_IPS, &mut rng).to_string();
            let fns = ["Read Class 0", "Read Class 1", "Read Class 2",
                       "Write", "Select", "Operate", "Direct Operate", "Unsolicited Response"];
            let fn_name = fns[rng.gen_range(0..fns.len())];
            let outstation: u16 = rng.gen_range(1..=10);
            (src, dst, Some(rng.gen_range(1024u16..=65535)), Some(20000),
             format!("{} outstation={}", fn_name, outstation))
        }
        "OPC-UA" => {
            let src = rand_ip(OT_IPS, &mut rng).to_string();
            let dst = rand_ip(OT_IPS, &mut rng).to_string();
            let services = ["ReadRequest", "WriteRequest", "Browse", "CreateSession",
                            "ActivateSession", "Subscribe", "Publish"];
            let svc = services[rng.gen_range(0..services.len())];
            let ns: u8 = rng.gen_range(1..=3);
            let node: u16 = rng.gen_range(1000..=9999);
            (src, dst, Some(rng.gen_range(1024u16..=65535)), Some(4840),
             format!("{} nodeId=ns={};i={}", svc, ns, node))
        }
        "S7comm" => {
            let src = rand_ip(OT_IPS, &mut rng).to_string();
            let dst = rand_ip(OT_IPS, &mut rng).to_string();
            let fns = ["Read Var", "Write Var", "Request Download", "Upload", "PLC Stop", "PLC Start"];
            let fn_name = fns[rng.gen_range(0..fns.len())];
            let db: u16 = rng.gen_range(1..=100);
            (src, dst, Some(rng.gen_range(1024u16..=65535)), Some(102),
             format!("{} DB{} offset=0x{:04x}", fn_name, db, rng.gen_range(0u16..=512)))
        }
        "EtherNet/IP" => {
            let src = rand_ip(OT_IPS, &mut rng).to_string();
            let dst = rand_ip(OT_IPS, &mut rng).to_string();
            let cmds = ["ListIdentity", "RegisterSession", "SendRRData", "SendUnitData"];
            let cmd = cmds[rng.gen_range(0..cmds.len())];
            (src, dst, Some(rng.gen_range(1024u16..=65535)), Some(44818),
             format!("{} session=0x{:08x}", cmd, rng.r#gen::<u32>()))
        }
        "NTP" => {
            let src = rand_ip(LOCAL_IPS, &mut rng).to_string();
            let ntp_servers = ["216.239.35.0", "129.6.15.28", "132.163.97.1", "time.cloudflare.com"];
            let dst = ntp_servers[rng.gen_range(0..ntp_servers.len())].to_string();
            let stratum: u8 = rng.gen_range(1..=4);
            let modes = ["client", "server", "broadcast"];
            let mode = modes[rng.gen_range(0..modes.len())];
            (src, dst, Some(rng.gen_range(1024u16..=65535)), Some(123),
             format!("NTP v4 {} stratum={}", mode, stratum))
        }
        "PTP" => {
            let src = rand_ip(LOCAL_IPS, &mut rng).to_string();
            let dst = rand_ip(LOCAL_IPS, &mut rng).to_string();
            let msg_types = ["Sync", "Delay_Req", "Follow_Up", "Delay_Resp", "Announce"];
            let msg = msg_types[rng.gen_range(0..msg_types.len())];
            let seq: u16 = rng.r#gen();
            (src, dst, Some(319u16), Some(319),
             format!("PTP {} seq={} domain=0", msg, seq))
        }
        "SIP" => {
            let src = rand_ip(LOCAL_IPS, &mut rng).to_string();
            let dst = rand_ip(LOCAL_IPS, &mut rng).to_string();
            let methods = ["INVITE", "BYE", "ACK", "REGISTER", "OPTIONS", "CANCEL", "REFER"];
            let m = methods[rng.gen_range(0..methods.len())];
            let ext: u16 = rng.gen_range(1000..=9999);
            (src, dst.clone(), Some(rng.gen_range(1024u16..=65535)), Some(5060),
             format!("{} sip:{}@{}", m, ext, dst))
        }
        "FTP" => {
            let src = rand_ip(LOCAL_IPS, &mut rng).to_string();
            let dst = rand_ip(REMOTE_IPS, &mut rng).to_string();
            let cmds = [
                "USER anonymous", "PASS secret@example.com", "LIST", "RETR file.txt",
                "STOR upload.bin", "PWD", "CWD /pub", "QUIT", "PASV", "PORT",
            ];
            let cmd = cmds[rng.gen_range(0..cmds.len())];
            (src, dst, Some(rng.gen_range(1024u16..=65535)), Some(21),
             format!("FTP Request: {}", cmd))
        }
        "BGP" => {
            let src = rand_ip(LOCAL_IPS, &mut rng).to_string();
            let dst = rand_ip(REMOTE_IPS, &mut rng).to_string();
            let types = ["OPEN", "UPDATE", "NOTIFICATION", "KEEPALIVE"];
            let t = types[rng.gen_range(0..types.len())];
            let asn: u32 = rng.gen_range(64512..=65534);
            (src, dst, Some(rng.gen_range(1024u16..=65535)), Some(179),
             format!("BGP {} AS={}", t, asn))
        }
        "WireGuard" => {
            let src = rand_ip(LOCAL_IPS, &mut rng).to_string();
            let dst = rand_ip(REMOTE_IPS, &mut rng).to_string();
            let types = ["Handshake Initiation", "Handshake Response", "Transport Data", "Cookie Reply"];
            let t = types[rng.gen_range(0..types.len())];
            (src, dst, Some(rng.gen_range(1024u16..=65535)), Some(51820),
             format!("WireGuard {}", t))
        }
        "VXLAN" => {
            let src = rand_ip(LOCAL_IPS, &mut rng).to_string();
            let dst = rand_ip(LOCAL_IPS, &mut rng).to_string();
            let vni: u32 = rng.gen_range(1..=16_777_215);
            (src, dst, Some(rng.gen_range(1024u16..=65535)), Some(4789),
             format!("VXLAN VNI={} encapsulated Ethernet", vni))
        }
        "GRE" => {
            let src = rand_ip(LOCAL_IPS, &mut rng).to_string();
            let dst = rand_ip(REMOTE_IPS, &mut rng).to_string();
            let encap = ["IPv4", "IPv6", "MPLS", "Ethernet"];
            let e = encap[rng.gen_range(0..encap.len())];
            (src, dst, None, None,
             format!("GRE Encapsulated {}", e))
        }
        "IGMP" => {
            let src = rand_ip(LOCAL_IPS, &mut rng).to_string();
            let mcast = format!("239.{}.{}.{}", rng.gen_range(1u8..=2), rng.r#gen::<u8>(), rng.gen_range(1u8..=254));
            let types = ["Membership Query", "Membership Report v3", "Leave Group"];
            let t = types[rng.gen_range(0..types.len())];
            (src, mcast, None, None,
             format!("IGMPv3 {} group={}", t, rand_ip(LOCAL_IPS, &mut rng)))
        }
        "SMB" => {
            let src = rand_ip(LOCAL_IPS, &mut rng).to_string();
            let dst = rand_ip(LOCAL_IPS, &mut rng).to_string();
            let cmds = ["Negotiate", "SessionSetup", "TreeConnect", "Create", "Read", "Write", "Close"];
            let cmd = cmds[rng.gen_range(0..cmds.len())];
            let session: u64 = rng.r#gen::<u64>() & 0xFFFFFFFFFFFF;
            (src, dst, Some(rng.gen_range(1024u16..=65535)), Some(445),
             format!("SMB2 {} session=0x{:012x}", cmd, session))
        }
        "RDP" => {
            let src = rand_ip(LOCAL_IPS, &mut rng).to_string();
            let dst = rand_ip(LOCAL_IPS, &mut rng).to_string();
            let pdus = ["X.224 Connection Request", "MCS Connect Initial", "Client Info", "Demand Active PDU", "Bitmap Update"];
            let pdu = pdus[rng.gen_range(0..pdus.len())];
            (src, dst, Some(rng.gen_range(1024u16..=65535)), Some(3389),
             format!("RDP {}", pdu))
        }
        "Kafka" => {
            let src = rand_ip(LOCAL_IPS, &mut rng).to_string();
            let dst = rand_ip(LOCAL_IPS, &mut rng).to_string();
            let apis = [("Produce",0u16), ("Fetch",1), ("Metadata",3), ("OffsetFetch",9), ("JoinGroup",11)];
            let (name, api_key) = apis[rng.gen_range(0..apis.len())];
            let corr: i32 = rng.r#gen();
            (src, dst, Some(rng.gen_range(1024u16..=65535)), Some(9092),
             format!("Kafka {} apiKey={} corr={}", name, api_key, corr))
        }
        "AMQP" => {
            let src = rand_ip(LOCAL_IPS, &mut rng).to_string();
            let dst = rand_ip(LOCAL_IPS, &mut rng).to_string();
            let methods = ["connection.start", "connection.open", "channel.open", "basic.publish", "basic.deliver", "queue.declare"];
            let m = methods[rng.gen_range(0..methods.len())];
            (src, dst, Some(rng.gen_range(1024u16..=65535)), Some(5672),
             format!("AMQP {}", m))
        }
        "NATS" => {
            let src = rand_ip(LOCAL_IPS, &mut rng).to_string();
            let dst = rand_ip(LOCAL_IPS, &mut rng).to_string();
            let ops = ["PUB", "SUB", "MSG", "PING", "PONG", "+OK"];
            let op = ops[rng.gen_range(0..ops.len())];
            let subjects = ["events.>", "orders.created", "users.login", "_INBOX.reply"];
            let subj = subjects[rng.gen_range(0..subjects.len())];
            (src, dst, Some(rng.gen_range(1024u16..=65535)), Some(4222),
             format!("NATS {} {}", op, subj))
        }
        "Kerberos" => {
            let src = rand_ip(LOCAL_IPS, &mut rng).to_string();
            let dst = rand_ip(LOCAL_IPS, &mut rng).to_string();
            let msgs = ["AS-REQ", "AS-REP", "TGS-REQ", "TGS-REP", "AP-REQ"];
            let m = msgs[rng.gen_range(0..msgs.len())];
            let users = ["administrator", "svc_backup", "john.doe", "svc_sql"];
            let u = users[rng.gen_range(0..users.len())];
            (src, dst, Some(rng.gen_range(1024u16..=65535)), Some(88),
             format!("Kerberos {} user={} realm=CORP.EXAMPLE.COM", m, u))
        }
        _ => {
            let src = rand_ip(LOCAL_IPS, &mut rng).to_string();
            let dst_pool = if rng.gen_bool(0.4) { LOCAL_IPS } else { REMOTE_IPS };
            let dst = rand_ip(dst_pool, &mut rng).to_string();
            let sp: u16 = rng.gen_range(1024..=65535);
            let dp_opts = [22u16, 80, 443, 3306, 5432, 6379, 8080, 9200];
            let dp = dp_opts[rng.gen_range(0..dp_opts.len())];
            let info = if proto == "TCP" {
                let flags = ["SYN", "ACK", "PSH, ACK", "FIN, ACK", "RST, ACK", "SYN, ACK"];
                let f = flags[rng.gen_range(0..flags.len())];
                let seq: u32 = rng.r#gen();
                let ack: u32 = rng.r#gen();
                format!("{}{} [{}] Seq={} Ack={}", sp, dp, f, seq, ack)
            } else {
                format!("{}{} Len={}", sp, dp, length.saturating_sub(42))
            };
            (src, dst, Some(sp), Some(dp), info)
        }
    };

    Packet {
        no: counter + 1,
        timestamp: ts,
        src,
        dst,
        protocol: proto.to_string(),
        length,
        info,
        src_port,
        dst_port,
        vlan_id: None,
        bytes,
    }
}

fn weighted_pick<'a>(items: &[&'a str], weights: &[u32], rng: &mut impl Rng) -> &'a str {
    let total: u32 = weights.iter().sum();
    let mut r = rng.gen_range(0..total);
    for (item, w) in items.iter().zip(weights.iter()) {
        if r < *w { return item; }
        r -= w;
    }
    items[0]
}

fn rand_ip<'a>(pool: &[&'a str], rng: &mut impl Rng) -> &'a str {
    pool[rng.gen_range(0..pool.len())]
}

pub fn rand_mac(rng: &mut impl Rng) -> String {
    (0..6).map(|i| {
        if i == 0 { format!("{:02x}", rng.r#gen::<u8>() & 0xfe) }
        else       { format!("{:02x}", rng.r#gen::<u8>()) }
    }).collect::<Vec<_>>().join(":")
}