pcapsql_core/protocol/
ipv4.rs

1//! IPv4 protocol parser.
2
3use smallvec::SmallVec;
4
5use etherparse::Ipv4HeaderSlice;
6
7use super::ethernet::ethertype;
8use super::{FieldValue, ParseContext, ParseResult, Protocol, TunnelType};
9use crate::schema::{DataKind, FieldDescriptor};
10
11/// IP protocol numbers for IP-in-IP encapsulation.
12mod ip_in_ip_protocol {
13    /// IPv4 encapsulated in IP (IPIP)
14    pub const IPIP: u8 = 4;
15    /// IPv6 encapsulated in IP
16    pub const IPV6_IN_IP: u8 = 41;
17}
18
19/// IPv4 protocol parser.
20#[derive(Debug, Clone, Copy)]
21pub struct Ipv4Protocol;
22
23impl Protocol for Ipv4Protocol {
24    fn name(&self) -> &'static str {
25        "ipv4"
26    }
27
28    fn display_name(&self) -> &'static str {
29        "IPv4"
30    }
31
32    fn can_parse(&self, context: &ParseContext) -> Option<u32> {
33        match context.hint("ethertype") {
34            Some(et) if et == ethertype::IPV4 as u64 => Some(100),
35            _ => None,
36        }
37    }
38
39    fn parse<'a>(&self, data: &'a [u8], _context: &ParseContext) -> ParseResult<'a> {
40        match Ipv4HeaderSlice::from_slice(data) {
41            Ok(ipv4) => {
42                let mut fields = SmallVec::new();
43
44                fields.push(("version", FieldValue::UInt8(4)));
45                fields.push(("ihl", FieldValue::UInt8(ipv4.ihl())));
46                fields.push(("dscp", FieldValue::UInt8(ipv4.dcp().value())));
47                fields.push(("ecn", FieldValue::UInt8(ipv4.ecn().value())));
48                fields.push(("total_length", FieldValue::UInt16(ipv4.total_len())));
49                fields.push(("identification", FieldValue::UInt16(ipv4.identification())));
50                fields.push(("dont_fragment", FieldValue::Bool(ipv4.dont_fragment())));
51                fields.push(("more_fragments", FieldValue::Bool(ipv4.more_fragments())));
52                fields.push((
53                    "fragment_offset",
54                    FieldValue::UInt16(ipv4.fragments_offset().value()),
55                ));
56                fields.push(("ttl", FieldValue::UInt8(ipv4.ttl())));
57                fields.push(("protocol", FieldValue::UInt8(ipv4.protocol().0)));
58                fields.push(("checksum", FieldValue::UInt16(ipv4.header_checksum())));
59                fields.push(("src_ip", FieldValue::ipv4(&ipv4.source())));
60                fields.push(("dst_ip", FieldValue::ipv4(&ipv4.destination())));
61
62                let mut child_hints = SmallVec::new();
63                let protocol = ipv4.protocol().0;
64                child_hints.push(("ip_protocol", protocol as u64));
65                child_hints.push(("ip_version", 4));
66
67                // Check for IP-in-IP encapsulation
68                if protocol == ip_in_ip_protocol::IPIP {
69                    // IPv4 encapsulated in IPv4 - signal tunnel boundary
70                    child_hints.push(("tunnel_type", TunnelType::IpInIp as u64));
71                    // Also set ethertype hint so inner IPv4 can be parsed
72                    child_hints.push(("ethertype", ethertype::IPV4 as u64));
73                } else if protocol == ip_in_ip_protocol::IPV6_IN_IP {
74                    // IPv6 encapsulated in IPv4 - signal tunnel boundary
75                    child_hints.push(("tunnel_type", TunnelType::Ip6InIp as u64));
76                    // Also set ethertype hint so inner IPv6 can be parsed
77                    child_hints.push(("ethertype", ethertype::IPV6 as u64));
78                }
79
80                let header_len = ipv4.slice().len();
81                ParseResult::success(fields, &data[header_len..], child_hints)
82            }
83            Err(e) => ParseResult::error(format!("IPv4 parse error: {e}"), data),
84        }
85    }
86
87    fn schema_fields(&self) -> Vec<FieldDescriptor> {
88        vec![
89            FieldDescriptor::new("ipv4.version", DataKind::UInt8).set_nullable(true),
90            FieldDescriptor::new("ipv4.ihl", DataKind::UInt8).set_nullable(true),
91            FieldDescriptor::new("ipv4.dscp", DataKind::UInt8).set_nullable(true),
92            FieldDescriptor::new("ipv4.ecn", DataKind::UInt8).set_nullable(true),
93            FieldDescriptor::new("ipv4.total_length", DataKind::UInt16).set_nullable(true),
94            FieldDescriptor::new("ipv4.identification", DataKind::UInt16).set_nullable(true),
95            FieldDescriptor::new("ipv4.dont_fragment", DataKind::Bool).set_nullable(true),
96            FieldDescriptor::new("ipv4.more_fragments", DataKind::Bool).set_nullable(true),
97            FieldDescriptor::new("ipv4.fragment_offset", DataKind::UInt16).set_nullable(true),
98            FieldDescriptor::new("ipv4.ttl", DataKind::UInt8).set_nullable(true),
99            FieldDescriptor::new("ipv4.protocol", DataKind::UInt8).set_nullable(true),
100            FieldDescriptor::new("ipv4.checksum", DataKind::UInt16).set_nullable(true),
101            FieldDescriptor::new("ipv4.src_ip", DataKind::String).set_nullable(true),
102            FieldDescriptor::new("ipv4.dst_ip", DataKind::String).set_nullable(true),
103        ]
104    }
105
106    fn child_protocols(&self) -> &[&'static str] {
107        &["tcp", "udp", "icmp"]
108    }
109
110    fn dependencies(&self) -> &'static [&'static str] {
111        &["ethernet", "vlan", "mpls", "gre", "vxlan", "gtp"]
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118    use std::net::IpAddr;
119
120    #[test]
121    fn test_parse_ipv4() {
122        // Minimal IPv4 header (20 bytes) with TCP protocol
123        let header = [
124            0x45, // Version (4) + IHL (5)
125            0x00, // DSCP + ECN
126            0x00, 0x28, // Total length: 40
127            0x00, 0x01, // Identification
128            0x00, 0x00, // Flags + Fragment offset
129            0x40, // TTL: 64
130            0x06, // Protocol: TCP (6)
131            0x00, 0x00, // Checksum (not validated)
132            0xc0, 0xa8, 0x01, 0x01, // Src: 192.168.1.1
133            0xc0, 0xa8, 0x01, 0x02, // Dst: 192.168.1.2
134        ];
135
136        let parser = Ipv4Protocol;
137        let mut context = ParseContext::new(1);
138        context.insert_hint("ethertype", ethertype::IPV4 as u64);
139
140        let result = parser.parse(&header, &context);
141
142        assert!(result.is_ok());
143        assert_eq!(result.get("ttl"), Some(&FieldValue::UInt8(64)));
144        assert_eq!(result.get("protocol"), Some(&FieldValue::UInt8(6)));
145        assert_eq!(result.hint("ip_protocol"), Some(6u64));
146    }
147
148    #[test]
149    fn test_parse_ipv4_udp() {
150        let header = [
151            0x45, // Version (4) + IHL (5)
152            0x00, // DSCP + ECN
153            0x00, 0x1c, // Total length: 28
154            0x12, 0x34, // Identification
155            0x40, 0x00, // Don't fragment, offset 0
156            0x80, // TTL: 128
157            0x11, // Protocol: UDP (17)
158            0x00, 0x00, // Checksum
159            0x0a, 0x00, 0x00, 0x01, // Src: 10.0.0.1
160            0x0a, 0x00, 0x00, 0x02, // Dst: 10.0.0.2
161        ];
162
163        let parser = Ipv4Protocol;
164        let mut context = ParseContext::new(1);
165        context.insert_hint("ethertype", ethertype::IPV4 as u64);
166
167        let result = parser.parse(&header, &context);
168
169        assert!(result.is_ok());
170        assert_eq!(result.get("ttl"), Some(&FieldValue::UInt8(128)));
171        assert_eq!(result.get("protocol"), Some(&FieldValue::UInt8(17)));
172        assert_eq!(result.get("dont_fragment"), Some(&FieldValue::Bool(true)));
173        assert_eq!(result.hint("ip_protocol"), Some(17u64));
174    }
175
176    #[test]
177    fn test_parse_ipv4_icmp() {
178        let header = [
179            0x45, // Version (4) + IHL (5)
180            0x00, // DSCP + ECN
181            0x00, 0x54, // Total length: 84
182            0x00, 0x00, // Identification
183            0x00, 0x00, // Flags + Fragment offset
184            0x40, // TTL: 64
185            0x01, // Protocol: ICMP (1)
186            0x00, 0x00, // Checksum
187            0x08, 0x08, 0x08, 0x08, // Src: 8.8.8.8
188            0xc0, 0xa8, 0x01, 0x01, // Dst: 192.168.1.1
189        ];
190
191        let parser = Ipv4Protocol;
192        let mut context = ParseContext::new(1);
193        context.insert_hint("ethertype", ethertype::IPV4 as u64);
194
195        let result = parser.parse(&header, &context);
196
197        assert!(result.is_ok());
198        assert_eq!(result.get("protocol"), Some(&FieldValue::UInt8(1)));
199        assert_eq!(result.hint("ip_protocol"), Some(1u64));
200    }
201
202    #[test]
203    fn test_parse_ipv4_with_payload() {
204        let packet = [
205            0x45, // Version (4) + IHL (5)
206            0x00, // DSCP + ECN
207            0x00, 0x28, // Total length: 40
208            0x00, 0x01, // Identification
209            0x00, 0x00, // Flags + Fragment offset
210            0x40, // TTL: 64
211            0x06, // Protocol: TCP
212            0x00, 0x00, // Checksum
213            0xc0, 0xa8, 0x01, 0x01, // Src
214            0xc0, 0xa8, 0x01, 0x02, // Dst
215            // TCP header (payload)
216            0x00, 0x50, 0x1f, 0x90, 0x00, 0x00, 0x00, 0x01,
217        ];
218
219        let parser = Ipv4Protocol;
220        let mut context = ParseContext::new(1);
221        context.insert_hint("ethertype", ethertype::IPV4 as u64);
222
223        let result = parser.parse(&packet, &context);
224
225        assert!(result.is_ok());
226        assert_eq!(result.remaining.len(), 8); // TCP header bytes
227    }
228
229    #[test]
230    fn test_can_parse_ipv4() {
231        let parser = Ipv4Protocol;
232
233        // Without hint
234        let ctx1 = ParseContext::new(1);
235        assert!(parser.can_parse(&ctx1).is_none());
236
237        // With IPv6 ethertype
238        let mut ctx2 = ParseContext::new(1);
239        ctx2.insert_hint("ethertype", ethertype::IPV6 as u64);
240        assert!(parser.can_parse(&ctx2).is_none());
241
242        // With IPv4 ethertype
243        let mut ctx3 = ParseContext::new(1);
244        ctx3.insert_hint("ethertype", ethertype::IPV4 as u64);
245        assert!(parser.can_parse(&ctx3).is_some());
246    }
247
248    #[test]
249    fn test_parse_ipv4_too_short() {
250        let short_header = [0x45, 0x00, 0x00, 0x28]; // Only 4 bytes
251
252        let parser = Ipv4Protocol;
253        let mut context = ParseContext::new(1);
254        context.insert_hint("ethertype", ethertype::IPV4 as u64);
255
256        let result = parser.parse(&short_header, &context);
257
258        assert!(!result.is_ok());
259        assert!(result.error.is_some());
260    }
261
262    #[test]
263    fn test_ipv4_ip_addresses() {
264        let header = [
265            0x45, 0x00, 0x00, 0x14, // Version, IHL, Length
266            0x00, 0x00, 0x00, 0x00, // ID, Flags, Offset
267            0x40, 0x06, 0x00, 0x00, // TTL, Protocol, Checksum
268            0x7f, 0x00, 0x00, 0x01, // Src: 127.0.0.1
269            0x0a, 0x0b, 0x0c, 0x0d, // Dst: 10.11.12.13
270        ];
271
272        let parser = Ipv4Protocol;
273        let mut context = ParseContext::new(1);
274        context.insert_hint("ethertype", ethertype::IPV4 as u64);
275
276        let result = parser.parse(&header, &context);
277
278        assert!(result.is_ok());
279        assert_eq!(
280            result.get("src_ip"),
281            Some(&FieldValue::IpAddr(IpAddr::V4(
282                "127.0.0.1".parse().unwrap()
283            )))
284        );
285        assert_eq!(
286            result.get("dst_ip"),
287            Some(&FieldValue::IpAddr(IpAddr::V4(
288                "10.11.12.13".parse().unwrap()
289            )))
290        );
291    }
292
293    #[test]
294    fn test_ipv4_fragment_flags() {
295        let header = [
296            0x45, 0x00, 0x00, 0x14, // Version, IHL, Length
297            0x12, 0x34, // Identification
298            0x20, 0x00, // More fragments flag set
299            0x40, 0x06, 0x00, 0x00, // TTL, Protocol, Checksum
300            0xc0, 0xa8, 0x01, 0x01, // Src
301            0xc0, 0xa8, 0x01, 0x02, // Dst
302        ];
303
304        let parser = Ipv4Protocol;
305        let mut context = ParseContext::new(1);
306        context.insert_hint("ethertype", ethertype::IPV4 as u64);
307
308        let result = parser.parse(&header, &context);
309
310        assert!(result.is_ok());
311        assert_eq!(result.get("more_fragments"), Some(&FieldValue::Bool(true)));
312        assert_eq!(result.get("dont_fragment"), Some(&FieldValue::Bool(false)));
313        assert_eq!(
314            result.get("identification"),
315            Some(&FieldValue::UInt16(0x1234))
316        );
317    }
318
319    #[test]
320    fn test_ipv4_child_hints() {
321        let header = [
322            0x45, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x40, 0x06, 0x00,
323            0x00, // Protocol: TCP (6)
324            0xc0, 0xa8, 0x01, 0x01, 0xc0, 0xa8, 0x01, 0x02,
325        ];
326
327        let parser = Ipv4Protocol;
328        let mut context = ParseContext::new(1);
329        context.insert_hint("ethertype", ethertype::IPV4 as u64);
330
331        let result = parser.parse(&header, &context);
332
333        assert!(result.is_ok());
334        assert_eq!(result.hint("ip_protocol"), Some(6u64));
335        assert_eq!(result.hint("ip_version"), Some(4u64));
336    }
337}