Implement a simplified DNS protocol in Motoko, as seen in the example below:
> dfx canister call Resolver resolve '("www.dfinity.org")'
("104.17.224.20")
Computers use an Internet Protocol (IP) address to locate and distinguish websites, but it isn’t exactly convenient for humans to memorize a long string of digits to access their desired websites. Instead, we simply type in the website’s domain name to our browser and let the computer take care of the rest. Domain Name System (DNS) is a protocol that translates domain names - like www.example.com - into their corresponding IP addresses. In essence, think of DNS as a phonebook for the Internet; in this analogy, contact names are to web domains as phone numbers are to IP addresses.
When a DNS query is conducted, a DNS Resolver splits the domain name from right to left at the "." delimiter. For example, the domain "www.subdomain.example.com" is split into three parts: ".com", ".example", and ".subdomain". Each of these domain labels are used as stepping stones to help determine the ultimate IP address of the original query.
The resolver begins by checking for the queried domain name in its local cache. This cache stores recently accessed domains and their corresponding IP addresses, drastically increasing lookup speed for frequently searched websites. If the domain is not found in the cache, then the following steps are carried out in the DNS lookup process.
- The initial DNS query is made after a user enters a domain name into the browser. This domain is sent to the DNS Resolver, which splits the domain into its component labels and finds the corresponding IP address.
- The Resolver reaches out to the DNS Root Name Server, which returns a relevant Top Level Domain (TLD) Name Server. The TLD Name Server corresponds to the top level domain in the query, like ".com".
- The TLD Name Server refers the Resolver to an Authoritative Name Server, which holds details for the main domain name, like ".example".
- The Authoritative Name Server returns the IP address for the full domain (in this case www.example.com) to the DNS Resolver, which allows the user to connect to that server using the IP address.
Source: Mohan Prasath, Medium
In this exercise, you will implement a simplified version of the DNS protocol described above using the Internet Computer. Given a URL, the canister will follow the basic steps outlined in the DNS Lookup Process to determine the corresponding IP address. We will use distinct canisters to represent the Root, TLD, and Authoritative servers, each of which must be queried successively to find the next corresponding server. You’ll need to use your understanding of the DNS protocol to decide what calls the resolver should make to which server.
After navigating to the dns/ directory, let’s take a look at the code in src/resolver/Main.mo. The cache
is our HashMap used to store recently looked-up domain
s and their corresponding Principal
. In the Internet Computer, a Principal is a unique identifier - like an IP address - for a canister. As such, our program will return a Principal for a given domain instead of an IP address.
The ask
function queries the specified server
for a domain
. For example, if you query the Root
server for the com
subdomain, the Root
server will return the Principal of the TLD server corresponding with com
. If, however, you call the authoritative name server with the main domain dfinity
(in our example of dfinity.com), ask
will return the final Principal that you’re looking for. In this way, ask
can be used to query all of the servers described in the DNS Lookup Process section for their respective subdomains.
The parseDomain
function splits a given domain
into its component parts on the "." delimiter. For simplicity, assume that all domains will not include the typical "www" prefix. The resolve
function is left for you to implement.
Task: Complete the implementation of the resolve
method, which acts the part of a resolver by taking in a domain
and returning the corresponding Principal
.
Things to consider:
- You must first check if the given
domain
is contained in thecache
. If so, you can just return the storedPrincipal
; otherwise, you must parse thedomain
into its component domain labels (separated by "."s) and use these to determine the correct IP address. You will findparseDomain
helpful in this task. - You should verify that your parsed domain actually contains values. If it is
null
, you should return the#addressNotFound
error. - After parsing the
url
, you must use the resulting domain labels to call the corresponding servers in the correct order. - Note that a
domain
doesn’t contain a set number of domain labels. While most URLs, such as example.com, have two domain labels, some may contain subdomains like subdomain.example.com. Additionally, assume that all domains will not include the typical "www" prefix.
The following test should run to completion:
> dfx deploy
Deploying all canisters.
All canisters have already been created.
Building canisters...
Installing canisters...
Upgrading code for canister Resolver, with canister_id ABCDEFGHIJKLMNOPQR
Upgrading code for canister Root, with canister_id ABCDEFGHIJKLMNOPQR
Upgrading code for canister Test, with canister_id ABCDEFGHIJKLMNOPQR
Deployed canisters.
Please read Instructor notes in nameServer.mo for the following methods to produce the following results:
> dfx canister call Resolver resolve '("dfinity.org")'
("104.17.224.20")
> dfx canister call Resolver isCached '("dfinity.org")'
(true)