You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
suricata/rust/src/dhcp/parser.rs

288 lines
9.0 KiB
Rust

/* Copyright (C) 2018 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
use std::cmp::min;
use dhcp::dhcp::*;
use nom::*;
pub struct DHCPMessage {
pub header: DHCPHeader,
pub options: Vec<DHCPOption>,
// Set to true if the options were found to be malformed. That is
// failing to parse with enough data.
pub malformed_options: bool,
// Set to true if the options failed to parse due to not enough
// data.
pub truncated_options: bool,
}
pub struct DHCPHeader {
pub opcode: u8,
pub htype: u8,
pub hlen: u8,
pub hops: u8,
pub txid: u32,
pub seconds: u16,
pub flags: u16,
pub clientip: Vec<u8>,
pub yourip: Vec<u8>,
pub serverip: Vec<u8>,
pub giaddr: Vec<u8>,
pub clienthw: Vec<u8>,
pub servername: Vec<u8>,
pub bootfilename: Vec<u8>,
pub magic: Vec<u8>,
}
pub struct DHCPOptClientId {
pub htype: u8,
pub data: Vec<u8>,
}
/// Option type for time values.
pub struct DHCPOptTimeValue {
pub seconds: u32,
}
pub struct DHCPOptGeneric {
pub data: Vec<u8>,
}
pub enum DHCPOptionWrapper {
ClientId(DHCPOptClientId),
TimeValue(DHCPOptTimeValue),
Generic(DHCPOptGeneric),
End,
}
pub struct DHCPOption {
pub code: u8,
pub data: Option<Vec<u8>>,
pub option: DHCPOptionWrapper,
}
named!(pub parse_header<DHCPHeader>,
do_parse!(
opcode: be_u8
>> htype: be_u8
>> hlen: be_u8
>> hops: be_u8
>> txid: be_u32
>> seconds: be_u16
>> flags: be_u16
>> clientip: take!(4)
>> yourip: take!(4)
>> serverip: take!(4)
>> giaddr: take!(4)
>> clienthw: take!(16)
>> servername: take!(64)
>> bootfilename: take!(128)
>> magic: take!(4)
>> (
DHCPHeader{
opcode: opcode,
htype: htype,
hlen: hlen,
hops: hops,
txid: txid,
seconds: seconds,
flags: flags,
clientip: clientip.to_vec(),
yourip: yourip.to_vec(),
serverip: serverip.to_vec(),
giaddr: giaddr.to_vec(),
clienthw: clienthw[0..min(hlen as usize, 16)].to_vec(),
servername: servername.to_vec(),
bootfilename: bootfilename.to_vec(),
magic: magic.to_vec(),
}
)
)
);
named!(pub parse_clientid_option<DHCPOption>,
do_parse!(
code: be_u8 >>
len: be_u8 >>
htype: be_u8 >>
data: take!(len - 1) >>
(
DHCPOption{
code: code,
data: None,
option: DHCPOptionWrapper::ClientId(DHCPOptClientId{
htype: 1,
data: data.to_vec(),
}),
}
)
)
);
named!(pub parse_address_time_option<DHCPOption>,
do_parse!(
code: be_u8 >>
len: be_u8 >>
seconds: be_u32 >>
(
DHCPOption{
code: code,
data: None,
option: DHCPOptionWrapper::TimeValue(DHCPOptTimeValue{
seconds: seconds,
}),
}
)
)
);
named!(pub parse_generic_option<DHCPOption>,
do_parse!(
code: be_u8 >>
len: be_u8 >>
data: take!(len) >> (
DHCPOption{
code: code,
data: None,
option: DHCPOptionWrapper::Generic(DHCPOptGeneric{
data: data.to_vec(),
}),
}
))
);
/// Parse a single DHCP option. When option 255 (END) is parsed, the remaining
/// data will be consumed.
named!(pub parse_option<DHCPOption>,
switch!(peek!(be_u8),
// End of options case. We consume the rest of the data
// so the parse is not called again. But is there a
// better way to "break"?
DHCP_OPT_END => do_parse!(
code: be_u8 >>
data: rest >> (DHCPOption{
code: code,
data: Some(data.to_vec()),
option: DHCPOptionWrapper::End,
})) |
DHCP_OPT_CLIENT_ID => call!(parse_clientid_option) |
DHCP_OPT_ADDRESS_TIME => call!(parse_address_time_option) |
DHCP_OPT_RENEWAL_TIME => call!(parse_address_time_option) |
DHCP_OPT_REBINDING_TIME => call!(parse_address_time_option) |
_ => call!(parse_generic_option)
));
/// Parse and return all the options. Upon the end of option indicator
/// all the data will be consumed.
named!(pub parse_all_options<Vec<DHCPOption>>, many0!(call!(parse_option)));
pub fn dhcp_parse(input: &[u8]) -> IResult<&[u8], DHCPMessage> {
match parse_header(input) {
IResult::Done(rem, header) => {
let mut options = Vec::new();
let mut next = rem;
let mut malformed_options = false;
let mut truncated_options = false;
loop {
match parse_option(next) {
IResult::Done(rem, option) => {
let done = option.code == DHCP_OPT_END;
options.push(option);
next = rem;
if done {
break;
}
}
IResult::Incomplete(_) => {
truncated_options = true;
break;
}
IResult::Error(_) => {
malformed_options = true;
break;
}
}
}
let message = DHCPMessage {
header: header,
options: options,
malformed_options: malformed_options,
truncated_options: truncated_options,
};
return IResult::Done(next, message);
}
IResult::Error(err) => {
return IResult::Error(err);
}
IResult::Incomplete(incomplete) => {
return IResult::Incomplete(incomplete);
}
}
}
#[cfg(test)]
mod tests {
use dhcp::dhcp::*;
use dhcp::parser::*;
#[test]
fn test_parse_discover() {
let pcap = include_bytes!("discover.pcap");
let payload = &pcap[24 + 16 + 42..];
match dhcp_parse(payload) {
IResult::Done(_rem, message) => {
let header = message.header;
assert_eq!(header.opcode, BOOTP_REQUEST);
assert_eq!(header.htype, 1);
assert_eq!(header.hlen, 6);
assert_eq!(header.hops, 0);
assert_eq!(header.txid, 0x00003d1d);
assert_eq!(header.seconds, 0);
assert_eq!(header.flags, 0);
assert_eq!(header.clientip, &[0, 0, 0, 0]);
assert_eq!(header.yourip, &[0, 0, 0, 0]);
assert_eq!(header.serverip, &[0, 0, 0, 0]);
assert_eq!(header.giaddr, &[0, 0, 0, 0]);
assert_eq!(&header.clienthw[..(header.hlen as usize)],
&[0x00, 0x0b, 0x82, 0x01, 0xfc, 0x42]);
assert!(header.servername.iter().all(|&x| x == 0));
assert!(header.bootfilename.iter().all(|&x| x == 0));
assert_eq!(header.magic, &[0x63, 0x82, 0x53, 0x63]);
assert!(!message.malformed_options);
assert!(!message.truncated_options);
assert_eq!(message.options.len(), 5);
assert_eq!(message.options[0].code, DHCP_OPT_TYPE);
assert_eq!(message.options[1].code, DHCP_OPT_CLIENT_ID);
assert_eq!(message.options[2].code, DHCP_OPT_REQUESTED_IP);
assert_eq!(message.options[3].code, DHCP_OPT_PARAMETER_LIST);
assert_eq!(message.options[4].code, DHCP_OPT_END);
}
_ => {
assert!(false);
}
}
}
}