Add an HTTP client example.
parent
946376897e
commit
8863eb8db1
11
Cargo.toml
11
Cargo.toml
|
@ -26,6 +26,8 @@ features = ["map"]
|
|||
log = "0.3"
|
||||
env_logger = "0.4"
|
||||
getopts = "0.2"
|
||||
rand = "0.3"
|
||||
url = "1.0"
|
||||
|
||||
[features]
|
||||
std = ["managed/std"]
|
||||
|
@ -38,10 +40,10 @@ verbose = []
|
|||
"socket-udp" = []
|
||||
"socket-tcp" = []
|
||||
"socket-icmp" = []
|
||||
default = ["std", "log",
|
||||
default = [
|
||||
"phy-raw_socket", "phy-tap_interface",
|
||||
"socket-raw", "socket-udp", "socket-tcp",
|
||||
"socket-icmp"]
|
||||
"socket-raw", "socket-icmp", "socket-udp", "socket-tcp"
|
||||
]
|
||||
|
||||
[[example]]
|
||||
name = "tcpdump"
|
||||
|
@ -52,6 +54,9 @@ name = "server"
|
|||
[[example]]
|
||||
name = "client"
|
||||
|
||||
[[example]]
|
||||
name = "httpclient"
|
||||
|
||||
[[example]]
|
||||
name = "ping"
|
||||
|
||||
|
|
69
README.md
69
README.md
|
@ -207,9 +207,52 @@ cargo build --example tcpdump
|
|||
sudo ./target/debug/tcpdump eth0
|
||||
```
|
||||
|
||||
### examples/httpclient.rs
|
||||
|
||||
_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`.
|
||||
|
||||
Read its [source code](/examples/httpclient.rs), then run it as:
|
||||
|
||||
```sh
|
||||
cargo run --example client -- tap0 ADDRESS URL
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```sh
|
||||
cargo run --example client -- tap0 93.184.216.34 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.
|
||||
|
||||
### 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.
|
||||
|
||||
### examples/server.rs
|
||||
|
||||
_examples/server.rs_ emulates a network host that can respond to requests.
|
||||
_examples/server.rs_ emulates a network host that can respond to basic requests.
|
||||
|
||||
The host is assigned the hardware address `02-00-00-00-00-01` and IPv4 address `192.168.69.1`.
|
||||
|
||||
|
@ -239,7 +282,7 @@ of testing resource exhaustion conditions.
|
|||
|
||||
### examples/client.rs
|
||||
|
||||
_examples/client.rs_ emulates a network host that can initiate requests.
|
||||
_examples/client.rs_ emulates a network host that can initiate basic requests.
|
||||
|
||||
The host is assigned the hardware address `02-00-00-00-00-02` and IPv4 address `192.168.69.2`.
|
||||
|
||||
|
@ -252,28 +295,6 @@ 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.
|
||||
|
||||
## Bare-metal usage examples
|
||||
|
||||
Examples that use no services from the host OS are necessarily less illustrative than examples
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate env_logger;
|
||||
extern crate getopts;
|
||||
extern crate rand;
|
||||
extern crate url;
|
||||
extern crate smoltcp;
|
||||
|
||||
mod utils;
|
||||
|
||||
use std::str::{self, FromStr};
|
||||
use std::collections::BTreeMap;
|
||||
use std::time::Instant;
|
||||
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::iface::{NeighborCache, EthernetInterfaceBuilder};
|
||||
use smoltcp::socket::{SocketSet, TcpSocket, TcpSocketBuffer};
|
||||
|
||||
fn main() {
|
||||
utils::setup_logging("");
|
||||
|
||||
let (mut opts, mut free) = utils::create_options();
|
||||
utils::add_tap_options(&mut opts, &mut free);
|
||||
utils::add_middleware_options(&mut opts, &mut free);
|
||||
free.push("ADDRESS");
|
||||
free.push("URL");
|
||||
|
||||
let mut matches = utils::parse_options(&opts, free);
|
||||
let device = utils::parse_tap_options(&mut matches);
|
||||
let fd = device.as_raw_fd();
|
||||
let device = utils::parse_middleware_options(&mut matches, device, /*loopback=*/false);
|
||||
let address = IpAddress::from_str(&matches.free[0]).expect("invalid address format");
|
||||
let url = Url::parse(&matches.free[1]).expect("invalid url format");
|
||||
|
||||
let startup_time = Instant::now();
|
||||
|
||||
let neighbor_cache = NeighborCache::new(BTreeMap::new());
|
||||
|
||||
let tcp_rx_buffer = TcpSocketBuffer::new(vec![0; 1024]);
|
||||
let tcp_tx_buffer = TcpSocketBuffer::new(vec![0; 1024]);
|
||||
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 default_v4_gw = Ipv4Address::new(192, 168, 69, 100);
|
||||
let mut iface = EthernetInterfaceBuilder::new(device)
|
||||
.ethernet_addr(ethernet_addr)
|
||||
.neighbor_cache(neighbor_cache)
|
||||
.ip_addrs(ip_addrs)
|
||||
.ipv4_gateway(default_v4_gw)
|
||||
.finalize();
|
||||
|
||||
let mut sockets = SocketSet::new(vec![]);
|
||||
let tcp_handle = sockets.add(tcp_socket);
|
||||
|
||||
enum State { Connect, Request, Response };
|
||||
let mut state = State::Connect;
|
||||
|
||||
loop {
|
||||
{
|
||||
let mut socket = sockets.get::<TcpSocket>(tcp_handle);
|
||||
|
||||
state = match state {
|
||||
State::Connect if !socket.is_active() => {
|
||||
debug!("connecting");
|
||||
let local_port = 49152 + rand::random::<u16>() % 16384;
|
||||
socket.connect((address, url.port().unwrap_or(80)), local_port).unwrap();
|
||||
State::Request
|
||||
}
|
||||
State::Request if socket.may_send() => {
|
||||
debug!("sending request");
|
||||
let http_get = "GET ".to_owned() + url.path() + " HTTP/1.1\r\n";
|
||||
socket.send_slice(http_get.as_ref()).expect("cannot send");
|
||||
let http_host = "Host: ".to_owned() + url.host_str().unwrap() + "\r\n";
|
||||
socket.send_slice(http_host.as_ref()).expect("cannot send");
|
||||
socket.send_slice(b"Connection: close\r\n").expect("cannot send");
|
||||
socket.send_slice(b"\r\n").expect("cannot send");
|
||||
State::Response
|
||||
}
|
||||
State::Response if socket.can_recv() => {
|
||||
socket.recv(|data| {
|
||||
println!("{}", str::from_utf8(data).unwrap_or("(invalid utf8)"));
|
||||
(data.len(), ())
|
||||
}).unwrap();
|
||||
State::Response
|
||||
}
|
||||
State::Response if !socket.may_recv() => {
|
||||
debug!("received complete response");
|
||||
break
|
||||
}
|
||||
_ => state
|
||||
}
|
||||
}
|
||||
|
||||
let timestamp = utils::millis_since(startup_time);
|
||||
let poll_at = iface.poll(&mut sockets, timestamp).expect("poll error");
|
||||
phy_wait(fd, poll_at.map(|at| at.saturating_sub(timestamp))).expect("wait error");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue