Add an HTTP client example.
This commit is contained in:
parent
946376897e
commit
8863eb8db1
11
Cargo.toml
11
Cargo.toml
|
@ -26,6 +26,8 @@ features = ["map"]
|
||||||
log = "0.3"
|
log = "0.3"
|
||||||
env_logger = "0.4"
|
env_logger = "0.4"
|
||||||
getopts = "0.2"
|
getopts = "0.2"
|
||||||
|
rand = "0.3"
|
||||||
|
url = "1.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
std = ["managed/std"]
|
std = ["managed/std"]
|
||||||
|
@ -38,10 +40,10 @@ verbose = []
|
||||||
"socket-udp" = []
|
"socket-udp" = []
|
||||||
"socket-tcp" = []
|
"socket-tcp" = []
|
||||||
"socket-icmp" = []
|
"socket-icmp" = []
|
||||||
default = ["std", "log",
|
default = [
|
||||||
"phy-raw_socket", "phy-tap_interface",
|
"phy-raw_socket", "phy-tap_interface",
|
||||||
"socket-raw", "socket-udp", "socket-tcp",
|
"socket-raw", "socket-icmp", "socket-udp", "socket-tcp"
|
||||||
"socket-icmp"]
|
]
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "tcpdump"
|
name = "tcpdump"
|
||||||
|
@ -52,6 +54,9 @@ name = "server"
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "client"
|
name = "client"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "httpclient"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "ping"
|
name = "ping"
|
||||||
|
|
||||||
|
|
69
README.md
69
README.md
|
@ -207,9 +207,52 @@ cargo build --example tcpdump
|
||||||
sudo ./target/debug/tcpdump eth0
|
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
|
||||||
|
|
||||||
_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`.
|
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
|
||||||
|
|
||||||
_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`.
|
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`),
|
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.
|
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
|
## Bare-metal usage examples
|
||||||
|
|
||||||
Examples that use no services from the host OS are necessarily less illustrative than 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