]> git.ipfire.org Git - people/ms/suricata.git/blob - rust/src/dhcp/dhcp.rs
detect: allows <> syntax for uint ranges
[people/ms/suricata.git] / rust / src / dhcp / dhcp.rs
1 /* Copyright (C) 2018-2020 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 crate::applayer::{self, *};
19 use crate::core;
20 use crate::core::{ALPROTO_UNKNOWN, AppProto, Flow, IPPROTO_UDP};
21 use crate::dhcp::parser::*;
22 use std;
23 use std::ffi::CString;
24
25 static mut ALPROTO_DHCP: AppProto = ALPROTO_UNKNOWN;
26
27 static DHCP_MIN_FRAME_LEN: u32 = 232;
28
29 pub const BOOTP_REQUEST: u8 = 1;
30 pub const BOOTP_REPLY: u8 = 2;
31
32 // DHCP option types. Names based on IANA naming:
33 // https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml
34 pub const DHCP_OPT_SUBNET_MASK: u8 = 1;
35 pub const DHCP_OPT_ROUTERS: u8 = 3;
36 pub const DHCP_OPT_DNS_SERVER: u8 = 6;
37 pub const DHCP_OPT_HOSTNAME: u8 = 12;
38 pub const DHCP_OPT_REQUESTED_IP: u8 = 50;
39 pub const DHCP_OPT_ADDRESS_TIME: u8 = 51;
40 pub const DHCP_OPT_TYPE: u8 = 53;
41 pub const DHCP_OPT_SERVER_ID: u8 = 54;
42 pub const DHCP_OPT_PARAMETER_LIST: u8 = 55;
43 pub const DHCP_OPT_RENEWAL_TIME: u8 = 58;
44 pub const DHCP_OPT_REBINDING_TIME: u8 = 59;
45 pub const DHCP_OPT_CLIENT_ID: u8 = 61;
46 pub const DHCP_OPT_END: u8 = 255;
47
48 /// DHCP message types.
49 pub const DHCP_TYPE_DISCOVER: u8 = 1;
50 pub const DHCP_TYPE_OFFER: u8 = 2;
51 pub const DHCP_TYPE_REQUEST: u8 = 3;
52 pub const DHCP_TYPE_DECLINE: u8 = 4;
53 pub const DHCP_TYPE_ACK: u8 = 5;
54 pub const DHCP_TYPE_NAK: u8 = 6;
55 pub const DHCP_TYPE_RELEASE: u8 = 7;
56 pub const DHCP_TYPE_INFORM: u8 = 8;
57
58 // DHCP parameter types.
59 // https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.txt
60 pub const DHCP_PARAM_SUBNET_MASK: u8 = 1;
61 pub const DHCP_PARAM_ROUTER: u8 = 3;
62 pub const DHCP_PARAM_DNS_SERVER: u8 = 6;
63 pub const DHCP_PARAM_DOMAIN: u8 = 15;
64 pub const DHCP_PARAM_ARP_TIMEOUT: u8 = 35;
65 pub const DHCP_PARAM_NTP_SERVER: u8 = 42;
66 pub const DHCP_PARAM_TFTP_SERVER_NAME: u8 = 66;
67 pub const DHCP_PARAM_TFTP_SERVER_IP: u8 = 150;
68
69 #[derive(AppLayerEvent)]
70 pub enum DHCPEvent {
71 TruncatedOptions,
72 MalformedOptions,
73 }
74
75 /// The concept of a transaction is more to satisfy the Suricata
76 /// app-layer. This DHCP parser is actually stateless where each
77 /// message is its own transaction.
78 pub struct DHCPTransaction {
79 tx_id: u64,
80 pub message: DHCPMessage,
81 tx_data: applayer::AppLayerTxData,
82 }
83
84 impl DHCPTransaction {
85 pub fn new(id: u64, message: DHCPMessage) -> DHCPTransaction {
86 DHCPTransaction {
87 tx_id: id,
88 message: message,
89 tx_data: applayer::AppLayerTxData::new(),
90 }
91 }
92 }
93
94 impl Transaction for DHCPTransaction {
95 fn id(&self) -> u64 {
96 self.tx_id
97 }
98 }
99
100 #[derive(Default)]
101 pub struct DHCPState {
102 // Internal transaction ID.
103 tx_id: u64,
104
105 // List of transactions.
106 transactions: Vec<DHCPTransaction>,
107
108 events: u16,
109 }
110
111 impl State<DHCPTransaction> for DHCPState {
112 fn get_transactions(&self) -> &[DHCPTransaction] {
113 &self.transactions
114 }
115 }
116
117 impl DHCPState {
118 pub fn new() -> Self {
119 Default::default()
120 }
121
122 pub fn parse(&mut self, input: &[u8]) -> bool {
123 match dhcp_parse(input) {
124 Ok((_, message)) => {
125 let malformed_options = message.malformed_options;
126 let truncated_options = message.truncated_options;
127 self.tx_id += 1;
128 let transaction = DHCPTransaction::new(self.tx_id, message);
129 self.transactions.push(transaction);
130 if malformed_options {
131 self.set_event(DHCPEvent::MalformedOptions);
132 }
133 if truncated_options {
134 self.set_event(DHCPEvent::TruncatedOptions);
135 }
136 return true;
137 }
138 _ => {
139 return false;
140 }
141 }
142 }
143
144 pub fn get_tx(&mut self, tx_id: u64) -> Option<&DHCPTransaction> {
145 for tx in &mut self.transactions {
146 if tx.tx_id == tx_id + 1 {
147 return Some(tx);
148 }
149 }
150 return None;
151 }
152
153 fn free_tx(&mut self, tx_id: u64) {
154 let len = self.transactions.len();
155 let mut found = false;
156 let mut index = 0;
157 for i in 0..len {
158 let tx = &self.transactions[i];
159 if tx.tx_id == tx_id + 1 {
160 found = true;
161 index = i;
162 break;
163 }
164 }
165 if found {
166 self.transactions.remove(index);
167 }
168 }
169
170 fn set_event(&mut self, event: DHCPEvent) {
171 if let Some(tx) = self.transactions.last_mut() {
172 tx.tx_data.set_event(event as u8);
173 self.events += 1;
174 }
175 }
176 }
177
178 #[no_mangle]
179 pub unsafe extern "C" fn rs_dhcp_probing_parser(_flow: *const Flow,
180 _direction: u8,
181 input: *const u8,
182 input_len: u32,
183 _rdir: *mut u8) -> AppProto
184 {
185 if input_len < DHCP_MIN_FRAME_LEN {
186 return ALPROTO_UNKNOWN;
187 }
188
189 let slice = build_slice!(input, input_len as usize);
190 match parse_header(slice) {
191 Ok((_, _)) => {
192 return ALPROTO_DHCP;
193 }
194 _ => {
195 return ALPROTO_UNKNOWN;
196 }
197 }
198 }
199
200 #[no_mangle]
201 pub extern "C" fn rs_dhcp_tx_get_alstate_progress(_tx: *mut std::os::raw::c_void,
202 _direction: u8) -> std::os::raw::c_int {
203 // As this is a stateless parser, simply use 1.
204 return 1;
205 }
206
207 #[no_mangle]
208 pub unsafe extern "C" fn rs_dhcp_state_get_tx(state: *mut std::os::raw::c_void,
209 tx_id: u64) -> *mut std::os::raw::c_void {
210 let state = cast_pointer!(state, DHCPState);
211 match state.get_tx(tx_id) {
212 Some(tx) => {
213 return tx as *const _ as *mut _;
214 }
215 None => {
216 return std::ptr::null_mut();
217 }
218 }
219 }
220
221 #[no_mangle]
222 pub unsafe extern "C" fn rs_dhcp_state_get_tx_count(state: *mut std::os::raw::c_void) -> u64 {
223 let state = cast_pointer!(state, DHCPState);
224 return state.tx_id;
225 }
226
227 #[no_mangle]
228 pub unsafe extern "C" fn rs_dhcp_parse(_flow: *const core::Flow,
229 state: *mut std::os::raw::c_void,
230 _pstate: *mut std::os::raw::c_void,
231 input: *const u8,
232 input_len: u32,
233 _data: *const std::os::raw::c_void,
234 _flags: u8) -> AppLayerResult {
235 let state = cast_pointer!(state, DHCPState);
236 let buf = build_slice!(input, input_len as usize);
237 if state.parse(buf) {
238 return AppLayerResult::ok();
239 }
240 return AppLayerResult::err();
241 }
242
243 #[no_mangle]
244 pub unsafe extern "C" fn rs_dhcp_state_tx_free(
245 state: *mut std::os::raw::c_void,
246 tx_id: u64)
247 {
248 let state = cast_pointer!(state, DHCPState);
249 state.free_tx(tx_id);
250 }
251
252 #[no_mangle]
253 pub extern "C" fn rs_dhcp_state_new(_orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto) -> *mut std::os::raw::c_void {
254 let state = DHCPState::new();
255 let boxed = Box::new(state);
256 return Box::into_raw(boxed) as *mut _;
257 }
258
259 #[no_mangle]
260 pub unsafe extern "C" fn rs_dhcp_state_free(state: *mut std::os::raw::c_void) {
261 std::mem::drop(Box::from_raw(state as *mut DHCPState));
262 }
263
264 export_tx_data_get!(rs_dhcp_get_tx_data, DHCPTransaction);
265
266 const PARSER_NAME: &'static [u8] = b"dhcp\0";
267
268 #[no_mangle]
269 pub unsafe extern "C" fn rs_dhcp_register_parser() {
270 SCLogDebug!("Registering DHCP parser.");
271 let ports = CString::new("[67,68]").unwrap();
272 let parser = RustParser {
273 name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char,
274 default_port : ports.as_ptr(),
275 ipproto : IPPROTO_UDP,
276 probe_ts : Some(rs_dhcp_probing_parser),
277 probe_tc : Some(rs_dhcp_probing_parser),
278 min_depth : 0,
279 max_depth : 16,
280 state_new : rs_dhcp_state_new,
281 state_free : rs_dhcp_state_free,
282 tx_free : rs_dhcp_state_tx_free,
283 parse_ts : rs_dhcp_parse,
284 parse_tc : rs_dhcp_parse,
285 get_tx_count : rs_dhcp_state_get_tx_count,
286 get_tx : rs_dhcp_state_get_tx,
287 tx_comp_st_ts : 1,
288 tx_comp_st_tc : 1,
289 tx_get_progress : rs_dhcp_tx_get_alstate_progress,
290 get_eventinfo : Some(DHCPEvent::get_event_info),
291 get_eventinfo_byid : Some(DHCPEvent::get_event_info_by_id),
292 localstorage_new : None,
293 localstorage_free : None,
294 get_files : None,
295 get_tx_iterator : Some(applayer::state_get_tx_iterator::<DHCPState, DHCPTransaction>),
296 get_tx_data : rs_dhcp_get_tx_data,
297 apply_tx_config : None,
298 flags : APP_LAYER_PARSER_OPT_UNIDIR_TXS,
299 truncate : None,
300 };
301
302 let ip_proto_str = CString::new("udp").unwrap();
303
304 if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
305 let alproto = AppLayerRegisterProtocolDetection(&parser, 1);
306 ALPROTO_DHCP = alproto;
307 if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
308 let _ = AppLayerRegisterParser(&parser, alproto);
309 }
310 } else {
311 SCLogDebug!("Protocol detector and parser disabled for DHCP.");
312 }
313 }