parent
2afc538fd9
commit
2d716883b6
|
@ -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"
|
||||
|
|
14
README.md
14
README.md
|
@ -190,6 +190,10 @@ 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:
|
||||
|
@ -197,6 +201,8 @@ It's possible to let _smoltcp_ access Internet by enabling routing for the tap i
|
|||
```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
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
@ -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![]);
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
@ -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()))
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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!()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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> {
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -341,7 +341,7 @@ impl<'a, 'b> IcmpSocket<'a, 'b> {
|
|||
let ip_repr = IpRepr::Ipv6(Ipv6Repr {
|
||||
src_addr: src_addr,
|
||||
dst_addr: ipv6_addr,
|
||||
next_header: IpProtocol::Icmp,
|
||||
next_header: IpProtocol::Icmpv6,
|
||||
payload_len: repr.buffer_len(),
|
||||
hop_limit: hop_limit,
|
||||
});
|
||||
|
@ -609,7 +609,7 @@ mod test_ipv6 {
|
|||
static LOCAL_IPV6_REPR: IpRepr = IpRepr::Ipv6(Ipv6Repr {
|
||||
src_addr: Ipv6Address::UNSPECIFIED,
|
||||
dst_addr: REMOTE_IPV6,
|
||||
next_header: IpProtocol::Icmp,
|
||||
next_header: IpProtocol::Icmpv6,
|
||||
payload_len: 24,
|
||||
hop_limit: 0x40
|
||||
});
|
||||
|
@ -617,7 +617,7 @@ mod test_ipv6 {
|
|||
static REMOTE_IPV6_REPR: IpRepr = IpRepr::Ipv6(Ipv6Repr {
|
||||
src_addr: REMOTE_IPV6,
|
||||
dst_addr: LOCAL_IPV6,
|
||||
next_header: IpProtocol::Icmp,
|
||||
next_header: IpProtocol::Icmpv6,
|
||||
payload_len: 24,
|
||||
hop_limit: 0x40
|
||||
});
|
||||
|
@ -683,7 +683,7 @@ mod test_ipv6 {
|
|||
assert_eq!(ip_repr, IpRepr::Ipv6(Ipv6Repr {
|
||||
src_addr: Ipv6Address::UNSPECIFIED,
|
||||
dst_addr: REMOTE_IPV6,
|
||||
next_header: IpProtocol::Icmp,
|
||||
next_header: IpProtocol::Icmpv6,
|
||||
payload_len: ECHOV6_REPR.buffer_len(),
|
||||
hop_limit: 0x2a,
|
||||
}));
|
||||
|
@ -757,7 +757,7 @@ mod test_ipv6 {
|
|||
header: Ipv6Repr {
|
||||
src_addr: LOCAL_IPV6,
|
||||
dst_addr: REMOTE_IPV6,
|
||||
next_header: IpProtocol::Icmp,
|
||||
next_header: IpProtocol::Icmpv6,
|
||||
payload_len: 12,
|
||||
hop_limit: 0x40
|
||||
},
|
||||
|
@ -766,7 +766,7 @@ mod test_ipv6 {
|
|||
let ip_repr = IpRepr::Unspecified {
|
||||
src_addr: REMOTE_IPV6.into(),
|
||||
dst_addr: LOCAL_IPV6.into(),
|
||||
protocol: IpProtocol::Icmp,
|
||||
protocol: IpProtocol::Icmpv6,
|
||||
payload_len: icmp_repr.buffer_len(),
|
||||
hop_limit: 0x40
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue