From 8863eb8db1901700c15cd2ea65dee1096c2d4f52 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 18 Dec 2017 14:53:09 +0000 Subject: [PATCH] Add an HTTP client example. --- Cargo.toml | 11 +++-- README.md | 69 ++++++++++++++++++---------- examples/httpclient.rs | 101 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 154 insertions(+), 27 deletions(-) create mode 100644 examples/httpclient.rs diff --git a/Cargo.toml b/Cargo.toml index 53fce52..8ca10a2 100644 --- a/Cargo.toml +++ b/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" diff --git a/README.md b/README.md index 2e39b80..65591ca 100644 --- a/README.md +++ b/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 diff --git a/examples/httpclient.rs b/examples/httpclient.rs new file mode 100644 index 0000000..ea16658 --- /dev/null +++ b/examples/httpclient.rs @@ -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::(tcp_handle); + + state = match state { + State::Connect if !socket.is_active() => { + debug!("connecting"); + let local_port = 49152 + rand::random::() % 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"); + } +}