use std::{
io,
net::SocketAddr,
pin::Pin,
task::{Context, Poll},
};
use etherparse::PacketBuilder;
use futures::{Sink, Stream, ready};
use smoltcp::wire::{IpProtocol, Ipv4Packet, Ipv6Packet, UdpPacket};
use tokio::sync::mpsc::{Receiver, Sender, error::TrySendError};
pub type PacketBuffer = Vec<u8>;
pub type UdpMessage = (Vec<u8>, SocketAddr, SocketAddr);
pub struct UdpHandler {
from_tun_rx: Receiver<PacketBuffer>,
to_tun_tx: Sender<PacketBuffer>,
}
impl UdpHandler {
pub fn new(from_tun_rx: Receiver<PacketBuffer>, to_tun_tx: Sender<PacketBuffer>) -> Self {
Self {
from_tun_rx,
to_tun_tx,
}
}
pub fn split(self) -> (UdpReader, UdpWriter) {
(
UdpReader {
from_tun_rx: self.from_tun_rx,
},
UdpWriter {
to_tun_tx: self.to_tun_tx,
},
)
}
}
pub struct UdpReader {
from_tun_rx: Receiver<PacketBuffer>,
}
pub struct UdpWriter {
to_tun_tx: Sender<PacketBuffer>,
}
impl UdpWriter {
pub fn send_sync(&self, message: UdpMessage) -> io::Result<()> {
let (payload, src_addr, dst_addr) = message;
let packet = build_udp_packet(&payload, src_addr, dst_addr)?;
match self.to_tun_tx.try_send(packet) {
Ok(()) | Err(TrySendError::Full(_)) => Ok(()),
Err(TrySendError::Closed(_)) => {
Err(io::Error::new(io::ErrorKind::BrokenPipe, "channel closed"))
}
}
}
}
impl Stream for UdpReader {
type Item = UdpMessage;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
loop {
match ready!(self.from_tun_rx.poll_recv(cx)) {
Some(packet) => {
if let Some(msg) = parse_udp_packet(&packet) {
return Poll::Ready(Some(msg));
}
continue;
}
None => return Poll::Ready(None),
}
}
}
}
impl Sink<UdpMessage> for UdpWriter {
type Error = io::Error;
fn poll_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn start_send(self: Pin<&mut Self>, item: UdpMessage) -> Result<(), Self::Error> {
self.send_sync(item)
}
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
}
fn parse_udp_packet(packet: &[u8]) -> Option<UdpMessage> {
if packet.is_empty() {
return None;
}
let version = packet[0] >> 4;
match version {
4 => parse_ipv4_udp(packet),
6 => parse_ipv6_udp(packet),
_ => None,
}
}
fn parse_ipv4_udp(packet: &[u8]) -> Option<UdpMessage> {
let ip_packet = Ipv4Packet::new_checked(packet).ok()?;
if ip_packet.next_header() != IpProtocol::Udp {
return None;
}
let src_ip = ip_packet.src_addr();
let dst_ip = ip_packet.dst_addr();
let payload = ip_packet.payload();
let udp_packet = UdpPacket::new_checked(payload).ok()?;
let src_port = udp_packet.src_port();
let dst_port = udp_packet.dst_port();
let src_addr = SocketAddr::new(
std::net::IpAddr::V4(std::net::Ipv4Addr::new(
src_ip.octets()[0],
src_ip.octets()[1],
src_ip.octets()[2],
src_ip.octets()[3],
)),
src_port,
);
let dst_addr = SocketAddr::new(
std::net::IpAddr::V4(std::net::Ipv4Addr::new(
dst_ip.octets()[0],
dst_ip.octets()[1],
dst_ip.octets()[2],
dst_ip.octets()[3],
)),
dst_port,
);
Some((udp_packet.payload().to_vec(), src_addr, dst_addr))
}
fn parse_ipv6_udp(packet: &[u8]) -> Option<UdpMessage> {
let ip_packet = Ipv6Packet::new_checked(packet).ok()?;
if ip_packet.next_header() != IpProtocol::Udp {
return None;
}
let src_ip = ip_packet.src_addr();
let dst_ip = ip_packet.dst_addr();
let payload = ip_packet.payload();
let udp_packet = UdpPacket::new_checked(payload).ok()?;
let src_port = udp_packet.src_port();
let dst_port = udp_packet.dst_port();
let src_addr = SocketAddr::new(
std::net::IpAddr::V6(std::net::Ipv6Addr::from(src_ip.octets())),
src_port,
);
let dst_addr = SocketAddr::new(
std::net::IpAddr::V6(std::net::Ipv6Addr::from(dst_ip.octets())),
dst_port,
);
Some((udp_packet.payload().to_vec(), src_addr, dst_addr))
}
pub fn build_udp_packet(
payload: &[u8],
src_addr: SocketAddr,
dst_addr: SocketAddr,
) -> io::Result<PacketBuffer> {
match (src_addr, dst_addr) {
(SocketAddr::V4(src), SocketAddr::V4(dst)) => {
let builder = PacketBuilder::ipv4(
src.ip().octets(),
dst.ip().octets(),
20, )
.udp(src.port(), dst.port());
let mut packet = Vec::with_capacity(builder.size(payload.len()));
builder
.write(&mut packet, payload)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
Ok(packet)
}
(SocketAddr::V6(src), SocketAddr::V6(dst)) => {
let builder = PacketBuilder::ipv6(
src.ip().octets(),
dst.ip().octets(),
20, )
.udp(src.port(), dst.port());
let mut packet = Vec::with_capacity(builder.size(payload.len()));
builder
.write(&mut packet, payload)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
Ok(packet)
}
_ => Err(io::Error::new(
io::ErrorKind::InvalidInput,
"IP version mismatch between source and destination",
)),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_build_and_parse_ipv4_udp() {
let payload = b"hello world";
let src = "192.168.1.1:12345".parse().unwrap();
let dst = "10.0.0.1:80".parse().unwrap();
let packet = build_udp_packet(payload, src, dst).unwrap();
let (parsed_payload, parsed_src, parsed_dst) = parse_udp_packet(&packet).unwrap();
assert_eq!(parsed_payload, payload);
assert_eq!(parsed_src, src);
assert_eq!(parsed_dst, dst);
}
#[test]
fn test_build_and_parse_ipv6_udp() {
let payload = b"hello ipv6";
let src: SocketAddr = "[2001:db8::1]:12345".parse().unwrap();
let dst: SocketAddr = "[2001:db8::2]:80".parse().unwrap();
let packet = build_udp_packet(payload, src, dst).unwrap();
let (parsed_payload, parsed_src, parsed_dst) = parse_udp_packet(&packet).unwrap();
assert_eq!(parsed_payload, payload);
assert_eq!(parsed_src, src);
assert_eq!(parsed_dst, dst);
}
}