use crate::arp::{self, Pending as ArpPending, Table as ArpTable};
use crate::ndp::{self, Table as NdpTable};
use crate::{
build_frame, EtherType, Frame, IpPrefix, L2Device, L2Handler, L3Device, L3Handler, MacAddr,
Packet, Protocol, Result,
};
use std::collections::HashMap;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::sync::{Arc, Mutex, Weak};
#[derive(Default, Debug, Clone)]
pub struct L2AdapterConfig {
pub mac: Option<MacAddr>,
pub gateway_v4: Option<Ipv4Addr>,
pub gateway_v6: Option<Ipv6Addr>,
}
pub struct L2Adapter {
mac: MacAddr,
l3: Arc<dyn L3Device>,
l2_handler: Mutex<Option<L2Handler>>,
gateway_v4: Mutex<Option<Ipv4Addr>>,
gateway_v6: Mutex<Option<Ipv6Addr>>,
arp: ArpTable,
arp_pending: ArpPending,
ndp: NdpTable,
ndp_pending: Mutex<HashMap<Ipv6Addr, Vec<Vec<u8>>>>,
#[cfg(feature = "dhcp")]
dhcp: Mutex<Option<Arc<crate::dhcp::Client>>>,
weak_self: Mutex<Weak<L2Adapter>>,
}
impl core::fmt::Debug for L2Adapter {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("L2Adapter").field("mac", &self.mac).finish()
}
}
impl L2Adapter {
pub fn new<D>(dev: D, cfg: L2AdapterConfig) -> Arc<L2Adapter>
where
D: L3Device + 'static,
{
Self::new_arc(Arc::new(dev), cfg)
}
pub fn new_arc(dev: Arc<dyn L3Device>, cfg: L2AdapterConfig) -> Arc<L2Adapter> {
let mac = cfg.mac.unwrap_or_else(MacAddr::random_local_unicast);
let a = Arc::new(L2Adapter {
mac,
l3: dev.clone(),
l2_handler: Mutex::new(None),
gateway_v4: Mutex::new(cfg.gateway_v4),
gateway_v6: Mutex::new(cfg.gateway_v6),
arp: ArpTable::new(),
arp_pending: ArpPending::new(),
ndp: NdpTable::new(),
ndp_pending: Mutex::new(HashMap::new()),
#[cfg(feature = "dhcp")]
dhcp: Mutex::new(None),
weak_self: Mutex::new(Weak::new()),
});
*a.weak_self.lock().unwrap() = Arc::downgrade(&a);
let weak = Arc::downgrade(&a);
let h: L3Handler = Arc::new(move |p: &Packet| {
if let Some(a) = weak.upgrade() {
a.handle_outgoing(p);
}
Ok(())
});
dev.set_handler(h);
a
}
pub fn hw_addr(&self) -> MacAddr {
self.mac
}
pub fn set_gateway_v4(&self, gw: Ipv4Addr) {
*self.gateway_v4.lock().unwrap() = Some(gw);
}
pub fn set_gateway_v6(&self, gw: Ipv6Addr) {
*self.gateway_v6.lock().unwrap() = Some(gw);
}
#[cfg(feature = "dhcp")]
pub fn start_dhcp(self: &Arc<Self>) {
let weak = Arc::downgrade(self);
let transport = AdapterDhcpTransport { weak };
let client = Arc::new(crate::dhcp::Client::new(
transport,
crate::dhcp::ClientConfig {
mac: Some(self.mac),
},
));
*self.dhcp.lock().unwrap() = Some(client.clone());
client.start();
}
#[cfg(feature = "dhcp")]
pub fn stop_dhcp(&self) {
if let Some(c) = self.dhcp.lock().unwrap().take() {
c.stop();
}
}
fn send_l2(&self, f: &Frame) {
let h = self.l2_handler.lock().unwrap().clone();
if let Some(h) = h {
let _ = h(f);
}
}
fn handle_incoming(&self, f: &Frame) {
if !f.is_valid() {
return;
}
let dst = f.dst_mac();
if !f.is_broadcast() && !f.is_multicast() && dst != Some(self.mac) {
return;
}
match f.ether_type() {
EtherType::ARP => self.handle_arp(f),
EtherType::IPV4 | EtherType::IPV6 => {
let payload = f.payload();
if payload.is_empty() {
return;
}
let pkt = Packet::from_slice(payload);
if !pkt.is_valid() {
return;
}
#[cfg(feature = "dhcp")]
if pkt.version() == 4 && pkt.ipv4_protocol() == Protocol::UDP {
let udp = pkt.ipv4_payload();
if udp.len() >= 8 {
let dport = u16::from_be_bytes([udp[2], udp[3]]);
if dport == 68 {
let dhcp = self.dhcp.lock().unwrap().clone();
if let Some(c) = dhcp {
if c.is_active() {
c.handle_packet(&udp[8..]);
return;
}
}
}
}
}
if pkt.version() == 6
&& pkt.ipv6_next_header() == Protocol::ICMPV6
&& pkt.ipv6_hop_limit() == 255
&& self.handle_ndp(pkt)
{
return;
}
let _ = self.l3.send(pkt);
}
_ => {}
}
}
fn handle_outgoing(&self, pkt: &Packet) {
if !pkt.is_valid() {
return;
}
let (dst_mac, ether_type) = match pkt.version() {
4 => {
if pkt.is_broadcast() {
(MacAddr::broadcast(), EtherType::IPV4)
} else if pkt.is_multicast() {
let d = pkt.ipv4_dst_addr().unwrap().octets();
(
MacAddr([0x01, 0x00, 0x5e, d[1] & 0x7f, d[2], d[3]]),
EtherType::IPV4,
)
} else {
let dst = pkt.ipv4_dst_addr().unwrap();
let prefix = self.l3.addr();
let target =
if prefix.is_valid() && prefix.is_v4() && !prefix.contains(IpAddr::V4(dst))
{
self.gateway_v4.lock().unwrap().unwrap_or(dst)
} else {
dst
};
match self.arp.lookup(target) {
Some(m) => (m, EtherType::IPV4),
None => {
let first = self.arp_pending.enqueue(target, pkt.as_bytes());
if first {
self.send_arp_request(target);
}
return;
}
}
}
}
6 => {
if pkt.is_multicast() {
let d = pkt.ipv6_dst_addr().unwrap().octets();
(
MacAddr([0x33, 0x33, d[12], d[13], d[14], d[15]]),
EtherType::IPV6,
)
} else {
let dst = pkt.ipv6_dst_addr().unwrap();
let prefix = self.l3.addr();
let target =
if prefix.is_valid() && prefix.is_v6() && !prefix.contains(IpAddr::V6(dst))
{
self.gateway_v6.lock().unwrap().unwrap_or(dst)
} else {
dst
};
match self.ndp.lookup(target) {
Some(m) => (m, EtherType::IPV6),
None => {
let mut pending = self.ndp_pending.lock().unwrap();
let entry = pending.entry(target).or_default();
let first = entry.is_empty();
entry.push(pkt.as_bytes().to_vec());
drop(pending);
if first {
self.send_neighbor_solicitation(target);
}
return;
}
}
}
}
_ => return,
};
let frame = build_frame(dst_mac, self.mac, ether_type, pkt.as_bytes());
self.send_l2(Frame::from_slice(&frame));
}
fn handle_arp(&self, f: &Frame) {
let p = match arp::parse(f.payload()) {
Some(p) => p,
None => return,
};
let (op, sender_mac, sender_ip, _, target_ip) = p;
self.arp.set(sender_ip, sender_mac, arp::DEFAULT_TTL);
for buf in self.arp_pending.drain(sender_ip) {
self.handle_outgoing(Packet::from_slice(&buf));
}
let our_addr = match self.l3.addr().addr() {
IpAddr::V4(a) => a,
_ => return,
};
if op == arp::OP_REQUEST && target_ip == our_addr {
let payload =
arp::build_packet(arp::OP_REPLY, self.mac, our_addr, sender_mac, sender_ip);
let frame = build_frame(sender_mac, self.mac, EtherType::ARP, &payload);
self.send_l2(Frame::from_slice(&frame));
}
}
fn send_arp_request(&self, target: Ipv4Addr) {
let our_addr = match self.l3.addr().addr() {
IpAddr::V4(a) => a,
_ => Ipv4Addr::UNSPECIFIED,
};
let payload =
arp::build_packet(arp::OP_REQUEST, self.mac, our_addr, MacAddr::zero(), target);
let frame = build_frame(MacAddr::broadcast(), self.mac, EtherType::ARP, &payload);
self.send_l2(Frame::from_slice(&frame));
}
fn handle_ndp(&self, pkt: &Packet) -> bool {
let icmp = pkt.ipv6_payload();
if icmp.len() < 4 {
return false;
}
let src = match pkt.ipv6_src_addr() {
Some(a) => a,
None => return false,
};
match icmp[0] {
ndp::NS_TYPE => {
if icmp.len() < 24 {
return false;
}
let mut t = [0u8; 16];
t.copy_from_slice(&icmp[8..24]);
let target = Ipv6Addr::from(t);
if !src.is_unspecified() {
if let Some(src_mac) = ndp::parse_option(&icmp[24..], ndp::OPT_SOURCE_LINK_ADDR)
{
self.ndp.set(src, src_mac, ndp::DEFAULT_TTL);
}
}
let ll = ndp::link_local_from_mac(self.mac);
let dev_addr = match self.l3.addr().addr() {
IpAddr::V6(a) => Some(a),
_ => None,
};
if target == ll || dev_addr == Some(target) {
if src.is_unspecified() {
let all_nodes: Ipv6Addr = "ff02::1".parse().unwrap();
self.send_neighbor_advertisement(all_nodes, target, false);
} else {
self.send_neighbor_advertisement(src, target, true);
}
}
true
}
ndp::NA_TYPE => {
if icmp.len() < 24 {
return false;
}
let mut t = [0u8; 16];
t.copy_from_slice(&icmp[8..24]);
let target = Ipv6Addr::from(t);
if let Some(target_mac) = ndp::parse_option(&icmp[24..], ndp::OPT_TARGET_LINK_ADDR)
{
self.ndp.set(target, target_mac, ndp::DEFAULT_TTL);
if let Some(pending) = self.ndp_pending.lock().unwrap().remove(&target) {
for buf in pending {
self.handle_outgoing(Packet::from_slice(&buf));
}
}
}
true
}
ndp::RS_TYPE | ndp::RA_TYPE => false, _ => false,
}
}
fn send_neighbor_solicitation(&self, target: Ipv6Addr) {
let src = ndp::link_local_from_mac(self.mac);
let dst = ndp::solicited_node_multicast(target);
let dst_mac = ndp::solicited_node_mac(target);
let mut payload = ndp::build_ns(self.mac, target);
let ip = ndp::wrap_icmpv6(src, dst, &mut payload);
let frame = build_frame(dst_mac, self.mac, EtherType::IPV6, &ip);
self.send_l2(Frame::from_slice(&frame));
}
fn send_neighbor_advertisement(&self, dst_addr: Ipv6Addr, target: Ipv6Addr, solicited: bool) {
let src = target;
let dst_mac = if let Some(m) = self.ndp.lookup(dst_addr) {
m
} else {
let d = dst_addr.octets();
MacAddr([0x33, 0x33, d[12], d[13], d[14], d[15]])
};
let mut payload = ndp::build_na(self.mac, target, solicited);
let ip = ndp::wrap_icmpv6(src, dst_addr, &mut payload);
let frame = build_frame(dst_mac, self.mac, EtherType::IPV6, &ip);
self.send_l2(Frame::from_slice(&frame));
}
}
impl L2Device for L2Adapter {
fn set_handler(&self, h: L2Handler) {
*self.l2_handler.lock().unwrap() = Some(h);
}
fn send(&self, f: &Frame) -> Result<()> {
self.handle_incoming(f);
Ok(())
}
fn hw_addr(&self) -> MacAddr {
self.mac
}
fn close(&self) -> Result<()> {
#[cfg(feature = "dhcp")]
self.stop_dhcp();
Ok(())
}
}
#[cfg(feature = "dhcp")]
struct AdapterDhcpTransport {
weak: Weak<L2Adapter>,
}
#[cfg(feature = "dhcp")]
impl crate::dhcp::ClientTransport for AdapterDhcpTransport {
fn mac(&self) -> MacAddr {
self.weak
.upgrade()
.map(|a| a.mac)
.unwrap_or(MacAddr::zero())
}
fn send_broadcast(&self, frame: &Frame) {
if let Some(a) = self.weak.upgrade() {
a.send_l2(frame);
}
}
fn send_unicast(&self, dst_ip: Ipv4Addr, frame: &Frame) {
let Some(a) = self.weak.upgrade() else {
return;
};
let bytes = frame.as_bytes();
let mut buf = bytes.to_vec();
if let Some(mac) = a.arp.lookup(dst_ip) {
buf[0..6].copy_from_slice(&mac.octets());
}
a.send_l2(Frame::from_slice(&buf));
}
fn on_bound(&self, prefix: IpPrefix, gateway: Option<Ipv4Addr>) {
if let Some(a) = self.weak.upgrade() {
let _ = a.l3.set_addr(prefix);
if let Some(gw) = gateway {
a.set_gateway_v4(gw);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::PipeL3;
#[test]
fn arp_reply_on_request_for_our_ip() {
let pipe = Arc::new(PipeL3::new("10.0.0.5/24".parse().unwrap()));
let adapter = L2Adapter::new_arc(pipe, L2AdapterConfig::default());
let out = Arc::new(Mutex::new(Vec::<Vec<u8>>::new()));
let oc = out.clone();
adapter.set_handler(Arc::new(move |f: &Frame| {
oc.lock().unwrap().push(f.as_bytes().to_vec());
Ok(())
}));
let sender_mac = MacAddr([2, 0, 0, 0, 0, 1]);
let payload = arp::build_packet(
arp::OP_REQUEST,
sender_mac,
Ipv4Addr::new(10, 0, 0, 1),
MacAddr::zero(),
Ipv4Addr::new(10, 0, 0, 5),
);
let frame = build_frame(adapter.mac, sender_mac, EtherType::ARP, &payload);
adapter.send(Frame::from_slice(&frame)).unwrap();
let frames = out.lock().unwrap();
assert_eq!(frames.len(), 1);
let f = Frame::from_slice(&frames[0]);
assert_eq!(f.ether_type(), EtherType::ARP);
let (op, sm, si, _, _) = arp::parse(f.payload()).unwrap();
assert_eq!(op, arp::OP_REPLY);
assert_eq!(sm, adapter.mac);
assert_eq!(si, Ipv4Addr::new(10, 0, 0, 5));
}
#[test]
fn outgoing_ipv4_triggers_arp_request() {
let pipe = Arc::new(PipeL3::new("10.0.0.5/24".parse().unwrap()));
let adapter = L2Adapter::new_arc(pipe.clone(), L2AdapterConfig::default());
let out = Arc::new(Mutex::new(Vec::<Vec<u8>>::new()));
let oc = out.clone();
adapter.set_handler(Arc::new(move |f: &Frame| {
oc.lock().unwrap().push(f.as_bytes().to_vec());
Ok(())
}));
let mut p = vec![0u8; 20];
p[0] = 0x45;
p[2..4].copy_from_slice(&20u16.to_be_bytes());
p[12..16].copy_from_slice(&[10, 0, 0, 5]);
p[16..20].copy_from_slice(&[10, 0, 0, 6]);
pipe.inject(Packet::from_slice(&p)).unwrap();
let frames = out.lock().unwrap();
assert_eq!(frames.len(), 1);
assert_eq!(Frame::from_slice(&frames[0]).ether_type(), EtherType::ARP);
}
}