Skip to content

Commit

Permalink
Refactor ARP code and add more tests
Browse files Browse the repository at this point in the history
  • Loading branch information
internetionals committed Mar 2, 2021
1 parent f5b795c commit 2ccaf6e
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 92 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "claim-ip"
version = "1.0.3"
version = "1.0.4"
authors = ["Justin Ossevoort <[email protected]>"]
description = "Respond to ARP replies for a some address on a network interface"
repository = "https://github.com/internetionals/claim-ip"
Expand Down
155 changes: 66 additions & 89 deletions src/arp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,110 +2,79 @@ use crate::types::Eui48Addr;
use std::convert::{TryFrom, TryInto};
use std::net::Ipv4Addr;

#[derive(Debug,Clone,Copy,Eq,PartialEq)]
pub enum ArpOp {
Request,
Reply,
}

#[derive(Debug, PartialEq, Eq)]
pub struct ArpRequest {
pub struct Arp {
pub op: ArpOp,
pub sha: Eui48Addr,
pub spa: Ipv4Addr,
pub tha: Eui48Addr,
pub tpa: Ipv4Addr,
}

impl ArpRequest {
pub fn new(sha: Eui48Addr, spa: Ipv4Addr, tha: Eui48Addr, tpa: Ipv4Addr) -> Self {
Self { tha, tpa, sha, spa }
}

pub fn reply<'a>(&'a self, ha: Eui48Addr) -> ArpReply {
ArpReply {
impl Arp {
pub fn reply<'a>(&'a self, ha: Eui48Addr) -> Result<Self, ()> {
if self.op != ArpOp::Request {
return Err(());
}
Ok(Self {
op: ArpOp::Reply,
sha: ha,
spa: self.tpa,
tha: self.sha,
tpa: self.spa,
}
})
}

pub fn fill<'a, 'b>(&'a self, buf: &'b mut [u8]) -> Option<&'b [u8]> {
pub fn fill<'a, 'b>(&'a self, buf: &'b mut [u8]) -> Result<&'b [u8], ()> {
if buf.len() < 28 {
return None;
}
buf[0..=7].copy_from_slice(&[0x08, 0x06, 0x08, 0x00, 0x06, 0x04, 0x00, 0x01]);
buf[8..=13].copy_from_slice(&self.tha);
buf[14..=17].copy_from_slice(&self.tpa.octets());
buf[18..=23].copy_from_slice(&self.sha);
buf[24..=27].copy_from_slice(&self.tpa.octets());
Some(&buf[0..=27])
}
}

#[derive(Debug, PartialEq, Eq)]
pub struct ArpReply {
pub sha: Eui48Addr,
pub spa: Ipv4Addr,
pub tha: Eui48Addr,
pub tpa: Ipv4Addr,
}

impl ArpReply {
pub fn new(sha: Eui48Addr, spa: Ipv4Addr, tha: Eui48Addr, tpa: Ipv4Addr) -> Self {
Self { tha, tpa, sha, spa }
}

pub fn fill<'a, 'b>(&'a self, buf: &'b mut [u8]) -> Option<&'b [u8]> {
if buf.len() < 28 {
return None;
return Err(());
}
buf[0..=7].copy_from_slice(&[0x08, 0x06, 0x08, 0x00, 0x06, 0x04, 0x00, 0x02]);
buf[8..=13].copy_from_slice(&self.tha);
buf[14..=17].copy_from_slice(&self.tpa.octets());
buf[18..=23].copy_from_slice(&self.sha);
buf[0..=6].copy_from_slice(&[0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00 ]);
buf[7] = match self.op {
ArpOp::Request => 1,
ArpOp::Reply => 2,
};
buf[8..=13].copy_from_slice(&self.sha);
buf[14..=17].copy_from_slice(&self.spa.octets());
buf[18..=23].copy_from_slice(&self.tha);
buf[24..=27].copy_from_slice(&self.tpa.octets());
Some(&buf[0..=27])
Ok(&buf[0..=27])
}
}

#[derive(Debug, PartialEq, Eq)]
pub enum Arp {
Request(ArpRequest),
Reply(ArpReply),
}

impl TryFrom<&'_ [u8]> for Arp {
type Error = ();

fn try_from(pkt: &'_ [u8]) -> Result<Arp, Self::Error> {
fn try_from(pkt: &'_ [u8]) -> Result<Self, Self::Error> {
if pkt.len() < 28 {
return Err(());
Err(())?;
}
if !pkt.starts_with(&[0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00]) {
return Err(());
}
match pkt[7] {
1 => Ok(Arp::Request(ArpRequest {
sha: pkt[8..=13].try_into().unwrap(),
spa: {
let bytes: [u8; 4] = pkt[14..=17].try_into().unwrap();
bytes.into()
},
tha: pkt[18..=23].try_into().unwrap(),
tpa: {
let bytes: [u8; 4] = pkt[24..=27].try_into().unwrap();
bytes.into()
},
})),
2 => Ok(Arp::Reply(ArpReply {
sha: pkt[8..=13].try_into().unwrap(),
spa: {
let bytes: [u8; 4] = pkt[14..=17].try_into().unwrap();
bytes.into()
},
tha: pkt[18..=23].try_into().unwrap(),
tpa: {
let bytes: [u8; 4] = pkt[24..=27].try_into().unwrap();
bytes.into()
},
})),
_ => Err(()),
Err(())?;
}
Ok(Self {
op: match pkt[7] {
1 => ArpOp::Request,
2 => ArpOp::Reply,
_ => Err(())?,
},
sha: pkt[8..=13].try_into().map_err(|_| ())?,
spa: {
let bytes: [u8; 4] = pkt[14..=17].try_into().map_err(|_| ())?;
bytes.into()
},
tha: pkt[18..=23].try_into().map_err(|_| ())?,
tpa: {
let bytes: [u8; 4] = pkt[24..=27].try_into().map_err(|_| ())?;
bytes.into()
},
})
}
}

Expand All @@ -116,36 +85,44 @@ mod tests {

#[test]
fn t1() {
let pkt: [u8; 28] = [
let request_pkt: [u8; 28] = [
0x00, 0x01, 0x08, 0x00, 6, 4, 0, 1, // arp header
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 10, 0, 0, 1, // sender
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 10, 0, 0, 2, // target
];
let arp: Arp = pkt.as_ref().try_into().unwrap();
let request: Arp = request_pkt.as_ref().try_into().unwrap();
assert_eq!(
arp,
Arp::Request(ArpRequest {
request,
Arp {
op: ArpOp::Request,
sha: [0x11, 0x22, 0x33, 0x44, 0x55, 0x66],
spa: "10.0.0.1".parse().unwrap(),
tha: [0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
tpa: "10.0.0.2".parse().unwrap(),
})
}
);

let mut buf = [0u8; 128];
assert_eq!(request.fill(&mut buf[..]), Ok(&request_pkt[..]));

let mac = [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff];
let reply = match arp {
Arp::Request(r) => r,
_ => panic!(),
};
let reply = reply.reply(mac);
let reply = request.reply(mac).expect("ARP reply");
assert_eq!(
reply,
ArpReply {
Arp {
op: ArpOp::Reply,
sha: [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff],
spa: "10.0.0.2".parse().unwrap(),
tha: [0x11, 0x22, 0x33, 0x44, 0x55, 0x66],
tpa: "10.0.0.1".parse().unwrap(),
}
);

let reply_pkt: [u8; 28] = [
0x00, 0x01, 0x08, 0x00, 6, 4, 0, 2, // arp header
0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 10, 0, 0, 2, // sender
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 10, 0, 0, 1, // target
];
assert_eq!(reply.fill(&mut buf[..]), Ok(&reply_pkt[..]));
}
}
3 changes: 2 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ fn main() {
log::trace!("received packet from {:?}: {:x?}", from, pkt);

match arp::Arp::try_from(pkt) {
Ok(arp::Arp::Request(req)) => {
Ok(req) if req.op == arp::ArpOp::Request => {
log::trace!("received arp request: {:x?}", req);
if from.addr() != req.sha {
log::warn!(
Expand All @@ -96,6 +96,7 @@ fn main() {
if let Err(err) = sendto(
socket,
req.reply(mac)
.expect("ARP reply")
.fill(&mut wbuf)
.expect("failed to construct reply packet"),
&SockAddr::Link(from),
Expand Down

0 comments on commit 2ccaf6e

Please sign in to comment.