Skip to main content

stackforge_core/layer/dhcp/
options.rs

1/// DHCP message types (option 53).
2pub mod msg_type {
3    pub const DISCOVER: u8 = 1;
4    pub const OFFER: u8 = 2;
5    pub const REQUEST: u8 = 3;
6    pub const DECLINE: u8 = 4;
7    pub const ACK: u8 = 5;
8    pub const NAK: u8 = 6;
9    pub const RELEASE: u8 = 7;
10    pub const INFORM: u8 = 8;
11}
12
13/// DHCP option codes.
14pub mod code {
15    pub const SUBNET_MASK: u8 = 1;
16    pub const ROUTER: u8 = 3;
17    pub const DNS: u8 = 6;
18    pub const HOSTNAME: u8 = 12;
19    pub const DOMAIN_NAME: u8 = 15;
20    pub const BROADCAST_ADDR: u8 = 28;
21    pub const REQUESTED_IP: u8 = 50;
22    pub const LEASE_TIME: u8 = 51;
23    pub const MESSAGE_TYPE: u8 = 53;
24    pub const SERVER_ID: u8 = 54;
25    pub const PARAM_REQUEST_LIST: u8 = 55;
26    pub const MAX_MSG_SIZE: u8 = 57;
27    pub const RENEWAL_TIME: u8 = 58;
28    pub const REBINDING_TIME: u8 = 59;
29    pub const CLIENT_ID: u8 = 61;
30    pub const END: u8 = 255;
31    pub const PAD: u8 = 0;
32}
33
34/// A parsed DHCP option.
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct DhcpOption {
37    pub code: u8,
38    pub data: Vec<u8>,
39}
40
41impl DhcpOption {
42    #[must_use]
43    pub fn new(code: u8, data: Vec<u8>) -> Self {
44        Self { code, data }
45    }
46
47    /// Create a message type option.
48    #[must_use]
49    pub fn message_type(msg_type: u8) -> Self {
50        Self::new(code::MESSAGE_TYPE, vec![msg_type])
51    }
52
53    /// Create a server identifier option.
54    #[must_use]
55    pub fn server_id(ip: std::net::Ipv4Addr) -> Self {
56        Self::new(code::SERVER_ID, ip.octets().to_vec())
57    }
58
59    /// Create a lease time option.
60    #[must_use]
61    pub fn lease_time(seconds: u32) -> Self {
62        Self::new(code::LEASE_TIME, seconds.to_be_bytes().to_vec())
63    }
64
65    /// Create a subnet mask option.
66    #[must_use]
67    pub fn subnet_mask(mask: std::net::Ipv4Addr) -> Self {
68        Self::new(code::SUBNET_MASK, mask.octets().to_vec())
69    }
70
71    /// Create a router option.
72    #[must_use]
73    pub fn router(ip: std::net::Ipv4Addr) -> Self {
74        Self::new(code::ROUTER, ip.octets().to_vec())
75    }
76
77    /// Create a DNS servers option.
78    #[must_use]
79    pub fn dns(servers: &[std::net::Ipv4Addr]) -> Self {
80        let mut data = Vec::with_capacity(servers.len() * 4);
81        for s in servers {
82            data.extend_from_slice(&s.octets());
83        }
84        Self::new(code::DNS, data)
85    }
86
87    /// Create a domain name option.
88    #[must_use]
89    pub fn domain_name(name: &str) -> Self {
90        Self::new(code::DOMAIN_NAME, name.as_bytes().to_vec())
91    }
92
93    /// Serialize this option to bytes.
94    #[must_use]
95    pub fn to_bytes(&self) -> Vec<u8> {
96        if self.code == code::PAD || self.code == code::END {
97            return vec![self.code];
98        }
99        let mut out = Vec::with_capacity(2 + self.data.len());
100        out.push(self.code);
101        out.push(self.data.len() as u8);
102        out.extend_from_slice(&self.data);
103        out
104    }
105
106    /// Get the message type value (if this is a message type option).
107    #[must_use]
108    pub fn as_message_type(&self) -> Option<u8> {
109        if self.code == code::MESSAGE_TYPE && !self.data.is_empty() {
110            Some(self.data[0])
111        } else {
112            None
113        }
114    }
115
116    /// Get an IPv4 address value (for 4-byte options like subnet mask, router, etc).
117    #[must_use]
118    pub fn as_ipv4(&self) -> Option<std::net::Ipv4Addr> {
119        if self.data.len() >= 4 {
120            Some(std::net::Ipv4Addr::new(
121                self.data[0],
122                self.data[1],
123                self.data[2],
124                self.data[3],
125            ))
126        } else {
127            None
128        }
129    }
130}
131
132/// Parse DHCP options from a byte slice (starting after the magic cookie).
133pub fn parse_options(data: &[u8]) -> Vec<DhcpOption> {
134    let mut opts = Vec::new();
135    let mut i = 0;
136
137    while i < data.len() {
138        let code = data[i];
139        if code == code::END {
140            break;
141        }
142        if code == code::PAD {
143            i += 1;
144            continue;
145        }
146        i += 1;
147        if i >= data.len() {
148            break;
149        }
150        let len = data[i] as usize;
151        i += 1;
152        if i + len > data.len() {
153            break;
154        }
155        opts.push(DhcpOption::new(code, data[i..i + len].to_vec()));
156        i += len;
157    }
158
159    opts
160}
161
162/// Serialize a list of DHCP options to bytes (including END marker).
163pub fn serialize_options(opts: &[DhcpOption]) -> Vec<u8> {
164    let mut out = Vec::new();
165    for opt in opts {
166        out.extend_from_slice(&opt.to_bytes());
167    }
168    out.push(code::END);
169    out
170}