Initial commit.

This commit is contained in:
Xnoe 2022-09-23 13:58:17 +01:00
commit 0aa92d8021
Signed by: xnoe
GPG Key ID: 45AC398F44F0DAFE
9 changed files with 2351 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

113
Cargo.lock generated Normal file
View File

@ -0,0 +1,113 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
dependencies = [
"memchr",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "dhcprs"
version = "0.1.0"
dependencies = [
"eui48",
"libc",
"nix",
]
[[package]]
name = "eui48"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "887418ac5e8d57c2e66e04bdc2fe15f9a5407be20b54a82c86bd0e368b709701"
dependencies = [
"regex",
"rustc-serialize",
]
[[package]]
name = "libc"
version = "0.2.133"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966"
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg",
]
[[package]]
name = "nix"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb"
dependencies = [
"autocfg",
"bitflags",
"cfg-if",
"libc",
"memoffset",
"pin-utils",
]
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "regex"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
[[package]]
name = "rustc-serialize"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"

16
Cargo.toml Normal file
View File

@ -0,0 +1,16 @@
[package]
name = "dhcprs"
version = "0.1.0"
edition = "2021"
description = "A library for encoding and decoding DHCP/BOOTP packets"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "dhcprs"
path = "src/lib.rs"
[dependencies]
nix = "0.25.0"
eui48 = "1.1.0"
libc = "0.2.133"

19
LICENSE Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2022 xnoe
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

11
README.md Normal file
View File

@ -0,0 +1,11 @@
# dhcprs
DHCP/BOOTP packet encode/decode library.
The purpose of this library is to provide an easy to use interface over
DHCP, BOOTP and UDP packets to enable the creation of programs that
make use of DHCP.
BOOTP specific functionality is provided by the `dhcprs::bootp` module.
DHCP specific functionality is provided by the `dhcprs::dhcp` module.
There is additionally simple UDP packet encode/decode provided by the `dhcprs::udpbuilder`.

192
src/bootp.rs Normal file
View File

@ -0,0 +1,192 @@
use eui48::MacAddress;
// Raw BOOTP Packet.
// Total size: 546 bytes
#[allow(dead_code)]
#[repr(packed)]
pub struct RawBOOTPPacket {
// RFC951
op: u8, // OpCode
htype: u8, // Host Type
hlen: u8, // Host Length
hops: u8, // Hops
xid: u32, // Transaction ID
secs: u16, // Seconds since boot
flags: u16, // Unused in BOOTP, flags in DHCP
ciaddr: u32, // Client Address (provided by client)
yiaddr: u32, // Client Address (provided by server)
siaddr: u32, // Server Address
giaddr: u32, // Gateway Address
chaddr: [u8; 16], // Client Hardware (MAC) Address
sname: [u8; 64], // Server hostname, null terminated string
file: [u8; 128], // Boot file name, null terminated string
vend: [u8; 312], // Vendor specific data. In RFC2131, this is required to be 312 octets for DHCP.
}
impl RawBOOTPPacket {
pub fn as_bytes(&self) -> &[u8] {
unsafe { std::slice::from_raw_parts(self as *const Self as *const u8, 546) }
}
}
pub enum OpCode {
BOOTREQUEST,
BOOTREPLY,
}
impl TryFrom<u8> for OpCode {
type Error = ();
fn try_from(item: u8) -> Result<Self, Self::Error> {
match item {
1 => Ok(Self::BOOTREQUEST),
2 => Ok(Self::BOOTREPLY),
_ => Err(()),
}
}
}
impl TryFrom<OpCode> for u8 {
type Error = ();
fn try_from(item: OpCode) -> Result<Self, Self::Error> {
match item {
OpCode::BOOTREQUEST => Ok(1),
OpCode::BOOTREPLY => Ok(2),
}
}
}
// Higher level BOOTP Packet representation
pub struct BOOTPPacket {
pub op: OpCode,
pub hops: u8,
pub xid: u32,
pub secs: u16,
pub flags: u16,
pub ciaddr: Option<std::net::Ipv4Addr>,
pub yiaddr: Option<std::net::Ipv4Addr>,
pub siaddr: Option<std::net::Ipv4Addr>,
pub giaddr: Option<std::net::Ipv4Addr>,
pub chaddr: MacAddress,
pub sname: [u8; 64],
pub file: [u8; 128],
vend: [u8; 312],
}
impl From<BOOTPPacket> for RawBOOTPPacket {
fn from(item: BOOTPPacket) -> Self {
let mut chaddr: [u8; 16] = [0; 16];
chaddr[..6].copy_from_slice(item.chaddr.as_bytes());
Self {
op: item.op.try_into().unwrap(),
htype: 1,
hlen: 6,
hops: item.hops,
xid: item.xid,
secs: item.secs,
flags: item.flags,
ciaddr: if item.ciaddr.is_some() {
item.ciaddr.unwrap().into()
} else {
0
},
yiaddr: if item.yiaddr.is_some() {
item.yiaddr.unwrap().into()
} else {
0
},
siaddr: if item.siaddr.is_some() {
item.siaddr.unwrap().into()
} else {
0
},
giaddr: if item.giaddr.is_some() {
item.giaddr.unwrap().into()
} else {
0
},
chaddr: chaddr,
sname: item.sname,
file: item.file,
vend: item.vend,
}
}
}
impl From<RawBOOTPPacket> for BOOTPPacket {
fn from(item: RawBOOTPPacket) -> Self {
Self {
op: item.op.try_into().unwrap(),
hops: item.hops,
xid: item.xid,
secs: item.secs,
flags: item.flags,
ciaddr: if item.ciaddr == 0 {
None
} else {
Some(std::net::Ipv4Addr::from(item.ciaddr))
},
yiaddr: if item.yiaddr == 0 {
None
} else {
Some(std::net::Ipv4Addr::from(item.yiaddr))
},
siaddr: if item.siaddr == 0 {
None
} else {
Some(std::net::Ipv4Addr::from(item.siaddr))
},
giaddr: if item.giaddr == 0 {
None
} else {
Some(std::net::Ipv4Addr::from(item.giaddr))
},
chaddr: MacAddress::from_bytes(&item.chaddr[0..6]).unwrap(),
sname: item.sname,
file: item.file,
vend: item.vend,
}
}
}
impl BOOTPPacket {
/// Create a new BOOTP packet.
///
/// dhcprs encourages the use of higher level interfaces which are then
/// converted to lower level (packed) structures that can be serialised
/// in to bytes and then sent down a socket.
pub fn new(
op: OpCode,
hops: u8,
xid: u32,
secs: u16,
flags: u16,
ciaddr: Option<std::net::Ipv4Addr>,
yiaddr: Option<std::net::Ipv4Addr>,
siaddr: Option<std::net::Ipv4Addr>,
giaddr: Option<std::net::Ipv4Addr>,
chaddr: MacAddress,
sname: [u8; 64],
file: [u8; 128],
vend: [u8; 312],
) -> Self {
Self {
op: op,
hops: hops,
xid: xid,
secs: secs,
flags: flags,
ciaddr: ciaddr,
yiaddr: yiaddr,
siaddr: siaddr,
giaddr: giaddr,
chaddr: chaddr,
sname: sname,
file: file,
vend: vend,
}
}
pub fn get_vend(&self) -> &[u8] {
return &self.vend;
}
}

1809
src/dhcp.rs Normal file

File diff suppressed because it is too large Load Diff

15
src/lib.rs Normal file
View File

@ -0,0 +1,15 @@
//! DHCP/BOOTP packet encode/decode library.
//!
//! The purpose of this library is to provide an easy to use interface over
//! DHCP, BOOTP and UDP packets to enable the creation of programs that
//! make use of DHCP.
//!
//! BOOTP specific functionality is provided by the `dhcprs::bootp` module
//! DHCP specific functionality is provided by the `dhcprs::dhcp` module
//!
//! There is additionally simple UDP packet encode/decode provided by the
//! `dhcprs::udpbuilder`.
pub mod bootp;
pub mod dhcp;
pub mod udpbuilder;

175
src/udpbuilder.rs Normal file
View File

@ -0,0 +1,175 @@
#[allow(dead_code)]
#[repr(packed)]
pub struct RawUDPPacket {
// IP Header
version_ihl: u8, // First 4 bits: Version (always 4), next 4 bits: Internet Header Length (Header size in u32s)
dscp_ecn: u8, // Differentiated Services Code Point / Explicit Congestion Notification, zero for our purposes
total_length: u16, // Total length of the IP header + UDP header + data
identification: u16, // Identification (for fragmentation purposes)
flags_fragment_offset: u16, // Flags and the fragment offset (0x4000 for DF)
ttl: u8, // Time to live
protocol: u8, // Proto (UDP is 0x11 / dec 17)
ip_checksum: u16, // Ones' complement of the ones' complement sum of all 16 bit words of the header
source_addr: u32, // Source address
dest_addr: u32, // Destination address
// UDP Header
source_port: u16, // Source port
dest_port: u16, // Destination port
length: u16, // Length of UDP Header + data
udp_checksum: u16, // Ones' complement of the ones' complement sum of all 16 bit words of the pseudoheader + udp header + data
// UDP Data
data: [u8; 546], // 546 byte array to store a RawBOOTPPacket
}
impl RawUDPPacket {
pub fn as_bytes(&self) -> &[u8] {
unsafe { std::slice::from_raw_parts(self as *const Self as *const u8, 574) }
}
}
impl From<UDPPacket> for RawUDPPacket {
fn from(item: UDPPacket) -> Self {
let ph: PseudoHeader = item.clone().into();
let udp_checksum = ph.checksum();
let source_addr: u32 = item.source_addr.into();
let dest_addr: u32 = item.dest_addr.into();
let mut packet = Self {
version_ihl: 0x45, // Version 4, IHL of 5 (20 bytes)
dscp_ecn: 0, // No need for either DSCP or ECN
total_length: 574_u16.to_be(), // Will always be 576 bytes
identification: 0x1337, // No need (no fragmentation)
flags_fragment_offset: 0,
ttl: 64, // Set TTL to 255 because why not
protocol: 17, // UDP
ip_checksum: 0, // Zero for now, will be set later
source_addr: source_addr.to_be(),
dest_addr: dest_addr.to_be(),
source_port: item.source_port.to_be(),
dest_port: item.dest_port.to_be(),
length: ((item.data.len() + 8) as u16).to_be(),
udp_checksum: udp_checksum,
data: item.data,
};
let header_u16 =
unsafe { std::slice::from_raw_parts(&packet as *const Self as *const u16, 10) };
let mut total: u16 = 0;
for &word in header_u16 {
let (value, carry) = total.overflowing_add(word);
if carry {
total = value + 1;
} else {
total = value;
}
}
packet.ip_checksum = !total;
return packet;
}
}
#[allow(dead_code)]
#[repr(packed)]
struct PseudoHeader {
source_addr: u32,
dest_addr: u32,
zero: u8,
protocol: u8,
udp_length: u16,
source_port: u16,
dest_port: u16,
udp_length2: u16,
udp_checksum: u16,
data: [u8; 546],
}
impl From<UDPPacket> for PseudoHeader {
fn from(item: UDPPacket) -> Self {
let source_addr: u32 = item.source_addr.into();
let dest_addr: u32 = item.dest_addr.into();
Self {
source_addr: source_addr.to_be(),
dest_addr: dest_addr.to_be(),
zero: 0,
protocol: 17,
udp_length: ((item.data.len() + 8) as u16).to_be(),
source_port: item.source_port.to_be(),
dest_port: item.dest_port.to_be(),
udp_length2: ((item.data.len() + 8) as u16).to_be(),
udp_checksum: 0,
data: item.data,
}
}
}
impl PseudoHeader {
pub fn checksum(&self) -> u16 {
let as_u16 = unsafe {
std::slice::from_raw_parts(
self as *const Self as *const u16,
(self.data.len() + 20) / 2,
)
};
let mut total: u16 = 0;
for &word in as_u16 {
let (value, carry) = total.overflowing_add(word);
if carry {
total = value + 1;
} else {
total = value;
}
}
!total
}
}
#[derive(Clone)]
pub struct UDPPacket {
pub source_addr: std::net::Ipv4Addr,
pub dest_addr: std::net::Ipv4Addr,
pub source_port: u16,
pub dest_port: u16,
data: [u8; 546],
}
impl From<RawUDPPacket> for UDPPacket {
fn from(item: RawUDPPacket) -> Self {
Self {
source_addr: std::net::Ipv4Addr::from(item.source_addr.to_be_bytes()),
dest_addr: std::net::Ipv4Addr::from(item.source_addr.to_be_bytes()),
source_port: u16::from_be(item.source_port),
dest_port: u16::from_be(item.dest_port),
data: item.data,
}
}
}
impl UDPPacket {
/// Creates a new UDPPacket
///
/// Should be converted to a `dhcprs::udpbuilder::RawUDPPacket` to send.
pub fn new(
source_addr: std::net::Ipv4Addr,
dest_addr: std::net::Ipv4Addr,
source_port: u16,
dest_port: u16,
data: [u8; 546],
) -> Self {
Self {
source_addr: source_addr,
dest_addr: dest_addr,
source_port: source_port,
dest_port: dest_port,
data: data,
}
}
/// Returns a reference to the payload of the UDP packet.
pub fn get_data(&self) -> &[u8] {
return &self.data;
}
}