mirror of
https://github.com/pj1234678/RustyDHCP.git
synced 2026-08-19 02:25:18 +08:00
Initial Upload of Files
This commit is contained in:
+311
@@ -0,0 +1,311 @@
|
||||
use crate::options::*;
|
||||
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
pub enum CustomErr<I> {
|
||||
NomError((I, ErrorKind)),
|
||||
NonUtf8String,
|
||||
UnrecognizedMessageType,
|
||||
InvalidHlen,
|
||||
}
|
||||
|
||||
pub enum ErrorKind {
|
||||
Tag,
|
||||
MapRes,
|
||||
ManyTill,
|
||||
Eof,
|
||||
Custom(u32),
|
||||
}
|
||||
|
||||
type IResult<I, O> = Result<(I, O), CustomErr<I>>;
|
||||
|
||||
|
||||
|
||||
/// DHCP Packet Structure
|
||||
#[derive(Debug)]
|
||||
pub struct Packet {
|
||||
pub reply: bool, // false = request, true = reply
|
||||
pub hops: u8,
|
||||
pub xid: u32, // Random identifier
|
||||
pub secs: u16,
|
||||
pub broadcast: bool,
|
||||
pub ciaddr: Ipv4Addr,
|
||||
pub yiaddr: Ipv4Addr,
|
||||
pub siaddr: Ipv4Addr,
|
||||
pub giaddr: Ipv4Addr,
|
||||
pub chaddr: [u8; 6],
|
||||
pub options: Vec<DhcpOption>,
|
||||
}
|
||||
|
||||
fn decode_reply(input: &[u8]) -> IResult<&[u8], bool> {
|
||||
let (input, reply) = custom_take(1usize)(input)?;
|
||||
Ok((
|
||||
input,
|
||||
match reply[0] {
|
||||
BOOT_REPLY => true,
|
||||
BOOT_REQUEST => false,
|
||||
_ => {
|
||||
// @TODO: Throw an error
|
||||
false
|
||||
}
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn decode_ipv4(p: &[u8]) -> IResult<&[u8], Ipv4Addr> {
|
||||
let (input, addr) = custom_take(4usize)(p)?;
|
||||
Ok((input, Ipv4Addr::new(addr[0], addr[1], addr[2], addr[3])))
|
||||
}
|
||||
fn custom_many0<I, O, F>(mut f: F) -> impl FnMut(I) -> IResult<I, Vec<O>>
|
||||
where
|
||||
I: Clone + PartialEq,
|
||||
F: FnMut(I) -> IResult<I, O>,
|
||||
{
|
||||
move |input: I| {
|
||||
let mut acc = Vec::new();
|
||||
let mut remaining = input.clone();
|
||||
|
||||
loop {
|
||||
match f(remaining.clone()) {
|
||||
Ok((input, o)) => {
|
||||
if input == remaining {
|
||||
return Ok((input, acc));
|
||||
}
|
||||
acc.push(o);
|
||||
remaining = input;
|
||||
}
|
||||
Err(CustomErr::NomError(_)) => return Ok((remaining, acc)),
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn decode_option(input: &[u8]) -> IResult<&[u8], DhcpOption> {
|
||||
let (input, code) = custom_be_u8(input)?;
|
||||
assert!(code != END);
|
||||
|
||||
let (input, len) = custom_be_u8(input)?;
|
||||
let (input, data) = custom_take(len.into())(input)?;
|
||||
let option = match code {
|
||||
DHCP_MESSAGE_TYPE => DhcpOption::DhcpMessageType(match MessageType::from(custom_be_u8(data)?.1) {
|
||||
Ok(x) => x,
|
||||
Err(_) => return Err(CustomErr::UnrecognizedMessageType),
|
||||
}),
|
||||
SERVER_IDENTIFIER => DhcpOption::ServerIdentifier(decode_ipv4(data)?.1),
|
||||
PARAMETER_REQUEST_LIST => DhcpOption::ParameterRequestList(data.to_vec()),
|
||||
REQUESTED_IP_ADDRESS => DhcpOption::RequestedIpAddress(decode_ipv4(data)?.1),
|
||||
HOST_NAME => DhcpOption::HostName(match std::str::from_utf8(data) {
|
||||
Ok(s) => s.to_string(),
|
||||
Err(_) => return Err(CustomErr::NonUtf8String),
|
||||
}),
|
||||
ROUTER => DhcpOption::Router(custom_many0(decode_ipv4)(data)?.1),
|
||||
DOMAIN_NAME_SERVER => DhcpOption::DomainNameServer(custom_many0(decode_ipv4)(data)?.1),
|
||||
IP_ADDRESS_LEASE_TIME => DhcpOption::IpAddressLeaseTime(custom_be_u32(data)?.1),
|
||||
SUBNET_MASK => DhcpOption::SubnetMask(decode_ipv4(data)?.1),
|
||||
MESSAGE => DhcpOption::Message(match std::str::from_utf8(data) {
|
||||
Ok(s) => s.to_string(),
|
||||
Err(_) => return Err(CustomErr::NonUtf8String),
|
||||
}),
|
||||
_ => DhcpOption::Unrecognized(RawDhcpOption {
|
||||
code,
|
||||
data: data.to_vec(),
|
||||
}),
|
||||
};
|
||||
Ok((input, option))
|
||||
}
|
||||
fn custom_take<'a>(n: usize) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], &'a [u8]> {
|
||||
move |input: &'a [u8]| {
|
||||
if input.len() >= n {
|
||||
Ok((&input[n..], &input[0..n]))
|
||||
} else {
|
||||
Err(CustomErr::InvalidHlen)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn custom_tag<'a>(tag: &'static [u8]) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], &'a [u8]> {
|
||||
move |input: &'a [u8]| {
|
||||
if input.starts_with(tag) {
|
||||
Ok((&input[tag.len()..], &input[..tag.len()]))
|
||||
} else {
|
||||
Err(CustomErr::NomError((input, ErrorKind::Tag)))
|
||||
}
|
||||
}
|
||||
}
|
||||
fn custom_be_u8(input: &[u8]) -> IResult<&[u8], u8> {
|
||||
if input.len() < 1 {
|
||||
return Err(CustomErr::InvalidHlen);
|
||||
}
|
||||
|
||||
Ok((&input[1..], input[0]))
|
||||
}
|
||||
|
||||
fn custom_be_u16(input: &[u8]) -> IResult<&[u8], u16> {
|
||||
if input.len() < 2 {
|
||||
return Err(CustomErr::InvalidHlen);
|
||||
}
|
||||
|
||||
let value = u16::from_be_bytes([input[0], input[1]]);
|
||||
Ok((&input[2..], value))
|
||||
}
|
||||
fn custom_be_u32(input: &[u8]) -> IResult<&[u8], u32> {
|
||||
if input.len() < 4 {
|
||||
return Err(CustomErr::InvalidHlen);
|
||||
}
|
||||
|
||||
let value = u32::from_be_bytes([input[0], input[1], input[2], input[3]]);
|
||||
Ok((&input[4..], value))
|
||||
}
|
||||
|
||||
/// Parses Packet from byte array
|
||||
fn decode(input: &[u8]) -> IResult<&[u8], Packet> {
|
||||
let (options_input, input) = custom_take(236usize)(input)?;
|
||||
|
||||
let (input, reply) = decode_reply(input)?;
|
||||
let (input, _htype) = custom_take(1usize)(input)?;
|
||||
let (input, hlen) = custom_be_u8(input)?;
|
||||
let (input, hops) = custom_be_u8(input)?;
|
||||
let (input, xid) = custom_be_u32(input)?;
|
||||
let (input, secs) = custom_be_u16(input)?;
|
||||
let (input, flags) = custom_be_u16(input)?;
|
||||
let (input, ciaddr) = decode_ipv4(input)?;
|
||||
let (input, yiaddr) = decode_ipv4(input)?;
|
||||
let (input, siaddr) = decode_ipv4(input)?;
|
||||
let (input, giaddr) = decode_ipv4(input)?;
|
||||
|
||||
if hlen != 6 {
|
||||
|
||||
return Err(CustomErr::InvalidHlen);
|
||||
}
|
||||
let (_, chaddr) = custom_take(6usize)(input)?;
|
||||
|
||||
let input = options_input;
|
||||
let (input, _) = custom_tag(&COOKIE)(input)?;
|
||||
|
||||
|
||||
let mut options = Vec::new();
|
||||
let mut rest = input;
|
||||
|
||||
loop {
|
||||
match decode_option(rest) {
|
||||
Ok((new_rest, option)) => {
|
||||
rest = new_rest;
|
||||
options.push(option);
|
||||
if rest.starts_with(&[END]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
|
||||
let input = rest.split_at(1).1; // Skip the END tag byte
|
||||
|
||||
Ok((
|
||||
input,
|
||||
Packet {
|
||||
reply,
|
||||
hops,
|
||||
secs,
|
||||
broadcast: flags & 128 == 128,
|
||||
ciaddr,
|
||||
yiaddr,
|
||||
siaddr,
|
||||
giaddr,
|
||||
options,
|
||||
chaddr: [
|
||||
chaddr[0], chaddr[1], chaddr[2], chaddr[3], chaddr[4], chaddr[5],
|
||||
],
|
||||
xid,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
impl Packet {
|
||||
pub fn from(input: &[u8]) -> Result<Packet, CustomErr<&[u8]>> {
|
||||
|
||||
Ok(decode(input)?.1)
|
||||
}
|
||||
|
||||
/// Extracts requested option payload from packet if available
|
||||
pub fn option(&self, code: u8) -> Option<&DhcpOption> {
|
||||
for option in &self.options {
|
||||
if option.code() == code {
|
||||
return Some(&option);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Convenience function for extracting a packet's message type.
|
||||
pub fn message_type(&self) -> Result<MessageType, String> {
|
||||
match self.option(DHCP_MESSAGE_TYPE) {
|
||||
Some(DhcpOption::DhcpMessageType(msgtype)) => Ok(*msgtype),
|
||||
Some(option) => Err(format![
|
||||
"Got wrong enum code {} for DHCP_MESSAGE_TYPE",
|
||||
option.code()
|
||||
]),
|
||||
None => Err("Packet does not have MessageType option".to_string()),
|
||||
}
|
||||
}
|
||||
pub fn encode<'a>(&'a self, p: &'a mut [u8]) -> &[u8] {
|
||||
let broadcast_flag = if self.broadcast { 128 } else { 0 };
|
||||
let mut length = 240;
|
||||
|
||||
p[..12].copy_from_slice(&[
|
||||
if self.reply { BOOT_REPLY } else { BOOT_REQUEST },
|
||||
1,
|
||||
6,
|
||||
self.hops,
|
||||
((self.xid >> 24) & 0xFF) as u8,
|
||||
((self.xid >> 16) & 0xFF) as u8,
|
||||
((self.xid >> 8) & 0xFF) as u8,
|
||||
(self.xid & 0xFF) as u8,
|
||||
(self.secs >> 8) as u8,
|
||||
(self.secs & 255) as u8,
|
||||
broadcast_flag,
|
||||
0,
|
||||
]);
|
||||
|
||||
p[12..16].copy_from_slice(&self.ciaddr.octets());
|
||||
p[16..20].copy_from_slice(&self.yiaddr.octets());
|
||||
p[20..24].copy_from_slice(&self.siaddr.octets());
|
||||
p[24..28].copy_from_slice(&self.giaddr.octets());
|
||||
p[28..34].copy_from_slice(&self.chaddr);
|
||||
p[34..236].fill(0);
|
||||
p[236..240].copy_from_slice(&COOKIE);
|
||||
|
||||
for option in &self.options {
|
||||
let option = option.to_raw();
|
||||
let option_len = option.data.len();
|
||||
if length + 2 + option_len >= 272 {
|
||||
break;
|
||||
}
|
||||
if let Some(dest) = p.get_mut(length..length + 2 + option_len) {
|
||||
dest[0] = option.code;
|
||||
dest[1] = option_len as u8;
|
||||
dest[2..].copy_from_slice(&option.data);
|
||||
}
|
||||
length += 2 + option_len;
|
||||
}
|
||||
|
||||
if let Some(end_segment) = p.get_mut(length..length + 1) {
|
||||
end_segment[0] = END;
|
||||
}
|
||||
length += 1;
|
||||
|
||||
if let Some(pad_segment) = p.get_mut(length..272) {
|
||||
pad_segment.fill(PAD);
|
||||
}
|
||||
|
||||
&p[..length]
|
||||
}
|
||||
}
|
||||
|
||||
const COOKIE: [u8; 4] = [99, 130, 83, 99];
|
||||
|
||||
const BOOT_REQUEST: u8 = 1; // From Client;
|
||||
const BOOT_REPLY: u8 = 2; // From Server;
|
||||
|
||||
const END: u8 = 255;
|
||||
const PAD: u8 = 0;
|
||||
Reference in New Issue
Block a user