mirror of
				https://github.com/edera-dev/krata.git
				synced 2025-11-03 23:29:39 +00:00 
			
		
		
		
	hypha: work in progress implementation of outbound internet access
This commit is contained in:
		@ -1,30 +1,21 @@
 | 
			
		||||
use std::os::fd::AsRawFd;
 | 
			
		||||
use std::panic::UnwindSafe;
 | 
			
		||||
use std::str::FromStr;
 | 
			
		||||
use std::sync::{Arc, Mutex};
 | 
			
		||||
use std::time::Duration;
 | 
			
		||||
use std::{panic, thread};
 | 
			
		||||
 | 
			
		||||
use advmac::MacAddr6;
 | 
			
		||||
use anyhow::{anyhow, Result};
 | 
			
		||||
use futures::TryStreamExt;
 | 
			
		||||
use log::{error, info, warn};
 | 
			
		||||
use ipstack::stream::IpStackStream;
 | 
			
		||||
use log::{debug, error, info, warn};
 | 
			
		||||
use netlink_packet_route::link::LinkAttribute;
 | 
			
		||||
use smoltcp::iface::{Config, Interface, SocketSet};
 | 
			
		||||
use smoltcp::phy::{self, RawSocket};
 | 
			
		||||
use smoltcp::time::Instant;
 | 
			
		||||
use smoltcp::wire::{EthernetAddress, HardwareAddress, IpCidr};
 | 
			
		||||
use raw_socket::{AsyncRawSocket, RawSocket};
 | 
			
		||||
use tokio::net::TcpStream;
 | 
			
		||||
use tokio::time::sleep;
 | 
			
		||||
use udp_stream::UdpStream;
 | 
			
		||||
 | 
			
		||||
mod raw_socket;
 | 
			
		||||
 | 
			
		||||
pub struct NetworkBackend {
 | 
			
		||||
    pub interface: String,
 | 
			
		||||
    pub device: RawSocket,
 | 
			
		||||
    pub addresses: Vec<IpCidr>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
unsafe impl Send for NetworkBackend {}
 | 
			
		||||
impl UnwindSafe for NetworkBackend {}
 | 
			
		||||
 | 
			
		||||
pub struct NetworkService {
 | 
			
		||||
    pub network: String,
 | 
			
		||||
}
 | 
			
		||||
@ -36,18 +27,9 @@ impl NetworkService {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl NetworkBackend {
 | 
			
		||||
    pub fn new(iface: &str, cidrs: &[&str]) -> Result<NetworkBackend> {
 | 
			
		||||
        let device = RawSocket::new(iface, smoltcp::phy::Medium::Ethernet)?;
 | 
			
		||||
        let mut addresses: Vec<IpCidr> = Vec::new();
 | 
			
		||||
        for cidr in cidrs {
 | 
			
		||||
            let address =
 | 
			
		||||
                IpCidr::from_str(cidr).map_err(|_| anyhow!("failed to parse cidr: {}", *cidr))?;
 | 
			
		||||
            addresses.push(address);
 | 
			
		||||
        }
 | 
			
		||||
    pub fn new(iface: &str) -> Result<NetworkBackend> {
 | 
			
		||||
        Ok(NetworkBackend {
 | 
			
		||||
            interface: iface.to_string(),
 | 
			
		||||
            device,
 | 
			
		||||
            addresses,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -73,34 +55,56 @@ impl NetworkBackend {
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn run(mut self) -> Result<()> {
 | 
			
		||||
        let result = panic::catch_unwind(move || self.run_maybe_panic());
 | 
			
		||||
    pub async fn run(&mut self) -> Result<()> {
 | 
			
		||||
        let mut config = ipstack::IpStackConfig::default();
 | 
			
		||||
        config.mtu(1500);
 | 
			
		||||
        config.tcp_timeout(std::time::Duration::from_secs(600)); // 10 minutes
 | 
			
		||||
        config.udp_timeout(std::time::Duration::from_secs(10)); // 10 seconds
 | 
			
		||||
 | 
			
		||||
        if result.is_err() {
 | 
			
		||||
            return Err(anyhow!("network backend has terminated"));
 | 
			
		||||
        let mut socket = RawSocket::new(&self.interface)?;
 | 
			
		||||
        socket.bind_interface()?;
 | 
			
		||||
        let socket = AsyncRawSocket::new(socket)?;
 | 
			
		||||
        let mut stack = ipstack::IpStack::new(config, socket);
 | 
			
		||||
 | 
			
		||||
        while let Ok(stream) = stack.accept().await {
 | 
			
		||||
            self.process_stream(stream).await?
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        result.unwrap()
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn run_maybe_panic(&mut self) -> Result<()> {
 | 
			
		||||
        let mac = MacAddr6::random();
 | 
			
		||||
        let mac = HardwareAddress::Ethernet(EthernetAddress(mac.to_array()));
 | 
			
		||||
        let config = Config::new(mac);
 | 
			
		||||
        let mut iface = Interface::new(config, &mut self.device, Instant::now());
 | 
			
		||||
        iface.update_ip_addrs(|addrs| {
 | 
			
		||||
            addrs
 | 
			
		||||
                .extend_from_slice(&self.addresses)
 | 
			
		||||
                .expect("failed to set ip addresses");
 | 
			
		||||
        });
 | 
			
		||||
    async fn process_stream(&mut self, stream: IpStackStream) -> Result<()> {
 | 
			
		||||
        match stream {
 | 
			
		||||
            IpStackStream::Tcp(mut tcp) => {
 | 
			
		||||
                debug!("tcp: {}", tcp.peer_addr());
 | 
			
		||||
                tokio::spawn(async move {
 | 
			
		||||
                    if let Ok(mut stream) = TcpStream::connect(tcp.peer_addr()).await {
 | 
			
		||||
                        let _ = tokio::io::copy_bidirectional(&mut stream, &mut tcp).await;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        warn!("failed to connect to tcp address: {}", tcp.peer_addr());
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        let mut sockets = SocketSet::new(vec![]);
 | 
			
		||||
        let fd = self.device.as_raw_fd();
 | 
			
		||||
        loop {
 | 
			
		||||
            let timestamp = Instant::now();
 | 
			
		||||
            iface.poll(timestamp, &mut self.device, &mut sockets);
 | 
			
		||||
            phy::wait(fd, iface.poll_delay(timestamp, &sockets))?;
 | 
			
		||||
            IpStackStream::Udp(mut udp) => {
 | 
			
		||||
                debug!("udp: {}", udp.peer_addr());
 | 
			
		||||
                tokio::spawn(async move {
 | 
			
		||||
                    if let Ok(mut stream) = UdpStream::connect(udp.peer_addr()).await {
 | 
			
		||||
                        let _ = tokio::io::copy_bidirectional(&mut stream, &mut udp).await;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        warn!("failed to connect to udp address: {}", udp.peer_addr());
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            IpStackStream::UnknownTransport(u) => {
 | 
			
		||||
                debug!("unknown transport: {}", u.dst_addr());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            IpStackStream::UnknownNetwork(packet) => {
 | 
			
		||||
                debug!("unknown network: {:?}", packet);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -156,15 +160,15 @@ impl NetworkService {
 | 
			
		||||
        spawned: Arc<Mutex<Vec<String>>>,
 | 
			
		||||
    ) -> Result<()> {
 | 
			
		||||
        let interface = interface.to_string();
 | 
			
		||||
        let mut network = NetworkBackend::new(&interface, &[&self.network])?;
 | 
			
		||||
        let mut network = NetworkBackend::new(&interface)?;
 | 
			
		||||
        info!("initializing network backend for interface {}", interface);
 | 
			
		||||
        network.init().await?;
 | 
			
		||||
        tokio::time::sleep(Duration::from_secs(1)).await;
 | 
			
		||||
        info!("spawning network backend for interface {}", interface);
 | 
			
		||||
        thread::spawn(move || {
 | 
			
		||||
            if let Err(error) = network.run() {
 | 
			
		||||
        tokio::spawn(async move {
 | 
			
		||||
            if let Err(error) = network.run().await {
 | 
			
		||||
                error!(
 | 
			
		||||
                    "failed to run network backend for interface {}: {}",
 | 
			
		||||
                    "network backend for interface {} has been stopped: {}",
 | 
			
		||||
                    interface, error
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										202
									
								
								network/src/raw_socket.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								network/src/raw_socket.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,202 @@
 | 
			
		||||
use futures::ready;
 | 
			
		||||
use std::os::unix::io::{AsRawFd, RawFd};
 | 
			
		||||
use std::pin::Pin;
 | 
			
		||||
use std::task::{Context, Poll};
 | 
			
		||||
use std::{io, mem};
 | 
			
		||||
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
use tokio::io::unix::AsyncFd;
 | 
			
		||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
 | 
			
		||||
 | 
			
		||||
const SIOCGIFINDEX: libc::c_ulong = 0x8933;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub struct RawSocket {
 | 
			
		||||
    protocol: libc::c_short,
 | 
			
		||||
    lower: libc::c_int,
 | 
			
		||||
    ifreq: Ifreq,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl AsRawFd for RawSocket {
 | 
			
		||||
    fn as_raw_fd(&self) -> RawFd {
 | 
			
		||||
        self.lower
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl RawSocket {
 | 
			
		||||
    pub fn new(name: &str) -> io::Result<RawSocket> {
 | 
			
		||||
        let protocol: libc::c_short = 0x0003;
 | 
			
		||||
        let lower = unsafe {
 | 
			
		||||
            let lower = libc::socket(
 | 
			
		||||
                libc::AF_PACKET,
 | 
			
		||||
                libc::SOCK_RAW | libc::SOCK_NONBLOCK,
 | 
			
		||||
                protocol.to_be() as i32,
 | 
			
		||||
            );
 | 
			
		||||
            if lower == -1 {
 | 
			
		||||
                return Err(io::Error::last_os_error());
 | 
			
		||||
            }
 | 
			
		||||
            lower
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        Ok(RawSocket {
 | 
			
		||||
            protocol,
 | 
			
		||||
            lower,
 | 
			
		||||
            ifreq: ifreq_for(name),
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn bind_interface(&mut self) -> io::Result<()> {
 | 
			
		||||
        let sockaddr = libc::sockaddr_ll {
 | 
			
		||||
            sll_family: libc::AF_PACKET as u16,
 | 
			
		||||
            sll_protocol: self.protocol.to_be() as u16,
 | 
			
		||||
            sll_ifindex: ifreq_ioctl(self.lower, &mut self.ifreq, SIOCGIFINDEX)?,
 | 
			
		||||
            sll_hatype: 1,
 | 
			
		||||
            sll_pkttype: 0,
 | 
			
		||||
            sll_halen: 6,
 | 
			
		||||
            sll_addr: [0; 8],
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        unsafe {
 | 
			
		||||
            let res = libc::bind(
 | 
			
		||||
                self.lower,
 | 
			
		||||
                &sockaddr as *const libc::sockaddr_ll as *const libc::sockaddr,
 | 
			
		||||
                mem::size_of::<libc::sockaddr_ll>() as libc::socklen_t,
 | 
			
		||||
            );
 | 
			
		||||
            if res == -1 {
 | 
			
		||||
                return Err(io::Error::last_os_error());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn recv(&self, buffer: &mut [u8]) -> io::Result<usize> {
 | 
			
		||||
        unsafe {
 | 
			
		||||
            let len = libc::recv(
 | 
			
		||||
                self.lower,
 | 
			
		||||
                buffer.as_mut_ptr() as *mut libc::c_void,
 | 
			
		||||
                buffer.len(),
 | 
			
		||||
                0,
 | 
			
		||||
            );
 | 
			
		||||
            if len == -1 {
 | 
			
		||||
                return Err(io::Error::last_os_error());
 | 
			
		||||
            }
 | 
			
		||||
            Ok(len as usize)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn send(&self, buffer: &[u8]) -> io::Result<usize> {
 | 
			
		||||
        unsafe {
 | 
			
		||||
            let len = libc::send(
 | 
			
		||||
                self.lower,
 | 
			
		||||
                buffer.as_ptr() as *const libc::c_void,
 | 
			
		||||
                buffer.len(),
 | 
			
		||||
                0,
 | 
			
		||||
            );
 | 
			
		||||
            if len == -1 {
 | 
			
		||||
                return Err(io::Error::last_os_error());
 | 
			
		||||
            }
 | 
			
		||||
            Ok(len as usize)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Drop for RawSocket {
 | 
			
		||||
    fn drop(&mut self) {
 | 
			
		||||
        unsafe {
 | 
			
		||||
            libc::close(self.lower);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[repr(C)]
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
struct Ifreq {
 | 
			
		||||
    ifr_name: [libc::c_char; libc::IF_NAMESIZE],
 | 
			
		||||
    ifr_data: libc::c_int, /* ifr_ifindex or ifr_mtu */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn ifreq_for(name: &str) -> Ifreq {
 | 
			
		||||
    let mut ifreq = Ifreq {
 | 
			
		||||
        ifr_name: [0; libc::IF_NAMESIZE],
 | 
			
		||||
        ifr_data: 0,
 | 
			
		||||
    };
 | 
			
		||||
    for (i, byte) in name.as_bytes().iter().enumerate() {
 | 
			
		||||
        ifreq.ifr_name[i] = *byte as libc::c_char
 | 
			
		||||
    }
 | 
			
		||||
    ifreq
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn ifreq_ioctl(
 | 
			
		||||
    lower: libc::c_int,
 | 
			
		||||
    ifreq: &mut Ifreq,
 | 
			
		||||
    cmd: libc::c_ulong,
 | 
			
		||||
) -> io::Result<libc::c_int> {
 | 
			
		||||
    unsafe {
 | 
			
		||||
        let res = libc::ioctl(lower, cmd as _, ifreq as *mut Ifreq);
 | 
			
		||||
        if res == -1 {
 | 
			
		||||
            return Err(io::Error::last_os_error());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(ifreq.ifr_data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct AsyncRawSocket {
 | 
			
		||||
    inner: AsyncFd<RawSocket>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl AsyncRawSocket {
 | 
			
		||||
    pub fn new(socket: RawSocket) -> Result<Self> {
 | 
			
		||||
        Ok(Self {
 | 
			
		||||
            inner: AsyncFd::new(socket)?,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl AsyncRead for AsyncRawSocket {
 | 
			
		||||
    fn poll_read(
 | 
			
		||||
        self: Pin<&mut Self>,
 | 
			
		||||
        cx: &mut Context<'_>,
 | 
			
		||||
        buf: &mut ReadBuf<'_>,
 | 
			
		||||
    ) -> Poll<io::Result<()>> {
 | 
			
		||||
        loop {
 | 
			
		||||
            let mut guard = ready!(self.inner.poll_read_ready(cx))?;
 | 
			
		||||
 | 
			
		||||
            let unfilled = buf.initialize_unfilled();
 | 
			
		||||
            match guard.try_io(|inner| inner.get_ref().recv(unfilled)) {
 | 
			
		||||
                Ok(Ok(len)) => {
 | 
			
		||||
                    buf.advance(len);
 | 
			
		||||
                    return Poll::Ready(Ok(()));
 | 
			
		||||
                }
 | 
			
		||||
                Ok(Err(err)) => return Poll::Ready(Err(err)),
 | 
			
		||||
                Err(_would_block) => continue,
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl AsyncWrite for AsyncRawSocket {
 | 
			
		||||
    fn poll_write(
 | 
			
		||||
        self: Pin<&mut Self>,
 | 
			
		||||
        cx: &mut Context<'_>,
 | 
			
		||||
        buf: &[u8],
 | 
			
		||||
    ) -> Poll<io::Result<usize>> {
 | 
			
		||||
        loop {
 | 
			
		||||
            let mut guard = ready!(self.inner.poll_write_ready(cx))?;
 | 
			
		||||
 | 
			
		||||
            match guard.try_io(|inner| inner.get_ref().send(buf)) {
 | 
			
		||||
                Ok(result) => return Poll::Ready(result),
 | 
			
		||||
                Err(_would_block) => continue,
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> {
 | 
			
		||||
        Poll::Ready(Ok(()))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn poll_shutdown(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> {
 | 
			
		||||
        Poll::Ready(Ok(()))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user