feat: gateway

untested
This commit is contained in:
catvayor 2025-02-21 16:25:48 +01:00
parent 4ffe38221b
commit 70b06cb6ef
Signed by: lbailly
GPG key ID: CE3E645251AC63F3
6 changed files with 99 additions and 38 deletions

View file

@ -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

26
src/complex_reply.rs Normal file
View file

@ -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<Env>, reading: PacketReading, mut raw: Vec<u8>) -> 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(())
}

View file

@ -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<IpNet>,
pub reserved_nets: Vec<IpNet>,
// 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<Self>, mac: MacAddr, vni: u32, vtep: IpAddr) {
self.mac_table.learn(mac, vni, vtep).await
}
}

View file

@ -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<Env>, raw: Vec<u8>, 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];

View file

@ -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<Env>, payload: &[u8], src: SocketAddr) -> Option<Pa
let mut action = InternalTransmit(eth.get_destination());
if vxlan.get_vni() == env.gateway_vni {
action = GatewayEnter;
action = GatewayEnter(eth.get_destination());
} else {
tokio::spawn(mac_learn(
env.clone(),
eth.get_source(),
vxlan.get_vni(),
src.ip(),
));
tokio::spawn(
env.clone()
.mac_learn(eth.get_source(), vxlan.get_vni(), src.ip()),
);
if eth.get_destination().is_broadcast() {
action = InternalBroadcast;
if eth.get_ethertype() == EtherTypes::Arp {

View file

@ -1,33 +1,48 @@
use pnet::util::MacAddr;
use pnet::{packet::vxlan::MutableVxlanPacket, util::MacAddr};
use std::{collections::HashMap, net::IpAddr, sync::Arc};
use tokio::sync::RwLock;
use crate::global::Env;
use crate::packet::PacketReading;
pub type MacTable = Vec<RwLock<HashMap<MacAddr, IpAddr>>>;
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<RwLock<HashMap<MacAddr, IpAddr>>>,
// TODO: HashMap<(MacAddr, IpAddr), (u32, IpAddr)>
per_mac: RwLock<HashMap<MacAddr, (u32, IpAddr)>>,
}
fn mac_table(env: &Arc<Env>, vni: u32) -> Option<&RwLock<HashMap<MacAddr, IpAddr>>> {
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<Env>, 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<HashMap<MacAddr, IpAddr>>> {
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<u8>,
) -> 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<Env>, reading: PacketReading, packet: Vec<u8>) -> 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<Env>, reading: PacketReading, packet: Vec<u8>) -
}
Some(())
}
pub async fn gateway_send(env: Arc<Env>, mut packet: Vec<u8>) -> 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<Env>, dst: MacAddr, mut packet: Vec<u8>) -> 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(())
}