baea0e523b
CRITICAL BUGS FIXED: 1. router/mod.rs: ICMP errors for Unreachable/Prohibit went to rx_buffer (infinite loop back to input) instead of tx_buffer (sent to source). Combined Unreachable/Prohibit arms; both now use tx_buffer. 2. scheme/socket.rs dup(): refcount leak in update_with branch. OLD code: new_handle.socket_handle() got +2 when update_with was Some, but only decremented once on close. Net: SH1 over-counted (+1), SH2 (new listening socket from update_with) never tracked at all. FIX: in update_with branch, increment refcount of 'socket_handle' (the new SH), not new_handle.socket_handle(). The always-run increment at the bottom covers new_handle. Both increments serve different purposes and are now distinct. 3. scheme/udp.rs: 4 .expect() panic vectors in bind/send/recv. 'Can't bind', 'Can't send', 'Can't receive', 'Can't recieve' all panicked the daemon. Now return EIO via ? operator. 4. link/vlan.rs (and vxlan/gre/ipip already partially fixed in R42): send() pushed tagged packets into self.recv_queue, creating a self-loop where packets were never delivered. Now drops packets with a debug log since no parent device reference exists. 5. link/qdisc.rs: TokenBucket token_add could overflow u64 on long elapsed durations. Changed to saturating_mul. DOC FIX: 6. filter/table.rs docstring example used --sport 1024:65535 (port range) but parse_port only accepts single port. Changed example to use single port value. Range support is a future enhancement. All 29 existing tests still pass.
212 lines
6.8 KiB
Rust
212 lines
6.8 KiB
Rust
//! VXLAN (Virtual eXtensible LAN) — mirrors Linux 7.1's `drivers/net/vxlan/`.
|
|
//!
|
|
//! Reference files:
|
|
//! - `drivers/net/vxlan/vxlan_core.c:1855` — `vxlan_xmit()` — encapsulation
|
|
//! - `drivers/net/vxlan/vxlan_core.c:1366` — `vxlan_rcv()` — decapsulation
|
|
//! - `include/net/vxlan.h` — VXLAN header format, VNI, default port 4789
|
|
//!
|
|
//! VXLAN encapsulates L2 Ethernet frames in UDP for overlay networking.
|
|
//! Each overlay network is identified by a 24-bit VNI (Virtual Network
|
|
//! Identifier). The default UDP destination port is 4789 (IANA-assigned).
|
|
//!
|
|
//! Packet structure:
|
|
//! [Outer Eth][Outer IP][UDP:4789][VXLAN 8B][Inner Eth][Inner IP][Payload]
|
|
|
|
use std::collections::VecDeque;
|
|
use std::rc::Rc;
|
|
|
|
use smoltcp::time::Instant;
|
|
use smoltcp::wire::{
|
|
EthernetAddress, EthernetFrame, EthernetProtocol, EthernetRepr, IpAddress, IpCidr,
|
|
Ipv4Address, Ipv4Packet, Ipv4Repr, IpProtocol, UdpPacket, UdpRepr,
|
|
};
|
|
|
|
use super::LinkDevice;
|
|
|
|
const VXLAN_PORT: u16 = 4789;
|
|
const VXLAN_FLAGS: u8 = 0x08;
|
|
|
|
pub struct VxlanDevice {
|
|
name: Rc<str>,
|
|
parent_name: Rc<str>,
|
|
local_ip: Ipv4Address,
|
|
remote_ip: Ipv4Address,
|
|
vni: u32,
|
|
vxlan_header: [u8; 8],
|
|
send_buffer: Vec<u8>,
|
|
recv_buffer: Vec<u8>,
|
|
recv_queue: VecDeque<Vec<u8>>,
|
|
virtual_mac: EthernetAddress,
|
|
ip_address: Option<IpCidr>,
|
|
}
|
|
|
|
impl VxlanDevice {
|
|
pub fn new(
|
|
name: &str,
|
|
parent_name: &str,
|
|
local_ip: Ipv4Address,
|
|
remote_ip: Ipv4Address,
|
|
vni: u32,
|
|
) -> Self {
|
|
let vni_bytes = vni.to_be_bytes();
|
|
Self {
|
|
name: name.into(),
|
|
parent_name: parent_name.into(),
|
|
local_ip,
|
|
remote_ip,
|
|
vni: vni & 0x00ffffff,
|
|
vxlan_header: [
|
|
VXLAN_FLAGS, 0, 0, 0,
|
|
vni_bytes[1], vni_bytes[2], vni_bytes[3], 0,
|
|
],
|
|
send_buffer: Vec::with_capacity(1550),
|
|
recv_buffer: Vec::with_capacity(1550),
|
|
recv_queue: VecDeque::new(),
|
|
virtual_mac: EthernetAddress([0x00, 0x00, 0x5e, 0x00, 0x01, 0x01]),
|
|
ip_address: None,
|
|
}
|
|
}
|
|
|
|
pub fn push_received(&mut self, packet: Vec<u8>) {
|
|
self.recv_queue.push_back(packet);
|
|
}
|
|
|
|
fn build_encapsulated(&mut self, inner_packet: &[u8]) -> &[u8] {
|
|
self.send_buffer.clear();
|
|
let inner_eth = {
|
|
let mut buf = [0u8; 14];
|
|
buf[..6].copy_from_slice(&EthernetAddress::BROADCAST.0);
|
|
buf[6..12].copy_from_slice(&self.virtual_mac.0);
|
|
let ethtype = if !inner_packet.is_empty() && inner_packet[0] >> 4 == 6 {
|
|
EthernetProtocol::Ipv6
|
|
} else {
|
|
EthernetProtocol::Ipv4
|
|
};
|
|
let proto_bytes: [u8; 2] = match ethtype {
|
|
EthernetProtocol::Ipv4 => [0x08, 0x00],
|
|
EthernetProtocol::Ipv6 => [0x86, 0xDD],
|
|
_ => [0x08, 0x00],
|
|
};
|
|
buf[12..14].copy_from_slice(&proto_bytes);
|
|
buf
|
|
};
|
|
let inner_frame_len = inner_eth.len() + inner_packet.len();
|
|
|
|
let udp_repr = UdpRepr {
|
|
src_port: VXLAN_PORT,
|
|
dst_port: VXLAN_PORT,
|
|
};
|
|
let udp_payload_len = 8 + inner_frame_len;
|
|
let outer_ip_repr = Ipv4Repr {
|
|
src_addr: self.local_ip,
|
|
dst_addr: self.remote_ip,
|
|
next_header: IpProtocol::Udp,
|
|
payload_len: 8 + udp_payload_len,
|
|
hop_limit: 64,
|
|
};
|
|
|
|
let total_len = outer_ip_repr.buffer_len() + 8 + 8 + inner_frame_len;
|
|
self.send_buffer.resize(total_len, 0);
|
|
let mut ip = Ipv4Packet::new_unchecked(&mut self.send_buffer);
|
|
outer_ip_repr.emit(&mut ip, &smoltcp::phy::ChecksumCapabilities::ignored());
|
|
|
|
let ip_hdr_len = outer_ip_repr.buffer_len();
|
|
let mut udp = UdpPacket::new_unchecked(&mut self.send_buffer[ip_hdr_len..]);
|
|
udp_repr.emit(
|
|
&mut udp,
|
|
&IpAddress::Ipv4(self.local_ip),
|
|
&IpAddress::Ipv4(self.remote_ip),
|
|
udp_payload_len,
|
|
|buf| {
|
|
buf[..8].copy_from_slice(&self.vxlan_header);
|
|
buf[8..8 + inner_eth.len()].copy_from_slice(&inner_eth);
|
|
buf[8 + inner_eth.len()..].copy_from_slice(inner_packet);
|
|
},
|
|
&smoltcp::phy::ChecksumCapabilities::ignored(),
|
|
);
|
|
&self.send_buffer
|
|
}
|
|
|
|
fn matches_endpoint(&self, outer_packet: &[u8]) -> bool {
|
|
if outer_packet.len() < 50 {
|
|
return false;
|
|
}
|
|
let Ok(ipv4) = Ipv4Packet::new_checked(outer_packet) else {
|
|
return false;
|
|
};
|
|
if u8::from(ipv4.next_header()) != 17 || ipv4.dst_addr() != self.local_ip {
|
|
return false;
|
|
}
|
|
let ip_hdr_len = 20;
|
|
let udp = &outer_packet[ip_hdr_len..];
|
|
if udp.len() < 18 {
|
|
return false;
|
|
}
|
|
let dst_port = u16::from_be_bytes([udp[2], udp[3]]);
|
|
if dst_port != VXLAN_PORT {
|
|
return false;
|
|
}
|
|
if udp[8] != VXLAN_FLAGS {
|
|
return false;
|
|
}
|
|
let pkt_vni = u32::from_be_bytes([0, udp[12], udp[13], udp[14]]) & 0x00ffffff;
|
|
pkt_vni == self.vni
|
|
}
|
|
|
|
fn decapsulate(&mut self, outer_packet: &[u8]) -> Option<&[u8]> {
|
|
if !self.matches_endpoint(outer_packet) {
|
|
return None;
|
|
}
|
|
let inner_frame_start = 20 + 8 + 8;
|
|
let inner_frame = &outer_packet[inner_frame_start..];
|
|
if inner_frame.len() < 14 {
|
|
return None;
|
|
}
|
|
let eth = EthernetFrame::new_unchecked(inner_frame);
|
|
let Ok(repr) = EthernetRepr::parse(ð) else {
|
|
return None;
|
|
};
|
|
if repr.ethertype != EthernetProtocol::Ipv4 && repr.ethertype != EthernetProtocol::Ipv6 {
|
|
return None;
|
|
}
|
|
self.recv_buffer.clear();
|
|
self.recv_buffer.extend_from_slice(eth.payload());
|
|
Some(&self.recv_buffer)
|
|
}
|
|
}
|
|
|
|
impl LinkDevice for VxlanDevice {
|
|
fn send(&mut self, _next_hop: IpAddress, packet: &[u8], _now: Instant) {
|
|
// No parent device reference — drop rather than self-loop.
|
|
log::debug!("vxlan: dropping {} byte frame (no parent device)", packet.len());
|
|
}
|
|
|
|
fn recv(&mut self, _now: Instant) -> Option<&[u8]> {
|
|
let packet = self.recv_queue.pop_front()?;
|
|
self.decapsulate(&packet)
|
|
}
|
|
|
|
fn name(&self) -> &Rc<str> {
|
|
&self.name
|
|
}
|
|
|
|
fn can_recv(&self) -> bool {
|
|
!self.recv_queue.is_empty()
|
|
}
|
|
|
|
fn mac_address(&self) -> Option<EthernetAddress> {
|
|
Some(self.virtual_mac)
|
|
}
|
|
|
|
fn set_mac_address(&mut self, addr: EthernetAddress) {
|
|
self.virtual_mac = addr;
|
|
}
|
|
|
|
fn ip_address(&self) -> Option<IpCidr> {
|
|
self.ip_address
|
|
}
|
|
|
|
fn set_ip_address(&mut self, addr: IpCidr) {
|
|
self.ip_address = Some(addr);
|
|
}
|
|
} |