Coverage Report

Created: 2025-07-23 07:29

/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
}