/src/suricata7/rust/src/dhcp/logger.rs
Line | Count | Source (jump to first uncovered line) |
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; |
19 | | use std::os::raw::c_void; |
20 | | |
21 | | use crate::dhcp::dhcp::*; |
22 | | use crate::dhcp::parser::{DHCPOptionWrapper,DHCPOptGeneric}; |
23 | | use crate::dns::log::dns_print_addr; |
24 | | use crate::conf::ConfNode; |
25 | | use crate::jsonbuilder::{JsonBuilder, JsonError}; |
26 | | |
27 | | pub struct DHCPLogger { |
28 | | extended: bool, |
29 | | } |
30 | | |
31 | | impl DHCPLogger { |
32 | | |
33 | 2 | pub fn new(conf: ConfNode) -> Self { |
34 | 2 | return Self { |
35 | 2 | extended: conf.get_child_bool("extended"), |
36 | 2 | } |
37 | 2 | } |
38 | | |
39 | 0 | fn get_type(&self, tx: &DHCPTransaction) -> Option<u8> { |
40 | 0 | let options = &tx.message.options; |
41 | 0 | for option in options { |
42 | 0 | let code = option.code; |
43 | 0 | #[allow(clippy::single_match)] |
44 | 0 | match &option.option { |
45 | 0 | DHCPOptionWrapper::Generic(option) => { |
46 | 0 | #[allow(clippy::single_match)] |
47 | 0 | match code { |
48 | | DHCP_OPT_TYPE => { |
49 | 0 | if !option.data.is_empty() { |
50 | 0 | return Some(option.data[0]); |
51 | 0 | } |
52 | | } |
53 | 0 | _ => {} |
54 | | } |
55 | | } |
56 | 0 | _ => {} |
57 | | } |
58 | | } |
59 | 0 | return None; |
60 | 0 | } |
61 | | |
62 | 18 | pub fn do_log(&self, tx: &DHCPTransaction) -> bool { |
63 | 18 | if !self.extended { |
64 | 0 | if let Some(DHCP_TYPE_ACK) = self.get_type(tx){ |
65 | 0 | return true; |
66 | 0 | } |
67 | 0 | return false; |
68 | 18 | } |
69 | 18 | return true; |
70 | 18 | } |
71 | | |
72 | 18 | pub fn log(&self, tx: &DHCPTransaction, js: &mut JsonBuilder) -> Result<(), JsonError> { |
73 | 18 | let header = &tx.message.header; |
74 | 18 | let options = &tx.message.options; |
75 | 18 | |
76 | 18 | js.open_object("dhcp")?; |
77 | | |
78 | 18 | match header.opcode { |
79 | | BOOTP_REQUEST => { |
80 | 12 | js.set_string("type", "request")?; |
81 | | } |
82 | | BOOTP_REPLY => { |
83 | 6 | js.set_string("type", "reply")?; |
84 | | } |
85 | | _ => { |
86 | 0 | js.set_string("type", "<unknown>")?; |
87 | | } |
88 | | } |
89 | | |
90 | 18 | js.set_uint("id", header.txid as u64)?; |
91 | 18 | js.set_string("client_mac", |
92 | 18 | &format_addr_hex(&header.clienthw))?; |
93 | 18 | js.set_string("assigned_ip", &dns_print_addr(&header.yourip))?; |
94 | | |
95 | 18 | if self.extended { |
96 | 18 | js.set_string("client_ip", &dns_print_addr(&header.clientip))?; |
97 | 18 | if header.opcode == BOOTP_REPLY { |
98 | 6 | js.set_string("relay_ip", |
99 | 6 | &dns_print_addr(&header.giaddr))?; |
100 | 6 | js.set_string("next_server_ip", |
101 | 6 | &dns_print_addr(&header.serverip))?; |
102 | 12 | } |
103 | 0 | } |
104 | | |
105 | 164 | for option in options { |
106 | 146 | let code = option.code; |
107 | 146 | match option.option { |
108 | 12 | DHCPOptionWrapper::ClientId(ref clientid) => { |
109 | 12 | js.set_string("client_id", |
110 | 12 | &format_addr_hex(&clientid.data))?; |
111 | | } |
112 | 6 | DHCPOptionWrapper::TimeValue(ref time_value) => { |
113 | 6 | match code { |
114 | | DHCP_OPT_ADDRESS_TIME => { |
115 | 6 | if self.extended { |
116 | 6 | js.set_uint("lease_time", |
117 | 6 | time_value.seconds as u64)?; |
118 | 0 | } |
119 | | } |
120 | | DHCP_OPT_REBINDING_TIME => { |
121 | 0 | if self.extended { |
122 | 0 | js.set_uint("rebinding_time", |
123 | 0 | time_value.seconds as u64)?; |
124 | 0 | } |
125 | | } |
126 | | DHCP_OPT_RENEWAL_TIME => { |
127 | 0 | js.set_uint("renewal_time", |
128 | 0 | time_value.seconds as u64)?; |
129 | | } |
130 | 0 | _ => {} |
131 | | } |
132 | | } |
133 | 110 | DHCPOptionWrapper::Generic(ref option) => { |
134 | 110 | match code { |
135 | | DHCP_OPT_SUBNET_MASK => { |
136 | 6 | if self.extended { |
137 | 6 | js.set_string("subnet_mask", |
138 | 6 | &dns_print_addr(&option.data))?; |
139 | 0 | } |
140 | | } |
141 | | DHCP_OPT_HOSTNAME => { |
142 | 12 | if !option.data.is_empty() { |
143 | 12 | js.set_string_from_bytes("hostname", |
144 | 12 | &option.data)?; |
145 | 0 | } |
146 | | } |
147 | | DHCP_OPT_TYPE => { |
148 | 18 | self.log_opt_type(js, option)?; |
149 | | } |
150 | | DHCP_OPT_REQUESTED_IP => { |
151 | 11 | if self.extended { |
152 | 11 | js.set_string("requested_ip", |
153 | 11 | &dns_print_addr(&option.data))?; |
154 | 0 | } |
155 | | } |
156 | | DHCP_OPT_PARAMETER_LIST => { |
157 | 12 | if self.extended { |
158 | 12 | self.log_opt_parameters(js, option)?; |
159 | 0 | } |
160 | | } |
161 | | DHCP_OPT_DNS_SERVER => { |
162 | 6 | if self.extended { |
163 | 6 | self.log_opt_dns_server(js, option)?; |
164 | 0 | } |
165 | | } |
166 | | DHCP_OPT_ROUTERS => { |
167 | 4 | if self.extended { |
168 | 4 | self.log_opt_routers(js, option)?; |
169 | 0 | } |
170 | | } |
171 | | DHCP_OPT_VENDOR_CLASS_ID => { |
172 | 12 | if self.extended && !option.data.is_empty(){ |
173 | 12 | js.set_string_from_bytes("vendor_class_identifier", |
174 | 12 | &option.data)?; |
175 | 0 | } |
176 | | } |
177 | 29 | _ => {} |
178 | | } |
179 | | } |
180 | 18 | _ => {} |
181 | | } |
182 | | } |
183 | | |
184 | 18 | js.close()?; |
185 | | |
186 | 18 | return Ok(()); |
187 | 18 | } |
188 | | |
189 | 18 | fn log_opt_type(&self, js: &mut JsonBuilder, option: &DHCPOptGeneric) -> Result<(), JsonError> { |
190 | 18 | if !option.data.is_empty() { |
191 | 18 | let dhcp_type = match option.data[0] { |
192 | 0 | DHCP_TYPE_DISCOVER => "discover", |
193 | 0 | DHCP_TYPE_OFFER => "offer", |
194 | 12 | DHCP_TYPE_REQUEST => "request", |
195 | 0 | DHCP_TYPE_DECLINE => "decline", |
196 | 6 | DHCP_TYPE_ACK => "ack", |
197 | 0 | DHCP_TYPE_NAK => "nak", |
198 | 0 | DHCP_TYPE_RELEASE => "release", |
199 | 0 | DHCP_TYPE_INFORM => "inform", |
200 | 0 | _ => "unknown" |
201 | | }; |
202 | 18 | js.set_string("dhcp_type", dhcp_type)?; |
203 | 0 | } |
204 | 18 | Ok(()) |
205 | 18 | } |
206 | | |
207 | 12 | fn log_opt_parameters(&self, js: &mut JsonBuilder, option: &DHCPOptGeneric) -> Result<(), JsonError> { |
208 | 12 | js.open_array("params")?; |
209 | 158 | for i in &option.data { |
210 | 146 | let param = match *i { |
211 | 12 | DHCP_PARAM_SUBNET_MASK => "subnet_mask", |
212 | 12 | DHCP_PARAM_ROUTER => "router", |
213 | 12 | DHCP_PARAM_DNS_SERVER => "dns_server", |
214 | 12 | DHCP_PARAM_DOMAIN => "domain", |
215 | 0 | DHCP_PARAM_ARP_TIMEOUT => "arp_timeout", |
216 | 0 | DHCP_PARAM_NTP_SERVER => "ntp_server", |
217 | 0 | DHCP_PARAM_TFTP_SERVER_NAME => "tftp_server_name", |
218 | 0 | DHCP_PARAM_TFTP_SERVER_IP => "tftp_server_ip", |
219 | 98 | _ => "" |
220 | | }; |
221 | 146 | if !param.is_empty() { |
222 | 48 | js.append_string(param)?; |
223 | 98 | } |
224 | | } |
225 | 12 | js.close()?; |
226 | 12 | Ok(()) |
227 | 12 | } |
228 | | |
229 | 6 | fn log_opt_dns_server(&self, js: &mut JsonBuilder, option: &DHCPOptGeneric) -> Result<(), JsonError> { |
230 | 6 | js.open_array("dns_servers")?; |
231 | 10 | for i in 0..(option.data.len() / 4) { |
232 | 10 | let val = dns_print_addr(&option.data[(i * 4)..(i * 4) + 4]); |
233 | 10 | js.append_string(&val)?; |
234 | | } |
235 | 6 | js.close()?; |
236 | 6 | Ok(()) |
237 | 6 | } |
238 | | |
239 | 4 | fn log_opt_routers(&self, js: &mut JsonBuilder, option: &DHCPOptGeneric) -> Result<(), JsonError> { |
240 | 4 | js.open_array("routers")?; |
241 | 4 | for i in 0..(option.data.len() / 4) { |
242 | 4 | let val = dns_print_addr(&option.data[(i * 4)..(i * 4) + 4]); |
243 | 4 | js.append_string(&val)?; |
244 | | } |
245 | 4 | js.close()?; |
246 | 4 | Ok(()) |
247 | 4 | } |
248 | | |
249 | | } |
250 | | |
251 | 30 | fn format_addr_hex(input: &[u8]) -> String { |
252 | 30 | let parts: Vec<String> = input.iter() |
253 | 180 | .map(|b| format!("{:02x}", b)) |
254 | 30 | .collect(); |
255 | 30 | return parts.join(":"); |
256 | 30 | } |
257 | | |
258 | | #[no_mangle] |
259 | 2 | pub extern "C" fn rs_dhcp_logger_new(conf: *const c_void) -> *mut std::os::raw::c_void { |
260 | 2 | let conf = ConfNode::wrap(conf); |
261 | 2 | let boxed = Box::new(DHCPLogger::new(conf)); |
262 | 2 | return Box::into_raw(boxed) as *mut _; |
263 | 2 | } |
264 | | |
265 | | #[no_mangle] |
266 | 0 | pub unsafe extern "C" fn rs_dhcp_logger_free(logger: *mut std::os::raw::c_void) { |
267 | 0 | std::mem::drop(Box::from_raw(logger as *mut DHCPLogger)); |
268 | 0 | } |
269 | | |
270 | | #[no_mangle] |
271 | 18 | pub unsafe extern "C" fn rs_dhcp_logger_log(logger: *mut std::os::raw::c_void, |
272 | 18 | tx: *mut std::os::raw::c_void, |
273 | 18 | js: &mut JsonBuilder) -> bool { |
274 | 18 | let logger = cast_pointer!(logger, DHCPLogger); |
275 | 18 | let tx = cast_pointer!(tx, DHCPTransaction); |
276 | 18 | logger.log(tx, js).is_ok() |
277 | 18 | } |
278 | | |
279 | | #[no_mangle] |
280 | 18 | pub unsafe extern "C" fn rs_dhcp_logger_do_log(logger: *mut std::os::raw::c_void, |
281 | 18 | tx: *mut std::os::raw::c_void) |
282 | 18 | -> bool { |
283 | 18 | let logger = cast_pointer!(logger, DHCPLogger); |
284 | 18 | let tx = cast_pointer!(tx, DHCPTransaction); |
285 | 18 | logger.do_log(tx) |
286 | 18 | } |