Click here to skip to the tutorial.
In this tutorial series, I will show how to build a simple web browser from start to finish, assuming only the existence of an operating system, sockets, and an ability to draw to the screen. The rest we will fill in ourselves. This web browser will be very basic and unable to interpret CSS, JavaScript, or really any kind of styling, but it will illustrate how the internals of a web browser function -- and be able to fetch and render the page you are currently reading.
This will be done in C++, but the reader may follow along in any language. This tutorial is meant for people who have at least some experience in HTML and web development.
An understanding of the following things is assumed before this tutorial proceeds. Links are included to read about the topics if you want to learn them.
Required knowledge for this article: Required knowledge for future articles:It may be beneficial to periodically refer to the official document, RFC 1035, to clear up any questions you may have and to learn more about DNS.
Outline of this post:
Term: The RFC defines on page 25 a message, the structure which all DNS messages, whether requests or responses, must follow.
Each message is divided into 5 sections, all of which (except for the header) can be empty.
In this tutorial, we will only concern ourselves with sections 1 (header), 2 (question), and 3 (answer). When crafting requests, we will be using the first two sections, and when parsing respones, we will use all three sections.
I like to learn by example, so let's start by crafting a request to find the IP of google.com
.
Typical requests to get an IP from a domain name include only a header and a question (from the message format shown above.
DNS is not an ASCII-based format like HTTP, but binary. While this makes it easy for computers to understand, it unfortunately requires some extra effort for us humans. Regardless, we've just gotta suck it up and read the spec.
To construct the header, we will refer to the following table given on page 26 of the RFC 1035:
1 1 1 1 1 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ID | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |QR| Opcode |AA|TC|RD|RA| Z | RCODE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | QDCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ANCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | NSCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ARCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
Each "row" on this table, (e.g. ID, QDCOUNT, NSCOUNT) contains 16 bits, shown as columns on the top row from 0 to 15. There are 6 rows, so our header will be 96 bits, or 12 bytes. Row 2 contains many smaller flags that are varying widths.
The header can be a little confusing because the exact same format is used for requests and responses, so some fields may not have meaning in a request and should simply be set to 0.
Try and skim the sections below, but don't stress too hard. We'll come back to all this later when we actually construct the request.
Also, you are not expected to know what everything means in the below list. Don't worry about it, we'll do an example.
Let's go through the rows one by one:
Let's go from left to right. Our first two bytes, the ID, are arbitrary (why?). I'll pick 0xDEAD
. For our flags, we'll let:
Flag | Value | Why that value? |
---|---|---|
QR | 0 | Because we're making a request |
Opcode | 0000 | Because we're making a standard query |
AA | 0 | Because this flag has no meaning in requests, only in responses. |
TC | 0 | Because we're going to send a short request, there is no need to truncate and break it up into multiple messages. |
RD | 1 | We want the DNS server to go through the hassle of doing recursive queries for us, because then we don't have to do all that work. |
RA | 0 | This bit has no meaning in a request, and should be 0. |
Z | 000 | This is listed as "reserved for future use" in the RFC, and should be 0 always. |
RCODE | 0000 | Again, no meaning in requests, only valid in responses. |
Most of these are unimportant, so I've highlighted the ones you should care about in blue.
To put our flags all together, we just concatenate the bits. So 0000 0001 0000 0000
is our flags.
As for QDCount, ANCount, NSCount, and ARCount, all we'll be sending is one question record and nothing else, so QDCount (number of questions) should be 1 and everything else 0. Each of those are 16-bit, so:
QDCount | 0000 0000 0000 0001 |
ANCount | 0000 0000 0000 0000 |
NSCount | 0000 0000 0000 0000 |
ARCount | 0000 0000 0000 0000 |
ID (0xDEAD) | 1101 1110 1010 1101 |
Header | 0000 0001 0000 0000 |
QDCount | 0000 0000 0000 0001 |
ANCount | 0000 0000 0000 0000 |
NSCount | 0000 0000 0000 0000 |
ARCount | 0000 0000 0000 0000 |