pcapsql_core/protocol/
vxlan.rs

1//! VXLAN (Virtual Extensible LAN) protocol parser.
2//!
3//! VXLAN is a network virtualization technology that attempts to address
4//! the scalability problems associated with large cloud computing deployments.
5//!
6//! RFC 7348: Virtual eXtensible Local Area Network (VXLAN)
7
8use smallvec::SmallVec;
9
10use super::{FieldValue, ParseContext, ParseResult, Protocol, TunnelType};
11use crate::schema::{DataKind, FieldDescriptor};
12
13/// Standard VXLAN UDP destination port.
14pub const VXLAN_PORT: u16 = 4789;
15
16/// VXLAN protocol parser.
17#[derive(Debug, Clone, Copy)]
18pub struct VxlanProtocol;
19
20impl Protocol for VxlanProtocol {
21    fn name(&self) -> &'static str {
22        "vxlan"
23    }
24
25    fn display_name(&self) -> &'static str {
26        "VXLAN"
27    }
28
29    fn can_parse(&self, context: &ParseContext) -> Option<u32> {
30        // Match when UDP dst_port hint equals 4789
31        match context.hint("dst_port") {
32            Some(port) if port == VXLAN_PORT as u64 => Some(100),
33            _ => None,
34        }
35    }
36
37    fn parse<'a>(&self, data: &'a [u8], _context: &ParseContext) -> ParseResult<'a> {
38        // VXLAN header is 8 bytes
39        if data.len() < 8 {
40            return ParseResult::error("VXLAN header too short".to_string(), data);
41        }
42
43        let mut fields = SmallVec::new();
44
45        // Byte 0: Flags
46        // Bit 3 (I flag): Must be 1 to indicate valid VNI per RFC 7348
47        // Bits 0-2, 4-7: Reserved (must be 0)
48        let flags = data[0];
49        let i_flag = (flags & 0x08) != 0;
50
51        fields.push(("flags", FieldValue::UInt8(flags)));
52
53        // RFC 7348: I flag MUST be set to 1 for valid VNI
54        // Store the validity for analysis (lenient parsing - still decode even if invalid)
55        fields.push(("i_flag_valid", FieldValue::Bool(i_flag)));
56
57        // Check if reserved bits are zero (for strict validation)
58        let reserved_flags_zero = (flags & 0xF7) == 0;
59        fields.push((
60            "flags_valid",
61            FieldValue::Bool(reserved_flags_zero && i_flag),
62        ));
63
64        // Bytes 1-3: Reserved (should be 0)
65        // We skip validation as some implementations may use these
66
67        // Bytes 4-6: VNI (24-bit VXLAN Network Identifier)
68        let vni = ((data[4] as u32) << 16) | ((data[5] as u32) << 8) | (data[6] as u32);
69        fields.push(("vni", FieldValue::UInt32(vni)));
70
71        // Byte 7: Reserved (should be 0)
72
73        // Calculate inner frame length
74        let inner_frame_len = data.len() - 8;
75        if inner_frame_len > 0 {
76            fields.push((
77                "inner_frame_length",
78                FieldValue::UInt32(inner_frame_len as u32),
79            ));
80        }
81
82        // Set up child hints for the inner Ethernet frame
83        let mut child_hints = SmallVec::new();
84
85        // VXLAN encapsulates Ethernet frames, so set link_type to Ethernet
86        child_hints.push(("link_type", 1u64)); // DLT_EN10MB = Ethernet
87
88        // Signal tunnel boundary for encapsulation tracking
89        child_hints.push(("tunnel_type", TunnelType::Vxlan as u64));
90        child_hints.push(("tunnel_id", vni as u64));
91
92        ParseResult::success(fields, &data[8..], child_hints)
93    }
94
95    fn schema_fields(&self) -> Vec<FieldDescriptor> {
96        vec![
97            FieldDescriptor::new("vxlan.flags", DataKind::UInt8).set_nullable(true),
98            FieldDescriptor::new("vxlan.vni", DataKind::UInt32).set_nullable(true),
99            FieldDescriptor::new("vxlan.i_flag_valid", DataKind::Bool).set_nullable(true),
100            FieldDescriptor::new("vxlan.flags_valid", DataKind::Bool).set_nullable(true),
101            FieldDescriptor::new("vxlan.inner_frame_length", DataKind::UInt32).set_nullable(true),
102        ]
103    }
104
105    fn child_protocols(&self) -> &[&'static str] {
106        // VXLAN encapsulates Ethernet frames
107        &["ethernet"]
108    }
109
110    fn dependencies(&self) -> &'static [&'static str] {
111        &["udp"] // VXLAN runs over UDP port 4789
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    /// Create a VXLAN header with the given VNI.
120    fn create_vxlan_header(vni: u32, i_flag: bool) -> [u8; 8] {
121        let mut header = [0u8; 8];
122
123        // Flags byte - I flag is bit 3
124        if i_flag {
125            header[0] = 0x08;
126        }
127
128        // VNI in bytes 4-6 (24-bit)
129        header[4] = ((vni >> 16) & 0xFF) as u8;
130        header[5] = ((vni >> 8) & 0xFF) as u8;
131        header[6] = (vni & 0xFF) as u8;
132
133        header
134    }
135
136    // Test 1: can_parse with UDP port 4789
137    #[test]
138    fn test_can_parse_with_udp_port_4789() {
139        let parser = VxlanProtocol;
140
141        // Without hint
142        let ctx1 = ParseContext::new(1);
143        assert!(parser.can_parse(&ctx1).is_none());
144
145        // With wrong port
146        let mut ctx2 = ParseContext::new(1);
147        ctx2.insert_hint("dst_port", 80);
148        assert!(parser.can_parse(&ctx2).is_none());
149
150        // With VXLAN port
151        let mut ctx3 = ParseContext::new(1);
152        ctx3.insert_hint("dst_port", 4789);
153        assert!(parser.can_parse(&ctx3).is_some());
154        assert_eq!(parser.can_parse(&ctx3), Some(100));
155    }
156
157    // Test 2: VNI extraction
158    #[test]
159    fn test_vni_extraction() {
160        let parser = VxlanProtocol;
161        let mut context = ParseContext::new(1);
162        context.insert_hint("dst_port", 4789);
163
164        // Test various VNI values
165        let test_vnis = [0u32, 1, 100, 1000, 0xFFFFFF]; // Max is 24-bit
166
167        for vni in test_vnis {
168            let header = create_vxlan_header(vni, true);
169            let result = parser.parse(&header, &context);
170
171            assert!(result.is_ok());
172            assert_eq!(result.get("vni"), Some(&FieldValue::UInt32(vni)));
173        }
174    }
175
176    // Test 3: Flags parsing
177    #[test]
178    fn test_flags_parsing() {
179        let parser = VxlanProtocol;
180        let mut context = ParseContext::new(1);
181        context.insert_hint("dst_port", 4789);
182
183        // With I flag set
184        let header_with_i = create_vxlan_header(100, true);
185        let result = parser.parse(&header_with_i, &context);
186        assert!(result.is_ok());
187        assert_eq!(result.get("flags"), Some(&FieldValue::UInt8(0x08)));
188
189        // Without I flag (should still parse)
190        let header_without_i = create_vxlan_header(100, false);
191        let result = parser.parse(&header_without_i, &context);
192        assert!(result.is_ok());
193        assert_eq!(result.get("flags"), Some(&FieldValue::UInt8(0x00)));
194    }
195
196    // Test 4: I flag validation
197    #[test]
198    fn test_i_flag_validation() {
199        let parser = VxlanProtocol;
200        let mut context = ParseContext::new(1);
201        context.insert_hint("dst_port", 4789);
202
203        // Valid VXLAN with I flag set
204        let valid_header = create_vxlan_header(12345, true);
205        let result = parser.parse(&valid_header, &context);
206        assert!(result.is_ok());
207
208        // VXLAN without I flag - should still parse (lenient)
209        let no_i_header = create_vxlan_header(12345, false);
210        let result = parser.parse(&no_i_header, &context);
211        assert!(result.is_ok());
212    }
213
214    // Test 5: Inner Ethernet frame detection
215    #[test]
216    fn test_inner_ethernet_frame_detection() {
217        let parser = VxlanProtocol;
218        let mut context = ParseContext::new(1);
219        context.insert_hint("dst_port", 4789);
220
221        let mut data = Vec::new();
222        data.extend_from_slice(&create_vxlan_header(100, true));
223        // Add inner Ethernet frame (at least 14 bytes)
224        data.extend_from_slice(&[
225            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // Dst MAC
226            0x00, 0x11, 0x22, 0x33, 0x44, 0x55, // Src MAC
227            0x08, 0x00, // EtherType (IPv4)
228        ]);
229
230        let result = parser.parse(&data, &context);
231
232        assert!(result.is_ok());
233        assert_eq!(result.remaining.len(), 14); // Inner Ethernet header
234        assert_eq!(result.hint("link_type"), Some(1u64));
235    }
236
237    // Test 6: Child protocol hint
238    #[test]
239    fn test_child_protocol_hint() {
240        let parser = VxlanProtocol;
241        let mut context = ParseContext::new(1);
242        context.insert_hint("dst_port", 4789);
243
244        let header = create_vxlan_header(42, true);
245        let result = parser.parse(&header, &context);
246
247        assert!(result.is_ok());
248        // Should set link_type hint for Ethernet
249        assert_eq!(result.hint("link_type"), Some(1u64));
250    }
251
252    // Test 7: Too short header
253    #[test]
254    fn test_vxlan_too_short() {
255        let parser = VxlanProtocol;
256        let mut context = ParseContext::new(1);
257        context.insert_hint("dst_port", 4789);
258
259        let short_header = [0x08, 0x00, 0x00, 0x00]; // Only 4 bytes
260        let result = parser.parse(&short_header, &context);
261
262        assert!(!result.is_ok());
263        assert!(result.error.is_some());
264    }
265
266    // Test 8: VNI with payload
267    #[test]
268    fn test_vni_with_payload() {
269        let parser = VxlanProtocol;
270        let mut context = ParseContext::new(1);
271        context.insert_hint("dst_port", 4789);
272
273        let mut data = Vec::new();
274        data.extend_from_slice(&create_vxlan_header(999999, true));
275        // Add some payload
276        data.extend_from_slice(&[0x01, 0x02, 0x03, 0x04]);
277
278        let result = parser.parse(&data, &context);
279
280        assert!(result.is_ok());
281        assert_eq!(result.get("vni"), Some(&FieldValue::UInt32(999999)));
282        assert_eq!(result.remaining.len(), 4);
283    }
284
285    // Test 9: Schema fields
286    #[test]
287    fn test_vxlan_schema_fields() {
288        let parser = VxlanProtocol;
289        let fields = parser.schema_fields();
290
291        assert!(!fields.is_empty());
292        let field_names: Vec<&str> = fields.iter().map(|f| f.name).collect();
293        assert!(field_names.contains(&"vxlan.flags"));
294        assert!(field_names.contains(&"vxlan.vni"));
295    }
296
297    // Test 10: Specific VNI values
298    #[test]
299    fn test_specific_vni_values() {
300        let parser = VxlanProtocol;
301        let mut context = ParseContext::new(1);
302        context.insert_hint("dst_port", 4789);
303
304        // Common VNI values
305        let header1 = create_vxlan_header(1, true);
306        let result = parser.parse(&header1, &context);
307        assert!(result.is_ok());
308        assert_eq!(result.get("vni"), Some(&FieldValue::UInt32(1)));
309
310        // Max VNI (24-bit)
311        let header2 = create_vxlan_header(0xFFFFFF, true);
312        let result = parser.parse(&header2, &context);
313        assert!(result.is_ok());
314        assert_eq!(result.get("vni"), Some(&FieldValue::UInt32(0xFFFFFF)));
315    }
316
317    // Test 11: i_flag_valid field
318    #[test]
319    fn test_i_flag_valid_field() {
320        let parser = VxlanProtocol;
321        let mut context = ParseContext::new(1);
322        context.insert_hint("dst_port", 4789);
323
324        // With I flag set (valid)
325        let valid_header = create_vxlan_header(100, true);
326        let result = parser.parse(&valid_header, &context);
327        assert!(result.is_ok());
328        assert_eq!(result.get("i_flag_valid"), Some(&FieldValue::Bool(true)));
329
330        // Without I flag (invalid per RFC)
331        let invalid_header = create_vxlan_header(100, false);
332        let result = parser.parse(&invalid_header, &context);
333        assert!(result.is_ok()); // Still parses (lenient)
334        assert_eq!(result.get("i_flag_valid"), Some(&FieldValue::Bool(false)));
335    }
336
337    // Test 12: flags_valid field (checks reserved bits too)
338    #[test]
339    fn test_flags_valid_field() {
340        let parser = VxlanProtocol;
341        let mut context = ParseContext::new(1);
342        context.insert_hint("dst_port", 4789);
343
344        // Valid: I flag set, reserved bits zero
345        let valid_header = create_vxlan_header(100, true); // flags = 0x08
346        let result = parser.parse(&valid_header, &context);
347        assert!(result.is_ok());
348        assert_eq!(result.get("flags_valid"), Some(&FieldValue::Bool(true)));
349
350        // Invalid: I flag not set
351        let no_i_header = create_vxlan_header(100, false); // flags = 0x00
352        let result = parser.parse(&no_i_header, &context);
353        assert!(result.is_ok());
354        assert_eq!(result.get("flags_valid"), Some(&FieldValue::Bool(false)));
355    }
356
357    // Test 13: flags_valid with reserved bits set
358    #[test]
359    fn test_flags_valid_reserved_bits() {
360        let parser = VxlanProtocol;
361        let mut context = ParseContext::new(1);
362        context.insert_hint("dst_port", 4789);
363
364        // Header with reserved bits set (bit 0 set in addition to I flag)
365        let mut header = [0u8; 8];
366        header[0] = 0x09; // I flag (0x08) + bit 0 (0x01)
367        header[4] = 0x00;
368        header[5] = 0x00;
369        header[6] = 0x64; // VNI = 100
370
371        let result = parser.parse(&header, &context);
372        assert!(result.is_ok());
373        // Should be invalid because reserved bit is set
374        assert_eq!(result.get("flags_valid"), Some(&FieldValue::Bool(false)));
375        // But i_flag_valid should still be true
376        assert_eq!(result.get("i_flag_valid"), Some(&FieldValue::Bool(true)));
377    }
378
379    // Test 14: inner_frame_length field
380    #[test]
381    fn test_inner_frame_length_field() {
382        let parser = VxlanProtocol;
383        let mut context = ParseContext::new(1);
384        context.insert_hint("dst_port", 4789);
385
386        // VXLAN header only (no inner frame)
387        let header_only = create_vxlan_header(100, true);
388        let result = parser.parse(&header_only, &context);
389        assert!(result.is_ok());
390        // No inner frame length when there's no payload
391        assert!(result.get("inner_frame_length").is_none());
392
393        // With inner Ethernet frame (14 bytes minimum)
394        let mut with_frame = Vec::new();
395        with_frame.extend_from_slice(&create_vxlan_header(100, true));
396        with_frame.extend_from_slice(&[0u8; 14]); // Minimum Ethernet header
397        let result = parser.parse(&with_frame, &context);
398        assert!(result.is_ok());
399        assert_eq!(
400            result.get("inner_frame_length"),
401            Some(&FieldValue::UInt32(14))
402        );
403
404        // With larger inner frame
405        let mut with_payload = Vec::new();
406        with_payload.extend_from_slice(&create_vxlan_header(100, true));
407        with_payload.extend_from_slice(&[0u8; 1500]); // Full MTU
408        let result = parser.parse(&with_payload, &context);
409        assert!(result.is_ok());
410        assert_eq!(
411            result.get("inner_frame_length"),
412            Some(&FieldValue::UInt32(1500))
413        );
414    }
415
416    // Test 15: Various VNI values with validation
417    #[test]
418    fn test_vni_range() {
419        let parser = VxlanProtocol;
420        let mut context = ParseContext::new(1);
421        context.insert_hint("dst_port", 4789);
422
423        let test_cases = [
424            (0, "zero VNI"),
425            (1, "minimum VNI"),
426            (4096, "common tenant VNI"),
427            (100000, "large VNI"),
428            (0xFFFFFF, "maximum VNI (24-bit)"),
429        ];
430
431        for (vni, _desc) in test_cases {
432            let header = create_vxlan_header(vni, true);
433            let result = parser.parse(&header, &context);
434            assert!(result.is_ok());
435            assert_eq!(result.get("vni"), Some(&FieldValue::UInt32(vni)));
436        }
437    }
438
439    // Test 16: Schema fields include new fields
440    #[test]
441    fn test_schema_fields_complete() {
442        let parser = VxlanProtocol;
443        let fields = parser.schema_fields();
444
445        let field_names: Vec<&str> = fields.iter().map(|f| f.name).collect();
446        assert!(field_names.contains(&"vxlan.flags"));
447        assert!(field_names.contains(&"vxlan.vni"));
448        assert!(field_names.contains(&"vxlan.i_flag_valid"));
449        assert!(field_names.contains(&"vxlan.flags_valid"));
450        assert!(field_names.contains(&"vxlan.inner_frame_length"));
451    }
452}