Andrew Tunnell-Jones / Log
2020-04-20

Cloudflare Authoritative DNS Papercuts

I've been using Cloudflare's authoritative DNS and I have some gripes.

Names are limited to visible ASCII

Both record owner names and names in record data are limited to printable ASCII. DNS Based Service Discovery may be the only application for such names so this isn't a big deal, but it'd be nice to have the option all the same.

Record Specific Issues

CERT, SMIMEA, and TLSA

The Certificate field should only accept hex or whitespace. The example placeholder shows PEM, and seems to accept any input. Queries are responded to with SERVFAIL if anything other than hex is in the field (including whitespace).

DS

Digest Field should be limited to hex and whitespace but is not. The length of the Digest field isn't matched to the Digest Type even though Digest Type is limited to the four currently assigned digest algorithms. Queries are responded to with SERVFAIL if anything other than hex is in the field (including whitespace).

DNSKEY

Similar to CERT, SMIMEA and TSLA, the Public Key field should be limited to Base64 and whitespace, but isn't. SERVFAIL is returned if anything other than Base64 is present in the field save for the combination of whitespace and Base64 which seems to result in that data being dropped from the response.

SSHFP

Fingerprint field should be limited to hex and whitespace but is not. Queries are responded to with SERVFAIL if anything other than hex is in the field (including whitespace).

SRV

Owner name is limited to the _service._protocol.name convention which excludes DNS Based Service Discovery and any other unusual applications that might be out there. Not that a big deal.

A target of "." (which signals "decidedly not available") leads to some strange behaviour. The web interface shows a blank target, and sometimes weight and port are blank too. The API returns a boolean false instead of a string for the target.

PTR

Both the owner name and PTR content are limited to hostname conventions. You've probably guessed what I'm going to say: this makes it impossible to do DNS Based Service Discovery. You might not have guessed that this time I think this limitation should be lifted. PTR records are a useful building block for pointing a client to some other location without aliasing the entire node. Please let us create PTR records with underscore labels in both the owner and content.

TXT

If the record content doesn't begin with a quotation mark the content is broken into character strings at 255 byte boundaries. So input like a b c is interpreted as "a b c" rather than the more conventional "a" "b" "c".

This splitting behaviour also applies to quoted character strings that exceed 255 ASCII characters which would be fine except that whatever implements this splitting behaviour isn't aware of the \DDD convention used to represent arbitrary bytes and so any TXT record containing a character string whose presentation form exceeds 255 bytes ends up mangled.

The front end does ASCII only comparisons of TXT data, so if for example TXT records with content "\097", \097, "a", and a are added all at the same name, four TXT datas containing "a" are served. DNSSEC implementations are free to handle duplicate record data as though there was one data, or as a protocol error. 8.8.8.8 does the later when served duplicate data from Cloudflare.

URI

The Target field is subject to the same comparison issue as TXT records.

In the API, the content field is target weight rather than weight target.

API

There's no sandbox and Cloudflare don't allow zones that aren't parented directly under a TLD. This makes testing more of a nuisance than it ought to be and is particularly irksome given this APIs eccentricities.

There's two standard formats for DNS data, ASCII and the wire format. The API doesn't leverage either of them and instead introduces its own JSON format with very loose conventions. Let me illustrate:

Fields named "priority" are pulled out of the "content" structure, so instead of an MX looking like:

{
    "name": "example",
    "type": "MX",
    "content": "10 exchange",
}

We get:

{
    "name": "example",
    "type": "MX",
    "content": "exchange",
    "priority": 10,
}

Not that bad, but not all data gets pulled out in this style. URI and other types look like this:

{
    "name": "example",
    "type": "URI",
    "content": "target 20",
    "priority": 10,
    "data": {
        "content": "target",
        "weight": 20,
    }
}

Putting aside that content is backwards and that data's content field should be called "target", for many (most?) types "content" is a read-only field. This means that for each type custom marshalling code is necessary. This is particularly annoying for the SRV record which has to be created with a payload like the following:

{
    "name": "example",
    "type": "SRV",
    "data": {
        "service": "_servicename",
        "proto": "_tcp",
        "priority": 0,
        "weight": 0,
        "port": 0,
        "target": "target",
    }
}

Here the record's owner name is broken out and "priority" has to go in the "data" map for some reason. Very cumbersome compared to:

{
    "name": "_servicename._tcp.example",
    "type": "SRV",
    "content": "0 0 0 target",
}

Also, I can't seem to create an SRV that's not directly below the zone apex. Maybe you can't?

Ideally Cloudflare would switch to RFC 2136. It's forward compatible with new types, data is in a standard unambiguous format, it's atomic, and it can represent in a single message what would take multiple requests to the current API. Cloudflare specific things like proxied status can likely still be represented by private-use types. Failing that a switch to the standard ASCII presentation format for the content field and allowing the content field to be used in updates would greatly improve the APIs usability.

Last, both "Import DNS Records" and "Export DNS Records" refer to a zone file as a "BIND config" even though they link to "zone file" on Wikipedia. That change is a gimme at least, right?