Beej's Guide To Network Concepts
Beej's Guide To Network Concepts
Beej's Guide To Network Concepts
1 Foreword 1
1.1 Audience . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 Official Homepage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.3 Email Policy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.4 Mirroring . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.5 Note for Translators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.6 Copyright and Distribution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.7 Dedication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
2 Networking Overview 3
2.1 Circuit Switched . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2.2 Packet Switched . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.3 Client/Server Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.4 The OS, Network Programming, and Sockets . . . . . . . . . . . . . . . . . . . . . . 4
2.5 Protocols . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.6 Network Layers and Abstraction . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.7 Wired versus Wireless . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.8 Reflect . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
iii
iv CONTENTS
11 Parsing Packets 51
11.1 You Know What Would Make This Easy? . . . . . . . . . . . . . . . . . . . . . . . 51
11.2 Processing a Stream into Packets . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
11.3 The Sentences Example Again . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
11.3.1 What If You Receive Multiple Sentences at Once? . . . . . . . . . . . . . . . . 53
11.4 The Grand Scheme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
11.5 Reflect . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
18 IP Routing 89
18.1 Routing Protocols . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
18.1.1 Interior Gateway Protocols . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
18.1.2 Exterior Gateway Protocols . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
18.2 Routing tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
18.3 Routing Algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
18.4 Routing Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
18.5 Routing Loops and Time-To-Live . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
18.6 The Broadcast Address . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
18.7 Reflect . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
29 Select 139
29.1 The Problem We’re Solving . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
29.2 Using select() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
29.3 Using select() with Listening Sockets . . . . . . . . . . . . . . . . . . . . . . . . 140
29.4 The Main Algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
29.5 What About Those Other Arguments to select()? . . . . . . . . . . . . . . . . . . 140
29.5.1 The Timeout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
29.6 Reflect . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
29.7 Using select() with send() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
36 Firewalls 165
36.1 Firewall Operation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
36.2 Firewalls and NAT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
36.3 Local Firewalls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
36.4 Reflect . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
Foreword
What is this? Well, it’s a guide to a bunch of concepts that you might see in networking. It’s not Network
Programming in C—see Beej’s Guide to Network Programming1 for that. But it is here to help make sense
of the terminology, and also to do a bit of network programming in Python.
Is it Beej’s Guide to Network Programming in Python? Well, kinda, actually. The C book is more about
how C’s (well, Unix’s) network API works. And this book is more about the concepts underlying it, using
Python as a vehicle.
I trust that’s perfectly confusing. Maybe just skip to the Audience section, below.
1.1 Audience
Are you completely new to networking and are confused by all these terms like ISO-OSI, TCP/IP, ports,
Ethernet, LANs, and all that? And maybe you want to write some network-capable code in Python?
Congrats! You’re the target audience!
But be forewarned: this guide assumes that you’ve already got some Python programming knowledge
under your belt.
1
2 Chapter 1. Foreword
1.4 Mirroring
You are more than welcome to mirror this site, whether publicly or privately. If you publicly mirror the
site and want me to link to it from the main page, drop me a line at beej@beej.us.
1.7 Dedication
The hardest things about writing these guides are:
• Learning the material in enough detail to be able to explain it
• Figuring out the best way to explain it clearly, a seemingly-endless iterative process
• Putting myself out there as a so-called authority, when really I’m just a regular human trying to
make sense of it all, just like everyone else
• Keeping at it when so many other things draw my attention
A lot of people have helped me through this process, and I want to acknowledge those who have made
this book possible.
• Everyone on the Internet who decided to help share their knowledge in one form or another. The
free sharing of instructive information is what makes the Internet the great place that it is.
• Everyone who submitted corrections and pull-requests on everything from misleading instructions
to typos.
Thank you! ♥
Chapter 2
Networking Overview
The Big Idea of networking is that we’re going to get arbitrary bytes of data exchanged between two (or
more) computers on the Internet or LAN.
So we need:
• A way of identifying the source and destination computers.
• A way of maintaining data integrity.
• A way of routing data from one computer to another (out of billions).
And we need all the hardware and software support to make this happen.
Let’s take a look at two basic kinds of communications networks.
3
4 Chapter 2. Networking Overview
Other languages and operating systems have added the same Internet functionality over time, and many
of them use different calls in their APIs. But as an homage to the original, many of these APIs are still
called “sockets” APIs even if they don’t match the original.
If you want to use the original sockets API, you can do it programming with C in Unix.
2.5 Protocols
You know that conversation that the client and server have? It’s written down very specifically what bytes
get sent when and from and to whom. You can’t just send any old data to a web server–it has to be wrapped
up a certain way.
Just like you can’t take a letter, wrap it up in aluminum foil with no address, and expect the post office to
deliver it to your intended recipient. That’s breaking post office protocol.
Both the sender and recipient have to be speaking the same protocol for correct communication to occur.
“Thank you for calling The Pizza Restaurant. Can I help you?” “Would you like fries with that?”
A person calling a pizza restaurant breaks protocol.
There are many protocols, and we’ll cover a few of them in detail later. These were invented by people to
solve different sorts of problems. If you need to pass data between two specialized programs you write,
you’ll have to define a protocol for that, too!
Here are some common ones you might have heard of:
• TCP - used to transmit data reliably.
• UDP - used to transmit data quickly and unreliably.
• IP - used to route packets over the network from one computer to another.
• HTTP - used to get web pages and make other web requests.
• Ethernet - used to send data over a LAN.
As we’ll see in a moment, these protocols “live” at different layers of the network software.
This works well because each layer is responsible for different parts of the process, e.g. one layer handles
data integrity, and another handles routing the packet over the network, and another handles the data itself
that is being transmitted between the programs. And each layer doesn’t care about what the layers below
it are doing with the data.
It’s that last concept that’s really important: when data is going over WiFi, the WiFi hardware doesn’t
even care what the data is, if it’s Internet data or not, how integrity is assured (or not). All WiFi cares
about is getting a big chunk of data transmitted over the air to another computer. When it arrives at the
other computer, that computer will strip off the Ethernet stuff and look deeper in the packet, deciding what
to do with it.
And since the layers don’t care what data is encapsulated below them, you can swap out protocols at
various layers and still have the rest of them work. So if you’re writing a program at the top layer (where
we tend to write them most commonly), you don’t care what’s happening at the layers below that. It’s
Somebody Else’s Problem.
For example, you might be getting a web page with HTTP/TCP/IP/Ethernet, or you might be transmitting
a file to another computer with TFTP/UDP/IP/Ethernet. IP and Ethernet work fine in both cases, because
they are indifferent about the data they are sending.
There are many, many details omitted from this description, but we’re still in high-level overview land.
2.8 Reflect
• What kind of switched network is the Internet?
• What is the relationship between a client program and a server program?
• What role does the OS play when you’re writing networked programs?
• What is a protocol?
• What are the reasons for having a protocol stack and data encapsulation?
• What are the practical differences between a WiFi network and a wired network?
Chapter 3
In Unix, the sockets API generally gives processes a way to communicate with one another. It supports a
variety of methods of communication, and one of those methods is over the Internet.
And that’s the one we’re interested in right now.
In C and Unix, the sockets API is a blend of library calls and system calls (functions that call the OS
directly).
In Python, the Python sockets API is a library that calls the lower-level C sockets API. At least on Unix-
likes. On other platforms, it will call whatever API that OS exposes for network communication.
We’re going to use this to write programs that communicate over the Internet!
7
8 Chapter 3. Introducing The Sockets API
4. Send data and receive data. This is the part we’ve been waiting for.
5. Close the connection. When we’re done, we close the socket indicating to the remote side that we
have nothing more to say. The remote side can also close the connection any time it wishes.
2. Bind the socket to a port. This is where you assign a port number to the server that other clients
can connect to. “I’m going to be listening on port 80!” for instance.
Caveat: programs that aren’t run as root/administrator can’t bind to ports under 1024–those are
reserved. Choose a big, uncommon port number for your servers, like something in the 15,000-
30,000 range. If you try to bind to a port another server is using, you’ll get an “Address already in
use” error.
Ports are per-computer. It’s OK if two different computers use the same port. But two programs on
the same computer cannot use the same port on that computer.
Fun fact: clients are bound to a port, as well. If you don’t explicitly bind them, they get assigned
an unused port when the connect–which is usually what we want.
3. Listen for incoming connections. We have to let the OS know when it gets an incoming connection
request on the port we selected.
4. Accept incoming connections. The server will block (it will sleep) when you try to accept a new
connection if none are pending. Then it wakes up when someone tries to connect.
Accept returns a new socket! This is confusing. The original socket the server made in step 1 is
still there listening for new connections. When the connection arrives, the OS makes a new socket
specifically for that one connection. This way the server can handle multiple clients at once.
Sometimes the server spawns a new thread or process to handle each new client. But there’s no law
that says it has to.
5. Send data and receive data. This is typically where the server would receive a request from the
client, and the server would send back the response to that request.
6. Go back and accept another connection. Servers tend to be long-running processes and handle
many requests over their lifetimes.
3.3 Reflect
• What role does bind() play on the server side?
• Would a client ever call bind()? (Might have to search this one on the Internet.)
• Speculate on why accept() returns a new socket as opposed to just reusing the one we called
listen() with.
• What would happen if the server didn’t loop to another accept() call? What would happen when
a second client tried to connect?
• If one computer is using TCP port 3490, can another computer use port 3490?
• Speculate about why ports exist. What functionality do they make possible that plain IP addresses
do not?
3.4. Resources 9
3.4 Resources
• Python Sockets Documentation
• Beej’s Guide to Network Programming1 –optional, for C devs
1
https://beej.us/guide/bgnet
10 Chapter 3. Introducing The Sockets API
Chapter 4
Ports under 1024 need root/administrator privileges to bind to (but not to connect to).
• TCP – Transmission Control Protocol, responsible for reliable, in-order data transmission. From a
higher-up perspective, makes a packet-switched network feel more like a circuit-switched network.
TCP uses port numbers to identify senders and receivers of data.
This protocol was invented in 1974 and is still in extremely heavy use today.
In the sockets API, TCP sockets are called stream sockets.
• UDP – sibling of TCP, except lighter weight. Doesn’t guarantee data will arrive, or that it will be
in order, or that it won’t be duplicated. If it arrives, it will be error-free, but that’s all you get.
In the sockets API, UDP sockets are called datagram sockets.
• IPv6 Address – Four bytes isn’t enough to hold a unique address, so IP version 6 ex-
pands the address size considerably to 16 bytes. IPv6 addresses look like this: ::1 or
2001:db8::8a2e:370:7334, or even bigger.
• NAT – Network Address Translation. A way to allow organizations to have private subnets with
non-globally-unique addresses that get translated to globally-unique addresses as they pass through
the router.
Private subnets commonly start with addresses 192.168.x.x or 10.x.x.x.
11
12 Chapter 4. The Layered Network Model
• Router – A specialized computer that forwards packets through the packet switching network. It
inspects destination IP addresses to determine which route will get the packet closer to its goal.
• IP – Internet Protocol. This is responsible for identifying computers by IP address and using those
addresses to route data to recipients through a variety of routers.
• LAN – Local Area Network. A network where all the computers are effectively directly connected,
not via a router.
• Interface – physical networking hardware on a computer. A computer might have a number of
interfaces. Your computer likely has two: a wired Ethernet interface and a wireless Ethernet inter-
face.
A router might have a large number of interfaces to be able to route packets to a large number of
destinations. Your home router probably only has two interfaces: one facing inward to your LAN
and the other facing outward to the rest of the Internet.
Each interface typically has one IP address and one MAC address.
The OS names the interfaces on your local machine. They might be something like wlan0 or eth2
or something else. It depends on the hardware and the OS.
• Header – Some data that is prepended to some other data by a particular protocol. The header con-
tains information appropriate for that protocol. A TCP header would include some error detection
and correction information and a source and destination port number. IP would include the source
and destination IP addresses. Ethernet would include the source and destination MAC addresses.
And an HTTP response would include things like the length of the data, the date modified, and
whether or not the request was successful.
Putting a header in front of the data is analogous to putting your letter in an envelope in the snail-mail
analogy. Or putting that envelope in another envelope.
As data moves through the network, additional headers are added and removed. Typically only the
top-most (front-most?) header is removed or added in normal operation, like a stack. (But some
software and hardware peeks deeper.)
Network Adapter – Another name for “network card”, the hardware on your computer that does
network stuff.
MAC Address – Ethernet interfaces have MAC addresses, which take the form aa:bb:cc:dd:ee:ff,
where the fields are random-ish one-byte hex numbers. MAC addresses are 6 bytes long, and must
be unique on the LAN. When a network adapter is manufactured, it is given a unique MAC address
that it keeps for life, typically.
GET / HTTP/1.1
Host: example.com
Connection: close
And that’s all the browser cares about. It doesn’t care about IP routing or TCP data integrity or
Ethernet.
It just says “Send this data to that computer on port 80”.
2. The OS takes over and says, “OK, you asked me to send this over a stream-oriented socket, and I’m
going to use the TCP protocol to do that and ensure all the data arrives intact and in order.”
So the OS takes the HTTP data and wraps it in a TCP header which includes the port number.
3. And then the OS says, “And you wanted to send it to this remote computer whose IP address is
198.51.100.2, so we’ll use the IP protocol to do that.”
And it takes the entire TCP-HTTP data and wraps it up in an IP header. So now we have data that
looks like this: IP-TCP-HTTP.
4. After that, the OS takes a look at its routing table and decides where to send the data next. Maybe
the web server is on the LAN, conveniently. More likely, it’s somewhere else, so the data would be
sent to the router for your house destined for the greater Internet.
In either case, it’s going to send the data to a server on the LAN, or to your outbound router, also
on the LAN. So it’s going to a computer on the LAN.
And computers on the LAN have an Ethernet address (AKA MAC address–which stands for “Me-
dia Access Control”), so the sending OS looks up the MAC address that corresponds to the next
destination IP address, whether that’s a local web server or the outbound router. (This happens via
a lookup in something called the ARP Cache, but we’ll get to that part of the story another time.)
And it wraps the whole IP-TCP-HTTP packet in an Ethernet header, so it becomes Ethernet-IP-TCP-
HTTP. The web request is still in there, buried under layers of protocols!
5. And finally, the data goes out on the wire (even if it’s WiFi, we still say “on the wire”).
The computer with the destination MAC address, listening carefully, sees the Ethernet packet on the wire
and reads it in. (Ethernet packets are called Ethernet frames.)
It strips off the Ethernet header, exposing the IP header below it. It looks at the destination IP address.
1. If the inspecting computer is a server and it has that IP address, its OS strips off the IP header and
looks deeper. (If it doesn’t have that IP address, something’s wrong and it discards the packet.)
2. It looks at the TCP header and does all the TCP magic needed to make sure the data isn’t corrupted.
If it is, it replies back with the magic TCP incantations, saying, “Hey, I need you to send that data
again, please.”
Note that the web browser or server never knows about this TCP conversation that’s happening. It’s
all behind the scenes. For all it can see, the data is just magically arriving intact and in order.
The reason is that they’re on a higher layer of the network. They don’t have to worry about routing
or anything. The lower layers take care of it.
3. If everything’s good with TCP, that header gets stripped and the OS is left with the HTTP data. It
wakes up the process (the web server) that was waiting to read it, and gives it the HTTP data.
But what if the destination Ethernet address was an intermediate router?
1. The router strips off the Ethernet frame as always.
2. The router looks at the destination IP address. It consults its routing table and decides to which
interface to forward the packet.
3. It sends it out to that interface, which wraps it up in another Ethernet frame and sends it to the next
router in line.
14 Chapter 4. The Layered Network Model
(Or maybe it’s not Ethernet! Ethernet is a protocol, and there are other low-level protocols in use
with fiber optic lines and so on. This is part of the beauty of these layers of abstraction–you can
switch protocols partway through transmission and the HTTP data above it is completely unaware
that any such thing has happened.)
You can see how different protocols take on the responsibilities of each layer in the model.
Another way to think of this is that all the programs that implement HTTP or FTP or SMTP can use TCP
or UDP to transmit data. (Typically all sockets programs and applications you write that implement any
protocol will live at the application layer.)
And all data that’s transmitted with TCP or UDP can use IP or IPv6 for routing.
And all data that uses IP or IPv6 for routing can use Ethernet or PPP, etc. for going over the wire.
And as a packet moves down through the layers before being transmitted over the wire, the protocols add
their own headers on top of everything else so far.
This model is complex enough for working on the Internet. You know what they say: as simple as possible,
but no simpler.
But there might be other networks in the Universe that aren’t the Internet, so there’s a more general model
out there that folks sometimes use: the OSI model.
And if we look at the OSI model, we can see some of the protocols that exist at those various layers, similar
to what we saw with the Internet model, above.
ISO OSI
Layer Responsibility Example Protocols
Application Structured application data HTTP, FTP, TFTP, Telnet, SMTP,
POP, IMAP
Presentation Encoding translation, encryption, compression MIME, SSL/TLS, XDR
Session Suspending, terminating, restarting sessions Sockets, TCP
between computers
Transport Data integrity, packet splitting and reassembly TCP, UDP
Network Routing IP IPv6, ICMP
Data link Encapsulation into frames Ethernet, PPP, SLIP
Physical Physical, signals on wires Ethernet physical layer, DSL,
ISDN
We’re going to stick with the Internet model for this course since it’s good enough for 99.9% of the network
programming work you’d ever be likely to do. But please be aware of the OSI model if you’re going into
an interview for a network-specific programming position.
4.5 Reflect
• When a router sees an IP address, how does it know where to forward it?
• If an IPv4 address is 4 bytes, roughly how many different computers can that represent in total,
assuming each computer has a unique IP address?
• Same question, except for IPv6 and its 16-byte addresses?
• Bonus question for stats nerds: The odds of winning the super lotto jackpot are approximately 300
million to 1. What are the odds of randomly picking my pre-selected 16-byte (128-bit) number?
• Speculate on why the IP header wraps up the TCP header in the layered model, and not the other
way around.
• If UDP is unreliable and TCP is reliable, speculate on why one might ever use UDP.
16 Chapter 4. The Layered Network Model
Chapter 5
We’re going to write a sockets program that can download files from a web server! This is going to be
our “web client”. This will work with almost any web server out there, if we code it right.
And as if that’s not enough, we’re going to follow it up by writing a simple web server! This program will
be able to handle requests from the web client we write… or indeed any other web client such as Chrome
or Firefox!
These programs are going to speak a protocol you have probably heard of: HTTP, the HyperText Transport
Protocol.
And because they speak HTTP, and web browsers like Chrome speak HTTP, they should be able to com-
municate!
5.1 Restrictions
In order to better understand the sockets API at a lower level, this project may not use any of the following
helper functions:
• The socket.create_connection() function.
• The socket.create_server() function.
• Anything in the urllib modules.
After coding up the project, it should be more obvious how these helper functions are implemented.
17
18 Chapter 5. Project: HTTP Client and Server
To convert from a byte sequence you received from a socket in ISO-8859-1 format to a string:
s = b.decode("ISO-8859-1")
GET / HTTP/1.1
Host: example.com
Connection: close
That shows the request header which consists of the request method, path, and protocol on the first line,
followed by any number of header fields. There is a blank line at the end of the header.
This request is saying “Get the root web page from the server example.com and I’m going to close the
connection as soon as I get your response.”
Ends-of-line are delimited by a Carriage Return/Linefeed combination. In Python or C, you write a CRLF
like this:
"\r\n"
If you were requesting a specific file, it would be on that first line, for example:
(And if there were a payload to go with this header, it would go just after the blank line. There would also
be a Content-Length header giving the length of the payload in bytes. We don’t have to worry about
this for this project.)
A simple HTTP response from a server looks like:
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 6
Connection: close
Hello!
This response says, “Your request succeeded and here’s a response that’s 6 bytes of plain text. Also, I’m
going to close the connection right after I send this to you. And the response payload is ‘Hello!’.”
Notice that the Content-Length is set to the size of the payload: 6 bytes for Hello!.
Another common Content-Type is text/html when the payload has HTML data in it.
5.4. The Client 19
HTTP/1.1 200 OK
Age: 586480
Cache-Control: max-age=604800
Content-Type: text/html; charset=UTF-8
Date: Thu, 22 Sep 2022 22:20:41 GMT
Etag: "3147526947+ident"
Expires: Thu, 29 Sep 2022 22:20:41 GMT
Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT
Server: ECS (sec/96EE)
Vary: Accept-Encoding
X-Cache: HIT
Content-Length: 1256
Connection: close
<!doctype html>
<html>
<head>
<title>Example Domain</title>
...
(Output truncated, but it would show the rest of the HTML for the site.)
Notice how the first part of the output is the HTTP response with all those fields from the server, and then
there’s a blank line, and everything following the blank line is the response payload.
ALSO: you need to be able specify a port number to connect to on the command line. This defaults to
port 80 if not specified. So you could connect to a webserver on a different port like so:
import socket
This function takes a tuple as an argument that contains the host and port to connect to, e.g.
20 Chapter 5. Project: HTTP Client and Server
("example.com", 80)
• Build and send the HTTP request. You can use the simple HTTP request shown above. Don’t
forget the blank line at the end of the header, and don’t forget to end all lines with "\r\n"!
I recommend using the s.sendall() method to do this. You could use .send() instead but it
might only send part of the data.
(C programmers will find an implementation of sendall() in Beej’s Guide.)
• Receive the web response with the s.recv() method. It will return some bytes in response. You’ll
have to call it several times in a loop to get all the data from bigger sites.
It will return a byte array of zero elements when the server closes the connection and there’s no
more data to read, e.g.:
$ python webserver.py
The server is going to going to run forever, handling incoming requests. (Forever means “until you hit
CTRL-C”.)
And it’s only going to send back one thing no matter what the request is. Have it send back the simple
server response, shown above.
So it’s not a very full-featured webserver. But it’s the start of one!
Here are some Python specifics:
• Get a socket just like you did for the client.
• After the call to socket(), you should add this crazy-looking line:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
where s is the socket descriptor you got from socket(). This will prevent an “Address already in
use” error on the bind() in certain circumstances which would certainly be confusing at the time.
Usually it happens after the server crashes. Later on we’ll figure out why that error occurs.
5.6. Hints and Help 21
If you do get that error and don’t feel like adding this line of code because you’re feeling contrary,
you can also just wait a few minutes for the OS to give up on the broken connection..
• Bind the socket to a port with s.bind(). This takes one argument, a tuple containing the address
and port you want to bind to. The address can be left blank to have it choose a local address. For
example, “Any local address, port 28333” would be passed like so:
('', 28333)
new_conn = s.accept()
new_socket = new_conn[0] # This is what we'll recv/send on
• Receive the request from the client. You should call new_socket.recv() in a loop similar to how
you did it with the client.
When you see a blank line (i.e. "\r\n\r\n") in the request, you’ve read enough and can quit
receiving.
(We don’t handle payloads in the request for this project. The right thing to do would be to look for
a Content-Length header and then receive the header plus that many bytes. But that’s a stretch
goal for you.)
Beware: you can’t just loop until recv() returns an empty string this time! This would only happen
if the client closed the connection, but the client isn’t closing the connection and it’s waiting for a
response. So you have to call recv() repeatedly until you see that blank line delimiting the end of
the header.
• Send the response. You should just send the “simple server reponse”, from above.
• Close the new socket with new_socket.close().
• Loop back to s.accept() to get the next request.
Now run the web server in one window and run the client in another, and see if it connects!
Once it’s working with webclient.py, try it with a web browser!
Run the server on an unused port (choose a big one at random):
Go to the URL http://localhost:20123/ to view the page. (localhost is the name of “this com-
puter”.)
If it works, great!
Try printing out the value returned by .accept(). What’s in there?
Did you notice that if you use a web browser to connect to your server, the browser actually makes two
connections? Dig into it and see if you can figure out why!
Notice the first line is telling us the resource we’re looking for has moved.
The second line with the Location: field tells us to where it has moved.
When a web browser sees a 301 redirect, it automatically goes to the other URL so you don’t have to
worry about it.
Try it! Enter google.com in your browser and watch it update to www.google.com after a moment.
5.7 Extensions
These are here if you have time to give yourself the additional challenge for greater understanding of the
material. Push yourself!
• Modify the server to print out the IP address and port of the client that just connected to it. Hint:
look at the value returned by accept() in Python.
• Modify the client to be able to send payloads. You’ll need to be able to set the Content-Type and
Content-Length based on the payload.
• Modify the server to extract and print the “request method” from the request. This is most often
GET, but it could also be POST or DELETE or many others.
• Modify the server to extract and print a payload sent by the client.
24 Chapter 5. Project: HTTP Client and Server
Chapter 6
This protocol is responsible for routing packets of data around the Internet, analogous to how the post
office is responsible for routing letters around the mail network.
Like with the post office, data on the Internet has to be labeled with a source and destination address,
called the IP address.
The IP address is a sequence of bytes that uniquely identifies every computer on the Internet.
6.1 Terminology
• Host - another name for “computer”.
6.3 Subnets
Every IP address is split into two portions.
The initial bits of the IP address identify individual networks.
The trailing bits of an IP address identify individual hosts (i.e. computers) on that network.
These individual networks are called subnets and the number of hosts they can support depends on how
many bits they’re reserved for identifying hosts on that subnet.
As a contrived non-Internet example, let’s look at an 8-bit “address”, and we’ll say the first 6 bits are the
network number and the last 2 bits are the host number.
So an address like this:
25
26 Chapter 6. The Internet Protocol (IP)
00010111
is split into two parts (because we said the first 6 bits were the network number):
Network Host
------- ----
000101 11
And this is OK because people aren’t generally trying to connect to servers on your laptop. It’s usually
the laptop that’s connecting to other servers.
How does it work? On one of the servers on the LAN is a program that is listening for such requests,
which conform to DHCP (the Dynamic Host Configuration Protocol). The DHCP server keeps track of
which IP addresses on the subnet are already allocated for use, and which are free. It allocates a free one
and sends back a DHCP response that has your laptop’s new IP address, as well as other data about the
LAN your computer needs (like subnet mask, etc.).
If you have WiFi at home, you very likely already have a DHCP server. Most routers come from your ISP
with DHCP already set up, which is how your laptop gets its IP address on your LAN.
6.7 Reflect
• How many times more IPv6 addresses are there than IPv4 addresses?
• Applications commonly also implement their own encryption (e.g. ssh or web browsers with
HTTPS). Speculate on the advantages or disadvantages for having IPSec at the Internet layer
instead of doing encryption at the Application layer.
• If subnet reserved 5 bits to identify hosts, how many hosts can it support? Don’t forget that all-zero-
bits and all-one-bits for the host are reserved.
• What is the benefit to having a static IP? How does it relate to DNS?
28 Chapter 6. The Internet Protocol (IP)
Chapter 7
This is the first popular version of the Internet Protocol, and it lives to this day in common use.
7.1 IP Addresses
An IPv4 address is written in “dots and numbers” notation, like so:
198.51.100.125
It’s always four numbers. Each number represents a byte, so it can go from 0 to 255 (00000000 to
11111111 binary).
This means that every IPv4 address is four bytes (32 bits) in size.
7.2 Subnets
The entire space of IP addresses is split up into subnets. The first part of the IP address indicates the subnet
number we’re talking about. The remaining part indicates the computer on that subnet in question.
And how many bits “the first part of the IP” constitutes is variable.
When you set up a network with public-facing IP addresses, you are allocated a subnet by whomever you
are paying to provide you with a connection. The more hosts your subnet supports, the more expensive it
is.
So you might say, “I need 180 IP static IP addresses.”
And your provider says, OK, that means you’ll have 180 IPs and 2 reserved (0 and the highest number),
so 182 total. We need 8 bits to represent the numbers 0-255, which is the smallest number of bits that
includes 182.
And so they allocate you a subnet that has 24 network bits and 8 host bits.
They could write out something like:
Your subnet is 198.51.100.0 and there are 24 network bits and 8 host
bits.
198.51.100.0/24
This tells us that 24 bits of the IP address represent the network number. (And therefore 32-24=8 bits
represent the host.) But what does that mean?
29
30 Chapter 7. The Internet Protocol version 4
Drawing it out:
24 network bits
----------
198.51.100.0
-
8 host bits
The upshot is that every single IP on our make-believe network here is going to start with 198.51.100.x.
And that last byte is going to indicate which host we’re talking about.
Here are some example IPs on our network:
198.51.100.2
198.51.100.3
198.51.100.4
198.51.100.30
198.51.100.212
198.51.100.0 Reserved
198.51.100.255 Broadcast (see below)
but other than those, we can use the other IPs as we see fit.
Now, I deliberately chose an example there where the subnet ended on a byte boundary because it’s easier
to see if the entire last byte is the host number.
But there’s no law about that. We could easily have a subnet like this:
198.51.100.96/28
and we could only fill those last 4 bits with different numbers to represent our hosts.
0000 and 1111 are reserved and broadcast, leaving us with 14 more we could use for host numbers.
For example, we could fill in those last 4 bits with host number 2 (which is 0010 binary):
All the IP addresses on this subnet are, exhaustively 198.51.100.96 through 198.51.100.111 (though
these first are last IPs are reserved and broadcast, respectively).
Finally, if you have a subnet you own, there’s nothing stopping you for further subnetting it down–
declaring that more bits are reserved for the network portion of the address.
ISPs (Internet Service Providers, like your cable or DSL company) do this all the time. They’ve given a
big subnet with, say, 12 network bits (20 host bits, for 1 million possible hosts). And they have customers
who want their own subnets. So the ISP decides the next 9 bits (for example) are going to be used to
uniquely identify additional subnets within the ISP’s subnet. And it sells those to customers, and each
customer gets 11 bits for hosts (supporting 2048 hosts).
But it doesn’t even stop there, necessarily. Maybe one of those customers you sold an 11-bit subnet to
wants to further subdivide it–they can add more network bits to define their own subnets. Of course, every
time you add more network bits, you’re taking away from the number of hosts you can have, but that’s
the tradeoff you have to make with subnetting.
So the subnet mask for 198.51.100.0/24 is 255.255.255.0. It’s the same subnet mask for any /24
subnet.
The subnet mask for a /16 subnet has the first 16 bits set to 1: 255.255.0.0.
But why? Turns out a router can take any IP address and quickly determine its destination subnet by
ANDing the IP address with the subnet mask.
And so the subnet for the IP address 198.51.100.67 with subnet mask 255.255.255.0 is
198.51.100.0.
7.7 Reflect
• 192.168.262.12 is not a valid IP address. Why?
• Reflect on some of the advantages of the subnet concept as a way of dividing the global address
space.
7.7. Reflect 33
• What is your computer’s IPv4 address and subnet mask right now? (You might have to search how
to find this for your particular OS.)
• If a IP address is listed as 10.37.129.212/17, how many bits are used to represent the hosts?
34 Chapter 7. The Internet Protocol version 4
Chapter 8
This is the new big thing! Since there are so few addresses representable in 32 bits (only 4,294,967,296
not counting the reserved ones), the Powers That Be decided we needed a new addressing scheme. One
with more bits. One that could last, for all intents and purposes, forever.
There was a problem: we were running out of IP addresses. Back in the 1970s, a world with billions of
computers was beyond imagination. But today, we’ve already exceeded this by orders of magnitude.
So they decided to increase the size of IP addresses from 32 bits to 128 bits, which gives us
79,228,162,514,264,337,593,543,950,336 times as much address space. This should genuinely last a
looooooong time.
Lots of this address space is reserved, so there aren’t really that many addresses. But there are still
a LOT, both imperial and metric.
That’s the main difference between IPv4 and IPv6.
For demonstration purposes, we’ll stick with IPv4 because it’s still common and a little easier to write out.
But this is good background information to know, since someday IPv6 will be the only game in town.
Someday.
8.1 Representation
With that much address space, dots-and-decimal numbers won’t cut it. So they came up with a new way
of displaying IPv6 addresses: colons-and-hex numbers. And each hex number is 16 bits (4 hex digits), so
we need 8 of those numbers to get us to 128 bits.
For example:
2001:0db8:6ffa:8939:163b:4cab:98bf:070a
Slash notation is used for subnetting just like IPv4. Here’s an example with 64 bits for network (as
specified with /64) and 64 bits for host (since 128-64=64):
2001:0db8:6ffa:8939:163b:4cab:98bf:070a/64
64 bits for host! That means this subnet can have 18,446,744,073,709,551,616 hosts!
There’s a lot of space in an IPv6 address!
When we’re talking about standard IPv6 addresses for particular hosts, /64 is the strongly-
suggested rule for how big your subnet is. Some protocols rely on it.
But when we’re just talking about subnets, you might see smaller numbers there representing larger
address spaces. But the expectation is that eventually that space will be partitioned down into /64
35
36 Chapter 8. The Internet Protocol version 6
2001:0db8:6ffa:0000:0000:00ab:98bf:070a
And we apply the first rule and get rid of leading zeros:
2001:db8:6ffa:0:0:ab:98bf:70a
And we see we have a run of two 0s in the middle, and we can replace that with two colons:
2001:db8:6ffa::ab:98bf:70a
fe80:0000:0000:0000:0000:0000:0000:0000
The first 10 bits being the network portion. In an IPv6 link-local address, the next 54 bits are reserved (0)
and then there are 64 bits remaining to identify the host.
When an IPv6 interface is brought up, it automatically computes its link-local address based on its Ethernet
address and other things.
Link-local addresses are unique on the LAN, but might not be globally-unique. Routers do not forward
any link-local packets out of the LAN to prevent issues with duplicate IPs.
An interface might get a different IP later if a DHCP server hands one out, for example, in which case it’ll
have two IPs.
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;example.com. IN AAAA
;; ANSWER SECTION:
example.com. 81016 IN AAAA 2606:2800:220:1:248:1893:25c8:1946
You can see the IPv6 address of example.com in the ANSWER SECTION, above.
http://[::1]:33490/
8.6 Reflect
• What are some benefits of IPv6 over IPv4?
• How can the address 2001:0db8:004a:0000:0000:00ab:ab4d:000a be written more simply?
38 Chapter 8. The Internet Protocol version 6
Chapter 9
9.1 Restrictions
In order to better understand the sockets API at a lower level, this project may not use any of the following
helper functions:
• The socket.create_connection() function.
• The socket.create_server() function.
• Anything in the urllib modules.
After coding up the project, it should be more obvious how these helper functions are implemented.
http://localhost:33490/file1.txt
The client will send a request to your server that looks like this:
Notice the file name is right there in the GET request on the first line!
Your server will:
1. Parse that request header to get the file name.
2. Strip the path off for security reasons.
3. Read the data from the named file.
4. Determine the type of data in the file, HTML or text.
5. Build an HTTP response packet with the file data in the payload.
6. Send that HTTP response back to the client.
The response will look like this example file:
39
40 Chapter 9. Project: A Better Web Server
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 357
Connection: close
<!DOCtype html>
<html>
<head>
...
[The rest of the HTML file has been truncated in this example.]
At this point, the browser should display the file.
Notice a couple things in the header that need to be computed: the Content-Type will be set according
to the type of data in the file being served, and the Content-Length will be set to the length in bytes of
that data.
We’re going to want to be able to display at least two different types of files: HTML and text files.
fullpath = "/foo/bar/baz.txt"
file_name = fullpath.split("/")[-1]
A more portable way is to use the standard library function os.path.split. The value returned by
os.path.split is will be a tuple with two elements, the second of which is the file name:
fullpath = "/foo/bar/baz.txt"
os.path.split(fullpath)
9.5. MIME and Getting the Content-Type 41
fullpath = "/foo/bar/baz.txt"
file_name = os.path.split(fullpath)[-1]
Content-Type: application/pdf
os.path.splitext('keyboardcat.gif')
('keyboardcat', '.gif')
You can just map the following extensions for this assignment:
Content-Type: text/plain
in your response.
If you really want to be correct, add charset to your header to specify the character encoding:
but that’s not necessary, since browsers typically default to that encoding.
try:
with open(filename, "rb") as fp:
data = fp.read() # Read entire file
return data
except:
# File not found or other error
# TODO send a 404
The data you get back from .read() is what will be the payload. Use len() to compute the number of
bytes.
The number of bytes will be send back in the Content-Length header, like so:
Content-Length: 357
You might be wondering what the "rb" thing is in the open() call. This causes the file to open for
reading in binary mode. In Python, a file open for reading in binary mode will return a bytestring
representing the file that you can send straight out on the socket.
What about this 404 Not Found thing? It’s common enough that you’ve probably seen it in normal web
usage from time to time.
This just means you’ve requested a file or other resource that doesn’t exist.
In our case, we’ll detect some kind of file open error (with the except block, above) and return a 404
response.
The 404 response is an HTTP response, except instead of
HTTP/1.1 200 OK
So when you try to open the file and it fails, you’re going to just return the following (verbatim) and close
the connection:
(Both the content length and the payload can just be hardcoded in this case, but of course have to be
.encode()’d to bytes.)
9.7 Extensions
These are here if you have time to give yourself the additional challenge for greater understanding of the
material. Push yourself!
• Add MIME support for other file types so you can serve JPEGs and other files.
• Add support for showing a directory listing. If the user doesn’t specify a file in the URL, show a
directory listing where each file name is a link to that file.
Hint: os.listdir and os.path.join()
• Instead of just dropping the entire path, allow serving out of subdirectories from a root directory
your specify on the server.
SECURITY RISK! Make sure the user can’t break out of the root directory by using a bunch of
..s in the path!
Normally you’d have some kind of configuration variable that specified the server root directory
as an absolute path. But if you’re in one of my classes, that would make my life miserable when I
went to grade projects. So if that’s the case, please use a relative path for your server root directory
and create a full path with the os.path.abspath() function.
This would set server_root to a full path to where you ran your server. For example, on my
machine, I might get:
/home/beej/src/webserver # This...
/home/beej/src/webserver/root # or something like this
Then when the user tries to GET some path, you can just append it to server root to get the path to
the file.
So if they tried to GET /foo/bar/index.html, then file_path would get set to:
/home/beej/src/webserver/foo/bar/index.html
And now the security crux! You have to make sure that file_path is within the server root
directory. See, a villain might try to:
And if they did that, we’d unknowingly serve out this file:
/home/beej/src/webserver/../../../../../etc/passwd
which would get them to my password file in /etc/passwd. I don’t want that.
So I need to make sure that wherever they end up is still within my server_root hierarchy. How?
We can use abspath() again.
44 Chapter 9. Project: A Better Web Server
If I run the crazy .. path above through abspath(), it just returns /etc/passwd to me. It resolves
all the ..s and other things and returns the “real” path.
But I know my server root in this example is /home/beej/src/webserver, so I can just verify
that the absolute file path begins with that. And 404 if it doesn’t.
9.8.1 file1.txt
This is a sample text file that has all kinds of words in it that
seemingly go on for a long time but really don't say much at all.
9.8.2 file2.html
<!DOCTYPE html>
<html>
<head>
<title>Test HTML File</title>
</head>
<body>
<h1>Test HTML</h1>
<p>This is my test file that has <i>some</i> HTML in in that the browser
should render as HTML.
<p>If you're seeing HTML tags that look like this <tt><p></tt>,
you're sending it out as the wrong MIME type! It should be
<tt>text/html</tt>!
<hr>
</body>
The idea is that these URLs would retrieve the above files (with the appropriate port given):
http://localhost:33490/file1.txt
http://localhost:33490/file2.html
Chapter 10
We’ve done some work transmitting text over the network. But now we want to do something else: we
want to transfer binary integer data.
Sure we could just convert the numbers to strings, but this is more wasteful than it needs to be. Binary
representation is more compact and saves bandwidth.
But the network can only send and receive bytes! How can we convert arbitrary numbers to single bytes?
That’s what this chapter is all about.
We want to:
• Convert integers to byte sequences
• Convert byte sequences back into integers
And in this chapter we’ll look at:
• How numbers are represented by sequences of bytes
• What order those bytes go in
• How to convert a number to a sequence of bytes in Python
• How to convert a sequence of bytes to a number in Python
Key points to look out for:
• Integers can be represented by sequences of bytes.
• We’ll convert integers to sequences of bytes before we transmit them over the network.
• We’ll convert sequences of bytes back into integers when we receive them over the network.
• Big-Endian and Little-Endian are two different ways of ordering those sequences of bytes.
• Python offers built-in functionality for converting integers to sequences of bytes and back again.
So what happens if you want to store number larger than 255? Like 256? In that case, you need to use a
second byte to store the additional value.
The more bytes you use to represent an integer, the larger the range of integers you can represent. One
byte can store from 0 to 255. Two bytes can store from 0 to 65535.
45
46 Chapter 10. Endianness and Integers
Thinking about it another way, 65536 is the number of combinations of 1s and 0s you can have in a 16-bit
number.
This section is talking about non-negative integers only. Floating point numbers use a different
encoding. Negative integers use a similar technique to positive, but we’ll keep it simple for now
and ignore them.
Let’s take a look what happens when we count up from 253 to 259 in a 16-bit number. Since 259 is bigger
than a single byte can hold, we’ll use two bytes (holding numbers from 0 to 255), with the corresponding
decimal value represented on the right:
Notice that the byte on the right “rolled over” from 255 to 0 like an odometer. It’s almost like that byte
is the “ones place” and the byte on the left is the “256s place”… like looking at a base-256 numbering
system, almost.
We could compute the decimal value of the number by taking the first byte and multiplying it by 256, then
adding on the value of the second byte:
1 * 256 + 3 = 259
Or in this example, where two bytes with values 17 and 178 represent the value 4530:
Neither 17 not 178 are larger than 255, so they both fit in a single byte each.
So every integer can be perfectly represented by a sequence of bytes. You just need more bytes in the
sequence to represent larger numbers.
But wait a second–see the pattern? If you just stick the two bytes together you end up with the exact same
number as the binary representation! (Ignoring leading zeros.)
Really all we’ve done is take the binary representation of a number and split it up into chunks of 8 bits.
We could take any arbitrary number like 1,256,616,290,962 decimal and convert it to binary:
10.2. Endianness 47
10010010010010100001010101110101010010010
Since we’re packing it into bytes, we should pad that leading 1 out to 8 bits like so:
And there you have it, the byte-by-byte representation of the number 1,256,616,290,962.
Look at that again! The hex representation of the number is the same as the two bytes just crammed
together! Super-duper convenient.
10.2 Endianness
Ready to get a wrench thrown in the works?
I just finished telling you that a number like (in hex):
45f2
45 f2
f2 45
It’s backwards! This is analogous to me saying “I want 123 pieces of toast” when in fact I really wanted
321!
There’s a name for putting the bytes backward like this. We say such representations are little endian.
This means the “little end” of the number (the “ones” byte, if I can call it that) comes at the front end.
The more-normal, more-forward way to write it (like we did at first, where the number 0x45f2 was
reasonably represented in the order 45 f2) is called big endian. The byte in the largest value slot (also
called the most-significant byte) is at the front end.
The bad news is that virtually all Intel CPU models are little-endian.
48 Chapter 10. Endianness and Integers
And when I say “all”, I mean “a certain amount”[^—Monty Python]. If both sides agree to transmit
in little endian, there’s no law against that. This would make sense if the sender and receiver were
both little-endian architectures—why waste time reversing bytes just to reverse them back? But
the majority of protocols specify big-endian.
Big-endian byte order is called network byte order in network contexts for this reason.
Newer versions of Python default to "big". In older versions, you still have to be explicit.
n = 3490
for b in bytes:
print(b)
13
162
Those are the big-endian byte values that make up the number 3490. We can verify that 13 * 256 + 162 == 3490
easily enough.
If you try to store the number 70,000 in two bytes, you’ll get an OverflowError. Two bytes isn’t large
enough to store values over 65535–you’ll need to add another byte.
Let’s do one more example in hex:
n = 0xABCD
bytes = n.to_bytes(2, "big")
for b in bytes:
print(f"{b:02X}") # Print in hex
prints:
10.4. Reflect 49
AB
CD
n = 0x0102
bytes = n.to_bytes(2, "big")
print(bytes)
b'\x01\x02'
The b at the front means this is a bytestring (as opposed to a regular string) and the \x is an escape sequence
that appears before a 2-digit hex number.
Since our original number was 0x0102, it makes sense that the two bytes in the byte string have values
\x01 and \x02.
v = int.from_bytes(bytes, "big")
print(f"{v:04x}")
0102
10.4 Reflect
• Using only the .to_bytes() and .from_bytes() methods, how can you swap the byte order in a
2-byte number? (That is reverse the bytes.) How can you do that without using any loops or other
methods? (Hint: "big" and "little"!)
• Describe in your own words the difference between Big-Endian and Little-Endian.
• What is Network Byte Order?
• Why not just send an entire number at once instead of breaking it into bytes?
• Little-endian just seems backwards. Why does it even exist? Do a little Internet searching to answer
this question.
50 Chapter 10. Endianness and Integers
Chapter 11
Parsing Packets
We’ve already seen some issues with receiving structured data from a server. You call recv(4096), and
you only get 20 bytes back. Or you call recv(4096) and it turns out the data is longer than that, and you
need to call it again.
There’s an even worse issue there, too. If the server is sending you multiple pieces of data, you might
receive the first and part of the next. You’ll have a complete packet and the next partially complete one!
How do you reconstruct this?
An analogy might be if I needed you to split up individual sentences from a block of text I give you, but
you can only get 20 characters at a time.
You call recv(20) and you get:
This is a test of th
That’s not a full sentence, so you can’t print it yet. So you call recv(20) again:
Hey! There’s a period in there, so we have a complete sentence. So we can print it out. But we also have
part of the next sentence already received!
How are we going to handle all this in a graceful way?
Isn’t that easier to think about? Once we have that code the extracts the next complete packet from the
data stream, we can just use it.
And if that code is complex enough, it could actually extract different types of packets from the stream:
51
52 Chapter 11. Parsing Packets
packet = get_next_packet()
if packet.type == PLAYER_POSITION:
set_player_position(packet.player_index, packet.player_position)
and so on.
Makes things soooo much easier than trying to reason about packets as collections of bytes that might or
might not be complete.
Of course, doing that processing is the real trick. Let’s talk about how to make it happen.
This buffer will hold the bytes you’ve seen so far. You will inspect the buffer to see if it holds a complete
data packet.
If there is a complete packet in there, you’ll return it (as a bytestring or processed). And also, critically,
you’ll strip it off the front of the buffer.
Otherwise, you’ll call recv() again to try to fill up the buffer until you have a complete packet.
In Python, remember to use the global keyword to access global variables, e.g.
packet_buffer = b''
def get_next_packet(s):
global packet_buffer
Otherwise Python will just make another local variable that shadows the global one.
Nothing. No data is received. There’s no period in there so we don’t have a sentence, so we have to call
recv(20) again to get more bytes:
This is a test of th
This is on
and we return the first sentence “This is a test of the emergency broadcast system.”
And the function that called get_sentence() can print it.
And then call get_sentence() again!
In get_sentence(), we look at the buffer again. (Remember, the buffer is global so it still has the data
in it from the last call.)
This is on
There’s no period, so we call recv(20) again, but this time we only get 10 bytes back:
But it’s a complete sentence, so we strip it from the buffer, leaving it empty, and then return it to the caller
for printing.
Well, it still works! The get_sentence() function will see the first period in there, strip off the first
sentence from the buffer so it contains:
Part 2. Part
Part
Part 4. Part 5.
function get_packet():
while True:
if buffer starts with a complete packet
extract the packet data
strip the packet data off the front of the buffer
return the packet data
In Python, you can slice off the buffer to get rid of the packet data from the front.
For example, if you know the packet data is 12 bytes, you can slice it off with:
11.5 Reflect
• Describe the advantages from a programming perspective to abstracting packets out of a stream of
data.
Chapter 12
You’re going to reach out to the atomic clock at NIST (National Institute of Standards and Technology)
and get the number of seconds since January 1, 1900 from their clocks. (That’s a lot of seconds.)
And you’ll print it out.
And then you’re going to print out the system time from the clock on your computer.
If your computer’s clock is accurate, the numbers should be very close in the output:
We’re just writing a client in this case. The server already exists and is already running.
12.3 Epoch
In computer parlance, we refer to “epoch” as meaning “the beginning of time” from a computer perspec-
tive.
Lots of libraries measure time in “number of seconds since epoch”, meaning since the dawn of time.
What do we mean by the dawn of time? Well, it’s depends.
But in Unix-land, the dawn of time is very specifically January 1, 1970 at 00:00 UTC (AKA Greenwich
Mean Time).
55
56 Chapter 12. Project: Atomic Time
In other epochs, the dawn of time might be another date. For example, the Time Protocol that we’ll be
speaking uses January 1, 1900 00:00 UTC, 70 years before Unix’s.
This means we’ll have to do some conversion. But luckily for you, we’ll just give you the code that will
return the value for you and you don’t have to worry about it.
import time
def system_seconds_since_1900():
"""
The time server returns the number of seconds since 1900, but Unix
systems return the number of seconds since 1970. This function
computes the number of seconds since 1900 on the system.
"""
seconds_since_unix_epoch = int(time.time())
seconds_since_1900_epoch = seconds_since_unix_epoch + seconds_delta
return seconds_since_1900_epoch
13.1 Overview
First: download these files:
• wordserver.py1 : a ready-to-run server that hands out lists of random words.
• wordclient.py2 : skeleton code for the client.
RESTRICTION! Do not modify any of the existing code! Just search for TODO and fill in that code.
You may add additional functions and variables if you wish.
REQUIREMENT! The code should work with any positive value passed to recv() between 1 and
4096! You might want to test values like 1, 5, and 4096 to make sure they all work.
REQUIREMENT! The code must work with words from length 1 to length 65535. The server won’t
send very long words, but you can modify it to test. To build a string in Python of a specific number of
characters, you can:
PROTIP! Read and understand all the existing client code before you start. This will save you all
kinds of trouble. And note how the structure of the main code doesn’t even care about bytes and streams–
it’s only concerned with entire packets. Cleaner, right?
You are going to complete two functions:
• get_next_word_packet(): gets the next complete word packet from the stream. This should
return the complete packet, the header and the data.
• extract_word(): extract and return the word from a complete word packet.
What do they do? Keep reading!
59
60 Chapter 13. Project: The Word Server
This stream is made up of a random number (1 to 10 inclusive) of words prefixed by the length of the
word in bytes.
Each word is UTF-8 encoded.
The length of the word is encoded as a big-endian 2-byte number.
For example, the word “hello” with length 5 would be encoded as the following bytes (in hex):
length 5
|
+-+-+
| | h e l l o
00 05 68 65 6C 6C 6F
The numbers corresponding to the letters are the UTF-8 encoding of those letters.
Fun fact: for alphabetic letters and numbers, UTF-8, ASCII, and ISO-8859-1 are all the same
encoding.
The word “hi” followed by “encyclopedia” would be encoded as two word packets, transmitted in the
stream like so:
length 2 length 12
| |
+-+-+ +-+-+
| | h i | | e n c y c l o p e d i a
00 02 68 69 00 0C 65 6E 63 79 63 6C 6F 70 65 64 69 61
h i
00 02 68 69 00
We see the first word is 2 bytes long, and we have captured those 2 bytes.
We would extract and return the first word (“hi”) and its length (bytes 00 02) and return this bytestring:
h i
00 02 68 69
We’d also strip those bytes out of the packet buffer so that all that remained was the zero that was at the
end.
00
At that point, lacking a complete word in the buffer, a subsequent call to the function would trigger a
recv(5) for the next chunk of data, giving us:
e n c y
00 0C 65 6E 63 79
13.4. Implementation: extract_word() 61
And so on.
h i
00 02 68 69
When someone puts a backhoe through a fiber optic cable, packets might be lost. (Entire countries have
been brought offline by having a boat anchor dragged through an undersea network cable!) Software
errors and computer crashes and router malfunctions can all cause problems.
But we don’t want to have to think about that. We want an entity to deal with all that and then let us know
when the data is complete and intact and in order.
TCP is that entity. It worries about lost packets so we don’t have to. And when it is sure it has all the
correct data, then it gives it to us.
We’ll look at:
• The overall goals of TCP
• Where it fits in the network stack
• A refresher on TCP ports
• How TCP makes, uses, and closes connections
• Data integrity mechanisms
• Maintaining packet order
• Detecting errors
• Flow control–how a receiver keeps from getting overwhelmed
• Congestion Control–how senders avoid overloading the Internet
TCP is a very complex topic and we’re only skimming the highlights here. If you want to learn more, the
go-to book is TCP/IP Illustrated Volume 1 by the late, great W. Richard Stevens.
63
64 Chapter 14. Transmission Control Protocol (TCP)
You can see TCP in there at the Transport Layer. IP below it is responsible for routing. And the application
layer above it takes advantage of all the features TCP has to offer.
That’s why when we wrote our HTTP client and server, we didn’t have to worry about data integrity at all.
We used TCP so that took care of it for us!
Analogy time: you do this on the phone when you tell the other party “You’re talking too fast for
me to understand! Slow down!”
The most simple way to do this (and this is not what TCP does) is for the sender to send the data, then wait
for the receiver to send back an ACK packet with that sequence number. Then send another data packet.
This way the receiver can delay the ACK if it needs the sender to slow down.
But this is a slow back and forth, and the network is usually reliable enough for the sender to push out
multiple segments without waiting for a response.
However, if we do this, we risk the sender sending data more quickly than the receiver can handle it!
In TCP, this is solved with something called a sliding window. This goes in the “window” field of the
TCP header in the receiver’s ACK packet.
In that field, the data receiver can specify how much more data (in bytes) it is willing to receive. The
sender must not send more than this without before getting an ACK from the receiver. And the ACK it
gets will contain new window information.
Using the mechanism, the receiver can get the sender “once you’ve sent X bytes, you have to wait for an
ACK telling you how many more you can send”.
It’s important to note that this is a byte count, not a segment count. The sender is free to send multiple
segments without receiving an ACK as long as the total bytes doesn’t exceed the receiver’s advertised
window size.
As the ACKs come in, the size of the congestion window increases by the number of acknowledged bytes.
So, loosely, after one segment gets ACKed, two can be sent out. If those two are successfully ACKed,
four can be sent out.
So it starts with a very limited number of unACKed segments allowed to be outstanding, but grows very
rapidly.
Eventually one of those ACKs is lost and that’s when Slow Start decides to slow way down. It cuts the
congestion window size by half and then TCP switches to the Congestion Avoidance algorithm.
14.8 Reflect
• Name a few protocols that rely on TCP for data integrity.
• Why is there a three-way handshake to set up a connection? Why not just start transmitting?
• How does a checksum protect against data corruption?
• What’s the main difference in the goals of Flow Control and Congestion Control?
• Reflect on the reasons for switching between Slow Start and Congestion Avoidance. What advan-
tages does each have in different phases of congestion detection?
• What is the purpose of Flow Control?
68 Chapter 14. Transmission Control Protocol (TCP)
Chapter 15
If you like to keep things simple and are a positive thinker, UDP is for you. It’s the near-ultimate in
lightweight data transfer over the Internet.
You fire UDP packets off and hope they arrive. Maybe they do, or maybe someone put a backhoe through
a fiber optic cable, or there were cosmic rays, or a router got too congested or irate and just dropped it.
Unceremoniously.
It’s living on the Internet data edge! Virtually all the pleasant and reliable guarantees of TCP–gone!
69
70 Chapter 15. User Datagram Protocol (UDP)
You can see UDP in there at the Transport Layer. IP below it is responsible for routing. And the application
layer above it takes advantage of all the features UDP has to offer. Which isn’t a lot.
0 7 8 15 16 23 24 31
+--------+--------+--------+--------+
| Source | Destination |
| Port | Port |
+--------+--------+--------+--------+
| | |
| Length | Checksum |
+--------+--------+--------+--------+
|
| data octets ...
+---------------- ...
When the receiver gets the packet, it computes its own checksum of that packet.
If the two checksums match, the data is assumed to be error-free. If they differ, the data is discarded.
That’s it. The receiver never even knows that there was some data aimed at it. It just vanishes into the
ether.
15.6. Maximum Payload Without Fragmentation 71
The checksum is a 16-bit number that is the result of piping all the UDP header and payload data and the
IP addresses involved into a function that digests them down.
This works the same way as the TCP checksum. (Jon Postel wrote the first RFCs for both TCP and UDP
so it’s no surprise they use the same algorithm.)
The details of how the checksum works are in this week’s project. Just substitute the UDP header for the
TCP header.
• You no longer call listen(), connect(), accept(), send(), or recv() because there’s no “con-
nection”.
• You call sendto() to send UDP data.
• You call recvfrom() to receive UDP data.
# UDP Server
import sys
import socket
# Bind to a port
s.bind(("", port))
# UDP Client
import socket
import sys
15.9. Reflect 73
s.close()
15.9 Reflect
• What does TCP provide that UDP does not in terms of delivery guarantees?
• Why do people recommend keeping UDP packets small?
• Why is the UDP header so much smaller than the TCP header?
• sendto() requires you specify a destination IP and port. Why does the TCP-oriented send()
function not require those arguments?
• Why would people use UDP over TCP if it’s relatively unreliable?
74 Chapter 15. User Datagram Protocol (UDP)
Chapter 16
In this project you’ll write some code that validates a TCP packet, making sure it hasn’t been corrupted
in transit.
Inputs: A sequence of pairs of files:
• One contains the source and destination IPv4 addresses in dots-and-numbers notation.
• The other contains the raw TCP packet, both the TCP header and the payload.
You can download the input files from the exercises folder1 .
Outputs:
• For each pair of files, print PASS if the TCP checksum is correct. Otherwise print FAIL.
There are a lot of parts to this project, so it is suggested you write and test as you go.
You should understand this specification 100% before you begin to plan your approach! Get clari-
fication before proceeding to the planning stage!
The ABSOLUTE HARDEST PART of this project is understanding it! Your code will never work
before you understand it!
The model solution is 37 lines of code! (Not including whitespace and comments.) This is not a number
to beat, but is an indication of how much effort you need to put in to understanding this spec versus typing
in code!
75
76 Chapter 16. Project: Validating a TCP Packet
tcp_addrs_0.txt
tcp_addrs_0.dat
tcp addrs_1.txt
tcp addrs_1.dat
192.0.2.207 192.0.2.244
These are the source IP address and destination IP address for this TCP packet.
Why do we need to know IP information if this is a TCP checksum? Stay tuned!
hexdump -C tcp_data_0.dat
00000000 3f d7 c9 c5 ed d8 23 52 6a 15 32 96 50 d9 78 d8 |?.....#Rj.2.P.x.|
00000010 67 be ba aa 2a 63 25 2d 7c 4f 2a 39 52 69 4b 75 |g...*c%-|O*9RiKu|
00000020 42 39 53 |B9S|
00000023
But for this project, the only things in that file you really will care about are:
• The length (in bytes) of the data
• The 16-bit big-endian checksum that’s stored at offset 16-17
More on that later!
Note: these files contain “semi-correct” TCP headers. All the parts are there, but the various values
(especially in the flags and options fields) might make no sense.
+--------+--------+--------+--------+
| Source Address |
+--------+--------+--------+--------+
| Destination Address |
+--------+--------+--------+--------+
| Zero | PTCL | TCP Length |
+--------+--------+--------+--------+
78 Chapter 16. Project: Validating a TCP Packet
Don’t let the grid layout fool you: the IP pseudo header is a string of bytes. It’s just in this layout for
easier human consumption.
Each + sign in diagram represents a byte delimiter.
So the Source Address is 4 bytes. (Hey! IPv4 addresses are 4 bytes long!) You get this out of the
tcp_addrs_n.txt files.
The Destination Address is 4 bytes. You get this out of the tcp_addrs_n.txt files, as well.
Zero is one byte, just set to byte value 0x00.
PTCL is the protocol, and that is always set to a byte value of 0x06. (IP has some magic numbers that
represent the higher level protocol above it. TCP’s number is 6. That’s where it comes from.)
TCP Length is the total length, in bytes of the TCP packet and data, big-endian. This is the length of the
data you’ll read out of the tcp_data_n.dat files.
So before you can compute the TCP checksum, you have to fabricate an IP pseudo header!
See how the bytes line up to the inputs? (255 is hex 0xff, 127 is hex 0x7f, etc.)
Be sure to use "rb" when reading binary data! That’s what the b is for! If you don’t do this, it might
break everything!
16.7. The TCP Header Checksum 79
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgment Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data | |U|A|P|R|S|F| |
| Offset| Reserved |R|C|S|S|Y|I| Window |
| | |G|K|H|T|N|N| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
(Again, like the IP pseudo header, this is encoded as a stream of bytes and the grid is only here to make
our human lives easier. This grid is 32-bits across, numbered along the top.)
See where it says Checksum? That’s the bit we care about for this project. And it’s a two-byte number
(big-endian) at byte offsets 16 and 17 inside the TCP header.
It will also be at byte offset 16-17 in the files tcp_data_n.dat since those files start with the TCP header.
(Followed by the TCP payload.)
You’ll need the checksum from that file. Use a slice to get those two bytes and then use
.from_bytes() to convert it into a number. This is the original checksum you’ll compare against
at the end of the day!
You’ll also need to generate a version of that TCP header and data where the checksum is set to
zero. You can do it like this:
See how we made a new version of the TCP data there? We sliced everything before and after the existing
checksum, and put two zero bytes in the middle.
The checksum field is the 16 bit one’s complement of the one’s complement sum of all 16 bit words
in the header and text.
All right, we’re already in trouble. The what of the what?
One’s complement is a way of representing positive and negative integers in binary. We don’t need to
know the details for this, gratefully.
But one thing we do need to notice is that we’re talking about all the “16 bit words”… what is that?
It means that instead of considering all this data to be a bunch of bytes, we’re considering it to be a bunch
of 16-bit values packed together.
So if you have the bytes:
01 02 03 04 05 06
And we’re going to be adding those together. With one’s complement addition. Whatever that means.
Hey–but what if there are an odd number of bytes?
If a segment contains an odd number of header and text octets to be checksummed, the last octet
is padded on the right with zeros to form a 16 bit word for checksum purposes.
So we’re going to have to look at the tcp_length we got from taking the length of the data from the
tcp_data_0.dat file. If it’s odd, just add a zero to the end of the entire data.
Conveniently, we have a copy of the TCP data we can use already: the version we made with the checksum
zeroed out. Since we’re going to be iterating over this anyway, might as well append the zero byte to that:
if len(tcp_zero_cksum) % 2 == 1:
tcp_zero_cksum += b'\x00'
Great. That iterates over all the words in the whole chunk of data. But what does that buy us?
What’s the checksum, already?
Let’s take that loop above and add the checksum code to it.
Here we’re back to that one’s-complement stuff. And some 16-bit stuff, which is tricky in Python because
it uses arbitrary-precision integers.
But here’s how we want to do it. In the following example, tcp_data is the TCP data padded to an even
length with zero for the checksum.
16.9. Final Comparison 81
# Pseudocode
total = 0
total += word
total = (total & 0xffff) + (total >> 16) # carry around
That “carry around” thing is part of the one’s complement math. The &0xffff stuff all over the place is
forcing Python to give us 16-bit integers.
Remember what the spec said?
The checksum field is the 16 bit one’s complement of the one’s complement sum of all 16 bit words
in the header and text.
The loop is getting us the “one’s complement sum”. The ~total at the end is getting us the “one’s
complement” of that.
16.10 Output
The output of your program should show which TCP data passes and which fails. That is, this should be
your output:
PASS
PASS
PASS
PASS
PASS
FAIL
FAIL
FAIL
FAIL
FAIL
16.11 Success
That was no easy thing. Treat yourself! You earned it.
82 Chapter 16. Project: Validating a TCP Packet
Chapter 17
[Everything in this chapter will use IPv4, not IPv6. The concepts are basically the same; it’s just easier
to learn with IPv4.]
If you’re needing to review your Bitwise Operations, please see the Appendix: Bitwise Operations.
In this chapter:
• Address representation
• Converting from dots-and-numbers to a value
• Converting from a value to dots-and-numbers
• Subnet and host refresher
• Subnet Mask refresher
• Finding the subnet mask from slash notation
• Finding the subnet for an IP address, given that address and a subnet mask
198.51.100.10
c6.33.64.0a
c633640a
3325256714
Fair enough?
83
84 Chapter 17. IP Subnets and Subnet Masks
But why?
Well, we’re about to do some math on IP addresses. Now, we could do that math one byte at a time and
it would work just fine.
But it turns out that if we pack all those bytes into a single number, we can do the math on all the bytes at
once, and it becomes easier. Stay tuned!
"198.51.100.10"
Now let’s convert each of those to integers. (Python could do this with a loop and the int() function, or
map(), or a list comprehension.)
Now I’m going to write these numbers in hex because it makes the future steps more clear. But remember
that they’re just stored as numeric values, so Python won’t print them as hex unless you ask it to.
To build our number, we’re going to rely on a couple bitwise operations: bitwise-OR and bitwise-shift.
For the sake of example, let’s hardcode the math:
Running that in Python gives the decimal number 3325256714. Converted to hex, we’re back to
0xc633640a.
You can use the above formula to convert any set of 4 bytes to a packed number.
There’s also a clever loop you can run to do it one byte at a time. See if you can figure that out as
an added challenge! DM the instructor to see how clever you were.
Now let’s look at that number shifted by 0 bits, 8 bits, 16 bits, and 24 bits:
17.4. Subnet and Host Refresher 85
If you look at just the two digits on the right, you’ll see they’re the bytes of the original number:
So we’re onto something, except looking at the right shift 8, for example, we get this:
So yes, I’m interested in the byte 0x64 like we see on the right, but not the 0xc633 part of it. How can I
zero that higher part out, leaving just the 0x64?
We can use an AND mask! The bitwise-AND operator can work like a stencil letting some of the number
through and zeroing other parts of it. Let’s do a bitwise-AND on that number with the byte 0xff, which
is all 8 bits set to 1 and all bits over the first 8 have implied value 0.
0x00c63364
& 0x000000ff
------------
0x00000064
Hey! 0x64 is the byte from the IP address we wanted! See how where there were binary 1s in the mask
(except here represented in hex) it let the value “show through”, while everywhere that had a zero it was
masked out?
Now we can extract our digits:
Subnet | Host
|
198.51.100.10
So that represents host 10 on subnet 198.51.100.0. (We replace the host bits with 0 when talking about
the subnet number.)
86 Chapter 17. IP Subnets and Subnet Masks
But I just said above, unilaterally, that there were 24 network bits in that IP address. That’s not very
concise. So they invented slash notation.
Get it?
In those examples we used a multiple of 8 so it would align visually on a byte boundary, but there’s no
reason you can’t have a fractional part of a byte left over for a subnet:
If you don’t see where the 4 and 64 came from in the previous example, try writing the bytes out in binary!
Let’s first convert to binary. (There’s a hint here that the subnet mask is a bitwise-AND mask!)
11000110.00110011.01100100.00001010 198.51.100.10
And now, above it, let’s draw a run of 24 1s (because this is a /24 subnet) followed by 8 0s (because the
IP address is 32 bits total).
11000110.00110011.01100100.00001010 198.51.100.10
Remember that subnets can end on any bit boundary. /17 is a fine subnet. It doesn’t have to be a multiple
of 8!
How can you extract just the subnet (198.51.100.0) and just the host
You can do it with bitwise-AND!
We can compute the subnet mask for /24 and get 255.255.255.0, as above.
After that, let’s take a look in binary and AND these together:
We can operate on the whole thing at once instead of a byte at a time, as well… we just need to cram those
numbers together into a single value, like we did in the section above:
The AND works on the whole thing at once! Then we can convert back to dots-and-numbers notation like
we did in the previous sections.
Now what if you had the IP address and the subnet mask and wanted to get the host bits out of the IP
address, not the network bits. Do you see how you could do it? (Hint: bitwise NOT!)
Routers use this all the time–they are given an IP address and they need to know if it matches any subnets
the router is connected to. So it masks out the IP address’s network number and compares it to all the
subnets that router knows. And then forwards it toward the right one.
17.8 Reflect
• What is the 32-bit (4-byte) representation of the IP address 10.100.30.90 in hex? In decimal? In
binary?
• What is the dots-and-numbers IP address represented by the 32-bit number 0xc0a88225?
• What is the dots-and-numbers IP address represented by the 32-bit decimal number 180229186?
• What bitwise operations do you need to extract the second byte from the left (0xff) of the number
0x12ff5678?
192.168.1.0 is the network number, but it’s not the subnet mask. The subnet mask is the dots-and-numbers
value obtained from the slash notation. In this case it’s /24, which gives us 255.255.255.0.
Chapter 18
IP Routing
This chapter is all about routing over IP. This task is handled by computers called routers that know how
to direct traffic down different lines that are connected to them.
As we’ll see, every computer attached to the Internet is actually a router, but they all rely on other routers
to get the packets to their destinations.
We’re going to talk about two big ideas:
• Routing Protocols: how routers learn the “map” of the network.
• Routing Algorithm: how routers decide which direction to send packets after they’ve learned the
map.
Let’s get into it!
89
90 Chapter 18. IP Routing
But of course you need to be able to communicate between these clumps of Internet–the whole Internet is
connected after all. It would be a bummer if you couldn’t use Google’s servers from Oregon State. But
clearly Google’s servers don’t have a map of OSU’s network… so how do they know how to route traffic?
BGP can work in two different modes: internal BGP and external BGP. In internal mode, it acts as an
interior gateway protocol, while external mode acts as an external gateway protocol.
There’s a great video from Eye on Tech1 that concisely covers it. I highly recommend spending the two
minutes watching this to tie it all together.
Let’s look at an example routing table from my Linux machine, which in this example has been assigned
address 192.168.1.230.
Let’s take a look at connecting from localhost to localhost (127.0.0.1). In that case, the OS looks
up what route matches and sends the data on the corresponding interface. In this case, that’s the loopback
interface (lo), a “fake” interface that the OS pretends is a network interface. (For performance reasons.)
But what if we send data from 127.0.0.1 to anything on the 127.0.0.0/8 subnet? It uses the lo interface
as well. And the same thing happens if we send data to the 127.255.255.255 broadcast address (on the
127.0.0.0/8 subnet).
The other entries are more interesting. Remember as you look at these that my machine has been assigned
192.168.1.230.
So if we look at line 4, above, we’re looking at the case where I send from my machine to itself. This is
like localhost except I’m deliberately using the IP attached to my wireless LAN device, wlan0. So the
OS is going to use that system, but is smart enough to not bother sending it over the wire–after all, this is
the destination.
After that, we have the case where we’re sending to any other host on the 192.168.1.0/24 subnet. So this
is like my sending from my machine as 192.168.1.230 to another machine, for example 192.168.1.22,
say. Or any other machine on that subnet.
And then on line 6 we have a broadcast address for the LAN, which also goes out on the WiFi device.
But what if all that fails? What if I’m sending from 192.168.1.230 to 203.0.113.37? That’s not an IP
or subnet listed on my destinations in my routing table.
This is what line 7 is for: the default gateway. This is where a router sends packets if it doesn’t know how
to otherwise route them.
At your house, this is the router that you got from your ISP. Or that you bought and installed yourself if
you were so-inclined.
So when I ping example.com (93.184.216.34 as of this writing) from my home computer, those packets
get sent to my default gateway because that IP and its corresponding subnet don’t appear in my computer’s
routing table.
(And they don’t appear in my default gateway’s routing table, either. So it forwards them to its default
route.)
Source Destination
10.1.23.12 10.2.1.16
10.1.99.2 10.1.99.6
192.168.2.30 8.8.8.8
10.2.12.37 192.168.2.12 10.0.0.0/8 and
192.168.0.0/16 are private
networks and don’t get routed
over the outside Internet
10.1.17.22 10.1.17.23
2
https://beej.us/guide/bgnet0/source/examples/ip-routing-demo.pdf
18.5. Routing Loops and Time-To-Live 93
Source Destination
10.2.12.2 10.1.23.12
18.7 Reflect
• What is the difference between an interior gateway protocol and an external gateway protocol?
• What is the goal of a routing protocol in general?
• What’s an example of a place where an interior gateway protocol would be used? And exterior?
• What does a router use its routing table to determine?
• What does an IP router do next with a packet if the destination IP address is not on one of its local
subnets?
• Why would a process send anything to the broadcast address?
94 Chapter 18. IP Routing
10.1.99.0/24 10.2.1.0/24
10.1.17.0/24
10.2.12.0/24
Router 5 Router 6
10.1.23.0/24
en0 Router 3 en1
ISP
Destination Interface
10.1.23.0/24 en0
Router 4 The Greater Internet
default en1
In preparation for our subsequent project that finds routes across the network, we need to do some work
in figuring out how IP addresses, subnet masks, and subnets all work together.
In this project we’ll put some of the work from the chapters into practice. We’ll:
• Write functions to convert dots-and-numbers IP addresses into single 32-bit values–and back again.
• Write a function that converts a subnet mask in slash notation into a single 32-bit value representing
that mask.
• Write a function to see if two IP addresses are on the same subnet.
19.1 Restrictions
You may not use:
• Any functionality from the socket module.
• Any functionality from the struct module.
• Any functionality from the netaddr module.
• The .to_bytes() or .from_bytes() methods.
Keep it in the realm of your own home-cooked bitwise operations.
19.2 What To Do
Grab the skeleton code and other files in this ZIP archive1 . This is what you’ll fill in for this project.
Implement the following functions in netfuncs.py:
• ipv4_to_value(ipv4_addr)
• value_to_ipv4(addr)
• get_subnet_mask_value(slash)
• ips_same_subnet(ip1, ip2, slash)
• get_network(ip_value, netmask)
• find_router_for_ip(routers, ip)
The descriptions of the functions are in the file in their respective docstrings. Be sure to pay special
attention to the input and output types in the examples shown there.
Note that none of the functions need be more than 5-15 lines long. If you’re getting a much bigger function
implementation, you might be off track.
1
https://beej.us/guide/bgnet0/source/exercises/netfuncs/netfuncs.zip
95
96 Chapter 19. Project: Computing and Finding Subnets
python netfuncs.py
It will read in the JSON data from the included example1.json and run your functions on various parts
of it.
The output, included in example1_output.txt, should look exactly like this if everything is working
correctly:
Routers:
10.34.166.1: netmask 255.255.255.0: network 10.34.166.0
10.34.194.1: netmask 255.255.255.0: network 10.34.194.0
10.34.209.1: netmask 255.255.255.0: network 10.34.209.0
10.34.250.1: netmask 255.255.255.0: network 10.34.250.0
10.34.46.1: netmask 255.255.255.0: network 10.34.46.0
10.34.52.1: netmask 255.255.255.0: network 10.34.52.0
10.34.53.1: netmask 255.255.255.0: network 10.34.53.0
10.34.79.1: netmask 255.255.255.0: network 10.34.79.0
10.34.91.1: netmask 255.255.255.0: network 10.34.91.0
10.34.98.1: netmask 255.255.255.0: network 10.34.98.0
IP Pairs:
10.34.194.188 10.34.91.252: different subnets
10.34.209.189 10.34.91.120: different subnets
10.34.209.229 10.34.166.26: different subnets
10.34.250.213 10.34.91.184: different subnets
10.34.250.228 10.34.52.119: different subnets
10.34.250.234 10.34.46.73: different subnets
10.34.46.25 10.34.166.228: different subnets
10.34.52.118 10.34.91.55: different subnets
10.34.52.158 10.34.166.1: different subnets
10.34.52.187 10.34.52.244: same subnet
10.34.52.23 10.34.46.130: different subnets
10.34.52.60 10.34.46.125: different subnets
10.34.79.218 10.34.79.58: same subnet
10.34.79.81 10.34.46.142: different subnets
19.4. Running the Program 97
If you’re getting different output, try to look through the code and see what functions are being used with
the incorrect output. Then test those in more detail in the my_tests() function.
98 Chapter 19. Project: Computing and Finding Subnets
Chapter 20
We’re getting down to the guts of the thing: The Link Layer.
The link layer is where all the action happens, where bytes turn into electricity.
This is where Ethernet lives, as we’ll soon see.
99
100 Chapter 20. The Link Layer and Ethernet
ac:d1:b8:df:20:85
ac-d1-b8-df-20-85
acd1.b8df.2085
MAC address must be unique on the LAN. The numbers are assigned at manufacturer and are not typically
changed by the end user. (You’d only want to change them if you happened to get unlucky and buy two
network cards that happened to have been assigned the same MAC address.)
The first three bytes of an Ethernet MAC address are called the OUI (Organizationally Unique Identifier)
that is assigned to a manufacturer. This leaves each manufacturer three bytes to uniquely represent the
cards they make. (That’s 16,777,216 possible unique combinations. If a manufacturer runs out, they can
always get another OUI–there are 16 million of those available, too!)
Funny Internet rumor: there was once a manufacturer of knockoffs of a network card called the
NE2000, itself already known as a “bargain” network card. The knockoff manufacturer took the
shortcut of burning the same MAC address into every card they made. This was discovered when
a company bought a large number of them and found that only one computer would work at a time.
Of course, in a home LAN where someone was only likely to have one of these cards, it wouldn’t
be a problem–which is what the manufacturer was banking on. To add insult (or perhaps injury)
to injury, there was no way to change the MAC address in the knockoff cards. The company was
forced to discard them all.
Except one, presumably.
When we’re talking “medium” here, we mean wires (if you’ve plugged your computer into the
network) or radio (if you’re on WiFi).
The method particular link layer protocols use to allow multiple entities access the shared medium is called
the multiple access method, or channel access method.
There are a number of ways of doing this. On the same medium:
• You could transmit packets on different frequencies.
• You could send packets at different times, like timesharing.
• You could use spread spectrum or frequency hopping.
• You could split the network into different “cells”.
• You could add another wire to allow traffic to flow both directions at once.
Let’s again use Ethernet as an example. Ethernet is most like the “timesharing” mode, above.
But that still leaves a lot of options open for exactly how we do that.
Wired Ethernet uses something called CSMA/CD (Carrier-Sense Multiple Access with Collision Detec-
tion). Easy for you to say.
This method works like this:
1. The Ethernet card waits for quiet in the room–when no other network card is transmitting. (This is
the “CSMA” part of CSMA/CD.)
2. It starts sending.
3. It also listens while it’s sending.
4. If it receives the same thing that it sent, all is well.
If it doesn’t receive the same thing it sent, it means that another network device also started trans-
mitting at the same time. This is a collision detection, the “CD” part of CSMA/CD.
5. To resolve the situation, the network card transmits a special signal called the “jam signal” to alert
other cards on the network that a collision has occurred and they should stop transmitting. The
network card then waits a small, partly random amount of time, and then goes back to step 1 to retry
the transmission.
WiFi (wireless) Ethernet uses something similar, except it’s CSMA/CA (Carrier-Sense Multiple Access
with Collision Avoidance). Also easy for you to say.
This method works like this:
1. The Ethernet card waits for quiet in the room–when no other network card is transmitting. (This is
the “CSMA” part of CSMA/CA.)
2. If the channel isn’t quiet, the network card waits a small, random amount of time, then goes back
to step 1 to retry.
There are a few more details omitted there, but that’s the gist of it.
20.6 Ethernet
Remember with the layered network model how each layer encapsulates the previous layer’s data into its
own header?
For example, HTTP data (Application Layer) gets wrapped up in a TCP (Transport Layer) header. And
then all of that gets wrapped up in an IP (Network Layer) header. And then all of that gets wrapped up in
an Ethernet (Link Layer) frame.
And recall that each protocol had its own header structure that helped it perform its job.
Ethernet is no different. It’s going to encapsulate the data from the layer above it.
Now, I want to get a little picky about terminology. The whole chunk of data that’s transmitted is the
Ethernet packet. But within it is the Ethernet frame. As we’ll see later, these correspond to two layers of
102 Chapter 20. The Link Layer and Ethernet
the ISO OSI layered network model (that have been condensed into a single “Link layer” in the Internet
layered network model).
Though I’ve written the frame “inside” the packet here, note that they are all transmitted as a single
bitstream.
• The Packet:
• 7 octets: Preamble (in hex: AA AA AA AA AA AA AA)
• 1 octet: Start frame delimiter (in hex: AB)
• The Frame:
• 6 octets: Destination MAC address
• 6 octets: Source MAC address
• 4 octets: “Dot1q” tag for virtual LAN differentiation.
• 2 octets: Payload Length/Ethertype (see below)
• 46-1500 octets: Payload
• 4 octets: CRC-32 checksum
• End of frame marker, loss of carrier signal
• Interpacket gap, enough time to transmit 12 octets
The Payload Length/EtherType field is used for the payload length in normal usage. But other values can
be put there that indicate an alternate payload structure.
The largest payload that can be transmitted is 1500 octets. This is known as the MTU (Maximum Trans-
mission Unit) of the network. Data that is larger must be fragmented down to this size.
Ethernet hardware can use this 1500 number to differentiate the Payload Length/EtherType header
field. If it’s 1500 octets or fewer, it’s a length. Otherwise it’s an EtherType value.
After the frame, there’s an end-of-frame marker. This is indicated by loss of carrier signal on the wire, or
by some explicit transmission in some versions of Ethernet.
Lastly, there’s a time gap between Ethernet packets which corresponds to the amount of time it would take
to transmit 12 octets.
What we call the Internet “Link Layer” is the “Data Link” layer and the “Physical” layer.
20.7. Reflect 103
The Data Link layer of Ethernet is the frame. It’s a subset of the entire packet (outlined above) which is
defined at the Physical Layer.
Another way to consider this is that the Data Link Layer is busy thinking about logical entities like who has
what MAC address and what the payload checksum is. And that that Physical Layer is all about figuring
out which patterns of signals to send that correspond to the start and end of the packet and frame, when
to lower the carrier signal, and how long to delay between transmissions.
20.7 Reflect
• What’s your MAC address on your computer? Do an Internet search to find how to look it up.
• What’s the deal with frames versus packets in Ethernet? Where in the ISO OSI network stack do
they live?
• What’s the difference between a byte and an octet?
• What’s the main difference between CSMA/CD and CSMA/CA?
104 Chapter 20. The Link Layer and Ethernet
Chapter 21
Again, for this section we’re talking about sending on the LAN, the local Ethernet. Not over the
Internet with IP. This is between two computers on the same Physical Layer network.
This section is all about ARP, the Address Resolution Protocol. This is how one computer can map a
different computer’s IP address to that computer’s MAC address.
105
106 Chapter 21. ARP: The Address Resolution Protocol
1. The source computer will broadcast a specialized Ethernet frame that contains the destination IP
address. This is the ARP request.
(Remember the EtherType field from the previous chapter? ARP packets have EtherType 0x0806
to differentiate them from regular data Ethernet packets.)
2. All computers on the LAN receive the ARP request and examine it. But only the computer with the
IP address specified in the ARP request will continue. The other computers discard the packet.
3. The destination computer with the specified IP address builds an ARP response. This Ethernet frame
contains the destination computer’s MAC address.
4. The destination computer sends the ARP response back to the source computer.
5. The source computer receives the ARP response, and now it knows the destination computer’s MAC
address.
And it’s game-on! Now that we know the MAC address, we can send with impunity.
Then, when we want to send to a particular IP on the LAN, we can look in the ARP cache and see if the
IP/Ethernet pair is already there. If so, no need to send out an ARP request–we can just transmit the data
right away.
The entries in the cache will timeout and be removed after a certain amount of time. There’s no standard
time to expiration, but I’ve seen numbers from 60 seconds to 4 hours.
Entries could go stale if the MAC address changes for a given IP address. Then the cache entry would be
out of date. The easiest way for that to happen would be if someone closed their laptop and left the network
(taking their MAC address with them), and then another person with a different laptop (and different MAC
address) showed up and was assigned the same IP address. If that happened, computers with the stale entry
would send the frames for that IP to the wrong (old) MAC address.
In the payload structure below, when it says “Hardware” it means the Link Layer (e.g. Ethernet in this
example) and when it says “Protocol” it means Network Layer (e.g. IP in this example). It uses those
generalized names for the fields since there’s no requirement that ARP use Ethernet or IP–it can work
with other protocols, too.
21.8 Reflect
• Describe the problem ARP is solving.
• Why do entries in ARP caches have to expire?
• Why can’t IPv6 use ARP?
108 Chapter 21. ARP: The Address Resolution Protocol
Chapter 22
Internal Gateway Protocols share information with one another such that every router has a complete make
of the network they’re a part of. This way, each router can make autonomous routing decisions given an
IP address. No matter where it’s going, the router can always forward the packet in the right direction.
IGPs like Open Shortest Path First (OSPF) use Dijkstra’s Algorithm to find the shortest path along a
weighted graph.
In this project, we’re going to simulate that routing. Were going to implement Dijkstra’s Algorithm to
print out the shortest path from one IP to another IP, showing the IPs of all the routers in between.
We won’t be using a real network for this. Rather, your program will read in a JSON file that contains the
network description and then compute the route from that.
109
110 Chapter 22. Project 6: Routing with Dijkstra’s
The gist of the algorithm is this: explore outward from the starting point, pursuing only the path with the
shortest total length so far.
Each path’s total weight is the sum of the weights of all its edges.
In a well-connected graph, there will be a lot of potential paths from the start to the destination. But since
we only pursue the shortest known path so far, we’ll never pursue one that takes us a million miles out of
the way, assuming we know of a path that is shorter than a million miles.
1
https://en.wikipedia.org/wiki/Dijkstra’s_algorithm
22.4. What About Our Project? 111
At this point, we have constructed our tree made up of all the parent pointers.
To find the shortest path from one point back to the start (at the root of the tree), you need to just follow
the parent pointers from that point back up the tree.
• Get Shortest Path from source to destination:
• Set our current node to the destination node.
• Set our path to be an empty array.
• While current node is not starting node:
• Append current node to path.
• current node = the parent of current node
• Append the starting node to the path.
Of course, this will build the path in reverse order. It has to, since the parent pointers all point back to
the starting node at the root of the tree. Either reverse it at the end, or run the main Dijkstra’s algorithm
passing the destination in for the source.
{
"10.34.98.1": {
"connections": {
"10.34.166.1": {
"netmask": "/24",
"interface": "en0",
"ad": 70
},
"10.34.194.1": {
"netmask": "/24",
"interface": "en1",
"ad": 93
},
"10.34.46.1": {
"netmask": "/24",
"interface": "en2",
"ad": 64
}
},
"netmask": "/24",
"if_count": 3,
"if_prefix": "en"
},
The top-level keys (e.g. "10.34.98.1") are the router IPs. These are the vertices of the graph.
For each of those, you have a list of "connections" which are the edges of the graph.
In each connection, you have a field "ad" which is the edge weight.
“AD” is short for Administrative Distance. This is a weight set manually or automatically (or a
mix of both) that defines how expensive a particular segment of the route is. The default value is
110. Higher numbers are more expensive.
The metric encompasses a number of ideas about the route, including how much bandwidth it
provides, how congested it is, how much the administrators want it used (or not), and so on.
The netmask for the router IP is in the "netmask" field, and there are additional "netmask" fields for all
the connection routers, as well.
The "interface" says which network device on the router is used to reach a neighboring router. It is
unused in this project.
22.5. Input File and Example Output 113
22.6 Hints
• Rely heavily on the network functions you wrote in the previous project!
• Fully understand this project description before coming up with a plan!
• Come up with a plan as much as possible before writing any code!
114 Chapter 22. Project 6: Routing with Dijkstra’s
Chapter 23
We’re going to take a look at some live network traffic with WireShark1 and see if we can capture some
ARP requests and replies.
Wireshark is a great tool for sniffing network packets. It gives you a way to trace packets as that move
across the LAN.
We’ll set up a filter in WireShark so that we’re only looking for ARP packets to and from our specific
machines so we don’t have to search for a needle in a haystack.
ifconfig
1
https://www.wireshark.org/
115
116 Chapter 23. Project: Sniff ARP packets with WireShark
ipconfig
Set up a display filter in WireShark to filter ARP packets that are only either to or from your machine.
Type this in the bar near the top of the window, just under the blue sharkfin button.
And if all goes well, you’ll have a reply that looks like this:
[If you’re not seeing anything, try changing your display filter to just say “arp”. Watch for a while
and see if you see a request/reply pair go by.]
Click on the request and look at the details in the lower left panel. Expand the “Address Resolution
Protocol (request)” panel.
Right click any line in that panel and select “Copy->All Visible Items”.
Here’s an example request (truncated for line length):
23.2. Step by Step 117
Click on the reply in the timeline. Copy the reply information in the same way.
Here’s an example reply (truncated for line length):
Network Hardware
119
120 Chapter 24. Network Hardware
• Hub: A device that allows you to connect several computers via Ethernet cables. It’ll have 4, 8,
or more ports on the front. All these devices plugged into those ports are effectively on the same
“wire” once connected, which is to say that any Ethernet packet transmitted is seen by all devices
plugged into the HUB. You don’t typically see these any longer, since switches perform the same
role better.
• Switch: A hub with some brains. It knows the MAC addresses on the other side of the ports so it
doesn’t have to retransmit Ethernet packets to everyone. It just sends them down the proper wire to
the correct destination. Help prevent network overload.
• Router: A Network Layer device that have multiple interfaces and chooses the correct one to send
traffic down so that it will eventually reach its destination. Routers contain routing tables that let
them decide where to forward a packet with a given IP address.
• Default Gateway: A router that handles traffic to all other destinations, if a specific route to the
destination isn’t known. A computer’s routing table specifies the default gateway. On a home LAN,
this is the IP of the “router” the ISP gave you.
Imagine an island with a small town on it. The island is connected to the mainland by a single
bridge. If someone wants to know where to go in town, you give them directions in town. For all
other destinations, they drive across the bridge. In this analogy, the bridge is the default gateway
for traffic.
• Broadcast: To send traffic to everyone on the LAN. This can be done at the Link Layer by sending
an Ethernet frame to MAC address ff:ff:ff:ff:ff, or at the Network Layer by sending an IP
packet with all the host bits set to 1. For instance, if the network is 192.168.0.0/16, the broadcast
address would be 192.168.255.255. You can also broadcast to 255.255.255.255 for the same
effect. IP routers do not forward IP broadcast packets, so they are always restricted to the LAN.
• Wi-Fi: Short for Wireless Fidelity (a non-technical marketing trademark presumably meant to pun
Hi-Fi), this is your wireless LAN connection. Speaks Ethernet at the Link Layer. Very similar to
using an Ethernet cable, except instead of electricity over copper, it uses radio waves.
• Firewall: A computer or device at or near where your LAN connects to your ISP that filters traffic,
preventing unwanted traffic from being transmitted on your LAN. Keeps the bad guys out, hope-
fully.
24.2. Reflect 121
• NAT: Network Address Translation. A way to have a private IP subnet behind the NAT device that
is not visible to the rest of the Internet. The NAT device translates internal IP addresses and ports
to an external IP on the router. This is why if you go to Google and ask “what is my ip”, you’ll
get a different number than you’ll see when you look at the settings on your computer. NAT is in
the middle, translating between your internal LAN IP address and the external, publicly-visible IP
address. We’ll talk more about the details of this mechanism later.
• Private Network IPv4 Addresses: For LANs not connected to the Internet or LANs behind NAT,
there are three common subnets that are used: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16.
These are reserved for private use; no public sites will ever use them, so you can put them on your
LAN without worrying about conflicts.
• WiFi Modem/WiFi Router: Loosely refers to a consumer-grade device that you get when you sign
up with an ISP for service. Often does a variety of things
• Rack-mount: If a device doesn’t come in a nice plastic case or isn’t meant to be plugged di-
rectly into a computer, it might be rack-mount. These are larger non-consumer devices like routers,
switches, or banks of disks, that get stacked up in “racks”.
• Upload: Transferring a file from your local device to a remote device.
• Download: Transferring a file from a remote device to your local device.
• Symmetric: In the context of transfer speeds, means that a connection offers the same speeds in
both directions. “1 Gbs symmetric” means that upload and download speeds are 1 Gbs.
• Asymmetric: In the context of transfer speeds, means that a connection offers different speeds
in either direction. Usually written as something like “600 Mbs down, 20 Mbs up”, for example,
indicating download and upload speeds. Often shortened to “600 by 20” conversationally. Most
general usage is people download things, not uploading, so companies that provide service allocate
more of their total bandwidth on the download side.
• Cable (from the cable company): Many cable television companies offer Internet connectivity over
the coaxial cable line they’ve run to your house. Speeds up to 1 Gbs aren’t unheard of. Typically
a neighborhood shares bandwidth, so your speeds will drop in the evening when everyone living
around you is watching movies. Most cable offerings are asymmetric.
• DSL: Digital Subscriber Line. Many telephone companies offer Internet connectivity over the
phone lines they’ve run to your house. It’s slower than cable at around 100 Mbs, but bandwidth is
not shared with neighbors. Most DSL offerings are asymmetric.
• Fiber: Short for optical fiber, uses light through glass “wires” instead of electricity through copper
wires. Very quick. Many ISPs that offer relatively-cheap fiber have packages that deliver 1 Gbs
symmetric.
• Modem: Short for Modulator/Demodulator, converts signals in one form to signals in another form.
Historically, this meant turning sounds transmitted over a telephone landline into data. In modern
usage, it means converting the signals on your Ethernet LAN to whatever form is needed by the ISP,
e.g. cable or DSL.
• Bridge: A device that connects two networks at the link level, allowing them to behave as a single
network. Some bridges blindly forward all traffic, other bridges are smarter about it. Cable modems
are a type of bridge, though they often come built into the same box as a router and switch.
• Vampire Tap: Back in the old days, when you wanted to connect a computer to a thicknet cable,
you used one of these awesomely-named devices to do it. Included here just for fun.
24.2 Reflect
• What’s the difference between a hub and a switch?
• What does a router do?
• Why would a router with only one network connection not make any sense?
122 Chapter 24. Network Hardware
• What kind of device do you connect to with your laptop where you live? Do you use a physical
cable to connect?
• No need to write anything for this reflection point unless you’re inclined, but ponder what life
was like with 300 bps modems. The author’s first modem was a VICMODEM, literally two million
times slower than a modern cable connection. That was 40 years ago. Now imagine network speeds
in the year 2062.
Chapter 25
In this project, we’re going to build a simple network between two computers using Cisco’s Packet Tracer1
tool. It’s free!
If you don’t have it installed, check out Appendix: Packet Tracer for installation information.
If at any time you make a mistake that you need to delete, choose the “Delete” tool from the second icon
row down.
Then drag two “PC”s out onto the work area from the panel in the lower middle.
123
124 Chapter 25. Project: Packet Tracer: Connect Two Computers
Click on the “Copper Cross-Over” icon in the lower middle. (The icon will change to an “anti” symbol.)
C:\>ping 192.168.0.3
If you see Request timed out. more than twice, something is misconfigured.
It’s not typical to wire two PCs directly together. Usually they’re connected through a switch.
Let’s set that up in the lab.
26.3 Wire It Up
None of the PCs will be directly connected. They’ll all connect directly to the switch.
We don’t use crossover cables here; the switch knows what it’s doing.
Note that when you first wire up the LAN, you might not have two green up arrows shown on the
connection. One or both of them might be orange circles indicating the link is in the process of
coming up. You can hit the >> fast-forward button in the lower left to jump ahead in time until you
get two green up arrows.
Choose the “Connections” selector in the lower left.
Choose the “Copper Straight-Through” cable. (The icon will change to an “anti” symbol.)
127
128 Chapter 26. Project: Packet Tracer: Using a Switch
We’re going to add a router between two independent networks in Packet Tracer.
The goal will be something that looks like this:
129
130 Chapter 27. Project: Packet Tracer: Using a Router
Connect the left PCs to one switch on any unused FastEthernet ports. Connect the right PCs to the
other switch in the same way.
At this point, you should have two separate LANs.
Hit the fast-forward button if all the links aren’t green yet. If they stay red, something is wrong–double-
check your settings.
For sanity, make sure 192.168.0.2 can ping the other two machines on its LAN. And make sure
192.168.1.3 can ping the other two machines on its LAN.
This router is going to be connected to both switches. Each “side” of the router will have a different IP
address.
We’re going to connect some wires here, but the link won’t come up yet. We’ll deal with that in a minute.
On the switch on LAN 192.168.0.0/24, use a Copper Straight-Through connecter to connect the
switch’s GigabitEthernet0/1 port to the router’s GigabitEthernet0/0/0 port.
On the switch on LAN 192.168.1.0/24, use the same type of connector to connect the switch’s
GigabitEthernet0/1 port to the router’s GigabitEthernet0/0/1 port.
Note this is a different port on the router than the other switch is connected to! Both switches are plugged
into different ports on the router.
We have the hardware in place now, but we don’t yet have any IP addresses assigned on the router. So
let’s do that.
In the “Config” for the router, choose the GigabitEthernet0/0/0 interface and give it the IP address
192.168.0.1–it’s now part of the 192.168.0.0/24 subnet. While you’re here, click the “On” checkbox
to power the port. This should bring up the connection and get you the green arrows.
Then go to the GigabitEthernet0/0/1 interface and give it the IP address 192.168.1.1–it’s now part
of the 192.168.1.0/24 subnet. While you’re here, click the “On” checkbox to power the port.
Let’s try pinging the router. Go to any PC on the 192.168.0.0/24 LAN and trying pinging 192.168.0.1
(the router). It should reply.
Then go to any PC on the 192.168.1.0/24 subnet and try pinging 192.168.1.1. It should reply.
Now the ultimate test: get a console on PC 192.168.0.2 and try to ping 192.168.1.2 on the other
subnet!
It… doesn’t work!
Why?
Do the same for all the PCs on the other subnet, except enter 192.168.1.1 as the default gateway.
In the last Packet Tracer project, we had a single router between two subnets. In this project, we’ll have
multiple routers between subnets.
This complicates matters, because each router’s routing table needs to be edited so that it knows out which
connection to forward packets.
In a larger LAN, a gateway protocol could be used to quickly distribute the information the routers needed.
But in our case, to keep it conceptually simple, we’ll just manually configure the routes in each of the
three routers we’ll have.
But, and this is the fun bit, the middle router is connected to two routers and another LAN!
Put straight-through copper connections between the components as shown in the diagram, above.
133
134 Chapter 28. Project: Packet Tracer: Multiple Routers
being on the same subnet, they send the traffic to their default gateway, i.e. the router that knows what to
do with it.
Click on each PC in turn. Under “Config” in sidebar “Global/Settings”, set a static “Default Gateway” of
that LAN’s router IP.
For example, if I’m on PC 192.168.1.2 and my router on that LAN is 192.168.1.1, I’ll set the PC’s
default gateway to 192.168.1.1.
In fact, I’ll set the default gateway for all the PCs on the LAN to that value.
Do the same for the other two LANs.
If a packet destined for 192.168.1.2 (on the right) leaves from 192.168.0.3 on the left, how does it
get there?
We can see it must travel through all three routers. But when it arrives at the first one at 192.168.0.1
(the router for the LAN), where does that router send it?
Well, from there, we’ll head out on the 10.0.0.0/16 subnet to router 10.0.0.2.
So we have to add a route for the leftmost router saying, “Hey, if you get anything for subnet
192.168.0.0/24, forward it to 10.0.0.2 because that’s the next hop on the way.”
We do this by clicking on the leftmost router, then going to “Config”, and “Routing/Static” in the left
sidebar.
The “Network” and “Mask” fields are the destination, and “Next Hop” is the router we should forward
that traffic to.
In the case of the diagram, we’d add a route to the leftmost router like so (recalling that a \24 network
has netmask 255.255.255.0):
Network: 192.168.1.0
Mask: 255.255.255.0
Next Hop: 10.0.0.2
And that gets us partway there! But, sadly and importantly, that middle router doesn’t know where to send
traffic for 192.168.1.0/24, either.
So we have to add a route to that middle router that sends it on the next hop, but this time out its
10.1.0.0/16 interface:
28.8. Test It Out! 137
Network: 192.168.1.0
Mask: 255.255.255.0
Next Hop: 10.1.0.2
And now we’re there! The router with IP 10.1.0.2 has an interface that is connected to 192.168.1.0/24.
Our original packet is going to 192.168.1.2, and that’s on the same subnet! The router knows it can just
send the traffic out on that interface.
Of course, that’s not all we have to do.
Add routing table entries for all the non-directly-connected subnets to each router.
Each router should have two static routing table entries so that all inbound and outbound traffic is covered.
Select
In this chapter we’re taking a look at the select() function. This is a function that looks at a whole set
of sockets and lets you know which ones have sent you data. That is, which ones are ready to call recv()
on.
This enables us to wait for data on a large number of sockets at the same time.
data1 = s1.recv(4096)
data2 = s2.recv(4096)
data3 = s3.recv(4096)
but there’s no data ready on s1, then the process will block there and not call recv() on s2 or s3 even if
there’s data to be received on those sockets.
We need a way to monitor s1, s2, and s3 at the same time, determine which of them have data ready to
receive, and then call recv() only on those sockets.
The select() function does this. Calling select() on a set of sockets will block until one or more of
those sockets is ready-to-read. And then it returns to you which sockets are ready and you can call recv()
specifically on those.
import select
If you have a bunch of connected sockets you want to test for being ready to recv(), you can add those
to a set() and pass that to select(). It will block until one is ready to read.
139
140 Chapter 29. Select
This set can be used as your canonical list of connected sockets. You need to keep track of them all
somewhere, and this set is a good place. As you get new connections, you add them to the set, and as the
connections hang up, you remove them from the set. In this way, it always holds the sockets of all the
current connections.
Here’s an example. select() takes three arguments and return three values. We’ll just look at the first
of each of these for now, ignoring the other ones.
At this point, we can go through the sockets that are ready and receive data.
for s in ready_to_read:
data = s.recv(4096)
main loop:
call select() and get the sockets that are ready to read
But again, for this project, we just use the first and ignore the rest.
29.6 Reflect
• Why can’t we just call recv() on all the connected sockets? What does select() buy us?
• When select() shows a socket “ready-to-read”, what does it mean if the socket is a listening
socket versus a non-listening socket?
• Why do we have to add the listener socket to the set, anyway? Why not just call accept() and
then call select()?
In this project we’re going to write a server that uses select() to handle multiple simultaneous connec-
tions.
The client is already provided. You fill in the server.
• When a client disconnects, the server prints out the late client’s connection info in this form:
Hint: You can use the .getpeername() method on a socket to get the address of the remote side
even after it has disconnected. It’ll come back as a tuple containing ("host", port), just like what
you pass to connect().
• When a client sends data, the server should print out the length of the data as well as the raw
bytestring object received:
1
https://beej.us/guide/bgnet0/source/exercises/select/select.zip
143
144 Chapter 30. Project: Using Select
The first argument to the client can be any string–the server prints it out with the data to help you identify
which client it came from.
Example output:
We’ve learned that IP is responsible for routing traffic around the Internet. We’ve also learned that it does
it with IP addresses, which we commonly show in dots-and-numbers format for IPv4, such as 10.1.2.3.
But as humans, we rarely use IP addresses. When you use your web browser, you don’t typically put an
IP address in the address bar.
Even in our projects, we tended to type localhost instead of our localhost address of 127.0.0.1.
The general process for converting a name like www.example.com that humans use into an IP address
that computers use is called domain name resolution, and is provided by a distributed group of servers
that comprise the Domain Name System, or DNS.
1. Contact a domain registrar (i.e. some company that has the authority to sell domains).
2. Choose a domain no one has picked yet.
3. Pay them some money annually to use the domain.
4. …
5. Profit!
But doing this is completely disconnected from the idea of an IP address. Indeed, domains can exist
without IP addresses–they just can’t be used.
Once you have your domain, you can contact a hosting company that will provide you with an IP address
on a server that you can use.
Now you have the two pieces: the domain and the IP address.
But you still have to connect them so people can look up your IP if they have your domain name.
145
146 Chapter 31. Domain Name System (DNS)
To do this, you add a database record with the pertinent information to a server that’s part of the DNS
landscape: a domain name server.
A subdomain is a domain administered by the owner of a domain. For example, the owner of
example.com might make subdomains sub1.example.com and sub2.example.com. These
aren’t hosts in this case–but they can have their own hosts, e.g. host1.sub1.example.com,
host2.sub1.example.com, somecompy.sub2.example.com.
Domain owners can make as many subdomains as they want. They just have to make sure they
have a name server set up to handle them.
A name server that’s authoritative for a specific domain can be asked about any host on that domain.
The host is often the first “word” of a domain name, though it’s not necessarily.
For example with www.example.com, the host is a computer called www on a domain example.com.
A single name server might be authoritative for many domains.
But even if a name server doesn’t know the IP address of the domain it’s been asked to provide, it can
contact some other name servers to figure it out. From a user perspective, this process is transparent.
So, easy-peasy. If I don’t know the domain in question, I’ll just contact the name server for that domain
and get the answer from them, right?
a.root-servers.net
b.root-servers.net
c.root-servers.net
...
k.root-servers.net
l.root-servers.net
m.root-servers.net
1. Let’s choose a random root server, say c.root-servers.net. We’ll contact it and say, “Hey, we’re
looking for www.example.com. Can you help us?”
But the root name server doesn’t know that. It says, “I don’t know about that, but I can tell you if
you’re looking for any .com domain, you can contact any one of these name servers.” It attaches a
list of name servers who know about the .com domains:
a.gtld-servers.net
b.gtld-servers.net
c.gtld-servers.net
d.gtld-servers.net
e.gtld-servers.net
f.gtld-servers.net
g.gtld-servers.net
h.gtld-servers.net
i.gtld-servers.net
j.gtld-servers.net
k.gtld-servers.net
l.gtld-servers.net
m.gtld-servers.net
a.iana-servers.net
b.iana-servers.net
31.6 Zones
The Domain Name System is split into logical administrative zones. A zone is, loosely, a collection of
domains under the authority of a particular name server.
But that’s an oversimplification. There could be one or more domains in the same zone. And there could
be a number of name servers working in that same zone.
Think of the zones as all the domains and subdomains some administration is responsible for.
For example, in the root zone, we saw there were a number of name servers responsible for that lookup.
And also in the .com zone, there were a number of different name servers there with authority.
s.connect(("example.com", 80))
148 Chapter 31. Domain Name System (DNS)
you didn’t have to worry about DNS at all. Behind the scenes, Python did all that work of looking up that
domain in DNS.
In C, there’s a function called getaddrinfo() that does the same thing.
The short of it is that there’s a library that we can use and we don’t have to write all that code ourselves.
The OS also has a record containing its default name server to use for lookups. (This is sometimes con-
figured by hand, but more commonly is configured through DHCP.) So when you request a lookup, your
computer first goes to this server.
But wait a minute–how does that tie into the whole root server hierarchy thing?
The answer: caching!
• AAAA: An address record for IPv6. Answers the question, “What is the IPv6 address for this host or
domain?”
• NS: A name server record for a particular domain. Answers the question, “What are the name
servers answering for this host or domain?”
• MX: A mail exchange record. Answers the question, “What computers are responsible for handling
mail on this domain?”
• TXT: A text record. Holds free-form text information. Is sometimes used for anti-spam purposes
and proof-of-ownership of a domain.
• CNAME: A canonical name record. Think of this as an alias. Makes the statement, “Domain
xyz.example.com is an alias for abc.example.com.”
• SOA: A start of authority record. This contains information about a domain, including its main name
server and contact information.
There are a lot of DNS record types.
31.12 Reflect
• What is your name server for your computer right now? Search the net for how to look it up on
your particular OS.
• Do the root name servers know every IP address in the world?
• Why would anyone use dynamic DNS?
• What is TTL used for?
150 Chapter 31. Domain Name System (DNS)
Chapter 32
32.2 Why?
NAT is very, very common. It almost certainly runs on every IPv4 LAN.
But why?
There are two main reasons to use this type of service.
1. You want to hide your network details from the rest of the world.
151
152 Chapter 32. Network Address Translation (NAT)
2. You need more IP addresses than either (a) anyone is willing to allocate to you or (b) you’re willing
to pay for.
The first thing my computer does is check to see if the destination IP address is on my LAN… Since my
LAN is 192.168.0.0/16 or smaller, then no, it’s not.
So my computer sends it to the default gateway, my router that has NAT enabled.
The router is going to play the role of the “anonymizer” middleman in the earlier analogy example. And
recall that the router has two interfaces on it–one faces the internal 192.168.0.0/16 private LAN, and
32.4. How it Works 153
the other faces the greater Internet with a public, external IP. Let’s use 192.168.1.1 as the private IP
address on the router and 198.51.100.99 as the public IP.
So the LAN looks like this:
+----------+-------------+
| Local | |
| Computer | 192.168.1.2 |>-------+
+----------+-------------+ |
|
^
+---------------+
| 192.168.1.1 | Internal IP
+---------------+
| Router | NAT-enabled
+---------------+
| 198.51.100.99 | External IP
+---------------+
v
|
|
{The Greater Internet}
The router now has to “repackage” the data for the greater Internet. This means rewriting the packet source
to be from the router’s public IP and some unused port on the public interface of the router. (The port
doesn’t need to be the same as the port originally on the packet. The router allocates a random unused
port when the new packet is sent out.))
So the router records all this information:
(Note: except for 80, since we’re connecting to HTTP in this example, all port numbers were randomly
chosen.)
So while the original packet was:
Local Remote
Computer Computer
the NAT router rewrites it to be the same destination, but the router as the source.
From the destination’s point of view, it is completely unaware that this isn’t originally from the router
itself.
So the destination replies with some HTTP data, sending it back to the router:
154 Chapter 32. Network Address Translation (NAT)
The router then looks at its records. It says, “Wait a moment–if I get data on port 5678, that means I need
to translate this back to a private IP on the LAN!
So it translates the message so that it’s no longer addressed to the router, but instead is sent to the private
source IP recorded earlier:
Remote Local
Computer Computer
And sends that out on the LAN. And its received! The LAN computer thinks it’s talking to the remote
server, and the remote server thinks it’s talking to the router! NAT!
It might be a little confusing in that last step that the packet is coming from the NAT router but is actually
IP addressed like it came from the Remote Computer. This is OK on the LAN because the NAT router
sends that IP packet out with an Ethernet address of the Local Computer on the LAN. Or, put another way,
the NAT router can use link layer addressing to get the packet delivered to the Local Computer and the IP
address of where that packet came from doesn’t have to match the internet IP of the NAT router.
fd00::/8
There’s some additional structure in the “host” portion of the address, but you can read about it on
Wikipedia if you want.
Like the IPv4 private subnets, IP addresses from this subnet are dropped by routers on the greater Internet.
NAT doesn’t work for IPv6, but there is another way to do the translation with network prefix translation.
But what about the whole idea that people can’t easily get unsolicited packets onto your LAN? Well,
you’re just going to have to configure your firewall properly to keep people out. But that’s a story for
another time.
on your computer at home.) So it’s more common to use some other uncommon port as the public SSH
port, and then forward it to port 22 on the LAN.
32.7 Review
• Look up your computer’s internal IP address. (Might have to search the net to see how to do this.)
Then go to google.com and type “what is my ip”. The numbers are (very probably) different. Why?
• What problems does NAT solve?
156 Chapter 32. Network Address Translation (NAT)
Chapter 33
When you first open your laptop at the coffee shop, it doesn’t have an IP address. It doesn’t even know
what IP address it should have. Or what its name servers are. Or what its subnet mask is.
Of course, you could manually configure it! Just type in the numbers that the cashier hands you with your
coffee!
OK, that doesn’t happen. No one would bother. Or they’d use a duplicate address. And things wouldn’t
work. And they’d probably rage-drink their coffee and never return.
It would be better if there were a way to automatically configure a computer that just arrived on the
network, wouldn’t it?
That’s what the Dynamic Host Configuration Protocol (DHCP) is for.
33.1 Operation
The overview:
The details:
When your laptop first tries to connect to the network, it sends a DHCPDISCOVER packet to the broadcast
address (255.255.255.255) over UDP to port 67, the DHCP server port.
Recall that the broadcast address only propagates on the LAN–the default gateway does not forward it.
On the LAN is another computer acting as the DHCP server. There’s a process running on it waiting on
port 67.
The DHCP server process sees the DISCOVER and decides what to do with it.
The typical use is that the client wants an IP address. We call this leasing an IP from the DHCP server.
The DHCP server is tracking which IPs have been allocated and which are free out of its pool.
In response to the DHCPDISCOVER packet, the DHCP server sends a DHCPOFFER response back to the client
on port 68.
157
158 Chapter 33. Dynamic Host Configuration Protocol (DHCP)
The offer contains an IP address and potentially a lot of other pieces of information including, but not
limited to:
• Subnet mask
• Default gateway address
• The lease time
• DNS servers
The client can accept or ignore the offer. (Maybe there are multiple DHCP servers making offers, but the
client may only accept one of them.)
If the offer is accepted, the client sends a DHCPREQUEST back to the server notifying it that it wants that
particular IP address.
Finally, if all’s well, the server replies with an acknowledgment packet, DHCPACK.
At that point, the client has all the information it needs to participate on the network.
33.2 Reflect
• Reflect on the advantages of using something like DHCP over manually configuring the devices on
your LAN.
• What types of information does a DHCP client receive from the DHCP server?
Chapter 34
The dig utility is a great command line tool for getting DNS information from your default name server,
or from any name server.
34.1 Installation
On Macs:
On WSL:
dig example.com
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;example.com. IN A
;; ANSWER SECTION:
example.com. 79753 IN A 93.184.216.34
159
160 Chapter 34. Project: Digging DNS Info
;; ANSWER SECTION:
example.com. 79753 IN A 93.184.216.34
That’s the IP address for example.com! Notice the A? That means this is an address record.
You can get other record types, as well. What if you want the mail exchange server for oregonstate.edu?
You can put that on the command line:
dig mx oregonstate.edu
We get:
;; ANSWER SECTION:
oregonstate.edu. 600 IN MX 5 oregonstate-edu.mail.protection.outlook.com.
dig ns example.com
;; ANSWER SECTION:
example.com. 78236 IN A 93.184.216.34
In this case, it’s 78236. This is the TTL of an entry in the cache. This is telling you that the name server
you used has cached that IP address, and it won’t expire that cache entry until 78,236 more seconds have
elapsed. (For reference, there are 86,400 seconds in a day.)
But if the entry comes directly from the name server that’s responsible for the domain, you’ll see
AUTHORITY: 1 (or some positive number).
And we get our expected answer. (Though the TTL is probably different–these are different servers and
their cache entries have different ages, after all.)
And we get:
Same thing again, more NS name server records. This is the c.gtld-servers.net name server telling
us, I don’t know the IP for example.com, but here are some name servers that might!”
So we try again:
You can also use +trace on the command line to watch the entire query from start to end:
34.8 What to Do
Try to answer the following:
• What’s the IP address of microsoft.com?
• What’s the mail exchange for google.com?
162 Chapter 34. Project: Digging DNS Info
Add to your document the dig commands you used to get the IP address. Each dig command
should be @ a different name server, starting with the root.
Chapter 35
Port Scanning
A NOTE ON LEGALITY: It’s unclear whether or not it is legal to portscan a computer you do not
own. We’ll be doing all our portscanning on localhost. Don’t portscan computers you do not have
permission to!
A port scan is a method of determining which ports on a computer a ready to accept connections. In other
words, which ports have a server listening on them.
The port scan is often the first line of attack on a system. Before being able to connect to see if some
vulnerabilities can be found in any listening servers, we need to know which servers are running in the
first place.
The job of the portscanner is to identify open ports, not to send or receive other data unnecessarily.
Calling connect() causes the TCP connection to complete its three-way handshake. This isn’t strictly
necessary, since if the portscanner gets any reply from the server (i.e. the second part of the three way
handshake) then we know the port is open. Completing the handshake would cause the remote OS to
wake up the server on that port for a connection we know we’re just going to close anyway.
Some TCP portscanners will instead build a TCP SYN packet to start the handshake and send that to
the port. If they get a SYN-ACK reply, they know the port is open. But then, instead of completing
the handshake with an ACK, they send a RST (reset) causing the remote OS to terminate the nascent
connection entirely–and causing it to not wake up the server on that port.
You can write software that builds custom TCP packets with raw sockets. Usually you need to be
a superuser/admin to use these.
163
164 Chapter 35. Port Scanning
Another option is to send a UDP packet to a known port that might have a server behind it. If you get a
response from the server, you know the port is open. If not, it’s closed or your traffic was filtered out.
35.2 Reflect
• Why shouldn’t you portscan random computers on the Internet?
• What’s the purpose of using a portscanner?
Chapter 36
Firewalls
A favorite component of every bad hacker movie ever is the firewall. It’s clear from those bad movies
that it has something to do with security, but the exact role is ambiguous; it’s only apparent that a bad guy
getting through the firewall is a bad thing.
But back to reality: a firewall is a computer (often a router) that restricts certain types of traffic between
one interface and another. So instead of forwarding every packet of every type from every port, it might
decide to drop those packets instead.
The upshot is that if someone in trying to connect to a port on the far side of a firewall, the firewall might
prevent that connection depending on how it is configured.
In this chapter, we’ll be speaking conceptually about firewalls, but won’t get into any specifics about
practical configuration. There are many implementations of firewalls out there, and they all tend to have
different methods of configuration.
165
166 Chapter 36. Firewalls
Stateful filtering is also used with UDP traffic, even though it’s connectionless. The firewall sees a UDP
packet go out, and it’ll allow incoming UDP packets to that port. (For a while. Since it’s connectionless,
there’s no way to tell when the server and client and done. So the firewall will timeout UDP rules after
they haven’t been used for a while.)
In order to keep the firewall from timing-out on any of its rules, the programs can send keepalive
messages. TCP actually has a specific message type for this, and the OS will periodically send
empty ACK packets out to keep anyone from timing out. (If programming with sockets, you will
likely need to set the SO_KEEPALIVE option.)
The keepalive can also be effectively implemented by a program using UDP, as well. It could be
configured to send a custom keepalive packet to the receiver (who would reply with some kind of
ACK) every so often.
Keepalive is only an issue for programs that have long periods of no network traffic.
Finally, filtering could also be done at the application layer. If the firewall digs deeply enough into the
packet, it might see that it’s HTTP or FTP data, for example. With that knowledge, it could allow or deny
all HTTP traffic, even if it’s arriving on a non-standard port.
36.4 Reflect
• What’s the difference/relationship between a firewall and a router?
• What’s the difference/relationship between a firewall and NAT?
• What’s the funniest or most painful thing in this NCIS clip?
Chapter 37
When your server receives data from someone on the Internet, it’s not good enough to trust that they have
good intentions.
There are lots of bad actors out there. You have to take steps to prevent them from sending things that
crash your server processes or, worse, give them access to your server machine itself.
In this chapter we’re going to take a high-level look at some issues that might arise from users trying to
send malicious data.
The two big ideas here are:
• Think like a villain. What could someone pass your code that would crash it or make it behave in
an unexpected way?
• Don’t trust anything from the remote side. Don’t trust that it will be a reasonable length. Don’t
trust that it will contain reasonable data.
167
168 Chapter 37. Trusting User Data
import os
os.system("ls")
Let’s say you write a server that receives some data from a user. The user will send 1, 2 or 3 as data,
depending on the function they want to select.
You run some code in your server like this:
So if the user sends 2, it will run mycommand 2 as expected and return the output to the user.
To be safe, the mycommand program verifies that the only allowable inputs are 1, 2, or 3 and returns an
error if something else is passed.
Are we safe?
No. Do you see how?
The user could pass the input:
1; cat /etc/passwd
The semicolon is the command separator in bash. This causes the mycommand program to execute, fol-
lowed by a command that shows the contents of the Unix password file.
To be safe, all the special characters in the input need to be stripped or escaped so that the shell doesn’t
interpret them.
So if I enter Alice for the user, we get the following perfectly valid query:
Alice' or 1=1 --
The -- is a comment delimiter in SQL. Now we’ve put together a query that shows all user information,
not just Alice’s.
Not only that, but a naive implementation might also support the ; command separator. If so, an attacker
could do something like this:
SELECT * FROM users WHERE name = 'Alice'; SELECT * FROM passwords -- '
Love, FriendlyTroll
LOL
<script>alert("Pwnd!")</script>
Now everyone will see that alert box whenever they view the comments!
And that’s a pretty innocuous example. The JavaScript could be anything and will be executed on the
remote site (meaning it can perform API calls and fetches as that domain). It could also rewrite the page
so that the login/password input submitted that information to the attacker’s website.
“It would be bad.”
Egon Spengler, Ghostbusters
Most HTML-oriented libraries come with a function to sterilize strings so that the browser will render
them and not interpret them (e.g. all < replaced with > and so on).
Definitely run any user-generated data through such a function before displaying it on a browser.
170 Chapter 37. Trusting User Data
37.3 Reflect
• In general terms, what’s the problem with trusting user input?
• Why aren’t buffer overflows as much of a problem with languages like Python, Go, or Rust as they
are with C?
Chapter 38
Windows WSL:
nmap localhost
171
172 Chapter 38. Project: Port Scanning
Chapter 39
It’s time to put it all together into this, the final project!
We’re going to build a multiuser chat server and a chat client to go along with it.
The chat server should allow an arbitrary number of connections from clients. All the clients will see what
the other ones are saying.
Not only that, but there should be messages for when a user joins or leaves the chat.
Here’s a sample screenshot. The prompt where this user (“pat” in this example) is typing what they’re
about to say. The region above it is where all the output accumulates.
39.1.1 Server
The server will run using select() to handle multiple connections to see which ones are ready to read.
The listener socket itself will also be included in this set. When it shows “ready-to-read”, it means there’s
a new connection to be accept()ed. If any of the other already-accepted sockets show “ready-to-read”,
it means the client has sent some data that needs to be handled.
When the server get a chat packet from one client, it rebroadcasts that chat message to every connected
client.
173
174 Chapter 39. Project: Multiuser Chat Client and Server
Note: when we use the term “broadcast” here, we’re using it in the generic sense of sending a thing
to a lot of people. We’re not talking about IP or Ethernet broadcast addresses. We won’t use those
in this project.
When a new client connects or disconnects, the server broadcasts that to all the clients, as well.
Since multiple clients will be sending data streams to the server, the server needs to maintain a packet
buffer for each client.
You can put this is a Python dict that uses the client’s socket itself as the key, so it maps from a
socket to a buffer.
The server will be launched by specifying a port number on the command line. This is mandatory; there
is no default port.
39.1.2 Client
When the client is launched, the user specifies their nickname (AKA “nick”) on the command line along
with the server information.
The very first packet it sends is a “hello” packet that has the nickname in it. (This is how the server
associates a connection with a nick and rebroadcasts the connection event to the other clients.)
After that, every line the user types into the client gets sent to the server as a chat packet.
Every chat packet (or connect or disconnect packet) the client gets from the server is shown on the output.
The client has a text user interface (TUI) that helps keep the output clean. Since the output is happening
asynchronously on a different part of the screen than the input, we need to do some terminal magic to keep
them from overwriting each other. This TUI code will be supplied and is described in the next section.
Since there can be data arriving while the user is typing something, we need a way to handle that. The
client will be multithreaded. There will be two threads of execution.
• The main sending thread will:
• Read keyboard input
• Send chat messages from the user to the server
• The receiving thread will:
• Receive packets from the server
• Display those results on-screen
Since there is no shared data between those threads, no synchronization (mutexes, etc.) will be required.
They do share the socket, but the OS makes sure that it’s OK for multiple threads to use that at the
same time without an issue. It’s threadsafe.
If you need more information on threading in Python, see the Appendix: Threading section.
The client will be started by specifying the user’s nickname, the server address, and the server port on the
command line. These are all required arguments; there are no defaults.
(The Client TUI section, below, has details about how to do this I/O.)
The client input line at the bottom of the screen should be the user’s nickname followed by > and a space.
The input takes place after that:
The output area of the screen has two main types of messages:
• Chat messages: these show the speaker nickname followed by : and a space, and then the message.
• Informational messages: these show when a user has joined or left the chat, or any other informa-
tion that needs printing. They consist of *** followed by a space, then the message. Joining and
leaving messages are shown here:
After that installs, you should just be able to use chatuicurses instead of chatui in the import line.
Known Mac Issue: my attempt complained that the curses library wasn’t installed when in fact it was.
This doesn’t seem to affect Linux or Windows.
One caveat here is that the input routine doesn’t obey CRTL-C to get out of the app. As such, you might
have to hit CTRL-C followed by RETURN to actually break out. On Windows, you might try CTRL-BREAK.
You can encode the JSON string to UTF-8 bytes by calling .encode() on the string. .encode()
takes an argument to specify the encoding, but it defaults to "UTF-8".
So the first thing you’ll have to do when looking at the data stream is make sure you have at least two
bytes in your buffer so you can determine the JSON data length. And then after that, see if you have the
length (plus 2 for the 2-byte header) in your buffer.
{
"type": "hello"
"nick": "[user nickname]"
}
39.6. Extensions 177
{
"type": "chat"
"message": "[message]"
}
{
"type": "chat"
"nick": "[sender nickname]"
"message": "[message]"
}
The client doesn’t need to send the sender’s nick along with the packet since the server can already make
that association from the hello packet sent earlier.
{
"type": "join"
"nick": "[joiner's nickname]"
}
{
"type": "leave"
"nick": "[leaver's nickname]"
}
39.6 Extensions
These aren’t worth any points, but if you want to push farther, here are some ideas. Caveat! Be sure
whatever you turn in has the official functionality as already described. These mods can be a strict superset
of that, or you can fork a new project to hold them.
At the very least, I recommend branching from your working version so it doesn’t get accidentally messed
up!
• Add direct messaging–if the user “pat” sends:
(What if the user doesn’t exist? Maybe you need to define an error packet to get back from the
server!)
• Add a way to list the names of all the people in the chat
• Add emotes–if the user “pat” sends:
179
180 Chapter 40. Appendix: Bitwise Operations
110101 decimal!
0b110101 binary!
0x110101 hex!
Let’s look at the decimal value 3490. I can convert that to hex and get 0xda2.
It’s important to remember these two values are identical:
>>> print(hex(3490))
0xda2
You can convert a value to a binary string with the bin() function.
>>> print(bin(3490))
0b110110100010
The f-strings have a nice feature of being able to pad to a field width with zeros. Let’s say you wanted an
8-digit hex number representation, you could do it like this:
40.3 Bitwise-AND
Bitwise-AND mashes two numbers together with an AND operation. The result of an AND operation is
1 if both of the two input bits are 1. Otherwise it’s 0.
40.4. Bitwise-OR 181
A B A AND B
0 0 0
0 1 0
1 0 0
1 1 1
Let’s do an example and AND two binary numbers together. Bitwise-AND uses the ampersand operator
(&) in Python and many other languages.
0 0 1 1
& 0 & 1 & 0 & 1
--- --- --- ---
0 0 0 1
You see the result is 1 only if both of the two input bits are 1.
Larger inputs are AND’d by pairs of individual bits, which are what appears in each column of the large
numbers below. (For completeness, the decimal result is shown on the right, but this is derived from the
binary representation. It’s not easy to look at two decimal numbers and ascertain their bitwise-AND.)
0100011111000101010 146986
& 1001111001001111000 & 324216
--------------------- --------
0000011001000101000 12840
See how any particular output bit is 1 only if both bits in the column above it are 1.
40.4 Bitwise-OR
Bitwise-OR mashes two numbers together with an OR operation. The result of an OR operation is 1 if
either or both of the two input bits are 1. Otherwise it’s 0.
A B A OR B
0 0 0
0 1 1
1 0 1
1 1 1
Let’s do an example and OR two binary numbers together. Bitwise-OR uses the pipe operator (|) in
Python and many other languages.
0 0 1 1
| 0 | 1 | 0 | 1
--- --- --- ---
0 1 1 1
You see the result is 1 if either of the two input bits are 1.
Larger inputs are OR’d by pairs of individual bits, which are what appears in each column of the large
numbers below. (For completeness, the decimal result is shown on the right, but this is derived from the
binary representation. It’s not easy to look at two decimal numbers and ascertain their bitwise-OR.)
0100011111000101010 146986
| 1001111001001111000 | 324216
--------------------- --------
182 Chapter 40. Appendix: Bitwise Operations
1101111111001111010 458362
See how any particular output bit is 1 if either or both bits in the column above it are 1.
40.5 Bitwise-NOT
Bitwise-NOT inverts a value by changing all the 1 bits to 0 and all the 0 bits to 1 This is a unary operator–it
just works on a single number.
A NOT A
0 1
1 0
Let’s do an example and NOT a single bit number. Bitwise-NOT uses the tilde operator (~) in Python and
many other languages.
~ 0 ~ 1
--- ---
1 0
You see the result is simply flipped to the other bit value.
Larger inputs are NOT’d by individual bits, which are what appears in each column of the large numbers
below. (For completeness, the decimal result is shown on the right, but this is derived from the binary
representation. It’s not easy to look at two decimal numbers and ascertain their bitwise-NOT.)
~ 0100011111000101010 ~ 146986
--------------------- --------
1011100000111010101 377301
See how any particular output bit is 1 only if both bits in the column above it are 1.
Python note: the bitwise-NOT of a number will frequently be negative because of Python’s arbitrary-
precision arithmetic. If you need it to be positive, bitwise-AND the result with the number of 1 bits you
need to represent the final value. For instance, to get a byte with 37 inverted, you can do any of these:
(Because, of course, those are all the same number in different bases!)
New bits on the left or right are set to zero, and bits that fall off the ends vanish forever.
I’m lying a little because of how a lot of languages handle right shifting of negative numbers. If
you shift a negative number right, the new bits might be 1 instead of 0 depending on the language.
Python uses arbitrary precision integer math, so there are actually an infinite number of 1 bits on
the left of any negative number in Python, making things even weirder.
For now, best to just think about positive numbers.
The operator is << for left shift and >> for right shift in most languages (sorry, Ruby!). Let’s do an example:
>>> v = 0b00000101
>>> print(f"{v << 2:08b}")
00010100
There we had a byte in binary and we left-shifted it by 2 with v << 2. And you can see the 101 has moved
over 2 bits to the left!
0b111111111111
32 100000
31 011111
Hey! It’s a run of 1s! Not only that, but it’s a run of 5 1s, just like we wanted! (This is analogous to
subtraction in decimal. 10,000 - 1 is 9,999. Just in binary we roll over to all 1s, not 9s.)
If you like these sorts of bit-twiddling hacks, you might enjoy the book Hacker’s Delight. Chapter 2,
which covers a lot of these techniques had historically been distributed for free; you might be able to find
a PDF floating around.
184 Chapter 40. Appendix: Bitwise Operations
40.8 Reflect
• What is 2342 & 2332?
• What is 0b110101 | 112?
• What is ~0b101010010101 in binary? (Python will show the result as a negative number, but you
can turn it back positive by ANDing it with 0b111111111111. And don’t forget that Python leaves
off leading zeroes when it prints!)
• What is 16 << 1?
• What is 64 << 1?
• What is 4200 << 1? See the pattern?
• What is 16 >> 2?
• What is 0b11100111 << 3?
• What is (1 << 8) - 1?
• What is 0x01020304 & ((1 << 16) - 1)?
Chapter 41
This week we’re going to building a number of small networks in a simulator. This simulator, called
Packet Tracer is provided for free by Cisco (as long as you sign up for a free class, which you don’t have
to take–though it is useful).
This project is to get an account set up at Cisco, download Packet Tracer, and run it.
185
186 Chapter 41. Appendix: Installing Packet Tracer
Chapter 42
Appendix: Multithreading
Multithreading is the idea that a process can have multiple threads of execution. That is, it can be running
a number of functions at the same time, as it were.
This is really useful if you have a function that is waiting for something to happen, and you need another
function to keep running at the same time.
This would be useful in a multiuser chat client because it has to do two things at once, both of which
block:
• Wait for the user to type in their chat message.
• Wait for the server to send more messages.
If we didn’t use multithreading, we wouldn’t be able to receive messages from the server while we were
waiting for user input and vice versa.
Side note: select() actually has the capability to add regular file descriptors to the set to listen
for. So it technically could listen for data on the sockets and the keyboard. This doesn’t work in
Windows, however. And the design of the client is simpler overall with multithreading.
42.1 Concepts
There are a few terms we should get straight first.
• Thread: a representation of a “thread of execution”, that is, a part of the program that is executing
at this particular moment. If you want multiple parts of the program to execute at the same time,
you can place them in separate threads.
• Main Thread: this is the thread that is running by default. We just never named it before. But the
code that you run without thinking about threads is technically running in the main thread.
• Spawning: We say we “spawn” a new thread to run a particular function. If we do this, the function
will execute at the same time as the main thread. They’ll both run at once!
• Join: A thread can wait for another to exit by calling that thread’s join() method. This conceptu-
ally joins the other thread back up to the calling thread. Usually this is the main thread join()ing
the threads it spawned back to itself.
• Target: The thread target is a function that the thread will run. When this function returns, the
thread exits.
It’s important to note that global objects are shared between threads! This means one thread can set the
value in a global object and other threads will see those changes. You don’t have to worry about this if
the shared data is read-only, but do have to consider this if it’s writable.
187
188 Chapter 42. Appendix: Multithreading
We are about to get into the weeds with concurrency and synchronization here, so for this project, let’s
just not use any global shared objects. Remember the old proverb:
Shared Nothing Is Happy Everybody
That’s not really an old proverb. I just made it up. And it sounds kind of selfish now that I read it again.
Back on track to a related notion: local objects are not shared between threads. This means threads get
their own local variables and parameter values. They can change them and those changes will not be
visible to other threads.
Also, if you have multiple threads running at the same time, the order in which they are executed is
unpredictable. This really only gets to be a problem if there is some kind of timing or data dependency
between threads, and again we’re starting to get out in the weeds. Let’s just be aware that the order of
execution is unpredictable and that’ll be OK for this project.
That should be enough to get started.
import threading
import time
for i in range(count):
print(f"Running: {name} {i}")
time.sleep(0.2) # seconds
# We need to keep track of them so that we can join() them later. We'll
# put all the thread references into this array
threads = []
# Set up the thread object. We're going to run the function called
# "runner" and pass it two arguments: the thread's name and count:
t = threading.Thread(target=runner, args=(name, i+3))
42.3. Daemon Threads 189
# Join all the threads back up to this, the main thread. The main thread
# will block on the join() call until the thread is complete. If the
# thread is already complete, the join() returns immediately.
for t in threads:
t.join()
Running: Thread0 0
Running: Thread1 0
Running: Thread2 0
Running: Thread1 1
Running: Thread0 1
Running: Thread2 1
Running: Thread1 2
Running: Thread0 2
Running: Thread2 2
Running: Thread1 3
Running: Thread2 3
Running: Thread2 4
t = threading.Thread(target=runner, daemon=True)
190 Chapter 42. Appendix: Multithreading
Then at least CTRL-C will get you out of the client easily.
42.4 Reflect
• Describe the type of problem using threads would solve.
• What’s the difference between a daemon and non-daemon thread in Python?
• What do you have to do to create the main thread in Python, if anything?
[
[1,5],
[20,22]
]
We want to:
• First add up 1+2+3+4+5 to get 15.
• Then add up 20+21+22 to get 63.
• Then add 15+63 to get 78, the final answer.
They want the range sums and the total sum printed out. For the example above, they’d want it to print:
[15, 63]
78
• The main thread should keep track of all the thread objects returned from threading.Thread()
in an array. It’ll need them in the next step.
• In another loop after that, call .join() on all the threads. This will cause the main thread to wait
until all the subthreads have completed.
• Print out the results. After all the join()s, the result array will have all the sums in it.
ranges = [
[10, 20],
[1, 5],
[70, 80],
[27, 92],
[0, 16]
]
Corresponding output:
42.5.6 Extensions
• If you’re using sum() or a for-loop, what’s your time complexity?
• The closed-form equation for the sum of integers from 1 to n is n*(n+1)//2. Can you use that to
get a better time complexity? How much better?
192 Chapter 42. Appendix: Multithreading
Chapter 43
Appendix: JSON
For the final project, we need to be able to encode and decode JSON data.
If you’re not familiar with that format, get a quick introduction at Wikipedia.
In this section, we’ll take a look at what it means to encode and decode JSON data.
{
"name": "Ada Lovelace",
"country": "England",
"years": [ 1815, 1852 ]
}
In Python, you can make a dict object that looks just like that:
d = {
"name": "Ada Lovelace",
"country": "England",
"years": [ 1815, 1852 ]
}
But here’s the key difference: all JSON data are strings. The JSON is a string representation of the data
in question.
import json
Likewise, if you have Python data, you can convert it into JSON string format with json.dumps():
193
194 Chapter 43. Appendix: JSON
import json
json_data = json.dumps(data)
d = {
"name": "Ada Lovelace",
"country": "England",
"years": [ 1815, 1852 ]
}
json.dumps(d)
outputs:
You can clean it up a bit by passing the indent argument to json.dumps(), giving it an indentation level.
json.dumps(d, indent=4)
outputs:
{
"name": "Ada Lovelace",
"country": "England",
"years": [
1815,
1852
]
}
Much cleaner.
43.5 Review
• What’s the difference between a JSON object and a Python dictionary?
• Looking at the Wikipedia article, what types of data can be represented in JSON?