Initial commit.
This commit is contained in:
commit
0aa92d8021
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
113
Cargo.lock
generated
Normal file
113
Cargo.lock
generated
Normal 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
16
Cargo.toml
Normal 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
19
LICENSE
Normal 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
11
README.md
Normal 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
192
src/bootp.rs
Normal 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
1809
src/dhcp.rs
Normal file
File diff suppressed because it is too large
Load Diff
15
src/lib.rs
Normal file
15
src/lib.rs
Normal 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
175
src/udpbuilder.rs
Normal 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;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user