Chapter 8: Networking
Introduces the chapter’s progression from high-level HTTP requests to raw TCP packets and previews related Rust topics.
After this chapter you will be able to
- HTTP in the protocol stack. Describe HTTP as an application-level protocol for transporting web content, with TLS providing encryption on top of TCP.
- Layer boundaries and interactions. Explain the basic separation between networking layers and give an example of how information can nevertheless cross a layer boundary.
- HTTP-aware library responsibilities. Explain why a short reqwest implementation can still handle more HTTP behavior than the lower-level implementations, including response handling and details the program would otherwise manage.
- GET request and response body. Trace what reqwest::get() does with a URL and how the program obtains the response body as text.
- Mixed-type game containers. Explain why trait objects are useful for keeping different character races in one container in the role-playing game example.
- Trait object dispatch tradeoff. Explain that trait objects let different types share an interface through dynamic dispatch, whereas generics use static dispatch. Describe the section’s tradeoff: generics use more disk space but run faster; trait objects use less disk space but add a small runtime cost from pointer indirection.
- Trait object pointer forms. Describe why trait objects appear behind pointers and distinguish the owned Box<dyn Trait> form from the borrowed &dyn Trait and &mut dyn Trait forms.
- Mixed character party. Explain how the rpg example uses a trait object to put humans, elves, and dwarves in one party and choose a character to attempt an enchantment.
- HTTP version and connection closure. Explain why the example specifies HTTP 1.0 rather than HTTP 1.1: the server closes the connection after responding, whereas an HTTP 1.1 connection could remain open while the client waits and sends no further request.
- Hostname in the Host header. Explain why the request includes a Host header even though the hostname was supplied when connecting: TcpStream::connect() uses an IP address, so the header puts the hostname into the HTTP request.
- Hostname to IP resolution. Explain why the application needs DNS to turn a hostname into an IP address before sending an internet message, and describe resolution as asking a server that may ask other servers.
- Resolve application message flow. Trace the resolve application's work from command-line input through constructing and sending a DNS message to decoding and printing the reply, including the role of trust-dns.
- DNS replies over UDP. Explain how a DNS request can receive a reply over UDP despite UDP having no long-lived, duplex connection.
- Mixed Result type challenge. Explain why the section says the ? operator becomes a challenge when a function incorporates Result types from two upstream crates.
- Question Mark Error Conversion. Explain how ? handles Ok and Err results, and why parsing an IPv6 address causes a compiler error when main returns std::io::Error.
- Boxed Error Trait Object. Explain how Box<dyn Error> lets main return errors from more than one source, and why the trait object is placed in a Box.
- Custom upstream error wrapper. Describe how an enum can hold multiple upstream error types and how map_err() converts each error into that enum before ? returns it.
- From conversion ergonomics. Explain how implementing From for each upstream error type removes explicit map_err() calls and who bears the extra work.
- Error handling shortcut tradeoff. Explain why unwrap() and expect() may be reasonable in main() but are not recommended for library authors when dealing with multiple error types.
- MAC and IP address roles. Distinguish the roles of MAC and IP addresses: IP addresses enable communication between networks, while MAC addresses are understood by devices sharing a physical or radio link.
- Universal and local addresses. Distinguish universally administered from locally administered MAC addresses and state which form software should use when setting one.
- Unicast and multicast recipients. Explain how unicast and multicast MAC addresses affect acceptance of an Ethernet frame, rather than its transmission.
- Generated MAC address flags. Explain how macgen marks its generated MAC addresses as locally assigned and unicast.
- Enum-Based Connectivity State Machine. Explain how a loop, a match, and a Rust enum form a simple state machine that updates its state as connectivity changes.
- Raw TCP access restrictions. Explain why working with raw TCP packets typically requires root or superuser access and how a Linux proxy device lets non-superusers communicate without making raw network requests directly.
- TAP device setup sequence. Describe the section’s sequence for creating and using the Linux TAP device named tap-rust, including how to check its creation and what remains to make it usable.
- mget project file roles. Describe how mget divides the work of a manual HTTP request among its source files, without needing to explain the implementations inside them.
What it builds on
- Making use of the Result return type (chapter 3) · Introduces the Result return type used by .text().
- Defining common behavior with traits (chapter 3) · Introduces traits as definitions of common behavior.
- Vectors (chapter 2) · The use of vectors to hold values.
- Generic functions (chapter 2) · Introduces generic functions.
- Using references (chapter 2) · Introduces references.
- What is an owner? Does it have any responsibilities? (chapter 4) · Introduces ownership.
- Supporting command-line arguments (chapter 2) · Supplying input through command-line arguments.
- Match: Type-aware pattern matching (chapter 2) · Using match for branching.
Section by section
Introduction
Introduces the chapter’s progression from high-level HTTP requests to raw TCP packets and previews related Rust topics.
8.1 All of networking in seven paragraphs
Introduces networking through HTTP, explaining the roles of protocol layers and why their boundaries are not always clean.
- HTTP in the protocol stack: Describe HTTP as an application-level protocol for transporting web content, with TLS providing encryption on top of TCP.
Why it matters: These roles give you a starting point for understanding what happens when web content is delivered. - Layer boundaries and interactions: Explain the basic separation between networking layers and give an example of how information can nevertheless cross a layer boundary.
Why it matters: A layered model is helpful, but real protocols do not always stay neatly within its boundaries. - HTTP as a practical entry point: Justify the section’s focus on HTTP by relating it to web frameworks and the practical benefits of understanding their protocol.
Why it matters: It connects the networking overview to work a web programmer may already recognize.
Key terms: HTTP, application-level protocol, TLS, TCP, HTTPS, layers, Connection: keep-alive, http-equiv, web frameworks
8.2 Generating an HTTP GET request with reqwest
This section uses reqwest to make an HTTP GET request and explains which HTTP details the library handles for the program.
- HTTP-aware library responsibilities: Explain why a short reqwest implementation can still handle more HTTP behavior than the lower-level implementations, including response handling and details the program would otherwise manage.
Why it matters: It explains what the convenience of an HTTP-specific library buys you. - GET request and response body: Trace what reqwest::get() does with a URL and how the program obtains the response body as text.
Why it matters: It identifies the two calls at the heart of the example and what each produces. - Boxed error trait object: Describe what Box<dyn std::error::Error> means in the example and why it is called a trait object.
Why it matters: It gives a first reading of the error type shown with the request example.
Key terms: reqwest, HTTP headers, content redirects, TLS, content’s encoding, reqwest::get(), response, .text(), Result, HTTP body
8.3 Trait objects
Introduces the upcoming discussion of trait objects and the rpg project, while pointing networking-focused readers to section 8.4.
8.3.1 What do trait objects enable?
This subsection explains why trait objects are useful when a container must hold values of different types.
- Mixed-type game containers: Explain why trait objects are useful for keeping different character races in one container in the role-playing game example.
Why it matters: This gives a concrete reason to use trait objects in the game example.
Key terms: trait objects, Vec<T>, struct, wrapper object
8.3.2 What is a trait object?
This section explains how trait objects provide polymorphism through dynamic dispatch, how they compare with generics, and why they appear behind pointers.
- Trait object dispatch tradeoff: Explain that trait objects let different types share an interface through dynamic dispatch, whereas generics use static dispatch. Describe the section’s tradeoff: generics use more disk space but run faster; trait objects use less disk space but add a small runtime cost from pointer indirection.
Why it matters: This tradeoff helps explain why a Rust program might use a trait object rather than a generic type. - Trait object pointer forms: Describe why trait objects appear behind pointers and distinguish the owned Box<dyn Trait> form from the borrowed &dyn Trait and &mut dyn Trait forms.
Why it matters: Recognizing these forms makes it easier to read whether code owns or borrows a trait object.
Key terms: trait objects, polymorphism, dynamic dispatch, static dispatch, pointer indirection, dynamically-sized types, &dyn Trait, &mut dyn Trait, Box<dyn Trait>, owned
8.3.3 Creating a tiny role-playing game: The rpg project
The rpg example shows how trait objects let a party contain different character types and explains how trait-object references differ from references to concrete types.
- Mixed character party: Explain how the rpg example uses a trait object to put humans, elves, and dwarves in one party and choose a character to attempt an enchantment.
Why it matters: The party gives a concrete reason to use a trait object rather than a container limited to one character type. - Trait object type erasure: Explain what type erasure means in this example and identify runtime dispatch as another use of trait objects.
Why it matters: This explains what is obscured when different character types are handled through one trait-object interface. - Trait and struct references: Distinguish a reference to a trait object such as &dyn Rng from a reference to a concrete struct value such as &ThreadRng.
Why it matters: The distinction makes similar-looking Rust references less likely to be confused.
Key terms: trait object, Human, Elf, Dwarf, Enchanter, Thing, type erasure, dynamic dispatch, enchant(), trait
8.4 TCP
This section explains two choices in an HTTP GET request built over a TCP connection: using HTTP 1.0 and including a Host header.
- HTTP version and connection closure: Explain why the example specifies HTTP 1.0 rather than HTTP 1.1: the server closes the connection after responding, whereas an HTTP 1.1 connection could remain open while the client waits and sends no further request.
Why it matters: The HTTP version determines whether this one-request client can rely on the server closing the connection. - Hostname in the Host header: Explain why the request includes a Host header even though the hostname was supplied when connecting: TcpStream::connect() uses an IP address, so the header puts the hostname into the HTTP request.
Why it matters: It explains why repeating the hostname in the request is necessary rather than redundant.
Key terms: HTTP 1.0, HTTP 1.1, keep alive, TcpStream::connect(), IP address, Host HTTP header
8.4.1 What is a port number?
8.4.2 Converting a hostname to an IP address
This section explains why a hostname must be resolved before making an internet request and uses a small DNS application to illustrate the request, response, and buffer handling involved.
- Hostname to IP resolution: Explain why the application needs DNS to turn a hostname into an IP address before sending an internet message, and describe resolution as asking a server that may ask other servers.
Why it matters: It explains the DNS step between having a website name and making a request. - Resolve application message flow: Trace the resolve application's work from command-line input through constructing and sending a DNS message to decoding and printing the reply, including the role of trust-dns.
Why it matters: Following the flow connects a human-readable lookup with the bytes sent to and received from a DNS server. - Receive buffer length requirement: Distinguish a vector's capacity from its length and explain why the DNS response buffer is initialized with 512 elements rather than merely reserving capacity for them.
Why it matters: A buffer can have allocated capacity yet still fail an API's check for usable length. - DNS replies over UDP: Explain how a DNS request can receive a reply over UDP despite UDP having no long-lived, duplex connection.
Why it matters: It clarifies how DNS can exchange a question and answer without a TCP-style connection.
Key terms: hostname, IP address, Domain Name System (DNS), domain name resolution, resolve, trust-dns, DNS message, stream of bytes, Vec::with_capacity(512), vec![0; 512]
8.5 Ergonomic error handling for libraries
This introduction identifies a limitation of the ? operator when a function combines Result types from different crates.
- Mixed Result type challenge: Explain why the section says the ? operator becomes a challenge when a function incorporates Result types from two upstream crates.
Why it matters: This explains why combining the domain-resolution and TCP code calls for attention to error handling.
Key terms: Result types, ? operator, upstream crates
8.5.1 Issue: Unable to return multiple error types
This subsection explains why a function returning one concrete error type cannot automatically handle a different error type, and how a boxed error trait object allows either error to be returned.
- Question Mark Error Conversion: Explain how ? handles Ok and Err results, and why parsing an IPv6 address causes a compiler error when main returns std::io::Error.
Why it matters: It explains why a convenient ? expression can still fail to compile when operations produce different errors. - Boxed Error Trait Object: Explain how Box<dyn Error> lets main return errors from more than one source, and why the trait object is placed in a Box.
Why it matters: It provides the section’s way past the compiler error without requiring both operations to produce the same concrete error type.
Key terms: Result<T, E>, ? operator, try! macro, std::net::AddrParseError, std::io::Error, Box<dyn Error>, trait object, Box, File::open()
8.5.2 Wrapping downstream errors by defining our own error type
This section shows how to preserve errors from dependencies in a custom error type and make those errors convenient to return.
- Trait object error tradeoff: Explain why returning Box<dyn Error> can be convenient but loses information about which upstream error type occurred.
Why it matters: This tradeoff explains why the section introduces a custom error type instead of relying solely on trait objects. - Custom upstream error wrapper: Describe how an enum can hold multiple upstream error types and how map_err() converts each error into that enum before ? returns it.
Why it matters: The wrapper lets a function return one error type without discarding which upstream error occurred. - From conversion ergonomics: Explain how implementing From for each upstream error type removes explicit map_err() calls and who bears the extra work.
Why it matters: Defining conversions once in a library makes error propagation less cumbersome for its users.
Key terms: trait objects, type erasure, Box<dyn Error>, upstream errors, enum, #[derive(Debug)], Display, Error, map_err(), std::convert::From
8.5.3 Cheating with unwrap() and expect()
This subsection presents unwrap() and expect() as a shortcut for dealing with multiple error types, while warning against their use in libraries.
- Error handling shortcut tradeoff: Explain why unwrap() and expect() may be reasonable in main() but are not recommended for library authors when dealing with multiple error types.
Why it matters: The appropriate way to handle an error depends on whether a crash affects only your program or a library’s users.
Key terms: unwrap(), expect(), main(), library authors
8.6 MAC addresses
This section explains what MAC addresses identify, how they differ from IP addresses, and how their administration and recipient modes work.
- MAC and IP address roles: Distinguish the roles of MAC and IP addresses: IP addresses enable communication between networks, while MAC addresses are understood by devices sharing a physical or radio link.
Why it matters: It explains why a device can have both an IP address and a MAC address. - MAC address structure: Describe how MAC addresses differ from IP addresses in hierarchy and width.
Why it matters: These differences prevent treating a MAC address as though it were simply another kind of IP address. - Universal and local addresses: Distinguish universally administered from locally administered MAC addresses and state which form software should use when setting one.
Why it matters: The distinction tells you which form to choose when creating a MAC address yourself. - Unicast and multicast recipients: Explain how unicast and multicast MAC addresses affect acceptance of an Ethernet frame, rather than its transmission.
Why it matters: It clarifies that unicast describes who accepts a frame, not a private transmission to just two devices.
Key terms: MAC addresses, IP addresses, Ethernet, physical link, hierarchical, 48 bits, IPv4, IPv6, universally administered, locally administered
8.6.1 Generating MAC addresses
The section explains how macgen sets flag bits when generating MAC addresses for a virtual device.
- Generated MAC address flags: Explain how macgen marks its generated MAC addresses as locally assigned and unicast.
Why it matters: Those flags give the virtual device MAC addresses with the properties described in the section.
Key terms: macgen, MAC address, octets[0], locally assigned, unicast
8.7 Implementing state machines with Rust’s enums
The section introduces a simple enum-based state machine for adapting network code to changes in connectivity.
- Enum-Based Connectivity State Machine: Explain how a loop, a match, and a Rust enum form a simple state machine that updates its state as connectivity changes.
Why it matters: This gives network code a simple way to represent and respond to changing connectivity.
Key terms: state machine, loop, match, enum, transitions
8.8 Raw TCP
The section explains why raw TCP access is restricted and introduces a Linux proxy device as a way for non-superusers to work with it.
- Raw TCP access restrictions: Explain why working with raw TCP packets typically requires root or superuser access and how a Linux proxy device lets non-superusers communicate without making raw network requests directly.
Why it matters: This explains the access constraint behind the Linux device introduced next.
Key terms: raw TCP packets, root/superuser access, proxy device
8.9 Creating a virtual networking device
This section explains why to use a virtual TAP device and outlines how to create, verify, configure, and remove one on Linux.
- Virtual networking hardware benefits: Explain why the section uses virtual networking hardware instead of changing physical network hardware settings.
Why it matters: It lets you experiment with network addresses without risking your existing connection. - TAP device setup sequence: Describe the section’s sequence for creating and using the Linux TAP device named tap-rust, including how to check its creation and what remains to make it usable.
Why it matters: Creating a device alone does not assign it an address or direct packets to it.
Key terms: virtual hardware, IP and MAC addresses, TAP device, tap-rust, ip tuntap list, del, add
8.10 “Raw” HTTP
Introduces mget as a manually assembled HTTP client, identifies each source file’s role, and contrasts it with a higher-level GET call.
- mget project file roles: Describe how mget divides the work of a manual HTTP request among its source files, without needing to explain the implementations inside them.
Why it matters: The file roles show how the chapter’s networking pieces fit into one client. - Manual HTTP complexity: Explain how the section compares mget with the earlier high-level GET call.
Why it matters: The comparison puts the effort of working closer to TCP in perspective.
Key terms: mget, main.rs, ethernet.rs, http.rs, dns.rs, DNS resolution, request::get(url)
8.11 Summary
Where this is used later
- Improving error handling (chapter 9) · Provides a prior example of simplifying error handling through conversions.
Concepts explained in this chapter
- trait objectA trait object is a dynamically sized Rust value representing a value of a type that implements a trait and accessed through a pointer for runtime polymorphism.
- DNS resolutionDomain name resolution is the process of retrieving network resource records for a domain name from the Domain Name System.
- IP addressAn IP address is a numerical network-layer identifier assigned to a host or interface in an Internet Protocol network.
- TCPTCP (Transmission Control Protocol) is a transport-layer network protocol that provides reliable, ordered, connection-oriented delivery of data.
- UDPUDP is a connectionless transport protocol that sends independent datagrams without guaranteeing delivery, ordering, or duplication control.
- Errorstd::error::Error is Rust's standard trait for representing errors that can provide descriptions and optional source errors.
- From traitFrom is a Rust standard-library trait for infallibly converting a value of one type into another type.
- HTTPHTTP (Hypertext Transfer Protocol) is an application-layer protocol for exchanging requests and responses between clients and servers.
- enumAn enum is a type whose value is one of a finite set of named variants, optionally carrying associated data.
- dns messageA DNS message is a structured network packet used to carry a DNS query or response.
- domain nameA domain name is a hierarchical, human-readable identifier for an Internet host or other DNS resource.
- domain name systemThe Domain Name System is a distributed naming system that maps domain names to network resource records such as IP addresses.
- trust-dns crateThe trust-dns crate is a Rust library for constructing, sending, and interpreting DNS protocol messages.
- mac addressA MAC address is a link-layer identifier assigned to a network interface for communication on a local physical or wireless link.
- DisplayThe Display trait is Rust's standard formatting trait for producing a user-facing textual representation of a value.
- BoxBox is Rust's owning smart pointer type for storing a value on the heap while keeping a pointer to it in the containing value.
- exceptionAn exception is a synchronous or otherwise special execution event that interrupts normal program flow because of an error or exceptional condition.
- result typeResult is Rust's standard result type that contains either a successful value or an error value.
- unwrapThe unwrap method extracts a successful value from a Result or Option and panics when it contains an error or no value.
These guides are generated from the text of Manning books by Manning's knowledge graph and link to the sections they draw on. The full books are available on liveBook and at manning.com.
