Skip to main content

packet_strata/packet/tunnel/
vxlan.rs

1//! VXLAN (Virtual Extensible LAN) protocol parser
2//!
3//! This module implements parsing for VXLAN tunnels as defined in RFC 7348.
4//! VXLAN allows Layer 2 Ethernet frames to be encapsulated in UDP datagrams
5//! for transport across Layer 3 networks.
6//!
7//! # VXLAN Header Format
8//!
9//! ```text
10//!  0                   1                   2                   3
11//!  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
12//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
13//! |R|R|R|R|I|R|R|R|            Reserved                           |
14//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
15//! |                VXLAN Network Identifier (VNI) |   Reserved    |
16//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
17//! ```
18//!
19//! # Key characteristics
20//!
21//! - UDP destination port: 4789 (IANA assigned)
22//! - VNI (VXLAN Network Identifier): 24 bits, supports up to 16 million virtual networks
23//! - I flag (bit 4): MUST be 1 to indicate valid VNI
24//! - Encapsulates complete Ethernet frames
25//!
26//! # Examples
27//!
28//! ## Basic VXLAN parsing
29//!
30//! ```
31//! use packet_strata::packet::tunnel::vxlan::VxlanHeader;
32//! use packet_strata::packet::protocol::EtherProto;
33//! use packet_strata::packet::{HeaderParser, PacketHeader};
34//!
35//! // VXLAN packet with VNI = 100
36//! let packet = vec![
37//!     0x08, 0x00, 0x00, 0x00,  // Flags (I=1) + Reserved
38//!     0x00, 0x00, 0x64, 0x00,  // VNI = 100, Reserved
39//!     // ... Ethernet frame follows ...
40//! ];
41//!
42//! let (header, payload) = VxlanHeader::from_bytes(&packet).unwrap();
43//! assert!(header.is_vni_valid());
44//! assert_eq!(header.vni(), 100);
45//! assert_eq!(header.inner_type(), EtherProto::TEB);
46//! ```
47//!
48//! ## VXLAN with specific VNI
49//!
50//! ```
51//! use packet_strata::packet::tunnel::vxlan::VxlanHeader;
52//! use packet_strata::packet::HeaderParser;
53//!
54//! // VXLAN with VNI = 0x123456
55//! let packet = vec![
56//!     0x08, 0x00, 0x00, 0x00,  // Flags (I=1) + Reserved
57//!     0x12, 0x34, 0x56, 0x00,  // VNI = 0x123456, Reserved
58//!     // ... Ethernet frame follows ...
59//! ];
60//!
61//! let (header, _) = VxlanHeader::from_bytes(&packet).unwrap();
62//! assert_eq!(header.vni(), 0x123456);
63//! ```
64
65use std::fmt::{self, Formatter};
66
67use zerocopy::byteorder::{BigEndian, U32};
68use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
69
70use crate::packet::protocol::EtherProto;
71use crate::packet::{HeaderParser, PacketHeader};
72
73/// VXLAN UDP destination port (IANA assigned)
74pub const VXLAN_PORT: u16 = 4789;
75
76/// Maximum VNI value (24-bit field)
77pub const VXLAN_MAX_VNI: u32 = 0xFFFFFF;
78
79/// VXLAN Header structure as defined in RFC 7348
80///
81/// The VXLAN header is 8 bytes and contains flags and the VNI.
82///
83/// Header format (8 bytes):
84/// ```text
85///  0                   1                   2                   3
86///  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
87/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
88/// |R|R|R|R|I|R|R|R|            Reserved                           |
89/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
90/// |                VXLAN Network Identifier (VNI) |   Reserved    |
91/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
92/// ```
93#[repr(C, packed)]
94#[derive(FromBytes, IntoBytes, Unaligned, Debug, Clone, Copy, KnownLayout, Immutable)]
95pub struct VxlanHeader {
96    flags_reserved: U32<BigEndian>,
97    vni_reserved: U32<BigEndian>,
98}
99
100impl VxlanHeader {
101    // VNI field masks
102    const VNI_MASK: u32 = 0xFFFFFF00;
103    const VNI_SHIFT: u32 = 8;
104
105    // Flags masks (in the first byte of flags_reserved)
106    const FLAGS_MASK: u32 = 0xFF000000;
107    const FLAG_I_MASK: u32 = 0x08000000;
108
109    // Reserved bits mask (should be 0)
110    const RESERVED1_MASK: u32 = 0x00FFFFFF;
111    const RESERVED2_MASK: u32 = 0x000000FF;
112
113    #[allow(unused)]
114    const NAME: &'static str = "VxlanHeader";
115
116    /// Returns the flags byte
117    #[inline]
118    pub fn flags(&self) -> u8 {
119        ((self.flags_reserved.get() & Self::FLAGS_MASK) >> 24) as u8
120    }
121
122    /// Check if the I flag is set (VNI is valid)
123    ///
124    /// According to RFC 7348, the I flag MUST be set to 1 for a valid VXLAN packet.
125    #[inline]
126    pub fn is_vni_valid(&self) -> bool {
127        self.flags_reserved.get() & Self::FLAG_I_MASK != 0
128    }
129
130    /// Returns the VXLAN Network Identifier (VNI) - 24 bits
131    ///
132    /// The VNI identifies the individual VXLAN segment. Valid values are 0 to 16,777,215.
133    #[inline]
134    pub fn vni(&self) -> u32 {
135        (self.vni_reserved.get() & Self::VNI_MASK) >> Self::VNI_SHIFT
136    }
137
138    /// Returns the raw 32-bit VNI field (VNI + reserved byte)
139    #[inline]
140    pub fn vni_raw(&self) -> u32 {
141        self.vni_reserved.get()
142    }
143
144    /// Returns the first reserved field (24 bits after flags)
145    #[inline]
146    pub fn reserved1(&self) -> u32 {
147        self.flags_reserved.get() & Self::RESERVED1_MASK
148    }
149
150    /// Returns the second reserved field (8 bits after VNI)
151    #[inline]
152    pub fn reserved2(&self) -> u8 {
153        (self.vni_reserved.get() & Self::RESERVED2_MASK) as u8
154    }
155
156    /// Validates the VXLAN header according to RFC 7348
157    ///
158    /// A valid VXLAN header must have the I flag set.
159    /// Reserved bits should be 0 but we don't enforce this strictly.
160    #[inline]
161    fn is_valid(&self) -> bool {
162        // I flag MUST be set
163        self.is_vni_valid()
164    }
165
166    /// Validates the VXLAN header strictly
167    ///
168    /// Checks that I flag is set and all reserved bits are 0.
169    #[inline]
170    pub fn is_valid_strict(&self) -> bool {
171        self.is_vni_valid() && self.reserved1() == 0 && self.reserved2() == 0
172    }
173}
174
175impl PacketHeader for VxlanHeader {
176    const NAME: &'static str = "VxlanHeader";
177    type InnerType = EtherProto;
178
179    #[inline]
180    fn inner_type(&self) -> Self::InnerType {
181        // VXLAN always encapsulates Ethernet frames
182        EtherProto::TEB
183    }
184
185    #[inline]
186    fn total_len(&self, _buf: &[u8]) -> usize {
187        Self::FIXED_LEN
188    }
189
190    #[inline]
191    fn is_valid(&self) -> bool {
192        self.is_valid()
193    }
194}
195
196impl HeaderParser for VxlanHeader {
197    type Output<'a> = &'a VxlanHeader;
198
199    #[inline]
200    fn into_view<'a>(header: &'a Self, _raw_options: &'a [u8]) -> Self::Output<'a> {
201        header
202    }
203}
204
205impl fmt::Display for VxlanHeader {
206    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
207        write!(
208            f,
209            "VXLAN vni={} flags=0x{:02x}{}",
210            self.vni(),
211            self.flags(),
212            if self.is_vni_valid() { " [I]" } else { "" }
213        )
214    }
215}
216
217/// Check if a UDP packet might be VXLAN based on destination port
218#[inline]
219pub fn is_vxlan_port(dst_port: u16) -> bool {
220    dst_port == VXLAN_PORT
221}
222
223#[cfg(test)]
224mod tests {
225    use super::*;
226
227    #[test]
228    fn test_vxlan_header_size() {
229        assert_eq!(std::mem::size_of::<VxlanHeader>(), 8);
230        assert_eq!(VxlanHeader::FIXED_LEN, 8);
231    }
232
233    #[test]
234    fn test_vxlan_basic_header() {
235        let header = VxlanHeader {
236            flags_reserved: U32::new(0x08000000), // I flag set
237            vni_reserved: U32::new(0x00006400),   // VNI = 100
238        };
239
240        assert!(header.is_vni_valid());
241        assert_eq!(header.vni(), 100);
242        assert_eq!(header.flags(), 0x08);
243        assert!(header.is_valid());
244        assert!(header.is_valid_strict());
245    }
246
247    #[test]
248    fn test_vxlan_vni_values() {
249        // VNI = 0x123456
250        let header = VxlanHeader {
251            flags_reserved: U32::new(0x08000000),
252            vni_reserved: U32::new(0x12345600),
253        };
254
255        assert_eq!(header.vni(), 0x123456);
256        assert!(header.is_valid());
257    }
258
259    #[test]
260    fn test_vxlan_max_vni() {
261        // VNI = 0xFFFFFF (max)
262        let header = VxlanHeader {
263            flags_reserved: U32::new(0x08000000),
264            vni_reserved: U32::new(0xFFFFFF00),
265        };
266
267        assert_eq!(header.vni(), VXLAN_MAX_VNI);
268        assert!(header.is_valid());
269    }
270
271    #[test]
272    fn test_vxlan_zero_vni() {
273        // VNI = 0
274        let header = VxlanHeader {
275            flags_reserved: U32::new(0x08000000),
276            vni_reserved: U32::new(0x00000000),
277        };
278
279        assert_eq!(header.vni(), 0);
280        assert!(header.is_valid());
281    }
282
283    #[test]
284    fn test_vxlan_invalid_no_i_flag() {
285        // I flag not set
286        let header = VxlanHeader {
287            flags_reserved: U32::new(0x00000000), // No I flag
288            vni_reserved: U32::new(0x00006400),
289        };
290
291        assert!(!header.is_vni_valid());
292        assert!(!header.is_valid());
293    }
294
295    #[test]
296    fn test_vxlan_reserved_bits() {
297        // Header with reserved bits set (not strictly valid)
298        let header = VxlanHeader {
299            flags_reserved: U32::new(0x08123456), // I flag + reserved bits set
300            vni_reserved: U32::new(0x00006401),   // VNI + reserved byte set
301        };
302
303        assert!(header.is_vni_valid());
304        assert!(header.is_valid()); // Basic validation passes
305        assert!(!header.is_valid_strict()); // Strict validation fails
306        assert_eq!(header.reserved1(), 0x123456);
307        assert_eq!(header.reserved2(), 0x01);
308    }
309
310    #[test]
311    fn test_vxlan_parsing_basic() {
312        let mut packet = Vec::new();
313
314        // VXLAN header
315        packet.extend_from_slice(&0x08000000u32.to_be_bytes()); // Flags (I=1)
316        packet.extend_from_slice(&0x00006400u32.to_be_bytes()); // VNI = 100
317
318        // Add some payload (Ethernet frame would follow)
319        packet.extend_from_slice(b"ethernet");
320
321        let result = VxlanHeader::from_bytes(&packet);
322        assert!(result.is_ok());
323
324        let (header, payload) = result.unwrap();
325        assert!(header.is_vni_valid());
326        assert_eq!(header.vni(), 100);
327        assert_eq!(payload, b"ethernet");
328    }
329
330    #[test]
331    fn test_vxlan_parsing_with_vni() {
332        let mut packet = Vec::new();
333
334        packet.extend_from_slice(&0x08000000u32.to_be_bytes()); // Flags
335        packet.extend_from_slice(&0xABCDEF00u32.to_be_bytes()); // VNI = 0xABCDEF
336
337        let result = VxlanHeader::from_bytes(&packet);
338        assert!(result.is_ok());
339
340        let (header, _) = result.unwrap();
341        assert_eq!(header.vni(), 0xABCDEF);
342    }
343
344    #[test]
345    fn test_vxlan_parsing_too_small() {
346        let packet = vec![0u8; 7]; // Only 7 bytes, need 8
347
348        let result = VxlanHeader::from_bytes(&packet);
349        assert!(result.is_err());
350    }
351
352    #[test]
353    fn test_vxlan_parsing_invalid_no_i_flag() {
354        let mut packet = Vec::new();
355
356        packet.extend_from_slice(&0x00000000u32.to_be_bytes()); // No I flag
357        packet.extend_from_slice(&0x00006400u32.to_be_bytes()); // VNI
358
359        let result = VxlanHeader::from_bytes(&packet);
360        assert!(result.is_err()); // Should fail validation
361    }
362
363    #[test]
364    fn test_vxlan_inner_type() {
365        let header = VxlanHeader {
366            flags_reserved: U32::new(0x08000000),
367            vni_reserved: U32::new(0x00006400),
368        };
369
370        // VXLAN encapsulates Ethernet (TEB)
371        assert_eq!(header.inner_type(), EtherProto::TEB);
372    }
373
374    #[test]
375    fn test_vxlan_display() {
376        let header = VxlanHeader {
377            flags_reserved: U32::new(0x08000000),
378            vni_reserved: U32::new(0x00006400),
379        };
380
381        let display = format!("{}", header);
382        assert!(display.contains("VXLAN"));
383        assert!(display.contains("vni=100"));
384        assert!(display.contains("[I]"));
385    }
386
387    #[test]
388    fn test_vxlan_display_no_i_flag() {
389        let header = VxlanHeader {
390            flags_reserved: U32::new(0x00000000), // No I flag
391            vni_reserved: U32::new(0x00006400),
392        };
393
394        let display = format!("{}", header);
395        assert!(display.contains("VXLAN"));
396        assert!(!display.contains("[I]"));
397    }
398
399    #[test]
400    fn test_vxlan_port_check() {
401        assert!(is_vxlan_port(4789));
402        assert!(!is_vxlan_port(4788));
403        assert!(!is_vxlan_port(80));
404    }
405
406    #[test]
407    fn test_vxlan_flags_byte() {
408        // Test various flag combinations
409        let header1 = VxlanHeader {
410            flags_reserved: U32::new(0x08000000), // Only I flag
411            vni_reserved: U32::new(0x00000000),
412        };
413        assert_eq!(header1.flags(), 0x08);
414
415        let header2 = VxlanHeader {
416            flags_reserved: U32::new(0xFF000000), // All flag bits set
417            vni_reserved: U32::new(0x00000000),
418        };
419        assert_eq!(header2.flags(), 0xFF);
420        assert!(header2.is_vni_valid()); // I flag is set
421    }
422
423    #[test]
424    fn test_vxlan_multicast_vni() {
425        // Test with typical multicast scenario VNI
426        let header = VxlanHeader {
427            flags_reserved: U32::new(0x08000000),
428            vni_reserved: U32::new(0x000FA000), // VNI = 4000 (common test value)
429        };
430
431        assert_eq!(header.vni(), 4000);
432        assert!(header.is_valid());
433    }
434
435    #[test]
436    fn test_vxlan_real_world_scenario() {
437        // Simulate a real VXLAN packet as captured from network
438        let mut packet = Vec::new();
439
440        // VXLAN header: I flag set, VNI = 5000
441        packet.extend_from_slice(&[0x08, 0x00, 0x00, 0x00]); // Flags
442        packet.extend_from_slice(&[0x00, 0x13, 0x88, 0x00]); // VNI = 5000
443
444        // Simulated Ethernet frame header (14 bytes)
445        packet.extend_from_slice(&[
446            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // Dst MAC (broadcast)
447            0x00, 0x11, 0x22, 0x33, 0x44, 0x55, // Src MAC
448            0x08, 0x00, // EtherType (IPv4)
449        ]);
450
451        let (header, payload) = VxlanHeader::from_bytes(&packet).unwrap();
452        assert_eq!(header.vni(), 5000);
453        assert!(header.is_vni_valid());
454        assert_eq!(payload.len(), 14); // Ethernet header
455    }
456
457    #[test]
458    fn test_vxlan_header_length() {
459        let header = VxlanHeader {
460            flags_reserved: U32::new(0x08000000),
461            vni_reserved: U32::new(0x00006400),
462        };
463
464        // VXLAN always has fixed 8-byte header
465        assert_eq!(header.total_len(&[]), 8);
466    }
467}