/src/suricata7/rust/src/dhcp/parser.rs
Line | Count | Source |
1 | | /* Copyright (C) 2018 Open Information Security Foundation |
2 | | * |
3 | | * You can copy, redistribute or modify this Program under the terms of |
4 | | * the GNU General Public License version 2 as published by the Free |
5 | | * Software Foundation. |
6 | | * |
7 | | * This program is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | | * GNU General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU General Public License |
13 | | * version 2 along with this program; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
15 | | * 02110-1301, USA. |
16 | | */ |
17 | | |
18 | | use std::cmp::min; |
19 | | |
20 | | use crate::dhcp::dhcp::*; |
21 | | use nom7::bytes::streaming::take; |
22 | | use nom7::combinator::verify; |
23 | | use nom7::number::streaming::{be_u16, be_u32, be_u8}; |
24 | | use nom7::IResult; |
25 | | |
26 | | pub struct DHCPMessage { |
27 | | pub header: DHCPHeader, |
28 | | |
29 | | pub options: Vec<DHCPOption>, |
30 | | |
31 | | // Set to true if the options were found to be malformed. That is |
32 | | // failing to parse with enough data. |
33 | | pub malformed_options: bool, |
34 | | |
35 | | // Set to true if the options failed to parse due to not enough |
36 | | // data. |
37 | | pub truncated_options: bool, |
38 | | } |
39 | | |
40 | | pub struct DHCPHeader { |
41 | | pub opcode: u8, |
42 | | pub htype: u8, |
43 | | pub hlen: u8, |
44 | | pub hops: u8, |
45 | | pub txid: u32, |
46 | | pub seconds: u16, |
47 | | pub flags: u16, |
48 | | pub clientip: Vec<u8>, |
49 | | pub yourip: Vec<u8>, |
50 | | pub serverip: Vec<u8>, |
51 | | pub giaddr: Vec<u8>, |
52 | | pub clienthw: Vec<u8>, |
53 | | pub servername: Vec<u8>, |
54 | | pub bootfilename: Vec<u8>, |
55 | | pub magic: Vec<u8>, |
56 | | } |
57 | | |
58 | | pub struct DHCPOptClientId { |
59 | | pub htype: u8, |
60 | | pub data: Vec<u8>, |
61 | | } |
62 | | |
63 | | /// Option type for time values. |
64 | | pub struct DHCPOptTimeValue { |
65 | | pub seconds: u32, |
66 | | } |
67 | | |
68 | | pub struct DHCPOptGeneric { |
69 | | pub data: Vec<u8>, |
70 | | } |
71 | | |
72 | | pub enum DHCPOptionWrapper { |
73 | | ClientId(DHCPOptClientId), |
74 | | TimeValue(DHCPOptTimeValue), |
75 | | Generic(DHCPOptGeneric), |
76 | | End, |
77 | | } |
78 | | |
79 | | pub struct DHCPOption { |
80 | | pub code: u8, |
81 | | pub data: Option<Vec<u8>>, |
82 | | pub option: DHCPOptionWrapper, |
83 | | } |
84 | | |
85 | 40.6k | pub fn parse_header(i: &[u8]) -> IResult<&[u8], DHCPHeader> { |
86 | 40.6k | let (i, opcode) = be_u8(i)?; |
87 | 40.6k | let (i, htype) = be_u8(i)?; |
88 | 40.6k | let (i, hlen) = be_u8(i)?; |
89 | 40.6k | let (i, hops) = be_u8(i)?; |
90 | 40.6k | let (i, txid) = be_u32(i)?; |
91 | 40.5k | let (i, seconds) = be_u16(i)?; |
92 | 40.5k | let (i, flags) = be_u16(i)?; |
93 | 40.5k | let (i, clientip) = take(4_usize)(i)?; |
94 | 40.5k | let (i, yourip) = take(4_usize)(i)?; |
95 | 40.5k | let (i, serverip) = take(4_usize)(i)?; |
96 | 40.5k | let (i, giaddr) = take(4_usize)(i)?; |
97 | 40.5k | let (i, clienthw) = take(16_usize)(i)?; |
98 | 40.4k | let (i, servername) = take(64_usize)(i)?; |
99 | 40.4k | let (i, bootfilename) = take(128_usize)(i)?; |
100 | 40.3k | let (i, magic) = take(4_usize)(i)?; |
101 | 40.3k | Ok(( |
102 | 40.3k | i, |
103 | 40.3k | DHCPHeader { |
104 | 40.3k | opcode, |
105 | 40.3k | htype, |
106 | 40.3k | hlen, |
107 | 40.3k | hops, |
108 | 40.3k | txid, |
109 | 40.3k | seconds, |
110 | 40.3k | flags, |
111 | 40.3k | clientip: clientip.to_vec(), |
112 | 40.3k | yourip: yourip.to_vec(), |
113 | 40.3k | serverip: serverip.to_vec(), |
114 | 40.3k | giaddr: giaddr.to_vec(), |
115 | 40.3k | clienthw: clienthw[0..min(hlen as usize, 16)].to_vec(), |
116 | 40.3k | servername: servername.to_vec(), |
117 | 40.3k | bootfilename: bootfilename.to_vec(), |
118 | 40.3k | magic: magic.to_vec(), |
119 | 40.3k | }, |
120 | 40.3k | )) |
121 | 40.6k | } |
122 | | |
123 | 90.9k | pub fn parse_clientid_option(i: &[u8]) -> IResult<&[u8], DHCPOption> { |
124 | 90.9k | let (i, code) = be_u8(i)?; |
125 | 90.9k | let (i, len) = verify(be_u8, |&v| v > 1)(i)?; |
126 | 89.6k | let (i, _htype) = be_u8(i)?; |
127 | 88.0k | let (i, data) = take(len - 1)(i)?; |
128 | 86.1k | Ok(( |
129 | 86.1k | i, |
130 | 86.1k | DHCPOption { |
131 | 86.1k | code, |
132 | 86.1k | data: None, |
133 | 86.1k | option: DHCPOptionWrapper::ClientId(DHCPOptClientId { |
134 | 86.1k | htype: 1, |
135 | 86.1k | data: data.to_vec(), |
136 | 86.1k | }), |
137 | 86.1k | }, |
138 | 86.1k | )) |
139 | 90.9k | } |
140 | | |
141 | 661k | pub fn parse_address_time_option(i: &[u8]) -> IResult<&[u8], DHCPOption> { |
142 | 661k | let (i, code) = be_u8(i)?; |
143 | 661k | let (i, _len) = be_u8(i)?; |
144 | 657k | let (i, seconds) = be_u32(i)?; |
145 | 651k | Ok(( |
146 | 651k | i, |
147 | 651k | DHCPOption { |
148 | 651k | code, |
149 | 651k | data: None, |
150 | 651k | option: DHCPOptionWrapper::TimeValue(DHCPOptTimeValue { seconds }), |
151 | 651k | }, |
152 | 651k | )) |
153 | 661k | } |
154 | | |
155 | 5.85M | pub fn parse_generic_option(i: &[u8]) -> IResult<&[u8], DHCPOption> { |
156 | 5.85M | let (i, code) = be_u8(i)?; |
157 | 5.85M | let (i, len) = be_u8(i)?; |
158 | 5.85M | let (i, data) = take(len)(i)?; |
159 | 5.85M | Ok(( |
160 | 5.85M | i, |
161 | 5.85M | DHCPOption { |
162 | 5.85M | code, |
163 | 5.85M | data: None, |
164 | 5.85M | option: DHCPOptionWrapper::Generic(DHCPOptGeneric { |
165 | 5.85M | data: data.to_vec(), |
166 | 5.85M | }), |
167 | 5.85M | }, |
168 | 5.85M | )) |
169 | 5.85M | } |
170 | | |
171 | | // Parse a single DHCP option. When option 255 (END) is parsed, the remaining |
172 | | // data will be consumed. |
173 | 6.62M | pub fn parse_option(i: &[u8]) -> IResult<&[u8], DHCPOption> { |
174 | 6.62M | let (_, opt) = be_u8(i)?; |
175 | 6.61M | match opt { |
176 | | DHCP_OPT_END => { |
177 | | // End of options case. We consume the rest of the data |
178 | | // so the parser is not called again. But is there a |
179 | | // better way to "break"? |
180 | 7.49k | let (data, code) = be_u8(i)?; |
181 | 7.49k | Ok(( |
182 | 7.49k | &[], |
183 | 7.49k | DHCPOption { |
184 | 7.49k | code, |
185 | 7.49k | data: Some(data.to_vec()), |
186 | 7.49k | option: DHCPOptionWrapper::End, |
187 | 7.49k | }, |
188 | 7.49k | )) |
189 | | } |
190 | 90.9k | DHCP_OPT_CLIENT_ID => parse_clientid_option(i), |
191 | 394k | DHCP_OPT_ADDRESS_TIME => parse_address_time_option(i), |
192 | 185k | DHCP_OPT_RENEWAL_TIME => parse_address_time_option(i), |
193 | 81.9k | DHCP_OPT_REBINDING_TIME => parse_address_time_option(i), |
194 | 5.85M | _ => parse_generic_option(i), |
195 | | } |
196 | 6.62M | } |
197 | | |
198 | 39.4k | pub fn dhcp_parse(input: &[u8]) -> IResult<&[u8], DHCPMessage> { |
199 | 39.4k | match parse_header(input) { |
200 | 39.1k | Ok((rem, header)) => { |
201 | 39.1k | let mut options = Vec::new(); |
202 | 39.1k | let mut next = rem; |
203 | 39.1k | let malformed_options = false; |
204 | 39.1k | let mut truncated_options = false; |
205 | | loop { |
206 | 6.62M | match parse_option(next) { |
207 | 6.59M | Ok((rem, option)) => { |
208 | 6.59M | let done = option.code == DHCP_OPT_END; |
209 | 6.59M | options.push(option); |
210 | 6.59M | next = rem; |
211 | 6.59M | if done { |
212 | 7.49k | break; |
213 | 6.58M | } |
214 | | } |
215 | | Err(_) => { |
216 | 31.6k | truncated_options = true; |
217 | 31.6k | break; |
218 | | } |
219 | | } |
220 | | } |
221 | 39.1k | let message = DHCPMessage { |
222 | 39.1k | header, |
223 | 39.1k | options, |
224 | 39.1k | malformed_options, |
225 | 39.1k | truncated_options, |
226 | 39.1k | }; |
227 | 39.1k | return Ok((next, message)); |
228 | | } |
229 | 328 | Err(err) => { |
230 | 328 | return Err(err); |
231 | | } |
232 | | } |
233 | 39.4k | } |
234 | | |
235 | | #[cfg(test)] |
236 | | mod tests { |
237 | | use crate::dhcp::dhcp::*; |
238 | | use crate::dhcp::parser::*; |
239 | | |
240 | | #[test] |
241 | | fn test_parse_discover() { |
242 | | let pcap = include_bytes!("discover.pcap"); |
243 | | let payload = &pcap[24 + 16 + 42..]; |
244 | | |
245 | | let (_rem, message) = dhcp_parse(payload).unwrap(); |
246 | | let header = message.header; |
247 | | assert_eq!(header.opcode, BOOTP_REQUEST); |
248 | | assert_eq!(header.htype, 1); |
249 | | assert_eq!(header.hlen, 6); |
250 | | assert_eq!(header.hops, 0); |
251 | | assert_eq!(header.txid, 0x00003d1d); |
252 | | assert_eq!(header.seconds, 0); |
253 | | assert_eq!(header.flags, 0); |
254 | | assert_eq!(header.clientip, &[0, 0, 0, 0]); |
255 | | assert_eq!(header.yourip, &[0, 0, 0, 0]); |
256 | | assert_eq!(header.serverip, &[0, 0, 0, 0]); |
257 | | assert_eq!(header.giaddr, &[0, 0, 0, 0]); |
258 | | assert_eq!( |
259 | | &header.clienthw[..(header.hlen as usize)], |
260 | | &[0x00, 0x0b, 0x82, 0x01, 0xfc, 0x42] |
261 | | ); |
262 | | assert!(header.servername.iter().all(|&x| x == 0)); |
263 | | assert!(header.bootfilename.iter().all(|&x| x == 0)); |
264 | | assert_eq!(header.magic, &[0x63, 0x82, 0x53, 0x63]); |
265 | | |
266 | | assert!(!message.malformed_options); |
267 | | assert!(!message.truncated_options); |
268 | | |
269 | | assert_eq!(message.options.len(), 5); |
270 | | assert_eq!(message.options[0].code, DHCP_OPT_TYPE); |
271 | | assert_eq!(message.options[1].code, DHCP_OPT_CLIENT_ID); |
272 | | assert_eq!(message.options[2].code, DHCP_OPT_REQUESTED_IP); |
273 | | assert_eq!(message.options[3].code, DHCP_OPT_PARAMETER_LIST); |
274 | | assert_eq!(message.options[4].code, DHCP_OPT_END); |
275 | | } |
276 | | |
277 | | #[test] |
278 | | fn test_parse_client_id_too_short() { |
279 | | // Length field of 0. |
280 | | let buf: &[u8] = &[ |
281 | | 0x01, 0x00, // Length of 0. |
282 | | 0x01, 0x01, // Junk data start here. |
283 | | 0x02, 0x03, |
284 | | ]; |
285 | | let r = parse_clientid_option(buf); |
286 | | assert!(r.is_err()); |
287 | | |
288 | | // Length field of 1. |
289 | | let buf: &[u8] = &[ |
290 | | 0x01, 0x01, // Length of 1. |
291 | | 0x01, 0x41, |
292 | | ]; |
293 | | let r = parse_clientid_option(buf); |
294 | | assert!(r.is_err()); |
295 | | |
296 | | // Length field of 2 -- OK. |
297 | | let buf: &[u8] = &[ |
298 | | 0x01, 0x02, // Length of 2. |
299 | | 0x01, 0x41, |
300 | | ]; |
301 | | let r = parse_clientid_option(buf); |
302 | | match r { |
303 | | Ok((rem, _)) => { |
304 | | assert_eq!(rem.len(), 0); |
305 | | } |
306 | | _ => { |
307 | | panic!("failed"); |
308 | | } |
309 | | } |
310 | | } |
311 | | } |