Implementation of the IP loopback. (#19)

This patch lets users connect to `ip:127.0.0.1/*`. Bytes written to
this connection can only be read from the same connection.
This commit is contained in:
David Teller 2016-10-24 22:17:12 +02:00 committed by Jeremy Soller
parent ead01ea2da
commit a72750cc41
3 changed files with 204 additions and 65 deletions

View file

@ -13,7 +13,10 @@
//! To open a IP connection, use `ip:[host]/protocol`. //! To open a IP connection, use `ip:[host]/protocol`.
//! //!
//! * If `host` is specified, it must be an ipv4 number (e.g. `192.168.0.1`) //! * If `host` is specified, it must be an ipv4 number (e.g. `192.168.0.1`)
//! and the connection may be used immediately to send/receive data. //! and the connection may be used immediately to send/receive data. Ip v4 number
//! `127.0.0.1` is hardwired to the loopback device (i.e. localhost), which doesn't
//! access any physical device and in which data can only be read by the same
//! connection that has written it.
//! * If `host` is omitted, this connectino will wait for a distant peer to //! * If `host` is omitted, this connectino will wait for a distant peer to
//! connect. //! connect.
//! * The `protocol` is the hex-based number of the ip protocol //! * The `protocol` is the hex-based number of the ip protocol
@ -47,3 +50,43 @@ fn main() {
} }
}); });
} }
#[cfg(test)]
fn test() {
use scheme::IpScheme;
println!("* Test that we can read a simple packet from the same connection.");
let bytes = "TEST".as_bytes();
let mut scheme = IpScheme::new();
let a = scheme.open("ip:127.0.0.1/11").unwrap();
let b = scheme.open("ip:127.0.0.1/11").unwrap();
let num_bytes_written = scheme.write(a, bytes).unwrap();
assert_eq!(num_bytes_written, bytes.len());
let mut buf = [0;65536];
let num_bytes_read = scheme.read(a, &mut buf).unwrap();
assert_eq!(num_bytes_read, num_bytes_written);
let bytes_read = &buf[0..num_bytes_read];
assert_eq!(bytes, bytes_read);
println!("* Test that we cannot read the same packet from a different connection.");
let num_bytes_read = scheme.read(b, &mut buf).unwrap();
assert_eq!(num_bytes_read, 0);
println!("* Push a number of packets, check that we get them in the right order.");
let mut payloads : Vec<String> = 0..100.map(|i| format!("TEST {}", i)).collect();
for payload in &payloads {
let bytes = payload.into_bytes();
let num_bytes_written = scheme.write(a, &bytes).unwrap();
assert_eq!(bytes.len(), num_bytes_written);
}
for payload in &payloads {
let bytes = payload.into_bytes();
let mut buf = [0;65536];
let num_bytes_read = scheme.read(a, &mut buf).unwrap();
assert_eq!(bytes.len(), num_bytes_read);
let bytes_read = &buf[0..num_bytes_read];
assert_eq!(bytes, bytes_read);
}
}

View file

@ -1,21 +1,20 @@
use std::{cmp, mem}; use std::{cmp, mem};
use std::collections::VecDeque;
use netutils::{n16, Ipv4Addr, Checksum, Ipv4Header, Ipv4}; use netutils::{n16, Ipv4Addr, Checksum, Ipv4Header, Ipv4};
use resource_scheme::Resource; use resource_scheme::Resource;
use syscall; use syscall;
use syscall::error::*; use syscall::error::*;
/// Max number of bytes in a packet.
const MAX_PACKET_LENGTH : usize = 65536;
/// A IP (internet protocol) resource. /// A IP (internet protocol) resource.
/// ///
/// Each instance represents a connection (~ a IP socket). /// Each instance represents a connection (~ a IP socket).
pub struct IpResource { pub struct IpResource {
/// Link to the underlying device (typically, an Ethernet card). /// The underlying mechanism ensured to connect to the peer.
pub link: usize, pub connection: Connection,
/// If this connection was opened waiting for a peer (i.e. `ip:/protocol`),
/// the data received when the peer actually connected. Otherwise, empty.
/// Emptied during the first call to `read()`.
pub init_data: Vec<u8>,
/// The IP address of the host (i.e. this machine). /// The IP address of the host (i.e. this machine).
pub host_addr: Ipv4Addr, pub host_addr: Ipv4Addr,
@ -33,6 +32,25 @@ pub struct IpResource {
pub id: u16, pub id: u16,
} }
pub enum Connection {
Device {
/// Link to the underlying device (typically, an Ethernet card).
link: usize,
/// If this connection was opened waiting for a peer (i.e. `ip:/protocol`),
/// the data received when the peer actually connected. Otherwise, empty.
/// Emptied during the first call to `read()`.
init_data: Vec<u8>,
},
Loopback {
/// FIFO queue of packets written to the connection and waiting to be read.
///
/// The data stored contains the exact data that has been added by the client
/// calling `write()`, without adding any headers.
packets: VecDeque<Vec<u8>>
}
}
impl Resource for IpResource { impl Resource for IpResource {
/// Duplicate the connection. /// Duplicate the connection.
/// ///
@ -43,14 +61,26 @@ impl Resource for IpResource {
/// Fails if the `link` to the underlying device cannot be /// Fails if the `link` to the underlying device cannot be
/// duplicated. /// duplicated.
fn dup(&self) -> Result<Box<Self>> { fn dup(&self) -> Result<Box<Self>> {
let link = try!(syscall::dup(self.link)); use self::Connection::*;
Ok(Box::new(IpResource { let connection = match self.connection {
Loopback { ref packets } => Loopback {
packets: packets.clone()
},
Device { link, ref init_data } => {
let link = try!(syscall::dup(link));
let init_data = init_data.clone();
Device {
link: link, link: link,
init_data: self.init_data.clone(), init_data: init_data
}
}
};
Ok(Box::new(IpResource {
host_addr: self.host_addr, host_addr: self.host_addr,
peer_addr: self.peer_addr, peer_addr: self.peer_addr,
proto: self.proto, proto: self.proto,
id: self.id, id: self.id,
connection: connection,
})) }))
} }
@ -79,13 +109,35 @@ impl Resource for IpResource {
/// can happen only if the connection was waiting for a remote peer to connect, i.e. /// can happen only if the connection was waiting for a remote peer to connect, i.e.
/// with a url `ip:/protocol`, without host. /// with a url `ip:/protocol`, without host.
/// ///
/// If this connection is a loopback, oldest unread packet written is read.
///
/// # Errors /// # Errors
/// ///
/// Fails if the call to `syscall::read()` fails for this device. /// Fails if the call to `syscall::read()` fails for this device.
///
/// # Data loss
///
/// If `buf` is too small, *exceeding data is discarded*. To be sure that you read
/// all data, you should provide a 64kb `buf`.
fn read(&mut self, buf: &mut [u8]) -> Result<usize> { fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
if !self.init_data.is_empty() { use self::Connection::*;
match self.connection {
Loopback { ref mut packets } => {
match packets.pop_front() {
None => Ok(0),
Some(data) => {
for (b, d) in buf.iter_mut().zip(data.iter()) {
*b = *d;
}
// Note: We're discarding excess `data`.
Ok(cmp::min(buf.len(), data.len()))
}
}
}
Device { ref mut init_data, link } => {
if !init_data.is_empty() {
let mut data: Vec<u8> = Vec::new(); let mut data: Vec<u8> = Vec::new();
mem::swap(&mut self.init_data, &mut data); mem::swap(init_data, &mut data);
for (b, d) in buf.iter_mut().zip(data.iter()) { for (b, d) in buf.iter_mut().zip(data.iter()) {
*b = *d; *b = *d;
@ -94,8 +146,8 @@ impl Resource for IpResource {
return Ok(cmp::min(buf.len(), data.len())); return Ok(cmp::min(buf.len(), data.len()));
} }
let mut bytes = [0; 65536]; let mut bytes = [0; MAX_PACKET_LENGTH];
let count = try!(syscall::read(self.link, &mut bytes)); let count = try!(syscall::read(link, &mut bytes));
if let Some(packet) = Ipv4::from_bytes(&bytes[..count]) { if let Some(packet) = Ipv4::from_bytes(&bytes[..count]) {
if packet.header.proto == self.proto && if packet.header.proto == self.proto &&
@ -111,6 +163,8 @@ impl Resource for IpResource {
Ok(0) Ok(0)
} }
}
}
/// Send data to the peer. /// Send data to the peer.
/// ///
@ -118,8 +172,22 @@ impl Resource for IpResource {
/// ///
/// Fails if the call to `syscall::write()` fails for this device. /// Fails if the call to `syscall::write()` fails for this device.
fn write(&mut self, buf: &[u8]) -> Result<usize> { fn write(&mut self, buf: &[u8]) -> Result<usize> {
let ip_data = Vec::from(buf); use self::Connection::*;
let ip_data = Vec::from(buf);
match self.connection {
Loopback { ref mut packets } => {
// Make sure that we're not going to store data that can't be read.
let buf =
if buf.len() > MAX_PACKET_LENGTH {
&buf[0..MAX_PACKET_LENGTH]
} else {
buf
};
packets.push_back(buf.to_vec());
return Ok(buf.len())
}
Device { link, .. } => {
self.id += 1; self.id += 1;
let mut ip = Ipv4 { let mut ip = Ipv4 {
header: Ipv4Header { header: Ipv4Header {
@ -145,19 +213,27 @@ impl Resource for IpResource {
Checksum::sum(ip.options.as_ptr() as usize, ip.options.len())); Checksum::sum(ip.options.as_ptr() as usize, ip.options.len()));
} }
match syscall::write(self.link, &ip.to_bytes()) { match syscall::write(link, &ip.to_bytes()) {
Ok(_) => Ok(buf.len()), Ok(_) => Ok(buf.len()),
Err(err) => Err(err), Err(err) => Err(err),
} }
} }
}
}
fn sync(&mut self) -> Result<usize> { fn sync(&mut self) -> Result<usize> {
syscall::fsync(self.link) if let Connection::Device { link, .. } = self.connection {
syscall::fsync(link)
} else {
Ok(0)
}
} }
} }
impl Drop for IpResource { impl Drop for IpResource {
fn drop(&mut self) { fn drop(&mut self) {
let _ = syscall::close(self.link); if let Connection::Device { link, .. } = self.connection {
let _ = syscall::close(link);
}
} }
} }

View file

@ -1,4 +1,5 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::VecDeque;
use std::rand; use std::rand;
use std::{str, u16}; use std::{str, u16};
@ -8,7 +9,10 @@ use syscall;
use syscall::error::{Error, Result, EACCES, ENOENT, EINVAL}; use syscall::error::{Error, Result, EACCES, ENOENT, EINVAL};
use syscall::flag::O_RDWR; use syscall::flag::O_RDWR;
use resource::IpResource; use resource::*;
/// The IP address of the localhost.
const LOCALHOST: Ipv4Addr = Ipv4Addr { bytes: [127, 0, 0, 1] };
/// A ARP entry (MAC + IP) /// A ARP entry (MAC + IP)
pub struct ArpEntry { pub struct ArpEntry {
@ -48,6 +52,18 @@ impl ResourceScheme<IpResource> for IpScheme {
let mut route_mac = MacAddr::BROADCAST; let mut route_mac = MacAddr::BROADCAST;
if ! peer_addr.equals(Ipv4Addr::BROADCAST) { if ! peer_addr.equals(Ipv4Addr::BROADCAST) {
if peer_addr.equals(LOCALHOST) {
return Ok(Box::new(IpResource {
connection: Connection::Loopback {
packets: VecDeque::new()
},
host_addr: ip_addr,
peer_addr: peer_addr,
proto: proto,
id: (rand() % 65536) as u16,
}));
}
let mut needs_routing = false; let mut needs_routing = false;
for octet in 0..4 { for octet in 0..4 {
@ -116,8 +132,10 @@ impl ResourceScheme<IpResource> for IpScheme {
if let Ok(link) = syscall::open(&format!("ethernet:{}/800", &route_mac.to_string()), O_RDWR) { if let Ok(link) = syscall::open(&format!("ethernet:{}/800", &route_mac.to_string()), O_RDWR) {
return Ok(Box::new(IpResource { return Ok(Box::new(IpResource {
connection: Connection::Device {
link: link, link: link,
init_data: Vec::new(), init_data: Vec::new(),
},
host_addr: ip_addr, host_addr: ip_addr,
peer_addr: peer_addr, peer_addr: peer_addr,
proto: proto, proto: proto,
@ -134,8 +152,10 @@ impl ResourceScheme<IpResource> for IpScheme {
if packet.header.proto == proto && if packet.header.proto == proto &&
(packet.header.dst.equals(ip_addr) || packet.header.dst.equals(Ipv4Addr::BROADCAST)) { (packet.header.dst.equals(ip_addr) || packet.header.dst.equals(Ipv4Addr::BROADCAST)) {
return Ok(Box::new(IpResource { return Ok(Box::new(IpResource {
connection: Connection::Device {
link: link, link: link,
init_data: packet.data, init_data: packet.data,
},
host_addr: ip_addr, host_addr: ip_addr,
peer_addr: packet.header.src, peer_addr: packet.header.src,
proto: proto, proto: proto,