vmm/dumbo/pdu/
arp.rs

1// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Contains logic that helps with handling ARP frames over Ethernet, which encapsulate requests
5//! or replies related to IPv4 addresses.
6//!
7//! A more detailed view of an ARP frame can be found [here].
8//!
9//! [here]: https://en.wikipedia.org/wiki/Address_Resolution_Protocol
10use std::convert::From;
11use std::fmt::Debug;
12use std::net::Ipv4Addr;
13
14use super::bytes::{InnerBytes, NetworkBytes, NetworkBytesMut};
15use super::ethernet::{self, ETHERTYPE_IPV4};
16use crate::utils::net::mac::{MAC_ADDR_LEN, MacAddr};
17
18/// ARP Request operation
19pub const OPER_REQUEST: u16 = 0x0001;
20
21/// ARP Reply operation
22pub const OPER_REPLY: u16 = 0x0002;
23
24/// ARP is for Ethernet hardware
25pub const HTYPE_ETHERNET: u16 = 0x0001;
26
27/// The length of an ARP frame for IPv4 over Ethernet.
28pub const ETH_IPV4_FRAME_LEN: usize = 28;
29
30const HTYPE_OFFSET: usize = 0;
31const PTYPE_OFFSET: usize = 2;
32const HLEN_OFFSET: usize = 4;
33const PLEN_OFFSET: usize = 5;
34const OPER_OFFSET: usize = 6;
35const SHA_OFFSET: usize = 8;
36
37// The following constants are specific to ARP requests/responses
38// associated with IPv4 over Ethernet.
39const ETH_IPV4_SPA_OFFSET: usize = 14;
40const ETH_IPV4_THA_OFFSET: usize = 18;
41const ETH_IPV4_TPA_OFFSET: usize = 24;
42
43const IPV4_ADDR_LEN: u8 = 4;
44
45/// Represents errors which may occur while parsing or writing a frame.
46#[derive(Debug, PartialEq, Eq, thiserror::Error, displaydoc::Display)]
47pub enum ArpError {
48    /// Invalid hardware address length.
49    HLen,
50    /// Invalid hardware type.
51    HType,
52    /// Invalid operation.
53    Operation,
54    /// Invalid protocol address length.
55    PLen,
56    /// Invalid protocol type.
57    PType,
58    /// The provided slice does not fit the size of a frame.
59    SliceExactLen,
60}
61
62/// The inner bytes will be interpreted as an ARP frame.
63///
64/// ARP is a generic protocol as far as data
65/// link layer and network layer protocols go, but this particular implementation is concerned with
66/// ARP frames related to IPv4 over Ethernet.
67#[derive(Debug)]
68pub struct EthIPv4ArpFrame<'a, T: 'a> {
69    bytes: InnerBytes<'a, T>,
70}
71
72#[allow(clippy::len_without_is_empty)]
73impl<T: NetworkBytes + Debug> EthIPv4ArpFrame<'_, T> {
74    /// Interprets the given bytes as an ARP frame, without doing any validity checks beforehand.
75    ///
76    ///  # Panics
77    ///
78    /// This method does not panic, but further method calls on the resulting object may panic if
79    /// `bytes` contains invalid input.
80    #[inline]
81    pub fn from_bytes_unchecked(bytes: T) -> Self {
82        EthIPv4ArpFrame {
83            bytes: InnerBytes::new(bytes),
84        }
85    }
86
87    /// Tries to interpret a byte slice as a valid IPv4 over Ethernet ARP request.
88    ///
89    /// If no error occurs, it guarantees accessor methods (which make use of various `_unchecked`
90    /// functions) are safe to call on the result, because all predefined offsets will be valid.
91    pub fn request_from_bytes(bytes: T) -> Result<Self, ArpError> {
92        // This kind of frame has a fixed length, so we know what to expect.
93        if bytes.len() != ETH_IPV4_FRAME_LEN {
94            return Err(ArpError::SliceExactLen);
95        }
96
97        let maybe = EthIPv4ArpFrame::from_bytes_unchecked(bytes);
98
99        if maybe.htype() != HTYPE_ETHERNET {
100            return Err(ArpError::HType);
101        }
102
103        if maybe.ptype() != ETHERTYPE_IPV4 {
104            return Err(ArpError::PType);
105        }
106
107        // We could theoretically skip the hlen and plen checks, since they are kinda implicit.
108        if maybe.hlen() != MAC_ADDR_LEN {
109            return Err(ArpError::HLen);
110        }
111
112        if maybe.plen() != IPV4_ADDR_LEN {
113            return Err(ArpError::PLen);
114        }
115
116        if maybe.operation() != OPER_REQUEST {
117            return Err(ArpError::Operation);
118        }
119
120        Ok(maybe)
121    }
122
123    /// Returns the hardware type of the frame.
124    #[inline]
125    pub fn htype(&self) -> u16 {
126        self.bytes.ntohs_unchecked(HTYPE_OFFSET)
127    }
128
129    /// Returns the protocol type of the frame.
130    #[inline]
131    pub fn ptype(&self) -> u16 {
132        self.bytes.ntohs_unchecked(PTYPE_OFFSET)
133    }
134
135    /// Returns the hardware address length of the frame.
136    #[inline]
137    pub fn hlen(&self) -> u8 {
138        self.bytes[HLEN_OFFSET]
139    }
140
141    /// Returns the protocol address length of the frame.
142    #[inline]
143    pub fn plen(&self) -> u8 {
144        self.bytes[PLEN_OFFSET]
145    }
146
147    /// Returns the type of operation within the frame.
148    #[inline]
149    pub fn operation(&self) -> u16 {
150        self.bytes.ntohs_unchecked(OPER_OFFSET)
151    }
152
153    /// Returns the sender hardware address.
154    #[inline]
155    pub fn sha(&self) -> MacAddr {
156        MacAddr::from_bytes_unchecked(&self.bytes[SHA_OFFSET..ETH_IPV4_SPA_OFFSET])
157    }
158
159    /// Returns the sender protocol address.
160    #[inline]
161    pub fn spa(&self) -> Ipv4Addr {
162        Ipv4Addr::from(self.bytes.ntohl_unchecked(ETH_IPV4_SPA_OFFSET))
163    }
164
165    /// Returns the target hardware address.
166    #[inline]
167    pub fn tha(&self) -> MacAddr {
168        MacAddr::from_bytes_unchecked(&self.bytes[ETH_IPV4_THA_OFFSET..ETH_IPV4_TPA_OFFSET])
169    }
170
171    /// Returns the target protocol address.
172    #[inline]
173    pub fn tpa(&self) -> Ipv4Addr {
174        Ipv4Addr::from(self.bytes.ntohl_unchecked(ETH_IPV4_TPA_OFFSET))
175    }
176
177    /// Returns the length of the frame.
178    #[inline]
179    pub fn len(&self) -> usize {
180        // This might as well return ETH_IPV4_FRAME_LEN directly, since we check this is the actual
181        // length in request_from_bytes(). For some reason it seems nicer leaving it as is.
182        self.bytes.len()
183    }
184}
185
186impl<T: NetworkBytesMut + Debug> EthIPv4ArpFrame<'_, T> {
187    #[allow(clippy::too_many_arguments)]
188    fn write_raw(
189        buf: T,
190        htype: u16,
191        ptype: u16,
192        hlen: u8,
193        plen: u8,
194        operation: u16,
195        sha: MacAddr,
196        spa: Ipv4Addr,
197        tha: MacAddr,
198        tpa: Ipv4Addr,
199    ) -> Result<Self, ArpError> {
200        if buf.len() != ETH_IPV4_FRAME_LEN {
201            return Err(ArpError::SliceExactLen);
202        }
203
204        // This is ok, because we've checked the length of the slice.
205        let mut frame = EthIPv4ArpFrame::from_bytes_unchecked(buf);
206
207        frame.set_htype(htype);
208        frame.set_ptype(ptype);
209        frame.set_hlen(hlen);
210        frame.set_plen(plen);
211        frame.set_operation(operation);
212        frame.set_sha(sha);
213        frame.set_spa(spa);
214        frame.set_tha(tha);
215        frame.set_tpa(tpa);
216
217        Ok(frame)
218    }
219
220    /// Attempts to write an ARP request to `buf`, based on the specified hardware and protocol
221    /// addresses.
222    #[inline]
223    pub fn write_request(
224        buf: T,
225        sha: MacAddr,
226        spa: Ipv4Addr,
227        tha: MacAddr,
228        tpa: Ipv4Addr,
229    ) -> Result<Self, ArpError> {
230        Self::write_raw(
231            buf,
232            HTYPE_ETHERNET,
233            ETHERTYPE_IPV4,
234            MAC_ADDR_LEN,
235            IPV4_ADDR_LEN,
236            OPER_REQUEST,
237            sha,
238            spa,
239            tha,
240            tpa,
241        )
242    }
243
244    /// Attempts to write an ARP reply to `buf`, based on the specified hardware and protocol
245    /// addresses.
246    #[inline]
247    pub fn write_reply(
248        buf: T,
249        sha: MacAddr,
250        spa: Ipv4Addr,
251        tha: MacAddr,
252        tpa: Ipv4Addr,
253    ) -> Result<Self, ArpError> {
254        Self::write_raw(
255            buf,
256            HTYPE_ETHERNET,
257            ETHERTYPE_IPV4,
258            MAC_ADDR_LEN,
259            IPV4_ADDR_LEN,
260            OPER_REPLY,
261            sha,
262            spa,
263            tha,
264            tpa,
265        )
266    }
267
268    /// Sets the hardware type of the frame.
269    #[inline]
270    pub fn set_htype(&mut self, value: u16) {
271        self.bytes.htons_unchecked(HTYPE_OFFSET, value);
272    }
273
274    /// Sets the protocol type of the frame.
275    #[inline]
276    pub fn set_ptype(&mut self, value: u16) {
277        self.bytes.htons_unchecked(PTYPE_OFFSET, value);
278    }
279
280    /// Sets the hardware address length of the frame.
281    #[inline]
282    pub fn set_hlen(&mut self, value: u8) {
283        self.bytes[HLEN_OFFSET] = value;
284    }
285
286    /// Sets the protocol address length of the frame.
287    #[inline]
288    pub fn set_plen(&mut self, value: u8) {
289        self.bytes[PLEN_OFFSET] = value;
290    }
291
292    /// Sets the operation within the frame.
293    #[inline]
294    pub fn set_operation(&mut self, value: u16) {
295        self.bytes.htons_unchecked(OPER_OFFSET, value);
296    }
297
298    /// Sets the sender hardware address.
299    #[inline]
300    pub fn set_sha(&mut self, addr: MacAddr) {
301        self.bytes[SHA_OFFSET..ETH_IPV4_SPA_OFFSET].copy_from_slice(addr.get_bytes());
302    }
303
304    /// Sets the sender protocol address.
305    #[inline]
306    pub fn set_spa(&mut self, addr: Ipv4Addr) {
307        self.bytes
308            .htonl_unchecked(ETH_IPV4_SPA_OFFSET, u32::from(addr));
309    }
310
311    /// Sets the target hardware address.
312    #[inline]
313    pub fn set_tha(&mut self, addr: MacAddr) {
314        self.bytes[ETH_IPV4_THA_OFFSET..ETH_IPV4_TPA_OFFSET].copy_from_slice(addr.get_bytes());
315    }
316
317    /// Sets the target protocol address.
318    #[inline]
319    pub fn set_tpa(&mut self, addr: Ipv4Addr) {
320        self.bytes
321            .htonl_unchecked(ETH_IPV4_TPA_OFFSET, u32::from(addr));
322    }
323}
324
325/// This function checks if `buf` may hold an Ethernet frame which encapsulates an
326/// `EthIPv4ArpRequest` for the given address. Cannot produce false negatives.
327#[inline]
328pub fn test_speculative_tpa(buf: &[u8], addr: Ipv4Addr) -> bool {
329    // The unchecked methods are safe because we actually check the buffer length beforehand.
330    if buf.len() >= ethernet::PAYLOAD_OFFSET + ETH_IPV4_FRAME_LEN {
331        let bytes = &buf[ethernet::PAYLOAD_OFFSET..];
332        if EthIPv4ArpFrame::from_bytes_unchecked(bytes).tpa() == addr {
333            return true;
334        }
335    }
336    false
337}
338
339#[cfg(test)]
340mod tests {
341    use std::str::FromStr;
342
343    use super::*;
344
345    #[test]
346    fn test_eth_ipv4_arp_frame() {
347        let mut a = [0u8; 1000];
348        let mut bad_array = [0u8; 1];
349
350        let sha = MacAddr::from_str("01:23:45:67:89:ab").unwrap();
351        let tha = MacAddr::from_str("cd:ef:01:23:45:67").unwrap();
352        let spa = Ipv4Addr::new(10, 1, 2, 3);
353        let tpa = Ipv4Addr::new(10, 4, 5, 6);
354
355        // Slice is too short.
356        assert_eq!(
357            EthIPv4ArpFrame::request_from_bytes(bad_array.as_ref()).unwrap_err(),
358            ArpError::SliceExactLen
359        );
360
361        // Slice is too short.
362        assert_eq!(
363            EthIPv4ArpFrame::write_reply(bad_array.as_mut(), sha, spa, tha, tpa).unwrap_err(),
364            ArpError::SliceExactLen
365        );
366
367        // Slice is too long.
368        assert_eq!(
369            EthIPv4ArpFrame::write_reply(a.as_mut(), sha, spa, tha, tpa).unwrap_err(),
370            ArpError::SliceExactLen
371        );
372
373        // We write a valid ARP reply to the specified slice.
374        {
375            let f = EthIPv4ArpFrame::write_reply(&mut a[..ETH_IPV4_FRAME_LEN], sha, spa, tha, tpa)
376                .unwrap();
377
378            // This is a bit redundant given the following tests, but assert away!
379            assert_eq!(f.htype(), HTYPE_ETHERNET);
380            assert_eq!(f.ptype(), ETHERTYPE_IPV4);
381            assert_eq!(f.hlen(), MAC_ADDR_LEN);
382            assert_eq!(f.plen(), IPV4_ADDR_LEN);
383            assert_eq!(f.operation(), OPER_REPLY);
384            assert_eq!(f.sha(), sha);
385            assert_eq!(f.spa(), spa);
386            assert_eq!(f.tha(), tha);
387            assert_eq!(f.tpa(), tpa);
388        }
389
390        // Now let's try to parse a request.
391
392        // Slice is too long.
393        assert_eq!(
394            EthIPv4ArpFrame::request_from_bytes(a.as_ref()).unwrap_err(),
395            ArpError::SliceExactLen
396        );
397
398        // The length is fine now, but the operation is a reply instead of request.
399        assert_eq!(
400            EthIPv4ArpFrame::request_from_bytes(&a[..ETH_IPV4_FRAME_LEN]).unwrap_err(),
401            ArpError::Operation
402        );
403
404        // Various requests
405        let requests = [
406            (
407                HTYPE_ETHERNET,
408                ETHERTYPE_IPV4,
409                MAC_ADDR_LEN,
410                IPV4_ADDR_LEN,
411                None,
412            ), // Valid request
413            (
414                HTYPE_ETHERNET + 1,
415                ETHERTYPE_IPV4,
416                MAC_ADDR_LEN,
417                IPV4_ADDR_LEN,
418                Some(ArpError::HType),
419            ), // Invalid htype
420            (
421                HTYPE_ETHERNET,
422                ETHERTYPE_IPV4 + 1,
423                MAC_ADDR_LEN,
424                IPV4_ADDR_LEN,
425                Some(ArpError::PType),
426            ), // Invalid ptype
427            (
428                HTYPE_ETHERNET,
429                ETHERTYPE_IPV4,
430                MAC_ADDR_LEN + 1,
431                IPV4_ADDR_LEN,
432                Some(ArpError::HLen),
433            ), // Invalid hlen
434            (
435                HTYPE_ETHERNET,
436                ETHERTYPE_IPV4,
437                MAC_ADDR_LEN,
438                IPV4_ADDR_LEN + 1,
439                Some(ArpError::PLen),
440            ), // Invalid plen
441        ];
442
443        for (htype, ptype, hlen, plen, err) in requests.iter() {
444            EthIPv4ArpFrame::write_raw(
445                &mut a[..ETH_IPV4_FRAME_LEN],
446                *htype,
447                *ptype,
448                *hlen,
449                *plen,
450                OPER_REQUEST,
451                sha,
452                spa,
453                tha,
454                tpa,
455            )
456            .unwrap();
457            match err {
458                None => {
459                    EthIPv4ArpFrame::request_from_bytes(&a[..ETH_IPV4_FRAME_LEN]).unwrap();
460                }
461                Some(arp_error) => assert_eq!(
462                    EthIPv4ArpFrame::request_from_bytes(&a[..ETH_IPV4_FRAME_LEN]).unwrap_err(),
463                    *arp_error
464                ),
465            }
466        }
467    }
468
469    #[test]
470    fn test_speculative() {
471        let mut a = [0u8; 1000];
472        let addr = Ipv4Addr::new(1, 2, 3, 4);
473
474        assert!(!test_speculative_tpa(a.as_ref(), addr));
475
476        {
477            let mac = MacAddr::from_bytes_unchecked(&[0; 6]);
478            let mut eth = crate::dumbo::pdu::ethernet::EthernetFrame::write_incomplete(
479                a.as_mut(),
480                mac,
481                mac,
482                0,
483            )
484            .unwrap();
485            let mut arp = EthIPv4ArpFrame::from_bytes_unchecked(eth.inner_mut().payload_mut());
486            arp.set_tpa(addr);
487        }
488
489        assert!(test_speculative_tpa(a.as_ref(), addr));
490
491        // Let's also test for a very small buffer.
492        let small = [0u8; 1];
493        assert!(!test_speculative_tpa(small.as_ref(), addr));
494    }
495}