diff --git a/readme.md b/readme.md index b81bd4e..49e6ca9 100644 --- a/readme.md +++ b/readme.md @@ -5,7 +5,7 @@ To use vxlan in isp, we need to implement a VTEP for vault01. ## Roadmap 1. [x] Basic router which forward to every connected VTEP -> bad idea without mac learning -2. Special case for when we are the target (internet, DNS) +2. [x] Special case for when we are the target (internet, DNS) 3. [x] Mac learning 4. DHCP, which removes user prefixes, (comes with ARP snooping ?) 5. Reverse path checking, filter auto ip attribution diff --git a/src/complex_reply.rs b/src/complex_reply.rs new file mode 100644 index 0000000..706a7e9 --- /dev/null +++ b/src/complex_reply.rs @@ -0,0 +1,26 @@ +use pnet::packet::{ + arp::{ArpOperations, MutableArpPacket}, + ethernet::MutableEthernetPacket, + vxlan::MutableVxlanPacket, + MutablePacket, +}; +use std::sync::Arc; + +use crate::{global::Env, packet::PacketReading}; + +pub async fn reply_arp(env: Arc, reading: PacketReading, mut raw: Vec) -> Option<()> { + let mut vxlan = MutableVxlanPacket::new(&mut raw[..]).unwrap(); + let mut eth = MutableEthernetPacket::new(vxlan.payload_mut()).unwrap(); + eth.set_destination(eth.get_source()); + eth.set_source(env.gateway_mac); + let mut arp = MutableArpPacket::new(eth.payload_mut()).unwrap(); + arp.set_operation(ArpOperations::Reply); + arp.set_target_hw_addr(arp.get_sender_hw_addr()); + arp.set_sender_hw_addr(env.gateway_mac); + let target_addr = arp.get_target_proto_addr(); + arp.set_target_proto_addr(arp.get_sender_proto_addr()); + arp.set_sender_proto_addr(target_addr); + + env.send(&raw, reading.vtep).await; + Some(()) +} diff --git a/src/global.rs b/src/global.rs index 6a1f6cb..7f25668 100644 --- a/src/global.rs +++ b/src/global.rs @@ -1,13 +1,13 @@ use core::net::{IpAddr, SocketAddr}; use ipnet::IpNet; use pnet::util::MacAddr; +use std::sync::Arc; use tokio::net::UdpSocket; use crate::redirect::MacTable; pub struct Env { pub socket: UdpSocket, - pub used_vnis: (u32, u32), // pub local_nets: Vec, pub reserved_nets: Vec, // pub gateway: IpAddr, @@ -29,4 +29,7 @@ impl Env { Err(err) => eprintln!("Encountered {} while sending packet to {}", err, addr), } } + pub async fn mac_learn(self: Arc, mac: MacAddr, vni: u32, vtep: IpAddr) { + self.mac_table.learn(mac, vni, vtep).await + } } diff --git a/src/main.rs b/src/main.rs index 4db857d..7e744bd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ use pnet::util::MacAddr; use std::{io, str::FromStr, sync::Arc}; use tokio::{net::UdpSocket, select, task}; +mod complex_reply; mod global; mod packet; mod redirect; @@ -24,9 +25,9 @@ async fn treat_packet(env: Arc, raw: Vec, src_vtep: SocketAddr) -> Opti match reading.action { InternalTransmit(dst) => redirect::send(env, reading, dst, raw).await, InternalBroadcast => redirect::broadcast(env, reading, raw).await, - GatewayArp => todo!(), - GatewayTransmit => todo!(), - GatewayEnter => todo!(), + GatewayArp => complex_reply::reply_arp(env, reading, raw).await, + GatewayTransmit => redirect::gateway_send(env, raw).await, + GatewayEnter(dst) => redirect::gateway_enter(env, dst, raw).await, } } @@ -50,7 +51,6 @@ async fn main() -> io::Result<()> { let env = Arc::new(Env { socket: UdpSocket::bind(SocketAddr::new(addr_parsing(addr), 4789)).await?, // local_nets: local_nets.into_iter().map(net_parsing).collect(), - used_vnis: vni_range, reserved_nets: reserved_nets.into_iter().map(net_parsing).collect(), // gateway: addr_parsing(gateway), gateway_vtep: addr_parsing(gateway_vtep), @@ -58,7 +58,7 @@ async fn main() -> io::Result<()> { gateway_mac: MacAddr::from_str(gateway_mac) .unwrap_or_else(|e| panic!("Error parsing {:?}: {}", gateway_mac, e)), - mac_table: redirect::new_mactable(vni_range), + mac_table: redirect::MacTable::new(vni_range), }); let mut buf = vec![0u8; 2000]; diff --git a/src/packet.rs b/src/packet.rs index b8f19c1..0131ac3 100644 --- a/src/packet.rs +++ b/src/packet.rs @@ -11,7 +11,6 @@ use pnet::{ use std::sync::Arc; use crate::global::Env; -use crate::redirect::mac_learn; #[derive(Clone, Copy)] /// Action to be taken for a packet @@ -29,7 +28,7 @@ pub enum Action { GatewayTransmit, /// Forward the packet bypassing VNIs - GatewayEnter, + GatewayEnter(MacAddr), } #[derive(Clone, Copy)] @@ -47,14 +46,12 @@ pub fn read_packet(env: &Arc, payload: &[u8], src: SocketAddr) -> Option>>; - -pub fn new_mactable(vni_range: (u32, u32)) -> MacTable { - let mut mac_table = Vec::new(); - mac_table.resize_with((vni_range.1 - vni_range.0) as usize, || { - RwLock::new(HashMap::new()) - }); - mac_table +pub struct MacTable { + vni_range: (u32, u32), + per_vni: Vec>>, + // TODO: HashMap<(MacAddr, IpAddr), (u32, IpAddr)> + per_mac: RwLock>, } -fn mac_table(env: &Arc, vni: u32) -> Option<&RwLock>> { - if env.used_vnis.0 <= vni && vni < env.used_vnis.1 { - Some(&env.mac_table[(vni - env.used_vnis.0) as usize]) - } else { - None +impl MacTable { + pub fn new(vni_range: (u32, u32)) -> Self { + let mut per_vni = Vec::new(); + per_vni.resize_with((vni_range.1 - vni_range.0) as usize, || { + RwLock::new(HashMap::new()) + }); + Self { + vni_range, + per_vni, + per_mac: RwLock::new(HashMap::new()), + } } -} -pub async fn mac_learn(env: Arc, mac: MacAddr, vni: u32, vtep: IpAddr) { - if let Some(vni_table) = mac_table(&env, vni) { - // avoid taking a write lock when unnecessary - if vni_table.read().await.get(&mac) != Some(&vtep) { - vni_table.write().await.insert(mac, vtep); + fn vni_table(&self, vni: u32) -> Option<&RwLock>> { + if self.vni_range.0 <= vni && vni < self.vni_range.1 { + Some(&self.per_vni[(vni - self.vni_range.0) as usize]) + } else { + None + } + } + + pub async fn learn(&self, mac: MacAddr, vni: u32, vtep: IpAddr) { + // we avoid taking a write lock when unnecessary + + if let Some(vni_table) = self.vni_table(vni) { + if vni_table.read().await.get(&mac) != Some(&vtep) { + vni_table.write().await.insert(mac, vtep); + } + } + if self.per_mac.read().await.get(&mac) != Some(&(vni, vtep)) { + self.per_mac.write().await.insert(mac, (vni, vtep)); } } } @@ -38,7 +53,12 @@ pub async fn send( dst: MacAddr, packet: Vec, ) -> Option<()> { - let vtep = *mac_table(&env, reading.vni)?.read().await.get(&dst)?; + let vtep = *env + .mac_table + .vni_table(reading.vni)? + .read() + .await + .get(&dst)?; if vtep != reading.vtep { env.send(&packet, vtep).await; } @@ -46,7 +66,7 @@ pub async fn send( } pub async fn broadcast(env: Arc, reading: PacketReading, packet: Vec) -> Option<()> { - let table = mac_table(&env, reading.vni)?.read().await; + let table = env.mac_table.vni_table(reading.vni)?.read().await; for vtep in table.values() { if *vtep != reading.vtep { // TODO: parallele send @@ -55,3 +75,18 @@ pub async fn broadcast(env: Arc, reading: PacketReading, packet: Vec) - } Some(()) } + +pub async fn gateway_send(env: Arc, mut packet: Vec) -> Option<()> { + let mut vxlan = MutableVxlanPacket::new(&mut packet[..]).unwrap(); + vxlan.set_vni(env.gateway_vni); + env.send(&packet, env.gateway_vtep).await; + Some(()) +} + +pub async fn gateway_enter(env: Arc, dst: MacAddr, mut packet: Vec) -> Option<()> { + let infos = env.mac_table.per_mac.read().await.get(&dst).copied()?; + let mut vxlan = MutableVxlanPacket::new(&mut packet[..]).unwrap(); + vxlan.set_vni(infos.0); + env.send(&packet, infos.1).await; + Some(()) +}