Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: smoltcp-rs/smoltcp
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 80c20adbf821
Choose a base ref
...
head repository: smoltcp-rs/smoltcp
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 78b717cd75af
Choose a head ref
  • 4 commits
  • 13 files changed
  • 1 contributor

Commits on Jun 21, 2017

  1. Factor out the RingBuffer container.

    batonius authored and whitequark committed Jun 21, 2017
    Copy the full SHA
    4418816 View commit details
  2. Copy the full SHA
    42ca732 View commit details
  3. Add RawSocket.

    batonius authored and whitequark committed Jun 21, 2017
    Copy the full SHA
    ed08b74 View commit details
  4. Add the ping example.

    batonius authored and whitequark committed Jun 21, 2017
    Copy the full SHA
    78b717c View commit details
Showing with 748 additions and 73 deletions.
  1. +3 −0 Cargo.toml
  2. +23 −1 README.md
  3. +141 −0 examples/ping.rs
  4. +11 −1 src/iface/ethernet.rs
  5. +1 −0 src/lib.rs
  6. +38 −0 src/socket/mod.rs
  7. +244 −0 src/socket/raw.rs
  8. +2 −0 src/socket/set.rs
  9. +9 −68 src/socket/udp.rs
  10. +16 −0 src/storage/mod.rs
  11. +129 −0 src/storage/ring_buffer.rs
  12. +130 −3 src/wire/ip.rs
  13. +1 −0 src/wire/mod.rs
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -39,3 +39,6 @@ name = "server"

[[example]]
name = "client"

[[example]]
name = "ping"
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ include complicated compile-time computations, such as macro or type tricks, eve
at cost of performance degradation.

_smoltcp_ does not need heap allocation *at all*, is [extensively documented][docs],
and compiles on stable Rust 1.15 and later.
and compiles on stable Rust 1.18 and later.

[docs]: https://docs.rs/smoltcp/

@@ -214,6 +214,28 @@ cargo run --example client -- tap0 ADDRESS PORT
It connects to the given address (not a hostname) and port (e.g. `socat stdio tcp4-listen 1234`),
and will respond with reversed chunks of the input indefinitely.

### examples/ping.rs

_examples/ping.rs_ implements a minimal version of the `ping` utility using raw sockets.

The host is assigned the hardware address `02-00-00-00-00-02` and IPv4 address `192.168.69.1`.

Read its [source code](/examples/ping.rs), then run it as:

```sh
cargo run --example ping -- tap0 ADDRESS
```

It sends a series of 4 ICMP ECHO\_REQUEST packets to the given address at one second intervals and
prints out a status line on each valid ECHO\_RESPONSE received.

The first ECHO\_REQUEST packet is expected to be lost since arp\_cache is empty after startup;
the ECHO\_REQUEST packet is dropped and an ARP request is sent instead.

Currently, netmasks are not implemented, and so the only address this example can reach
is the other endpoint of the tap interface, `192.168.1.100`. It cannot reach itself because
packets entering a tap interface do not loop back.

License
-------

141 changes: 141 additions & 0 deletions examples/ping.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
#[macro_use]
extern crate log;
extern crate env_logger;
extern crate getopts;
extern crate smoltcp;
extern crate byteorder;

mod utils;

use std::str::{self, FromStr};
use std::time::{Duration, Instant};
use smoltcp::Error;
use smoltcp::wire::{EthernetAddress, IpVersion, IpProtocol, IpAddress,
Ipv4Address, Ipv4Packet, Ipv4Repr,
Icmpv4Repr, Icmpv4Packet};
use smoltcp::iface::{ArpCache, SliceArpCache, EthernetInterface};
use smoltcp::socket::{AsSocket, SocketSet};
use smoltcp::socket::{RawSocket, RawSocketBuffer, RawPacketBuffer};
use std::collections::HashMap;
use byteorder::{ByteOrder, NetworkEndian};

const PING_INTERVAL_S: u64 = 1;
const PING_TIMEOUT_S: u64 = 5;
const PINGS_TO_SEND: usize = 4;

fn main() {
utils::setup_logging();
let (device, args) = utils::setup_device(&["ADDRESS"]);

let startup_time = Instant::now();

let arp_cache = SliceArpCache::new(vec![Default::default(); 8]);

let remote_addr = Ipv4Address::from_str(&args[0]).unwrap();
let local_addr = Ipv4Address::new(192, 168, 69, 1);

let raw_rx_buffer = RawSocketBuffer::new(vec![RawPacketBuffer::new(vec![0; 256])]);
let raw_tx_buffer = RawSocketBuffer::new(vec![RawPacketBuffer::new(vec![0; 256])]);
let raw_socket = RawSocket::new(IpVersion::Ipv4, IpProtocol::Icmp,
raw_rx_buffer, raw_tx_buffer);

let hardware_addr = EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02]);
let mut iface = EthernetInterface::new(
Box::new(device), Box::new(arp_cache) as Box<ArpCache>,
hardware_addr, [IpAddress::from(local_addr)]);

let mut sockets = SocketSet::new(vec![]);
let raw_handle = sockets.add(raw_socket);

let mut send_next = Duration::default();
let mut seq_no = 0;
let mut received = 0;
let mut echo_payload = [0xffu8; 40];
let mut waiting_queue = HashMap::new();

loop {
{
let socket: &mut RawSocket = sockets.get_mut(raw_handle).as_socket();

let timestamp = Instant::now().duration_since(startup_time);
let timestamp_us = (timestamp.as_secs() * 1000000) +
(timestamp.subsec_nanos() / 1000) as u64;

if seq_no == PINGS_TO_SEND as u16 && waiting_queue.is_empty() {
break;
}

if socket.can_send() && seq_no < PINGS_TO_SEND as u16 && send_next <= timestamp {
NetworkEndian::write_u64(&mut echo_payload, timestamp_us);
let icmp_repr = Icmpv4Repr::EchoRequest {
ident: 1,
seq_no,
data: &echo_payload,
};
let ipv4_repr = Ipv4Repr {
/*src_addr: Ipv4Address::UNSPECIFIED,*/
src_addr: Ipv4Address::new(0, 0, 0, 0),
dst_addr: remote_addr,
protocol: IpProtocol::Icmp,
payload_len: icmp_repr.buffer_len(),
};

let raw_payload = socket
.send(ipv4_repr.buffer_len() + icmp_repr.buffer_len())
.unwrap();

let mut ipv4_packet = Ipv4Packet::new(raw_payload).unwrap();
ipv4_repr.emit(&mut ipv4_packet);
let mut icmp_packet = Icmpv4Packet::new(ipv4_packet.payload_mut()).unwrap();
icmp_repr.emit(&mut icmp_packet);

waiting_queue.insert(seq_no, timestamp);
seq_no += 1;
send_next += Duration::new(PING_INTERVAL_S, 0);
}

if socket.can_recv() {
let payload = socket.recv().unwrap();
let ipv4_packet = Ipv4Packet::new(payload).unwrap();
let ipv4_repr = Ipv4Repr::parse(&ipv4_packet).unwrap();

if ipv4_repr.src_addr == remote_addr && ipv4_repr.dst_addr == local_addr {
let icmp_packet = Icmpv4Packet::new(ipv4_packet.payload()).unwrap();
let icmp_repr = Icmpv4Repr::parse(&icmp_packet);

if let Ok(Icmpv4Repr::EchoReply { seq_no, data, .. }) = icmp_repr {
if let Some(_) = waiting_queue.get(&seq_no) {
let packet_timestamp_us = NetworkEndian::read_u64(data);
println!("{} bytes from {}: icmp_seq={}, time={:.3}ms",
data.len(), remote_addr, seq_no,
(timestamp_us - packet_timestamp_us) as f64 / 1000.0);
waiting_queue.remove(&seq_no);
received += 1;
}
}
}
}

waiting_queue.retain(|seq, from| {
if (timestamp - *from).as_secs() < PING_TIMEOUT_S {
true
} else {
println!("From {} icmp_seq={} timeout", remote_addr, seq);
false
}
})
}

let timestamp = Instant::now().duration_since(startup_time);
let timestamp_ms = (timestamp.as_secs() * 1000) +
(timestamp.subsec_nanos() / 1000000) as u64;
match iface.poll(&mut sockets, timestamp_ms) {
Ok(()) | Err(Error::Exhausted) => (),
Err(e) => debug!("poll error: {}", e),
}
}

println!("--- {} ping statistics ---", remote_addr);
println!("{} packets transmitted, {} received, {:.0}% packet loss",
seq_no, received, 100.0 * (seq_no - received) as f64 / seq_no as f64);
}
12 changes: 11 additions & 1 deletion src/iface/ethernet.rs
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ use wire::{Ipv4Packet, Ipv4Repr};
use wire::{Icmpv4Packet, Icmpv4Repr, Icmpv4DstUnreachable};
use wire::{IpAddress, IpProtocol, IpRepr};
use wire::{TcpPacket, TcpRepr, TcpControl};
use socket::SocketSet;
use socket::{Socket, SocketSet, RawSocket, AsSocket};
use super::ArpCache;

/// An Ethernet network interface.
@@ -179,6 +179,16 @@ impl<'a, 'b, 'c, DeviceT: Device + 'a> Interface<'a, 'b, 'c, DeviceT> {
&eth_frame.src_addr());
}

// Pass every IP packet to all raw sockets we have registered.
for raw_socket in sockets.iter_mut().filter_map(
<Socket as AsSocket<RawSocket>>::try_as_socket) {
match raw_socket.process(timestamp, &IpRepr::Ipv4(ipv4_repr),
ipv4_packet.payload()) {
Ok(()) | Err(Error::Rejected) => (),
_ => unreachable!(),
}
}

match ipv4_repr {
// Ignore IP packets not directed at us.
Ipv4Repr { dst_addr, .. } if !self.has_protocol_addr(dst_addr) => (),
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -101,6 +101,7 @@ macro_rules! net_trace_enabled {

use core::fmt;

pub mod storage;
pub mod phy;
pub mod wire;
pub mod iface;
38 changes: 38 additions & 0 deletions src/socket/mod.rs
Original file line number Diff line number Diff line change
@@ -14,10 +14,15 @@ use Error;
use phy::DeviceLimits;
use wire::IpRepr;

mod raw;
mod udp;
mod tcp;
mod set;

pub use self::raw::PacketBuffer as RawPacketBuffer;
pub use self::raw::SocketBuffer as RawSocketBuffer;
pub use self::raw::RawSocket;

pub use self::udp::PacketBuffer as UdpPacketBuffer;
pub use self::udp::SocketBuffer as UdpSocketBuffer;
pub use self::udp::UdpSocket;
@@ -44,6 +49,7 @@ pub use self::set::{Iter as SocketSetIter, IterMut as SocketSetIterMut};
/// since the lower layers treat the packet as an opaque octet sequence.
#[derive(Debug)]
pub enum Socket<'a, 'b: 'a> {
Raw(RawSocket<'a, 'b>),
Udp(UdpSocket<'a, 'b>),
Tcp(TcpSocket<'a>),
#[doc(hidden)]
@@ -53,6 +59,7 @@ pub enum Socket<'a, 'b: 'a> {
macro_rules! dispatch_socket {
($self_:expr, |$socket:ident [$( $mut_:tt )*]| $code:expr) => ({
match $self_ {
&$( $mut_ )* Socket::Raw(ref $( $mut_ )* $socket) => $code,
&$( $mut_ )* Socket::Udp(ref $( $mut_ )* $socket) => $code,
&$( $mut_ )* Socket::Tcp(ref $( $mut_ )* $socket) => $code,
&$( $mut_ )* Socket::__Nonexhaustive => unreachable!()
@@ -118,6 +125,23 @@ pub trait IpPayload {
/// concrete types.
pub trait AsSocket<T> {
fn as_socket(&mut self) -> &mut T;
fn try_as_socket(&mut self) -> Option<&mut T>;
}

impl<'a, 'b> AsSocket<RawSocket<'a, 'b>> for Socket<'a, 'b> {
fn as_socket(&mut self) -> &mut RawSocket<'a, 'b> {
match self {
&mut Socket::Raw(ref mut socket) => socket,
_ => panic!(".as_socket::<RawSocket> called on wrong socket type")
}
}

fn try_as_socket(&mut self) -> Option<&mut RawSocket<'a, 'b>> {
match self {
&mut Socket::Raw(ref mut socket) => Some(socket),
_ => None,
}
}
}

impl<'a, 'b> AsSocket<UdpSocket<'a, 'b>> for Socket<'a, 'b> {
@@ -127,6 +151,13 @@ impl<'a, 'b> AsSocket<UdpSocket<'a, 'b>> for Socket<'a, 'b> {
_ => panic!(".as_socket::<UdpSocket> called on wrong socket type")
}
}

fn try_as_socket(&mut self) -> Option<&mut UdpSocket<'a, 'b>> {
match self {
&mut Socket::Udp(ref mut socket) => Some(socket),
_ => None,
}
}
}

impl<'a, 'b> AsSocket<TcpSocket<'a>> for Socket<'a, 'b> {
@@ -136,4 +167,11 @@ impl<'a, 'b> AsSocket<TcpSocket<'a>> for Socket<'a, 'b> {
_ => panic!(".as_socket::<TcpSocket> called on wrong socket type")
}
}

fn try_as_socket(&mut self) -> Option<&mut TcpSocket<'a>> {
match self {
&mut Socket::Tcp(ref mut socket) => Some(socket),
_ => None,
}
}
}
Loading