When you opened this page in your web browser, your computer exchanged data with quite a few servers on the internet, almost unnoticeably to you. In this lesson, we’ll build a network client application, which does something similar to your browser in this situation.
It all starts with an address: you want to send a message to your friend Alice, but your computer needs to know where these messages should be sent for the message to be delivered to the right computer—just like you need to provide a recipient address when you send an email.
We are used to one kind of address system: domain names, like
duckduckgo.com. However, these
addresses are intended to be used by human beings; machines use another addressing system.
Internet protocol (or IP) addresses look like
192.168.0.1. In order to find out which IP address we should use
for a domain name like
lowlvl.org, we ask a name server, a computer with a well-known IP address like
188.8.131.52. A name server understands DNS, or the domain name service protocol. Protocols are languages in
which computers can talk to each other.
So why don't we give it a try? Let's translate a domain name into an IP address:
Below you see a code example written in Rust (if you are not familiar with the Rust syntax, you can refer to The Rust Book and learn by example).
This should give you a taste of what it's like to work with computer networks. Now let's start looking into what makes this code tick!
We have now seen how IP addresses are used in a small, imaginary network of two computers. But just knowing an address isn’t enough to do something useful: machines need to follow a well-known protocol to make sure they can communicate with each other in a predictable way. This is what the Internet protocol suite, also known as TCP/IP, is about: it's a standard collection of protocols used on the Internet.
The protocol suite consists of several protocols, of which we are interested in two: IP and UDP (later we will also cover TCP and other protocols).
IP is the bread and butter of networking. It serves as a foundation for high-level protocols. When computers exchange messages, they encode them into IP packets, which you can view as individual message units of up to 65 kilobytes in size. Usually, the operating system takes care of the encoding for us and we don't need to do it manually, but it's still good to know how it works under the hood, so let's see what a typical IP packet looks like:
When you run this code, you see the destructured IP packet in a table. Each table cell contains an individual field of the packet header. The header fields we set in code are highlighted in green—we can skip the rest of them, for now.
On the right side, you can see the exact same packet represented in the
hexadecimal numerical system which is often used
to represent bytes because of its compactness (every single byte can be encoded by a hexadecimal
number ranging from
0xFF, which corresponds to decimal
255). This demonstrates that the IP header is just a sequence of numbers that we can interpret
and represent in different ways. If you are not comfortable with hexadecimal numbers, you can refer to
the prerequisite lesson on number encoding.
UDP, or User Datagram Protocol, is a thin layer above IP which adds some more contextual information for a message. A UDP datagram is also divided into a header and a payload. The fun thing about it is that an entire UDP datagram is the payload of an IP packet! This is called encapsulation and it's one of the core ideas in networking. A single layer of the network stack like IP doesn’t know and doesn’t care about its payload, which can be a protocol from the Internet protocol suite like TCP and UDP or even a protocol defined by you.
UDP headers are only 8 bytes long. They contain source and destination port numbers which help to identify different services running on the same computer—it's very common for a server to have many functions. Usually, we designate a specific port number for a given service by convention. For example, name servers commonly use port number 53.
A UDP header also includes the total size of a datagram in bytes and a checksum which is used to verify that the original network packet is not corrupted during transmission. And that's all it adds to the IP header!
Let's construct a UDP datagram header and add it to the previous example:
Now we are ready to move on to sending messages we have constructed over the network!
Now we know how UDP datagrams and IP packets are constructed, but we
don't have to do this ourselves all the time. Instead, we can use
tools and functions provided by the operating system and the
standard library of our programming language. In Rust, these
functions are part of the
We can use the
function to construct sockets, which are virtual files that associate your program with a given port number.
All UDP datagrams incoming to this port will be redirected to your program by the operating system.
UdpSocket::send_to can be used
to construct UDP datagrams from a given payload and send them over the network. We can wait for a response by using
function, which returns an IP address and a port number of a sender along with the message payload. In the case of
the name server request, we can interpret the payload as an IP address. If we send a request for Alice’s address to
the name server, we should get it back in this response!
Let's give it a try:
When you run this code, you can find a list of all IP packets you send and receive on the right. You can browse the contents of IP packet headers and UDP datagrams. You can also find contents of an original message represented in hexadecimal format below the packet browser.
With everything we have learned, you should be ready for a final test. In this test, you need to send a message to
your friend Alice. You know that she uses the port number
1000—but you don't know her IP address yet! Maybe the
name server can help?
Congratulations, you have finished this lesson!
In the next one, we will cover the topic of fragmentation. So far we have been dealing with small messages only, but what if you want to send a larger file, like a photo to share with your friend? We will learn how to break large messages into small parts and how to reconstruct original files in the next lesson.