Add an HTTP client example.

v0.7.x
whitequark 2017-12-18 14:53:09 +00:00
parent 946376897e
commit 8863eb8db1
3 changed files with 154 additions and 27 deletions

View File

@ -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"

View File

@ -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

101
examples/httpclient.rs Normal file
View File

@ -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");
}
}