smoltcp-rs / smoltcp

a smol tcp/ip stack
BSD Zero Clause License
3.63k stars 402 forks source link

Connecting a Raw socket and a TCP socket #930

Closed tomDev5 closed 1 month ago

tomDev5 commented 1 month ago

I am trying to convert a list of (IP) packets into a tcp stream. I have been trying to modify the loopback example like so, to get an initial POC:

[dependencies]
env_logger = "0.11.3"
hex = "0.4.3"
log = "0.4.21"
managed = "0.8.0"
smoltcp = "0.11.0"
use log::debug;
use managed::ManagedSlice;
use smoltcp::{
    iface::{Config, Interface, SocketSet},
    phy::{Loopback, Medium},
    socket::{raw, tcp},
    time::Instant,
    wire::{IpAddress, IpCidr},
};

fn main() {
    env_logger::init();

    let mut device = Loopback::new(Medium::Ip);

    let mut iface = Interface::new(
        Config::new(smoltcp::wire::HardwareAddress::Ip),
        &mut device,
        Instant::now(),
    );
    iface.update_ip_addrs(|ip_addrs| {
        ip_addrs
            .push(IpCidr::new(IpAddress::v4(127, 0, 0, 1), 8))
            .unwrap();
    });

    let server_socket = tcp::Socket::new(
        tcp::SocketBuffer::new(ManagedSlice::Owned(vec![0; 1024])),
        tcp::SocketBuffer::new(ManagedSlice::Owned(vec![0; 1024])),
    );

    let client_packets = vec![
        hex::decode("450000340000400040063cc27f0000017f000001fde804d277368c50000000008002040070af00000204ffd70303000402000000").unwrap(), // syn
    ];

    let client_socket = raw::Socket::new(
        smoltcp::wire::IpVersion::Ipv4,
        smoltcp::wire::IpProtocol::Tcp,
        raw::PacketBuffer::new(
            ManagedSlice::Owned(vec![raw::PacketMetadata::EMPTY; 1024]),
            ManagedSlice::Owned(vec![0; 1024]),
        ),
        raw::PacketBuffer::new(
            ManagedSlice::Owned(vec![raw::PacketMetadata::EMPTY; 1024]),
            ManagedSlice::Owned(vec![0; 1024]),
        ),
    );

    let mut sockets: [_; 2] = Default::default();
    let mut sockets = SocketSet::new(&mut sockets[..]);
    let server_handle = sockets.add(server_socket);
    let client_handle = sockets.add(client_socket);

    iface.poll(Instant::now(), &mut device, &mut sockets);
    let server_socket = sockets.get_mut::<tcp::Socket>(server_handle);
    debug!("server listening");
    server_socket.listen(1234).unwrap();

    let client_socket = sockets.get_mut::<raw::Socket>(client_handle);
    debug!("client connecting");
    client_socket
        .send_slice(client_packets[0].as_slice())
        .unwrap(); // syn, for connect

    iface.poll(Instant::now(), &mut device, &mut sockets);
}

The problem is that the logs show that a RST packet from the client to the server is sent after the synack (after adding logs for the packets transmitted):

server listening
client connecting
127.0.0.1:65000 => 127.0.0.1:1234 (SYN)
127.0.0.1:1234 => 127.0.0.1:65000 (SYN ACK)
127.0.0.1:65000 => 127.0.0.1:1234 (RST)

Looking at how the interface processes IPv4 packets, it appears that it expects a TCP socket to handle the incoming SYN ACK, or else an RST response will be sent (thanks to eggyal on GitHub for figuring this out) So - is there a better way to achieve this?

Dirbaio commented 1 month ago

you probably want to feed the packets to smoltcp via a custom fake Device implementation, not loopback + raw socket. Set the TCP socket to listen, then call .poll() on the interface.

However, if you want to "offline" decode packet captures, Interface is probably not the right tool for the job. Interface is designed for interactively participating in a network. Maybe use wire, or perhaps reuse chunks of TcpSocket code.

tomDev5 commented 1 month ago

I will describe my problem better, the API is preferably two sided (raw to tcp and vice versa), and it is not offline

Won't a fake device cause the same problem where there is no tcp socket on the other side?

Even if it somehow works, a fake device seems to me like a one-sided solution

eggyal commented 1 month ago

If I've correctly understood @Dirbaio's suggestion, the idea is that you only create one socket (the server in your example above) for which your custom device generates the packets (from whatever source): therefore you don't have/need any raw sockets at all.

tomDev5 commented 1 month ago

I don't think that would prevent the reset packet. It is sent because only one tcp socket exists for smoltcp Having no raw socket would still cause that issue

Edit: nvm worked. Thanks!

durch commented 3 days ago

@tomDev5 could you share the final implementation on this, I'm after something similar, can can't wrap my head around a "fake" Device implementation

tomDev5 commented 3 days ago

sure, here is a sample device:

use std::collections::VecDeque;
use smoltcp::phy::{self, Device, DeviceCapabilities, Medium};
use smoltcp::time::Instant;

#[derive(Debug)]
pub struct VecDevice {
    to_smoltcp: VecDeque<Vec<u8>>,
    from_smoltcp: VecDeque<Vec<u8>>,
    medium: Medium,
}

impl VecDevice {
    pub fn new(medium: Medium) -> VecDevice {
        VecDevice {
            to_smoltcp: VecDeque::new(),
            from_smoltcp: VecDeque::new(),
            medium,
        }
    }

    pub fn send_raw(&mut self, packet: Vec<u8>) {
        self.to_smoltcp.push_back(packet);
    }

    pub fn recv_raw(&mut self) -> Option<Vec<u8>> {
        self.from_smoltcp.pop_front()
    }
}

impl Device for VecDevice {
    type RxToken<'a> = RxToken;
    type TxToken<'a> = TxToken<'a>;

    fn capabilities(&self) -> DeviceCapabilities {
        let mut d = DeviceCapabilities::default();
        d.medium = self.medium;
        d.max_transmission_unit = 65535;
        d
    }

    fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
        self.to_smoltcp.pop_front().map(move |buffer| {
            let rx = RxToken { buffer };
            let tx = TxToken {
                queue: &mut self.from_smoltcp,
            };
            (rx, tx)
        })
    }

    fn transmit(&mut self, _timestamp: Instant) -> Option<Self::TxToken<'_>> {
        Some(TxToken {
            queue: &mut self.from_smoltcp,
        })
    }
}

pub struct RxToken {
    buffer: Vec<u8>,
}

impl phy::RxToken for RxToken {
    fn consume<R, F>(mut self, f: F) -> R
    where
        F: FnOnce(&mut [u8]) -> R,
    {
        f(&mut self.buffer)
    }
}

pub struct TxToken<'a> {
    queue: &'a mut VecDeque<Vec<u8>>,
}

impl<'a> phy::TxToken for TxToken<'a> {
    fn consume<R, F>(self, len: usize, f: F) -> R
    where
        F: FnOnce(&mut [u8]) -> R,
    {
        let mut buffer = Vec::new();
        buffer.resize(len, 0);
        let result = f(&mut buffer);
        self.queue.push_back(buffer);
        result
    }
}

It is worth mentioning that inserting raw traffic is not done via a smolTCP raw socket, but through the api in the device send_raw and recv_raw