mirror of
https://github.com/edera-dev/krata.git
synced 2025-08-03 13:11:31 +00:00
hypha: work in progress implementation of outbound internet access
This commit is contained in:
@ -13,11 +13,16 @@ rtnetlink = { workspace = true }
|
||||
netlink-packet-route = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
smoltcp = { workspace = true }
|
||||
libc = { workspace = true }
|
||||
udp-stream = { workspace = true }
|
||||
|
||||
[dependencies.advmac]
|
||||
path = "../libs/advmac"
|
||||
|
||||
[dependencies.ipstack]
|
||||
path = "../libs/ipstack"
|
||||
features = ["log"]
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
|
||||
|
@ -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