Dynamic DNS Updates in a Ping


Many users of the Internet need to access their home networks from somewhere else. Usually, these networks have one public IPv4 address which was assigned to the user by an ISP. Since most ISPs allocate IP addresses dynamically on the per-connection basis, a home network's IP address may change every time a connection is established. This fact fuels the entire industry of Dynamic DNS where the user is allowed to update a DNS server with the IP address of the home network whenever necessary.

More advanced uses involve owning a domain name and renting a dedicated or virtual machine that hosts a DNS server which the user updates with the current IP address of the home network.

There are people who develop their own software for that purpose. Unfortunately, this software often abuses computing and network resources, security mechanisms, or both. Most acceptable solutions of the problem rely on secure shell sessions but there are also outrageous examples of Web applications written in a server-side scripting language.

Below I discuss a method of updating a DNS server securely while using the minimal amount of resources.

Theory

Please allow me to re-state the problem:

  1. The user needs to inform a DNS server of a public IP address that is assigned to the gateway of this user's home network by the user's ISP;
  2. The user needs to ensure that other entities cannot impersonate (in other words, send an unrelated IP address to this user's DNS server pretending that this IP address comes from the user).
  3. The user needs to ensure that the IP address was transferred to the authentic authoritative DNS server (in other words, to the correct DNS server that is responsible for resolving host names of this user's domain, i.e. to the intended destination and not to anyone else's DNS server);
  4. The user is worried that the IP address may become known to another party as a result of an intentional attack on the DNS server or due to an error, misconfiguration, etc. (I will show below that this issue does not exist and that it cannot be resolved even if existed.);
  5. The user needs to ensure that the update was completed successfully.

The key to the correct solution of the problem is understanding of the fact that the IP address that an ISP assigns to the user is public—not only by definition but also in practice: without exception, every IP packet that travels accross the Internet carries the IP address of the sender. For this very reason, there is absolutely no sense in:

The above means that the user needs to send any IP packet, even without payload, to the DNS server in order for the IP address to become known. This satisfies the requirement (1) above.

Extraction of the source IP address directly from the IP header offers the user an interesting advantage over a potential attacker: even without cryptographic authentication of DNS updates (as described below), this trick alone drives the attacker to an uncomfortable battlefield: the attacker must use the technique that is commonly known as IP address spoofing because sending false information from a true IP address will immediately disclose the attacker. Since sensible ISPs should perform egress filtering (in this case, block packets that have a source IP address that differs from the IP address that the ISP allocated for the current connection), spoofing across unrelated networks is rarely practical. To spoof an IP address reliably, the attcker will have to resort to a spoofing proxy—another computer that connects from a network where packet sanitation is known to be relaxed or may not exist at all.

To prevent impersonation explained in the requirement (2), the user must present to the DNS server an authentication token, a piece of information that proves that the packet was sent indeed by the user and not by anyone else. Message authentication code algorithms (MACs) were designed specifically for that purpose.

Since modern gateways are often built on computers having very modest computational capabilities, I recommend using the least demanding code that still provides a very strong security, the HMAC-SHA1 algorithm. The HMAC algorithm requires a secret key to be pre-shared between the sender and the receiver of a message. To quote the specification,

The authentication key K can be of any length up to B, the block length of the hash function. Applications that use keys longer than B bytes will first hash the key using H and then use the resultant L byte string as the actual key to HMAC. In any case the minimal recommended length for K is L bytes (as the hash output length).
and
The key for HMAC can be of any length (keys longer than B bytes are first hashed using H). However, less than L bytes is strongly discouraged as it would decrease the security strength of the function. Keys longer than L bytes are acceptable but the extra length would not significantly increase the function strength. (A longer key may be advisable if the randomness of the key is considered weak.)
(the emphasis is mine.) Considering the above, the secret key must be from 160-bit (20 bytes, the output length of SHA-1) to 512-bit (64 bytes, the block length of SHA-1) long. Since there is no specific reason not to use the longest key, I recommend using a 512-bit key. If that number does not seem large enough, please take into account the amount of time required for testing one forged HMAC in this setup: if the attacker does not possess a valid packet sent by the user in the past, validation of a single key must be done by (a) sending a forged packet (preferably with a spoofed source IP address); (b) waiting for an actual update of the DNS server; (c) querying the DNS server. Exhaustive search (brute-forcing) of even a shorter key is not feasible in such circumstances.

There are authors that recommend truncation of the output of HMAC and transferring only a number (more than a half) of leftmost bits of an HMAC value. The motivation for that is to complicate finding collisions. Truncation of HMAC-SHA1 to 96 (HMAC-SHA1-96) provides a very good level of security in practice, which is comparable to IPSEC.

There is one additional type of attacks that needs addressing: replay attacks, where the attacker eavesdrops on the communication link somewhere between the gateway and the DNS server and then resends a packet that the attacker has seen before. The usual means of protection in that case is attaching the current value of a running counter to the plaintext message. The simplest counter is the machine clock. If the user needs more security, a dedicated counter can be used (unlike the machine clock, this counter will be incremented by the application, only when a new value is required).

To satisfy requirements (1) and (2) above, it is necessary to send an IP packet with the result of HMAC-SHA1-96 computed with the IP address and a value of a 32-bit counter as the input using a 512-bit secret key. The result of HMAC-SHA1-96 is always 12 bytes long, which means that the user must select the simplest IP protocol that allows transferring 16 bytes of information as one IP packet.

The word simple is subjective and it may have several meanings, depending on the context. In terms of computing and network resources, the simplest IP protocol is ICMP message type 8, the echo request, commonly known as `ping'. Section 3.2.2.6 of RFC 1122 requires all Internet hosts to support ICMP echo requests which means that all compliant operating systems do handle ICMP echo requests without user intervention: there is no need in additional software, the kernel of the operating system at hand does all the processing.

A very convenient feature of the ICMP echo request is having the 16-bit identifier field and the 16-bit sequence number in its header which are enough for transferring the anti-replay counter divided equally among these fields.

Another interesting aspect of using the payload of the ICMP echo request is deniability: an external observer will not be able to tell if a particular host processes ICMP echo requests in a special way: the kernel of the host's operating system will always send a reply for every incoming echo request with the payload copied verbatim. Server-side processing of ICMP echo requests is virtually undetectable.

The requrements (3) and (5) above can be easily satisfied by sending a query directly to the DNS server after an update.

The requirement (4) is void: issues of this kind are typically resolved using TLS server authentication but the user's IP address will be immediately known at the destination even if the subsequent TLS authentication of the server fails (because the server presents an invalid certificate (or does not present a certificate at all). Public IP addresses are impossible to hide.

To summarise, the problem is solved by sending 12 bytes of HMAC-SHA1-96 as the payload of an ICMP echo request and then querying the DNS server to ensure that the update is complete. Since the final DNS query is unavoidable, processing the ICMP echo reply at the client side is completely optional (although it may be required for a more comfortable debugging).

Practice

I discuss the implementation of the entire system (both client and server sides) on OpenBSD 5.7 in order to show how simple such a setup can be.

Assumptions

The explanations given below make certain assumptions that the reader should have in mind.

Key Generation and Distribution

Security of the proposed solution depends on the authentication key, therefore it is very important to generate and distribute the key as accurately and securely as possible.

To generate a random key it is best to use an offline system that runs in the single-user mode. Immediately after boot, invoke dd(1) for reading 512 bits from random(4):

dd if=/dev/random bs=$((512 / 8)) count=1 \
    | hexdump -ve '/1 "%02x"' > /tmp/ddns-key
The generated key wil be stored in the file /tmp/ddns-key. Transfer the key to the gateway of the home network using a removable media (such as a USB disk-on-key) and store the key in the directory /etc/keys. Transfer the file from the gateway to the DNS server using scp(1). Both on the gateway and on the DNS server the directory /etc/keys as well as the file /etc/keys/ddns-key must belong to root:wheel. The file mode of the file /etc/keys/ddns-key must be 0440. On systems that run in the securelevel(7) of 2 (the highly secure mode) it makes perfect sense to set the file flag schg of the file /etc/keys/ddns-key using chflags(1).

Do not forget to zero the disk block that is occupied by the key file and then remove the key from the machine were the key was generated:

dd if=/dev/zero bs=$((512 / 8)) count=1 of=/tmp/ddns-key conv=notrunc
rm -f /tmp/ddns-key
Use the same procedure to remove the key file from the removable media that was used for transferring the key from the machine were the key was generated.

Gateway Configuration

Use the exclamation mark feature of hostname.if(5) to execute a shell script every time when the PPPoE network interface comes up. Among other commands, this script must contain the following statement (assuming that the shell variable IF_IPv4_ADDR contains the IP address that the ISP allocated for the user):

CTR="$(date '+%s')" && echo -n "${IF_IPv4_ADDR}:${CTR}" \
    | openssl dgst -mac hmac \
        -macopt hexkey:$(sudo cat /etc/keys/ddns-key) -binary \
    | dd bs=1 count=$((96 / 8)) 2>/dev/null \
    | sudo icmp8c 10.9.8.7 "${CTR}"
This command uses the icmp8c utility that sends an IPCM echo request with a user-specified payload and a counter. You can find a sample code of such a utility here.

Eventually the system will have to check if the DNS server was updated successfully. This can be achieved as follows:

[ "$(dig +short gw.my.domain @10.9.8.7)" = "${IF_IPv4_ADDR}" ]

Server Configuration

Create a dedicated instance of the pflog(4) interface by having the file /etc/hostname.pflog1 that contains one word up as its contents.

Instruct pf(4) to log all ICMP packets that arrive from the user's ISP to a dedicated instance of pflog(4):

pass in on egress log (to pflog1) inet proto icmp icmp-type echoreq \
    from 192.168.45.1-192.168.45.254

Process logged ICMP packets using tcpdump(8):

sudo tcpdump -w - -i pflog1 ip[2:2] = 0x28
The `magic' expression ip[2:2] extracts two bytes from the offset 2 at the IP header which derives the total length of the IP packet. The `magic' number 0x28 equals 20 (bytes of the IP header) + 8 (bytes of the ICMP echo request header) + 12 (bytes of HMAC-SHA1-96). This is a useful optimisation which is not related to security: most typical uses of ping(8) involve packets of a different size.

Feed the output of the command above to a shell script that parses packets generated by pflog(4):

read_bytes ()
{
    dd bs=1 count=${1} 2>/dev/null | hexdump -ve '/1 "%02x"'
}

skip_bytes ()
{
    dd bs=1 count=${1} of=/dev/null 2>/dev/null
}

skip_bytes $((4 + 2 * 2 + 4 * 4))

while true; do
    skip_bytes $((4 * 4 + 100 + 12))
    srcip=$(read_bytes 4)
    dstip=$(read_bytes 4)
    skip_bytes 4
    ctr=$(read_bytes 4)
    payload=$(read_bytes $((96 / 8)))
    msg="$(printf "%d.%d.%d.%d:%d" $(echo "${srcip}" \
        | sed 's/\(..\)/0x\1  /g') $((0x${ctr})))"
    hmac="$(echo -n "${msg}" \
        | openssl dgst -mac hmac \
            -macopt hexkey:$(sudo cat /etc/keys/ddns-key) -binary \
        | dd bs=1 count=$((96 / 8)) 2>/dev/null \
        | hexdump -ve '/1 "%02x"')"
done
      
The first invocation of read_bytes (the one that is made before the main loop) skips over the global header that is generated by tcpdump(8).

The second invocation of read_bytes (the first statement inside the body of the main loop) skips 16 bytes of the record header that is generated by tcpdump(8), 100 bytes of the record header generated by pflog(4) (see the manual page for the description of the format) and 12 bytes of the IP header that precede the source IP address.

The last invocation of read_bytes skips 4 bytes of the ICMP header that precede the identifier field.

As soon as the source IP address, the value of the counter, and the payload are known, verify the payload by calculation of HMAC-SHA1-96 of the dot notation the source IP address, a colon, and the value of the counter as it was performed by the gateway, i.e. the value of the variable hmac must be equal to the value of the variable payload. If the verification succeeded, proceed to the actual update of the DNS record of gw.my.domain. with the value of the variable srcip.

Answers to Expected Questions

Where is the complete source code?
I could not support the complete, publicly available source code for any considerable amount of time therefore I do not publish it by intent. I carefully described the idea and outlined a possible implementation, you are welcome to take it from there. It is not complex at all.

Why all of the code above is in shell?
I assume that shell is understood well by readers who are interested in the subject of this article. Please write in any language you like.

I do want to use a stronger hash algorithm which permits longer keys. How do I do that?
Pass an appropriate option to openssl. Use openssl dgst -h to see which algorithms are available. Note that you will need to adjust truncation (hint: look for places where I divide 96 by 8.)

I do want even more secrecy in my system. What else can be done?
Insert a salt between the dot notation of the IP address and the value of the counter before you calculate the HMAC. Do not do that if the previous sentence was not immediately clear. Please note that secrecy is not a synonym for security.

Someone is after me, I need a system that actively resists attacks. What can I do?
Program an automatic key rotation mechanism that derives the actual HMAC key from a long, randomly chosen secret sequence of bits (the one that you never transfer in clear between systems) and the number of the current month, or of the current week, or of the current day, or of the current hour since the UNIX Epoch (that would be your key period). This mechanism must use a secret combination of known, well-designed hash functions (do not invent hash functions yourself—save that for real scientists). Cache—in the RAM—the keys that you have computed: the previous, the current, and the future values. Prepare for a key changeover by accepting either the previous key or the current key when the server's system time is somehwat after a boundary of the key period, or either the current key or the future key when the server's system clock is somewhat before a boundary. The value of the counter in the request will give your system the client's idea of the current time (assuming that the client uses its machine clock as the counter). Do not accept requests that are way off the clock but accomodate for mutual drift of machine clocks on both ends of the network. The above assumes that you do have a reliable, secure routine of clock synchronisation. With such a system, you are secure as long as your machines are not broken into physically or remotely and the secret sequence is stolen along with your secret combination of hash functions—or you are tortured by evil aliens.

You seem to imply that sending one ICMP packet is enough for an update. A lone packet can be lost. Is that reliable?
Send several identical ICMP packets in a quick succession (for instance, with a one-second interval between them). The first of them that reaches the server will create a packet filter state and only this packet will be logged, the rest of the packets will be processed by the kernel as usual but not logged. If none of, say, five packets reach the destination, the chances are that the link is so bad that it is better be re-established or considered not operational.

I am worried about buffer overflows. Do you propose something that is inherently safe?
Well, I propose an idea that is somewhat safer, by design, than a regular code of a network server. The pleasant part of it is that additional client code that you must develop never handles input. The server's code parses data that is generated locally or already have passed through serveral functional layers inside the kernel and then survived libcap's filtering in the user space. In the additional server code, as I demonstrate above, there is no need to examine the contents of data sent by the client—this data is passed around as opaque chunks of memory. Considering the very bad security past of the ICMP echo request, the relevant kernel code usually receives very close attention of kernel developers. When a critical bug in a kernel's implementation of handling of the ICMP echo request will be discovered, "There will be weeping there and gnashing of teeth" all over the Internet and the chances are that the bug will be fixed quite quickly. You will know about that—the same way that you heard about the Heartbleed and the Shellshock. It will be big, which is very much better than a situation when a clever person finds and publishes a bug that was discovered in an obscure library which is not used by many people except yourself.

I am using another system's kernel, how do I implement this idea?
It seems that on other systems one would be better off with filtering the external network interface directly using tcpdump. Besides that, the icmp8c utility mentioned above will have to be adopted for the system in question.

My ISP allocates IP addresses from several netblocks. How do I manage that?
Have a pf rule per netblock or use tables.

Can I update several unrelated DNS records with this technique?
That is a different problem that requires its own security analysis. Still, it should be possible to use the ICMP echo request as the transport protocol as long as bulk transfers are not required.

Can I run the client on a different machine behind the gateway?
Yes, as long as the gateway forwards ICMP properly and there is a way of knowing the IP address that the ISP allocated for the current session.

For some time after an update, queries sent directly to the DNS server return an old IP address of gw.my.domain. What is wrong?
Check that your DNS server software invalidates its own cache when a record is updated.

For some time after an update, queries sent to another DNS server return an old IP address of gw.my.domain. What is wrong?
Check the TTL of the DNS record in question. If the ISP changes the allocated IP address before TTL expiration, you may observe this situation.

I am already tired. Is there anything else?
Always log complete packets—along with the local timestamp—that failed HMAC verification: if those packets are not yours, this may be your evidence in the court. Never attempt to retaliate online: usually that hurts wrong people, including yourself.

Vadim Penzin, August 20th, 2015


I hereby place this article into the public domain.
You are welcome to contact me by writing to dyndns at this domain.
I publish this information in the hope that it will be useful, but without ANY WARRANTY.
You are responsible for any and all consequences that may arise as the result of using this information.