diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 87c8e8c..eaaeb0e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,16 +12,60 @@ on: jobs: test: runs-on: ubuntu-latest + strategy: + matrix: + rust: + - stable + - beta + - nightly + target: + - aarch64-unknown-linux-gnu + - aarch64-unknown-linux-musl + - riscv64gc-unknown-linux-gnu + - x86_64-unknown-linux-gnu + - x86_64-unknown-linux-musl + env: + CARGO_BUILD_TARGET: ${{ matrix.target }} + RUST_BACKTRACE: full steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - override: true + - name: Install cross-compilation tools + run: | + sudo apt-get update + sudo apt-get install -y gcc-aarch64-linux-gnu gcc-riscv64-linux-gnu musl-tools - - name: Test - env: - RUST_BACKTRACE: full + - name: Configure Cargo for cross-compilation run: | - cargo test --verbose + mkdir -p .cargo + cat > .cargo/config.toml <(); + + /// Returns the Geneve protocol version (2 bits). + /// + /// According to RFC 8926, the current version is 0. + #[inline] + pub fn ver(&self) -> u8 { + (self.ver_opt_len >> 6) & 0x03 + } + + /// Sets the Geneve protocol version (2 bits). + /// + /// `ver` should be a 2-bit value (0-3). + #[inline] + pub fn set_ver(&mut self, ver: u8) { + let preserved_bits = self.ver_opt_len & 0x3F; + self.ver_opt_len = preserved_bits | ((ver & 0x03) << 6); + } + + /// Returns the length of the option fields in 4-byte multiples (6 bits). + #[inline] + pub fn opt_len(&self) -> u8 { + self.ver_opt_len & 0x3F + } + + /// Sets the length of the option fields (6 bits). + /// + /// `opt_len` should be a 6-bit value (0-63). + #[inline] + pub fn set_opt_len(&mut self, opt_len: u8) { + let preserved_bits = self.ver_opt_len & 0xC0; + self.ver_opt_len = preserved_bits | (opt_len & 0x3F); + } + + /// Returns the OAM (Operations, Administration, and Maintenance) packet flag (1 bit). + /// + /// If set (1), this packet is an OAM packet. Referred to as 'O' bit in RFC 8926. + #[inline] + pub fn o_flag(&self) -> u8 { + (self.o_c_rsvd >> 7) & 0x01 + } + + /// Sets the OAM packet flag (1 bit). + /// + /// `o_flag` should be a 1-bit value (0 or 1). + #[inline] + pub fn set_o_flag(&mut self, o_flag: u8) { + let preserved_bits = self.o_c_rsvd & 0x7F; + self.o_c_rsvd = preserved_bits | ((o_flag & 0x01) << 7); + } + + /// Returns the Critical Options Present flag (1 bit). + /// + /// If set (1), one or more options are marked as critical. Referred to as 'C' bit in RFC 8926. + #[inline] + pub fn c_flag(&self) -> u8 { + (self.o_c_rsvd >> 6) & 0x01 + } + + /// Sets the Critical Options Present flag (1 bit). + /// + /// `c_flag` should be a 1-bit value (0 or 1). + #[inline] + pub fn set_c_flag(&mut self, c_flag: u8) { + let preserved_bits = self.o_c_rsvd & 0xBF; + self.o_c_rsvd = preserved_bits | ((c_flag & 0x01) << 6); + } + + /// Returns the Protocol Type of the encapsulated payload (16 bits, network byte order). + /// + /// This follows the Ethertype convention. + #[inline] + pub fn protocol_type(&self) -> u16 { + u16::from_be_bytes(self.protocol_type) + } + + /// Sets the Protocol Type (16 bits). + /// + /// The value is stored in network byte order. + #[inline] + pub fn set_protocol_type(&mut self, protocol_type: u16) { + self.protocol_type = protocol_type.to_be_bytes(); + } + + /// Returns the Virtual Network Identifier (VNI) (24 bits). + #[inline] + pub fn vni(&self) -> u32 { + u32::from_be_bytes([0, self.vni[0], self.vni[1], self.vni[2]]) + } + + /// Sets the Virtual Network Identifier (VNI) (24 bits). + /// + /// `vni` should be a 24-bit value. Higher bits are masked. + /// The value is stored in network byte order. + #[inline] + pub fn set_vni(&mut self, vni: u32) { + let vni_val = vni & 0x00FFFFFF; + let bytes = vni_val.to_be_bytes(); + self.vni[0] = bytes[1]; + self.vni[1] = bytes[2]; + self.vni[2] = bytes[3] + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_len() { + assert_eq!(GeneveHdr::LEN, 8); + } + + #[test] + fn test_default() { + let hdr = GeneveHdr::default(); + assert_eq!(hdr.ver_opt_len, 0); + assert_eq!(hdr.o_c_rsvd, 0); + assert_eq!(hdr.protocol_type, [0, 0]); + assert_eq!(hdr.vni, [0, 0, 0]); + assert_eq!(hdr.reserved2, 0); + + assert_eq!(hdr.ver(), 0); + assert_eq!(hdr.opt_len(), 0); + assert_eq!(hdr.o_flag(), 0); + assert_eq!(hdr.c_flag(), 0); + assert_eq!(hdr.protocol_type(), 0); + assert_eq!(hdr.vni(), 0); + } + + #[test] + fn test_ver() { + let mut hdr = GeneveHdr::default(); + hdr.set_ver(0b10); // Version 2 + assert_eq!(hdr.ver(), 0b10); + assert_eq!(hdr.ver_opt_len, 0b10000000, "Raw byte for ver failed"); + + hdr.set_ver(0b111); // Input 7 (3 bits), should be masked to 0b11 (3) + assert_eq!(hdr.ver(), 0b11); + assert_eq!(hdr.ver_opt_len, 0b11000000, "Masking for ver failed"); + + // Test interaction with opt_len + hdr.ver_opt_len = 0; // Reset + hdr.set_opt_len(0x3F); // Max opt_len (all lower 6 bits set) + hdr.set_ver(0b01); + assert_eq!(hdr.ver(), 0b01); + assert_eq!(hdr.opt_len(), 0x3F, "opt_len altered by set_ver"); + assert_eq!(hdr.ver_opt_len, 0b01111111, "Interaction with opt_len failed"); + } + + #[test] + fn test_opt_len() { + let mut hdr = GeneveHdr::default(); + hdr.set_opt_len(0x2A); // 42 + assert_eq!(hdr.opt_len(), 0x2A); + assert_eq!(hdr.ver_opt_len, 0b00101010, "Raw byte for opt_len failed"); + + hdr.set_opt_len(0xFF); // Input 255, should be masked to 0x3F (63) + assert_eq!(hdr.opt_len(), 0x3F); + assert_eq!(hdr.ver_opt_len, 0b00111111, "Masking for opt_len failed"); + + // Test interaction with ver + hdr.ver_opt_len = 0; // Reset + hdr.set_ver(0b11); // Max ver (top 2 bits set) + hdr.set_opt_len(0x15); // 21 + assert_eq!(hdr.ver(), 0b11, "ver altered by set_opt_len"); + assert_eq!(hdr.opt_len(), 0x15); + assert_eq!(hdr.ver_opt_len, 0b11010101, "Interaction with ver failed"); + } + + #[test] + fn test_o_flag() { + let mut hdr = GeneveHdr::default(); + hdr.set_o_flag(1); + assert_eq!(hdr.o_flag(), 1); + assert_eq!(hdr.o_c_rsvd, 0b10000000, "Raw byte for o_flag failed"); + + // The implementation correctly masks the input, so an input of 2 (0b10) becomes 0. + // This sets the o_flag back to 0. + hdr.set_o_flag(0b10); + assert_eq!(hdr.o_flag(), 0); + assert_eq!(hdr.o_c_rsvd, 0b00000000, "Masking for o_flag failed"); + + // Test that setting the O flag preserves the C flag and reserved bits. + hdr.o_c_rsvd = 0; // Reset + hdr.set_c_flag(1); // o_c_rsvd is now 0b01000000 + + // Now, set the O flag. + hdr.set_o_flag(1); + + // Verify that only the O flag bit changed. + assert_eq!(hdr.o_flag(), 1, "o_flag should be 1"); + assert_eq!(hdr.c_flag(), 1, "c_flag should be preserved"); + } + + #[test] + fn test_c_flag() { + let mut hdr = GeneveHdr::default(); + hdr.set_c_flag(1); + assert_eq!(hdr.c_flag(), 1); + assert_eq!(hdr.o_c_rsvd, 0b01000000, "Raw byte for c_flag failed"); + + // The implementation correctly masks the input, so an input of 2 (0b10) becomes 0. + // This sets the c_flag back to 0. + hdr.set_c_flag(0b10); + assert_eq!(hdr.c_flag(), 0); + assert_eq!(hdr.o_c_rsvd, 0b00000000, "Masking for c_flag failed"); + + // Test that setting the C flag preserves the O flag and reserved bits. + hdr.o_c_rsvd = 0; // Reset + hdr.set_o_flag(1); // o_c_rsvd is now 0b10000000 + + // Now, set the C flag. + hdr.set_c_flag(1); + + // Verify that only the C flag bit changed. + assert_eq!(hdr.c_flag(), 1, "c_flag should be 1"); + assert_eq!(hdr.o_flag(), 1, "o_flag should be preserved"); + } + + #[test] + fn test_protocol_type() { + let mut hdr = GeneveHdr::default(); + hdr.set_protocol_type(0xABCD); + assert_eq!(hdr.protocol_type(), 0xABCD); + assert_eq!(hdr.protocol_type, [0xAB, 0xCD], "Raw bytes for protocol_type failed (Big Endian check)"); + } + + #[test] + fn test_vni() { + let mut hdr = GeneveHdr::default(); + hdr.set_vni(0x00123456); + assert_eq!(hdr.vni(), 0x00123456); + assert_eq!(hdr.vni, [0x12, 0x34, 0x56], "Raw bytes for VNI failed (Big Endian check)"); + + hdr.set_vni(0xFF123456); // Input with >24 bits + assert_eq!(hdr.vni(), 0x00123456, "Masking for VNI failed"); + assert_eq!(hdr.vni, [0x12, 0x34, 0x56], "Raw bytes after VNI masking failed"); + } +} diff --git a/src/lib.rs b/src/lib.rs index b5df8b5..d9373eb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ #![doc = include_str!("../README.md")] -#![cfg_attr(not(feature = "std"), no_std)] +#![no_std] pub mod arp; pub mod bitfield; @@ -14,3 +14,4 @@ pub mod udp; pub mod vlan; pub mod vxlan; pub mod llc; +pub mod geneve; diff --git a/src/vxlan.rs b/src/vxlan.rs index 1b3fc27..8caaa71 100644 --- a/src/vxlan.rs +++ b/src/vxlan.rs @@ -1,33 +1,245 @@ use core::mem; -use crate::bitfield::BitfieldUnit; - -/// VXLAN header, which is present at the beginning of every UDP payload containing VXLAN packets. +/// VXLAN (Virtual eXtensible Local Area Network) header. +/// +/// Encapsulates OSI layer 2 Ethernet frames within layer 4 UDP packets. +/// Uses a 24-bit VXLAN Network Identifier (VNI) for traffic segregation. +/// Header length: 8 bytes. +/// Reference: RFC 7348. #[repr(C, packed)] #[derive(Debug, Copy, Clone)] #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] pub struct VxlanHdr { - /// VXLAN flags. See [`VxlanHdr::vni_valid`] and [`VxlanHdr::set_vni_valid`]. - pub flags: BitfieldUnit<[u8; 1usize]>, - pub _reserved: [u8; 3], - /// VXLAN Virtual Network Identifier. - /// - /// This is a 24-bit number combined with reserved bytes, see [`VxlanHdr::vni`] and - /// [`VxlanHdr::set_vni`]. + /// Flags (8 bits). Bit 3 (I flag) must be 1 if VNI is present. Other bits are reserved (R). + pub flags: u8, + /// Reserved field (24 bits). Must be zero on transmission. + pub _reserved1: [u8; 3], + /// Contains the 24-bit VNI (upper 3 bytes) and an 8-bit reserved field (the lowest byte). + /// The reserved field (the lowest byte) must be zero on transmission. pub vni: [u8; 3], - pub _padding: u8, + pub _reserved2: u8, } +/// Mask for the I-flag (VNI Present flag, bit 3) in the `flags` field. +pub const VXLAN_I_FLAG_MASK: u8 = 0x08; + impl VxlanHdr { - pub const LEN: usize = mem::size_of::(); + /// Length of the VXLAN header in bytes (8 bytes). + pub const LEN: usize = mem::size_of::(); + + /// Creates a new `VxlanHdr`. + /// + /// Sets the I-flag, zeros reserved fields, and sets the VNI. + /// + /// # Parameters + /// - `vni`: The 24-bit VXLAN Network Identifier. + /// + /// # Returns + /// A new `VxlanHdr` instance. + pub fn new(vni: u32) -> Self { + let mut hdr = VxlanHdr { + flags: VXLAN_I_FLAG_MASK, + _reserved1: [0u8; 3], + vni: [0u8; 3], + _reserved2: 0u8, + }; + hdr.set_vni(vni); + hdr + } + + /// Creates a new `VxlanHdr`. + /// + /// Sets the I-flag, zeros reserved fields, and sets the VNI. + /// + /// # Parameters + /// - `flags`: The 8-bit value to set for the flag field. + /// - `vni`: The 24-bit VXLAN Network Identifier. + /// + /// # Returns + /// A new `VxlanHdr` instance. + pub fn with_flags(flags: u8, vni: [u8; 3]) -> Self { + Self { + flags, + _reserved1: [0; 3], + vni, + _reserved2: 0, + } + } + + /// Returns the raw flags' byte. + /// + /// # Returns + /// The 8-bit flags field. + #[inline] + pub fn flags(&self) -> u8 { + self.flags + } + + /// Sets the raw flags byte. + /// + /// # Parameters + /// - `flags`: The 8-bit value to set for the flags field. + #[inline] + pub fn set_flags(&mut self, flags: u8) { + self.flags = flags; + } + + /// Checks if the I-flag (VNI Present) is set. + /// + /// # Returns + /// `true` if the I-flag is set, `false` otherwise. + #[inline] + pub fn vni_present(&self) -> bool { + (self.flags & VXLAN_I_FLAG_MASK) == VXLAN_I_FLAG_MASK + } + /// Sets or clears the I-flag (VNI Present). + /// + /// Preserves other flag bits. + /// + /// # Parameters + /// - `present`: If `true`, sets the I-flag; otherwise, clears it. #[inline] - pub fn vni_valid(&self) -> bool { - self.flags.get_bit(4) + pub fn set_vni_present(&mut self, present: bool) { + if present { + self.flags |= VXLAN_I_FLAG_MASK; + } else { + self.flags &= !VXLAN_I_FLAG_MASK; + } } + /// Returns the VXLAN Network Identifier (VNI). + /// + /// # Returns + /// The 24-bit VNI as a `u32`. + #[inline] + pub fn vni(&self) -> u32 { + u32::from_be_bytes([0, self.vni[0], self.vni[1], self.vni[2]]) + } + + /// Sets the VXLAN Network Identifier (VNI). + /// + /// Masks the input `vni` to 24 bits. Preserves the `reserved2` field. + /// + /// # Parameters + /// - `vni`: The 24-bit VNI value. #[inline] - pub fn set_vni_valid(&mut self, val: bool) { - self.flags.set_bit(4, val) + pub fn set_vni(&mut self, vni: u32) { + let vni_24bit = vni & 0x00FF_FFFF; + let vni_bytes = vni_24bit.to_be_bytes(); + self.vni[0] = vni_bytes[1]; + self.vni[1] = vni_bytes[2]; + self.vni[2] = vni_bytes[3]; + } +} +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_vxlanhdr_len() { + assert_eq!(VxlanHdr::LEN, 8, "VXLAN header length should be 8 bytes"); + } + + #[test] + fn test_vxlanhdr_new() { + let vni_val: u32 = 0xABCDEF; + let hdr = VxlanHdr::new(vni_val); + assert_eq!( + hdr.flags(), + VXLAN_I_FLAG_MASK, + "Flags should have I-flag set and others zero" + ); + assert!(hdr.vni_present(), "VNI present flag should be set by new()"); + assert_eq!(hdr.vni(), vni_val, "VNI should be set correctly by new()"); + } + + #[test] + fn test_flags_management() { + let mut hdr = VxlanHdr::new(0x123); + hdr.set_flags(0xFF); + assert_eq!(hdr.flags(), 0xFF); + assert!( + hdr.vni_present(), + "I-flag should be set if flags byte is 0xFF" + ); + hdr.set_flags(0x00); + assert!( + !hdr.vni_present(), + "I-flag should be clear if flags byte is 0x00" + ); + hdr.set_vni_present(true); + assert_eq!( + hdr.flags(), + VXLAN_I_FLAG_MASK, + "set_vni_present(true) should set I-flag" + ); + assert!(hdr.vni_present()); + hdr.set_vni_present(false); + assert_eq!( + hdr.flags(), + 0x00, + "set_vni_present(false) should clear I-flag" + ); + assert!(!hdr.vni_present()); + hdr.set_flags(0xF0); + assert!(!hdr.vni_present()); + hdr.set_vni_present(true); + assert_eq!( + hdr.flags(), + 0xF0 | VXLAN_I_FLAG_MASK, + "Setting I-flag should preserve other bits" + ); + assert!(hdr.vni_present()); + hdr.set_vni_present(false); + assert_eq!( + hdr.flags(), + 0xF0 & !VXLAN_I_FLAG_MASK, + "Clearing I-flag should preserve other bits" + ); + assert!(!hdr.vni_present()); + } + + #[test] + fn test_vni_management() { + let mut hdr = VxlanHdr::new(0); + let vni_val: u32 = 0xABCDEF; + hdr.set_vni(vni_val); + assert_eq!(hdr.vni(), vni_val, "VNI should be set correctly"); + let large_vni: u32 = 0x12ABCDEF; + hdr.set_vni(large_vni); + assert_eq!(hdr.vni(), 0xABCDEF, "VNI should be masked to 24 bits"); + hdr.set_vni(0); + assert_eq!(hdr.vni(), 0, "VNI should be settable to 0"); + let max_vni: u32 = 0xFFFFFF; + hdr.set_vni(max_vni); + assert_eq!(hdr.vni(), max_vni, "Max 24-bit VNI should be settable"); + hdr.set_vni(0x123456); + assert_eq!(hdr.vni(), 0x123456, "VNI should be updated"); + } + + #[test] + fn test_field_storage_and_retrieval_direct_manipulation() { + let mut hdr = VxlanHdr::with_flags(0x08, [0xAB, 0xCD, 0xEF]); + hdr.flags = 0x08; + hdr.vni = [0xAB, 0xCD, 0xEF]; + assert_eq!(hdr.flags(), 0x08); + assert!(hdr.vni_present()); + assert_eq!(hdr.vni(), 0xABCDEF); + hdr.set_vni_present(false); + assert_eq!( + hdr.flags, 0x00, + "Direct field check after set_vni_present(false)" + ); + hdr.set_vni(0x654321); + assert_eq!(hdr.vni[0], 0x65, "Byte 0 of vni after set_vni"); + assert_eq!(hdr.vni[1], 0x43, "Byte 1 of vni after set_vni"); + assert_eq!(hdr.vni[2], 0x21, "Byte 2 of vni after set_vni"); + assert_eq!(hdr.vni(), 0x654321); + assert_eq!( + hdr.vni[0], 0x65, + "Byte 0 of vni preserved after set_reserved2" + ); + assert_eq!(hdr.vni(), 0x654321); } }