nvpn 4.1.13

CLI and daemon for Nostr VPN private mesh networks
#[cfg(test)]
mod linux_vnet_tun_tests {
    use super::*;
    use std::net::Ipv4Addr;

    #[test]
    fn linux_vnet_plain_read_strips_virtio_header() {
        let packet = ipv4_tcp_gso_packet(16, 16, 0x10);
        let mut frame = vec![0_u8; LINUX_VIRTIO_NET_HDR_LEN + packet.len()];
        LinuxVirtioNetHdr {
            flags: 0,
            gso_type: LINUX_VIRTIO_NET_HDR_GSO_NONE,
            hdr_len: 0,
            gso_size: 0,
            csum_start: 0,
            csum_offset: 0,
        }
        .encode(&mut frame[..LINUX_VIRTIO_NET_HDR_LEN]);
        frame[LINUX_VIRTIO_NET_HDR_LEN..].copy_from_slice(&packet);

        let mut batch = Vec::new();
        let count = handle_linux_vnet_read(&mut frame, &mut batch).expect("plain vnet read");
        assert_eq!(count, 1);
        assert_eq!(batch.len(), 1);
        assert_eq!(batch[0].bytes.len(), packet.len());
        assert_eq!(&batch[0].bytes[..20], &packet[..20]);
    }

    #[test]
    fn linux_vnet_gro_can_be_disabled_for_constrained_guest_kernels() {
        let mut first = ipv4_tcp_packet(1000, 800, LINUX_TCP_FLAG_ACK);
        let mut second = ipv4_tcp_packet(1800, 600, LINUX_TCP_FLAG_ACK | LINUX_TCP_FLAG_PSH);
        nostr_vpn_core::packet_checksums::finalize_ipv4_transport_checksum(&mut first);
        nostr_vpn_core::packet_checksums::finalize_ipv4_transport_checksum(&mut second);

        let mut preparer = LinuxVnetWritePreparer::with_gro(false);
        let frames = linux_vnet_collect_prepared_write_frames(&mut preparer, vec![first, second]);

        assert_eq!(frames.len(), 2);
        assert!(frames
            .iter()
            .all(|(frame, _)| matches!(frame, LinuxVnetPreparedWriteFrame::RawPacket(_))));
    }

    #[test]
    fn linux_vnet_tcp4_gso_read_splits_into_checked_segments() {
        let packet = ipv4_tcp_gso_packet(2400, 1200, 0x18);
        let mut frame = vec![0_u8; LINUX_VIRTIO_NET_HDR_LEN + packet.len()];
        LinuxVirtioNetHdr {
            flags: LINUX_VIRTIO_NET_HDR_F_NEEDS_CSUM,
            gso_type: LINUX_VIRTIO_NET_HDR_GSO_TCPV4,
            hdr_len: 40,
            gso_size: 1200,
            csum_start: 20,
            csum_offset: 16,
        }
        .encode(&mut frame[..LINUX_VIRTIO_NET_HDR_LEN]);
        frame[LINUX_VIRTIO_NET_HDR_LEN..].copy_from_slice(&packet);

        let mut batch = Vec::new();
        let count = handle_linux_vnet_read(&mut frame, &mut batch).expect("tcp4 gso read");
        assert_eq!(count, 2);
        assert_eq!(batch.len(), 2);
        assert_eq!(
            batch[0].destination,
            Some(IpAddr::V4(Ipv4Addr::new(10, 44, 0, 2)))
        );
        assert_eq!(
            batch[1].destination,
            Some(IpAddr::V4(Ipv4Addr::new(10, 44, 0, 2)))
        );

        let first = &batch[0].bytes;
        let second = &batch[1].bytes;
        assert_eq!(first.len(), 1240);
        assert_eq!(second.len(), 1240);
        assert_eq!(u16::from_be_bytes([first[2], first[3]]), 1240);
        assert_eq!(u16::from_be_bytes([second[2], second[3]]), 1240);
        assert_eq!(u16::from_be_bytes([first[4], first[5]]), 0x1234);
        assert_eq!(u16::from_be_bytes([second[4], second[5]]), 0x1235);
        assert_eq!(u32::from_be_bytes([first[24], first[25], first[26], first[27]]), 1000);
        assert_eq!(
            u32::from_be_bytes([second[24], second[25], second[26], second[27]]),
            2200
        );
        assert_eq!(first[33] & LINUX_TCP_FLAG_PSH, 0);
        assert_ne!(second[33] & LINUX_TCP_FLAG_PSH, 0);
        assert_eq!(linux_vnet_checksum(&first[..20], 0), 0xffff);
        assert_eq!(linux_vnet_checksum(&second[..20], 0), 0xffff);
        assert_eq!(ipv4_transport_sum(first), 0xffff);
        assert_eq!(ipv4_transport_sum(second), 0xffff);
    }

    #[test]
    fn linux_vnet_tcp4_gso_read_keeps_final_tiny_segment_in_order() {
        let packet = ipv4_tcp_packet(1000, 2500, 0x18);
        let mut frame = vec![0_u8; LINUX_VIRTIO_NET_HDR_LEN + packet.len()];
        LinuxVirtioNetHdr {
            flags: LINUX_VIRTIO_NET_HDR_F_NEEDS_CSUM,
            gso_type: LINUX_VIRTIO_NET_HDR_GSO_TCPV4,
            hdr_len: 40,
            gso_size: 1200,
            csum_start: 20,
            csum_offset: 16,
        }
        .encode(&mut frame[..LINUX_VIRTIO_NET_HDR_LEN]);
        frame[LINUX_VIRTIO_NET_HDR_LEN..].copy_from_slice(&packet);

        let mut batch = Vec::new();
        let count = handle_linux_vnet_read(&mut frame, &mut batch).expect("tcp4 gso read");
        assert_eq!(count, 3);
        assert_eq!(batch.len(), 3);

        assert_eq!(batch[0].bytes[33] & LINUX_TCP_FLAG_PSH, 0);
        assert_eq!(batch[1].bytes[33] & LINUX_TCP_FLAG_PSH, 0);
        assert_ne!(batch[2].bytes[33] & LINUX_TCP_FLAG_PSH, 0);
        assert_eq!(batch[2].bytes.len(), 140);
    }

    #[test]
    fn linux_vnet_tcp4_gso_read_wraps_sequence_numbers() {
        let first_seq = u32::MAX - 599;
        let packet = ipv4_tcp_packet(first_seq, 2400, 0x18);
        let mut frame = vec![0_u8; LINUX_VIRTIO_NET_HDR_LEN + packet.len()];
        LinuxVirtioNetHdr {
            flags: LINUX_VIRTIO_NET_HDR_F_NEEDS_CSUM,
            gso_type: LINUX_VIRTIO_NET_HDR_GSO_TCPV4,
            hdr_len: 40,
            gso_size: 1200,
            csum_start: 20,
            csum_offset: 16,
        }
        .encode(&mut frame[..LINUX_VIRTIO_NET_HDR_LEN]);
        frame[LINUX_VIRTIO_NET_HDR_LEN..].copy_from_slice(&packet);

        let mut batch = Vec::new();
        let count = handle_linux_vnet_read(&mut frame, &mut batch).expect("tcp4 gso read");
        assert_eq!(count, 2);
        assert_eq!(
            u32::from_be_bytes([
                batch[0].bytes[24],
                batch[0].bytes[25],
                batch[0].bytes[26],
                batch[0].bytes[27]
            ]),
            first_seq
        );
        assert_eq!(
            u32::from_be_bytes([
                batch[1].bytes[24],
                batch[1].bytes[25],
                batch[1].bytes[26],
                batch[1].bytes[27]
            ]),
            first_seq.wrapping_add(1200)
        );
        assert_eq!(ipv4_transport_sum(&batch[0].bytes), 0xffff);
        assert_eq!(ipv4_transport_sum(&batch[1].bytes), 0xffff);
    }

    #[test]
    fn linux_vnet_tcp4_gro_write_coalesces_adjacent_segments() {
        let mut first = ipv4_tcp_packet(1000, 800, LINUX_TCP_FLAG_ACK);
        let mut second = ipv4_tcp_packet(1800, 600, LINUX_TCP_FLAG_ACK | LINUX_TCP_FLAG_PSH);
        nostr_vpn_core::packet_checksums::finalize_ipv4_transport_checksum(&mut first);
        nostr_vpn_core::packet_checksums::finalize_ipv4_transport_checksum(&mut second);

        let packets = vec![first, second];
        let original_packets = packets.clone();
        let frames = linux_vnet_prepare_write_frames(&packets);
        assert_eq!(packets, original_packets);
        assert_eq!(frames.len(), 1);

        let frame = prepared_write_frame_bytes(&frames[0]);
        let hdr = LinuxVirtioNetHdr::decode(&frame).expect("virtio header");
        assert_eq!(hdr.flags, LINUX_VIRTIO_NET_HDR_F_NEEDS_CSUM);
        assert_eq!(hdr.gso_type, LINUX_VIRTIO_NET_HDR_GSO_TCPV4);
        assert_eq!(hdr.hdr_len, 40);
        assert_eq!(hdr.gso_size, 800);
        assert_eq!(hdr.csum_start, 20);
        assert_eq!(hdr.csum_offset, 16);

        let packet = &frame[LINUX_VIRTIO_NET_HDR_LEN..];
        assert_eq!(packet.len(), 20 + 20 + 1400);
        assert_eq!(u16::from_be_bytes([packet[2], packet[3]]), 1440);
        assert_eq!(linux_vnet_checksum(&packet[..20], 0), 0xffff);
        assert_ne!(packet[33] & LINUX_TCP_FLAG_PSH, 0);

        let pseudo = linux_vnet_pseudo_header_sum(
            LINUX_IPPROTO_TCP,
            &packet[12..16],
            &packet[16..20],
            (packet.len() - 20) as u16,
        );
        let expected_partial = !linux_vnet_checksum(&[], pseudo);
        assert_eq!(
            u16::from_be_bytes([packet[36], packet[37]]),
            expected_partial
        );
    }

    #[test]
    fn linux_vnet_tcp4_gro_write_coalesces_interleaved_flows() {
        let mut first_a = ipv4_tcp_packet_with_ports(1000, 800, LINUX_TCP_FLAG_ACK, 443, 45172);
        let mut first_b = ipv4_tcp_packet_with_ports(7000, 800, LINUX_TCP_FLAG_ACK, 443, 45173);
        let mut second_a = ipv4_tcp_packet_with_ports(
            1800,
            600,
            LINUX_TCP_FLAG_ACK | LINUX_TCP_FLAG_PSH,
            443,
            45172,
        );
        let mut second_b = ipv4_tcp_packet_with_ports(
            7800,
            600,
            LINUX_TCP_FLAG_ACK | LINUX_TCP_FLAG_PSH,
            443,
            45173,
        );
        for packet in [&mut first_a, &mut first_b, &mut second_a, &mut second_b] {
            nostr_vpn_core::packet_checksums::finalize_ipv4_transport_checksum(packet);
        }

        let packets = vec![first_a, first_b, second_a, second_b];
        let frames = linux_vnet_prepare_write_frames(&packets);
        assert_eq!(frames.len(), 2);

        let first = prepared_write_frame_bytes(&frames[0]);
        let first_hdr = LinuxVirtioNetHdr::decode(&first).expect("first virtio header");
        assert_eq!(first_hdr.gso_type, LINUX_VIRTIO_NET_HDR_GSO_TCPV4);
        assert_eq!(first_hdr.gso_size, 800);
        assert_eq!(u16::from_be_bytes([first[32], first[33]]), 45172);
        assert_eq!(first.len(), LINUX_VIRTIO_NET_HDR_LEN + 20 + 20 + 1400);

        let second = prepared_write_frame_bytes(&frames[1]);
        let second_hdr = LinuxVirtioNetHdr::decode(&second).expect("second virtio header");
        assert_eq!(second_hdr.gso_type, LINUX_VIRTIO_NET_HDR_GSO_TCPV4);
        assert_eq!(second_hdr.gso_size, 800);
        assert_eq!(u16::from_be_bytes([second[32], second[33]]), 45173);
        assert_eq!(second.len(), LINUX_VIRTIO_NET_HDR_LEN + 20 + 20 + 1400);
    }

    #[test]
    fn linux_vnet_tcp4_gro_write_keeps_noncandidate_boundary() {
        let mut first = ipv4_tcp_packet(1000, 800, LINUX_TCP_FLAG_ACK);
        let mut fin = ipv4_tcp_packet(1800, 1, LINUX_TCP_FLAG_FIN | LINUX_TCP_FLAG_ACK);
        let mut second = ipv4_tcp_packet(1800, 600, LINUX_TCP_FLAG_ACK | LINUX_TCP_FLAG_PSH);
        for packet in [&mut first, &mut fin, &mut second] {
            nostr_vpn_core::packet_checksums::finalize_ipv4_transport_checksum(packet);
        }

        let packets = vec![first, fin, second];
        let frames = linux_vnet_prepare_write_frames(&packets);
        assert_eq!(frames.len(), 3);
        for frame in [frames.first().unwrap(), frames.last().unwrap()] {
            let frame = prepared_write_frame_bytes(frame);
            let hdr = LinuxVirtioNetHdr::decode(&frame).expect("virtio header");
            assert_eq!(hdr.gso_type, LINUX_VIRTIO_NET_HDR_GSO_NONE);
        }
    }

    #[test]
    fn linux_vnet_tcp4_gro_write_coalesces_wrapped_sequences() {
        let first_seq = u32::MAX - 399;
        let mut first = ipv4_tcp_packet(first_seq, 800, LINUX_TCP_FLAG_ACK);
        let mut second = ipv4_tcp_packet(
            first_seq.wrapping_add(800),
            600,
            LINUX_TCP_FLAG_ACK | LINUX_TCP_FLAG_PSH,
        );
        nostr_vpn_core::packet_checksums::finalize_ipv4_transport_checksum(&mut first);
        nostr_vpn_core::packet_checksums::finalize_ipv4_transport_checksum(&mut second);

        let packets = vec![first, second];
        let frames = linux_vnet_prepare_write_frames(&packets);
        assert_eq!(frames.len(), 1);

        let frame = prepared_write_frame_bytes(&frames[0]);
        let hdr = LinuxVirtioNetHdr::decode(&frame).expect("virtio header");
        assert_eq!(hdr.gso_type, LINUX_VIRTIO_NET_HDR_GSO_TCPV4);
        assert_eq!(hdr.gso_size, 800);

        let packet = &frame[LINUX_VIRTIO_NET_HDR_LEN..];
        assert_eq!(
            u32::from_be_bytes([packet[24], packet[25], packet[26], packet[27]]),
            first_seq
        );
        assert_eq!(packet.len(), 20 + 20 + 1400);
        assert_eq!(u16::from_be_bytes([packet[2], packet[3]]), 1440);
        assert_ne!(packet[33] & LINUX_TCP_FLAG_PSH, 0);
        assert_eq!(linux_vnet_checksum(&packet[..20], 0), 0xffff);
    }

    #[test]
    fn linux_vnet_tcp4_gro_write_keeps_sequence_gap_separate() {
        let mut first = ipv4_tcp_packet(1000, 800, LINUX_TCP_FLAG_ACK);
        let mut second = ipv4_tcp_packet(2000, 600, LINUX_TCP_FLAG_ACK);
        nostr_vpn_core::packet_checksums::finalize_ipv4_transport_checksum(&mut first);
        nostr_vpn_core::packet_checksums::finalize_ipv4_transport_checksum(&mut second);

        let packets = vec![first, second];
        let frames = linux_vnet_prepare_write_frames(&packets);
        assert_eq!(frames.len(), 2);
        for frame in frames {
            let frame = prepared_write_frame_bytes(&frame);
            let hdr = LinuxVirtioNetHdr::decode(&frame).expect("virtio header");
            assert_eq!(hdr.gso_type, LINUX_VIRTIO_NET_HDR_GSO_NONE);
            assert_eq!(hdr.gso_size, 0);
        }
    }

    fn prepared_write_frame_bytes(frame: &(LinuxVnetPreparedWriteFrame, Vec<u8>)) -> Vec<u8> {
        frame.1.clone()
    }

    fn ipv4_tcp_gso_packet(payload_len: usize, gso_size: usize, flags: u8) -> Vec<u8> {
        let packet = ipv4_tcp_packet(1000, payload_len, flags);
        assert_eq!(payload_len % gso_size, 0);
        packet
    }

    fn ipv4_tcp_packet(seq: u32, payload_len: usize, flags: u8) -> Vec<u8> {
        ipv4_tcp_packet_with_ports(seq, payload_len, flags, 443, 45172)
    }

    fn ipv4_tcp_packet_with_ports(
        seq: u32,
        payload_len: usize,
        flags: u8,
        src_port: u16,
        dst_port: u16,
    ) -> Vec<u8> {
        let total_len = 20 + 20 + payload_len;
        let mut packet = vec![0_u8; total_len];
        packet[0] = 0x45;
        packet[2..4].copy_from_slice(&(total_len as u16).to_be_bytes());
        packet[4..6].copy_from_slice(&0x1234_u16.to_be_bytes());
        packet[8] = 64;
        packet[9] = LINUX_IPPROTO_TCP;
        packet[12..16].copy_from_slice(&Ipv4Addr::new(10, 44, 0, 1).octets());
        packet[16..20].copy_from_slice(&Ipv4Addr::new(10, 44, 0, 2).octets());
        packet[20..22].copy_from_slice(&src_port.to_be_bytes());
        packet[22..24].copy_from_slice(&dst_port.to_be_bytes());
        packet[24..28].copy_from_slice(&seq.to_be_bytes());
        packet[28..32].copy_from_slice(&777_u32.to_be_bytes());
        packet[32] = 5 << 4;
        packet[33] = flags;
        packet[34..36].copy_from_slice(&65535_u16.to_be_bytes());
        for i in 0..payload_len {
            packet[40 + i] = (i % 251) as u8;
        }
        packet
    }

    fn ipv4_transport_sum(packet: &[u8]) -> u16 {
        let transport_len = packet.len() - 20;
        let pseudo = linux_vnet_pseudo_header_sum(
            packet[9],
            &packet[12..16],
            &packet[16..20],
            transport_len as u16,
        );
        linux_vnet_checksum(&packet[20..], pseudo)
    }
}