Skip to content

Commit

Permalink
Add support for IPv6 gateways.
Browse files Browse the repository at this point in the history
  • Loading branch information
progval committed May 13, 2018
1 parent ce2fbb7 commit c08b8b5
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 41 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Expand Up @@ -57,7 +57,7 @@ required-features = ["std", "phy-raw_socket", "proto-ipv4"]

[[example]]
name = "httpclient"
required-features = ["std", "phy-tap_interface", "proto-ipv4", "socket-tcp"]
required-features = ["std", "phy-tap_interface", "proto-ipv4", "proto-ipv6", "socket-tcp"]

[[example]]
name = "ping"
Expand Down
14 changes: 13 additions & 1 deletion README.md
Expand Up @@ -190,13 +190,19 @@ a specific user:
sudo ip tuntap add name tap0 mode tap user $USER
sudo ip link set tap0 up
sudo ip addr add 192.168.69.100/24 dev tap0
sudo ip -6 addr add fe80::100/64 dev tap0
sudo ip -6 addr add fdaa::100/64 dev tap0
sudo ip -6 route add fe80::/64 dev tap0
sudo ip -6 route add fdaa::/64 dev tap0
```

It's possible to let _smoltcp_ access Internet by enabling routing for the tap interface:

```sh
sudo iptables -t nat -A POSTROUTING -s 192.168.69.0/24 -j MASQUERADE
sudo sysctl net.ipv4.ip_forward=1
sudo ip6tables -t nat -A POSTROUTING -s fdaa::/64 -j MASQUERADE
sudo sysctl -w net.ipv6.conf.all.forwarding=1
```

### Fault injection
Expand Down Expand Up @@ -245,7 +251,7 @@ sudo ./target/debug/examples/tcpdump eth0

_examples/httpclient.rs_ emulates a network host that can initiate HTTP requests.

The host is assigned the hardware address `02-00-00-00-00-02` and IPv4 address `192.168.69.1`.
The host is assigned the hardware address `02-00-00-00-00-02`, IPv4 address `192.168.69.1`, and IPv6 address `fdaa::1`.

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

Expand All @@ -259,6 +265,12 @@ For example:
cargo run --example httpclient -- tap0 93.184.216.34 http://example.org/
```

or:

```sh
cargo run --example httpclient -- tap0 2606:2800:220:1:248:1893:25c8:1946 http://example.org/
```

It connects to the given address (not a hostname) and URL, and prints any returned response data.
The TCP socket buffers are limited to 1024 bytes to make packet traces more interesting.

Expand Down
8 changes: 6 additions & 2 deletions examples/httpclient.rs
Expand Up @@ -13,7 +13,7 @@ use std::collections::BTreeMap;
use std::os::unix::io::AsRawFd;
use url::Url;
use smoltcp::phy::wait as phy_wait;
use smoltcp::wire::{EthernetAddress, Ipv4Address, IpAddress, IpCidr};
use smoltcp::wire::{EthernetAddress, Ipv4Address, Ipv6Address, IpAddress, IpCidr};
use smoltcp::iface::{NeighborCache, EthernetInterfaceBuilder};
use smoltcp::socket::{SocketSet, TcpSocket, TcpSocketBuffer};
use smoltcp::time::Instant;
Expand Down Expand Up @@ -42,13 +42,17 @@ fn main() {
let tcp_socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer);

let ethernet_addr = EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02]);
let ip_addrs = [IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24)];
let ip_addrs = [IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24),
IpCidr::new(IpAddress::v6(0xfdaa, 0, 0, 0, 0, 0, 0, 1), 64),
IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64)];
let default_v4_gw = Ipv4Address::new(192, 168, 69, 100);
let default_v6_gw = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x100);
let mut iface = EthernetInterfaceBuilder::new(device)
.ethernet_addr(ethernet_addr)
.neighbor_cache(neighbor_cache)
.ip_addrs(ip_addrs)
.ipv4_gateway(default_v4_gw)
.ipv6_gateway(default_v6_gw)
.finalize();

let mut sockets = SocketSet::new(vec![]);
Expand Down
101 changes: 74 additions & 27 deletions examples/ping.rs
Expand Up @@ -15,12 +15,47 @@ use smoltcp::time::{Duration, Instant};
use smoltcp::phy::Device;
use smoltcp::phy::wait as phy_wait;
use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr,
Ipv6Address, Icmpv6Repr, Icmpv6Packet,
Ipv4Address, Icmpv4Repr, Icmpv4Packet};
use smoltcp::iface::{NeighborCache, EthernetInterfaceBuilder};
use smoltcp::socket::{SocketSet, IcmpSocket, IcmpSocketBuffer, IcmpPacketMetadata, IcmpEndpoint};
use std::collections::HashMap;
use byteorder::{ByteOrder, NetworkEndian};

macro_rules! send_icmp_ping {
( $repr_type:ident, $packet_type:ident, $ident:expr, $seq_no:expr,
$echo_payload:expr, $socket:expr, $remote_addr:expr ) => {{
let icmp_repr = $repr_type::EchoRequest {
ident: $ident,
seq_no: $seq_no,
data: &$echo_payload,
};

let icmp_payload = $socket
.send(icmp_repr.buffer_len(), $remote_addr)
.unwrap();

let mut icmp_packet = $packet_type::new(icmp_payload);
(icmp_repr, icmp_packet)
}}
}

macro_rules! get_icmp_pong {
( $repr_type:ident, $repr:expr, $payload:expr, $waiting_queue:expr, $remote_addr:expr,
$timestamp:expr, $received:expr ) => {{
if let $repr_type::EchoReply { seq_no, data, .. } = $repr {
if let Some(_) = $waiting_queue.get(&seq_no) {
let packet_timestamp_ms = NetworkEndian::read_i64(data);
println!("{} bytes from {}: icmp_seq={}, time={}ms",
data.len(), $remote_addr, seq_no,
$timestamp.total_millis() - packet_timestamp_ms);
$waiting_queue.remove(&seq_no);
$received += 1;
}
}
}}
}

fn main() {
utils::setup_logging("warn");

Expand All @@ -40,7 +75,7 @@ fn main() {
let fd = device.as_raw_fd();
let device = utils::parse_middleware_options(&mut matches, device, /*loopback=*/false);
let device_caps = device.capabilities();
let address = Ipv4Address::from_str(&matches.free[0]).expect("invalid address format");
let address = IpAddress::from_str(&matches.free[0]).expect("invalid address format");
let count = matches.opt_str("count").map(|s| usize::from_str(&s).unwrap()).unwrap_or(4);
let interval = matches.opt_str("interval")
.map(|s| Duration::from_secs(u64::from_str(&s).unwrap()))
Expand All @@ -52,19 +87,23 @@ fn main() {
let neighbor_cache = NeighborCache::new(BTreeMap::new());

let remote_addr = address;
let local_addr = Ipv4Address::new(192, 168, 69, 1);

let icmp_rx_buffer = IcmpSocketBuffer::new(vec![IcmpPacketMetadata::EMPTY], vec![0; 256]);
let icmp_tx_buffer = IcmpSocketBuffer::new(vec![IcmpPacketMetadata::EMPTY], vec![0; 256]);
let icmp_socket = IcmpSocket::new(icmp_rx_buffer, icmp_tx_buffer);

let ethernet_addr = EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02]);
let ip_addr = IpCidr::new(IpAddress::from(local_addr), 24);
let src_ipv6 = IpAddress::v6(0xfdaa, 0, 0, 0, 0, 0, 0, 1);
let ip_addrs = [IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24),
IpCidr::new(src_ipv6, 64),
IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64)];
let default_v4_gw = Ipv4Address::new(192, 168, 69, 100);
let default_v6_gw = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x100);
let mut iface = EthernetInterfaceBuilder::new(device)
.ethernet_addr(ethernet_addr)
.ip_addrs([ip_addr])
.ip_addrs(ip_addrs)
.ipv4_gateway(default_v4_gw)
.ipv6_gateway(default_v6_gw)
.neighbor_cache(neighbor_cache)
.finalize();

Expand All @@ -77,7 +116,6 @@ fn main() {
let mut echo_payload = [0xffu8; 40];
let mut waiting_queue = HashMap::new();
let ident = 0x22b;
let endpoint = IpAddress::Ipv4(remote_addr);

loop {
iface.poll(&mut sockets, Instant::now()).unwrap();
Expand All @@ -93,18 +131,23 @@ fn main() {
if socket.can_send() && seq_no < count as u16 &&
send_at <= timestamp {
NetworkEndian::write_i64(&mut echo_payload, timestamp.total_millis());
let icmp_repr = Icmpv4Repr::EchoRequest {
ident: ident,
seq_no,
data: &echo_payload,
};

let icmp_payload = socket
.send(icmp_repr.buffer_len(), endpoint)
.unwrap();

let mut icmp_packet = Icmpv4Packet::new(icmp_payload);
icmp_repr.emit(&mut icmp_packet, &device_caps.checksum);
match remote_addr {
IpAddress::Ipv4(_) => {
let (icmp_repr, mut icmp_packet) = send_icmp_ping!(
Icmpv4Repr, Icmpv4Packet, ident, seq_no,
echo_payload, socket, remote_addr);
icmp_repr.emit(&mut icmp_packet, &device_caps.checksum);
},
IpAddress::Ipv6(_) => {
let (icmp_repr, mut icmp_packet) = send_icmp_ping!(
Icmpv6Repr, Icmpv6Packet, ident, seq_no,
echo_payload, socket, remote_addr);
icmp_repr.emit(&src_ipv6, &remote_addr,
&mut icmp_packet, &device_caps.checksum);
},
_ => unimplemented!()
}

waiting_queue.insert(seq_no, timestamp);
seq_no += 1;
Expand All @@ -113,18 +156,22 @@ fn main() {

if socket.can_recv() {
let (payload, _) = socket.recv().unwrap();
let icmp_packet = Icmpv4Packet::new(&payload);
let icmp_repr = Icmpv4Repr::parse(&icmp_packet, &device_caps.checksum).unwrap();

if let Icmpv4Repr::EchoReply { seq_no, data, .. } = icmp_repr {
if let Some(_) = waiting_queue.get(&seq_no) {
let packet_timestamp_ms = NetworkEndian::read_i64(data);
println!("{} bytes from {}: icmp_seq={}, time={}ms",
data.len(), remote_addr, seq_no,
timestamp.total_millis() - packet_timestamp_ms);
waiting_queue.remove(&seq_no);
received += 1;

match remote_addr {
IpAddress::Ipv4(_) => {
let icmp_packet = Icmpv4Packet::new(&payload);
let icmp_repr = Icmpv4Repr::parse(&icmp_packet, &device_caps.checksum).unwrap();
get_icmp_pong!(Icmpv4Repr, icmp_repr, payload,
waiting_queue, remote_addr, timestamp, received);
}
IpAddress::Ipv6(_) => {
let icmp_packet = Icmpv6Packet::new(&payload);
let icmp_repr = Icmpv6Repr::parse(&remote_addr, &src_ipv6,
&icmp_packet, &device_caps.checksum).unwrap();
get_icmp_pong!(Icmpv6Repr, icmp_repr, payload,
waiting_queue, remote_addr, timestamp, received);
},
_ => unimplemented!()
}
}

Expand Down
62 changes: 58 additions & 4 deletions src/iface/ethernet.rs
Expand Up @@ -65,6 +65,8 @@ struct InterfaceInner<'b, 'c> {
ip_addrs: ManagedSlice<'c, IpCidr>,
#[cfg(feature = "proto-ipv4")]
ipv4_gateway: Option<Ipv4Address>,
#[cfg(feature = "proto-ipv6")]
ipv6_gateway: Option<Ipv6Address>,
device_capabilities: DeviceCapabilities,
}

Expand All @@ -77,6 +79,8 @@ pub struct InterfaceBuilder <'b, 'c, DeviceT: for<'d> Device<'d>> {
ip_addrs: ManagedSlice<'c, IpCidr>,
#[cfg(feature = "proto-ipv4")]
ipv4_gateway: Option<Ipv4Address>,
#[cfg(feature = "proto-ipv6")]
ipv6_gateway: Option<Ipv6Address>,
}

impl<'b, 'c, DeviceT> InterfaceBuilder<'b, 'c, DeviceT>
Expand Down Expand Up @@ -113,7 +117,9 @@ impl<'b, 'c, DeviceT> InterfaceBuilder<'b, 'c, DeviceT>
neighbor_cache: None,
ip_addrs: ManagedSlice::Borrowed(&mut []),
#[cfg(feature = "proto-ipv4")]
ipv4_gateway: None
ipv4_gateway: None,
#[cfg(feature = "proto-ipv6")]
ipv6_gateway: None,
}
}

Expand Down Expand Up @@ -158,11 +164,28 @@ impl<'b, 'c, DeviceT> InterfaceBuilder<'b, 'c, DeviceT>
where T: Into<Ipv4Address>
{
let addr = gateway.into();
InterfaceInner::check_gateway_addr(&addr);
InterfaceInner::check_ipv4_gateway_addr(&addr);
self.ipv4_gateway = Some(addr);
self
}

/// Set the IPv6 gateway the interface will use. See also
/// [ipv6_gateway].
///
/// # Panics
/// This function panics if the given address is not unicast.
///
/// [ipv6_gateway]: struct.EthernetInterface.html#method.ipv6_gateway
#[cfg(feature = "proto-ipv6")]
pub fn ipv6_gateway<T>(mut self, gateway: T) -> InterfaceBuilder<'b, 'c, DeviceT>
where T: Into<Ipv6Address>
{
let addr = gateway.into();
InterfaceInner::check_ipv6_gateway_addr(&addr);
self.ipv6_gateway = Some(addr);
self
}

/// Set the Neighbor Cache the interface will use.
pub fn neighbor_cache(mut self, neighbor_cache: NeighborCache<'b>) ->
InterfaceBuilder<'b, 'c, DeviceT> {
Expand Down Expand Up @@ -192,6 +215,8 @@ impl<'b, 'c, DeviceT> InterfaceBuilder<'b, 'c, DeviceT>
ip_addrs: self.ip_addrs,
#[cfg(feature = "proto-ipv4")]
ipv4_gateway: self.ipv4_gateway,
#[cfg(feature = "proto-ipv6")]
ipv6_gateway: self.ipv6_gateway,
}
}
},
Expand Down Expand Up @@ -299,7 +324,24 @@ impl<'b, 'c, DeviceT> Interface<'b, 'c, DeviceT>
pub fn set_ipv4_gateway<GatewayAddrT>(&mut self, gateway: GatewayAddrT)
where GatewayAddrT: Into<Option<Ipv4Address>> {
self.inner.ipv4_gateway = gateway.into();
self.inner.ipv4_gateway.map(|addr| InterfaceInner::check_gateway_addr(&addr));
self.inner.ipv4_gateway.map(|addr| InterfaceInner::check_ipv4_gateway_addr(&addr));
}

/// Get the IPv6 gateway of the interface.
#[cfg(feature = "proto-ipv6")]
pub fn ipv6_gateway(&self) -> Option<Ipv6Address> {
self.inner.ipv6_gateway
}

/// Set the IPv6 gateway of the interface.
///
/// # Panics
/// This function panics if the given address is not unicast.
#[cfg(feature = "proto-ipv6")]
pub fn set_ipv6_gateway<GatewayAddrT>(&mut self, gateway: GatewayAddrT)
where GatewayAddrT: Into<Option<Ipv6Address>> {
self.inner.ipv6_gateway = gateway.into();
self.inner.ipv6_gateway.map(|addr| InterfaceInner::check_ipv6_gateway_addr(&addr));
}

/// Transmit packets queued in the given sockets, and receive packets queued
Expand Down Expand Up @@ -490,7 +532,14 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
}

#[cfg(feature = "proto-ipv4")]
fn check_gateway_addr(addr: &Ipv4Address) {
fn check_ipv4_gateway_addr(addr: &Ipv4Address) {
if !addr.is_unicast() {
panic!("gateway IP address {} is not unicast", addr);
}
}

#[cfg(feature = "proto-ipv6")]
fn check_ipv6_gateway_addr(addr: &Ipv6Address) {
if !addr.is_unicast() {
panic!("gateway IP address {} is not unicast", addr);
}
Expand Down Expand Up @@ -1146,6 +1195,11 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
Some(gateway) => Ok(gateway.into()),
None => Err(Error::Unaddressable),
}
#[cfg(feature = "proto-ipv6")]
&IpAddress::Ipv6(_) => match self.ipv6_gateway {
Some(gateway) => Ok(gateway.into()),
None => Err(Error::Unaddressable),
}
_ => Err(Error::Unaddressable)
}
}
Expand Down

0 comments on commit c08b8b5

Please sign in to comment.