ppproto/ppp/
ipv4cp.rs

1use core::net::Ipv4Addr;
2
3use num_enum::{FromPrimitive, IntoPrimitive};
4
5use super::option_fsm::{Protocol, Verdict};
6use crate::wire::ProtocolType;
7
8#[derive(FromPrimitive, IntoPrimitive, Copy, Clone, Eq, PartialEq, Debug)]
9#[cfg_attr(feature = "defmt", derive(defmt::Format))]
10#[repr(u8)]
11enum OptionCode {
12    #[num_enum(default)]
13    Unknown = 0,
14    IpAddress = 3,
15    Dns1 = 129,
16    Dns2 = 131,
17}
18
19struct IpOption {
20    address: Ipv4Addr,
21    is_rejected: bool,
22}
23
24impl IpOption {
25    fn new() -> Self {
26        Self {
27            address: Ipv4Addr::UNSPECIFIED,
28            is_rejected: false,
29        }
30    }
31
32    fn get(&self) -> Option<Ipv4Addr> {
33        if self.is_rejected || self.address.is_unspecified() {
34            None
35        } else {
36            Some(self.address)
37        }
38    }
39
40    fn nacked(&mut self, data: &[u8], is_rej: bool) {
41        if is_rej {
42            self.is_rejected = true
43        } else {
44            match <[u8; 4]>::try_from(data) {
45                // Peer addr is OK
46                Ok(data) => self.address = Ipv4Addr::from(data),
47                // Peer wants us to use an address that's not 4 bytes.
48                // Should never happen, but mark option as rejected just in case to
49                // avoid endless loop.
50                Err(_) => self.is_rejected = true,
51            }
52        }
53    }
54}
55
56/// Status of the IPv4 connection.
57#[derive(Debug)]
58#[cfg_attr(feature = "defmt", derive(defmt::Format))]
59pub struct Ipv4Status {
60    /// Our adress
61    pub address: Option<Ipv4Addr>,
62    /// The peer's address
63    pub peer_address: Option<Ipv4Addr>,
64    /// DNS servers provided by the peer.
65    pub dns_servers: [Option<Ipv4Addr>; 2],
66}
67
68pub(crate) struct IPv4CP {
69    peer_address: Ipv4Addr,
70
71    address: IpOption,
72    dns_server_1: IpOption,
73    dns_server_2: IpOption,
74}
75
76impl IPv4CP {
77    pub fn new() -> Self {
78        Self {
79            peer_address: Ipv4Addr::UNSPECIFIED,
80
81            address: IpOption::new(),
82            dns_server_1: IpOption::new(),
83            dns_server_2: IpOption::new(),
84        }
85    }
86
87    pub fn status(&self) -> Ipv4Status {
88        let peer_address = if self.peer_address.is_unspecified() {
89            None
90        } else {
91            Some(self.peer_address)
92        };
93
94        Ipv4Status {
95            address: self.address.get(),
96            peer_address,
97            dns_servers: [self.dns_server_1.get(), self.dns_server_2.get()],
98        }
99    }
100}
101
102impl Protocol for IPv4CP {
103    fn protocol(&self) -> ProtocolType {
104        ProtocolType::IPv4CP
105    }
106
107    fn peer_options_start(&mut self) {}
108
109    fn peer_option_received(&mut self, code: u8, data: &[u8]) -> Verdict {
110        let opt = OptionCode::from(code);
111        trace!("IPv4CP: rx option {:?} {:?} {:?}", code, opt, data);
112        match opt {
113            OptionCode::IpAddress => match <[u8; 4]>::try_from(data) {
114                Ok(data) => {
115                    self.peer_address = Ipv4Addr::from(data);
116                    Verdict::Ack
117                }
118                Err(_) => Verdict::Rej,
119            },
120            _ => Verdict::Rej,
121        }
122    }
123
124    fn own_options(&mut self, mut f: impl FnMut(u8, &[u8])) {
125        if !self.address.is_rejected {
126            f(OptionCode::IpAddress.into(), &self.address.address.octets());
127        }
128        if !self.dns_server_1.is_rejected {
129            f(OptionCode::Dns1.into(), &self.dns_server_1.address.octets());
130        }
131        if !self.dns_server_2.is_rejected {
132            f(OptionCode::Dns2.into(), &self.dns_server_2.address.octets());
133        }
134    }
135
136    fn own_option_nacked(&mut self, code: u8, data: &[u8], is_rej: bool) {
137        let opt = OptionCode::from(code);
138        trace!("IPv4CP nak {:?} {:?} {:?} {:?}", code, opt, data, is_rej);
139        match opt {
140            OptionCode::Unknown => {}
141            OptionCode::IpAddress => self.address.nacked(data, is_rej),
142            OptionCode::Dns1 => self.dns_server_1.nacked(data, is_rej),
143            OptionCode::Dns2 => self.dns_server_2.nacked(data, is_rej),
144        }
145    }
146}