dns_domain (dns_erlang v5.0.5)

View Source

Domain name processing module providing operations for converting between text representation, label lists, and DNS wire format.

This module provides strictly reversible domain name operations for use in DNS message encoding and decoding.

Summary

Types

Compression map: maps label sequences to positions.

Decode error types.

Text representation of domain name: "www.example.com".

Encode error types.

Single label: "www".

List of labels.

Wire format binary.

Functions

Compare two domain names case-insensitively.

Compare two label lists case-insensitively.

Escape special characters in a label.

Convert wire format to domain name.

Convert wire format to domain name with compression support.

Join labels into domain name.

Split domain name into labels.

Returns provided name with case-insensitive characters in lowercase.

Returns provided name with case-insensitive characters in uppercase.

Convert domain name to wire format.

Convert domain name to wire format with compression.

Unescape a label by removing escape sequences.

Types

compmap()

-type compmap() :: #{labels() => non_neg_integer()}.

Compression map: maps label sequences to positions.

decode_error()

-type decode_error() ::
          {error, truncated} |
          {error, invalid_label_length, non_neg_integer()} |
          {error, bad_pointer, non_neg_integer()}.

Decode error types.

dname()

-type dname() :: binary().

Text representation of domain name: "www.example.com".

encode_error()

-type encode_error() :: {error, label_too_long, label()} | {error, name_too_long, dname()}.

Encode error types.

label()

-type label() :: binary().

Single label: "www".

labels()

-type labels() :: [label()].

List of labels.

wire()

-type wire() :: binary().

Wire format binary.

Functions

are_equal/2

-spec are_equal(dname(), dname()) -> boolean().

Compare two domain names case-insensitively.

Returns true if the names are equal, false otherwise.

are_equal_labels(LabelsA, LabelsB)

-spec are_equal_labels(labels(), labels()) -> boolean().

Compare two label lists case-insensitively.

Returns true if the label lists are equal, false otherwise.

escape_label(Label)

-spec escape_label(label()) -> label().

Escape special characters in a label.

Escapes dots (.) and backslashes (\) in a label by prefixing them with backslashes. Returns the original label unchanged if no escaping is needed.

Use this when you need to include literal dots or backslashes in a label that will be joined with other labels.

Examples:

1> dns_domain:escape_label(~"test").
~"test"
2> dns_domain:escape_label(~"test.label").
~"test\\.label"
3> dns_domain:escape_label(~"test\\label").
~"test\\\\label"
4> dns_domain:escape_label(~"test\\.label").
~"test\\\\.label"

from_wire(Bin)

-spec from_wire(wire()) -> {dname(), wire()}.

Convert wire format to domain name.

Decodes a DNS wire format binary into a domain name string. Handles escaped characters in labels automatically.

Returns {Dname, Rest} where Dname is the decoded domain name and Rest is any remaining binary data after the name.

Raises truncated if the wire format is incomplete or malformed. Raises {invalid_label_length, Len} if a label length byte exceeds 63. Raises {name_too_long, Size} if the decoded name exceeds 255 bytes. Raises {too_many_labels, Count} if the name contains more than 127 labels.

Examples:

1> Wire = <<3,119,119,119,7,101,120,97,109,112,108,101,3,99,111,109,0>>.
2> {Dname, Rest} = dns_domain:from_wire(Wire).
{~"www.example.com", <<>>}
3> Wire2 = <<7,101,120,97,109,112,108,101,3,99,111,109,0,1,2,3>>.
4> {Dname2, Rest2} = dns_domain:from_wire(Wire2).
{~"example.com", <<1,2,3>>}
5> Wire3 = <<0>>.
6> {Dname3, Rest3} = dns_domain:from_wire(Wire3).
{<<>>, <<>>}

from_wire(MsgBin, DataBin)

-spec from_wire(MsgBin :: wire(), DataBin :: wire()) -> {dname(), wire()}.

Convert wire format to domain name with compression support.

Decodes a DNS wire format binary that may contain compression pointers. Compression pointers allow names to reference earlier parts of the message to reduce size.

MsgBin is the complete message binary needed to resolve compression pointers. DataBin is the binary data starting at the name to decode.

Returns {Dname, Rest} where Dname is the decoded domain name and Rest is any remaining binary data after the name.

Raises the same errors as from_wire/1, plus {bad_pointer, Pos} if a compression pointer is invalid or points outside the message.

Examples:

1> MsgBin = <<7,101,120,97,109,112,108,101,3,99,111,109,0,3,119,119,119,192,0>>.
%% First name at position 0: "example.com"
%% Second name at position 13: "www.example.com" (uses compression pointer)
2> {Dname1, Rest1} = dns_domain:from_wire(MsgBin, MsgBin).
{~"example.com", <<3,119,119,119,192,0>>}
3> {Dname2, Rest2} = dns_domain:from_wire(MsgBin, Rest1).
{~"www.example.com", <<>>}
%% Resolved compression pointer to decode "www.example.com"

join(Labels)

-spec join(Labels :: labels()) -> dname().

Equivalent to join(Labels, subdomain).

join/2

-spec join(Labels :: labels(), subdomain | fqdn) -> dname().

Join labels into domain name.

Converts a list of labels into a domain name string. Automatically escapes dots and backslashes in labels as needed.

Returns an empty binary for an empty list.

Note that it does not automatically append a trailing dot at the end of the domain.

Examples:

1> dns_domain:join([~"www", ~"example", ~"com"], subdomain).
~"www.example.com"
2> dns_domain:join([~"test.label", ~"com"], subdomain).
~"test\\.label.com"
3> dns_domain:join([~"test\\label", ~"com"], subdomain).
~"test\\\\label.com"
4> dns_domain:join([], subdomain).
<<>>
5> dns_domain:join([], fqdn).
~"."
5> dns_domain:join([~"example"], fqdn).
~"example."

split(Name)

-spec split(dname()) -> labels().

Split domain name into labels.

Converts a domain name string into a list of labels. Handles escaped dots and backslashes, removing escape sequences from the resulting labels.

Returns an empty list for empty names or root (single dot).

Raises {invalid_dname, empty_label} if the name contains contiguous dots.

Examples:

1> dns_domain:split(~"www.example.com").
[~"www", ~"example", ~"com"]
2> dns_domain:split(~"example.com.").
[~"example", ~"com"]
3> dns_domain:split(~"test\.label.com").
[~"test.label", ~"com"]
4> dns_domain:split(<<>>).
[]
5> dns_domain:split(~"example..com").
** exception error: {invalid_dname, empty_label}

to_lower(Data)

-spec to_lower(dname()) -> dname().

Returns provided name with case-insensitive characters in lowercase.

to_upper(Data)

-spec to_upper(dname()) -> dname().

Returns provided name with case-insensitive characters in uppercase.

to_wire(Name)

-spec to_wire(dname()) -> wire().

Convert domain name to wire format.

Converts a domain name string to DNS wire format binary. The wire format consists of length-prefixed labels followed by a null byte terminator.

Raises {label_too_long, Label} if any label exceeds 63 bytes. Raises name_too_long if the total encoded name exceeds 255 bytes. Raises {invalid_dname, empty_label} if the name contains empty labels (contiguous dots).

Returns <<0>> for empty names or root.

Examples:

1> dns_domain:to_wire(~"www.example.com").
<<3,119,119,119,7,101,120,97,109,112,108,101,3,99,111,109,0>>
2> dns_domain:to_wire(~"example.com").
<<7,101,120,97,109,112,108,101,3,99,111,109,0>>
3> dns_domain:to_wire(<<>>).
<<0>>
4> dns_domain:to_wire(~"example..com").
** exception error: {invalid_dname, empty_label}

to_wire(CompMap, Pos, Name)

-spec to_wire(compmap(), non_neg_integer(), dname()) -> {wire(), compmap()}.

Convert domain name to wire format with compression.

Converts a domain name to wire format, using DNS name compression to reduce message size. Maintains a compression map tracking previously encoded names and emits compression pointers when a name (or suffix) has been seen before.

CompMap is the compression map mapping label sequences to their positions. Pos is the current position in the message where encoding starts. Returns {Wire, NewCompMap} where Wire is the encoded name and NewCompMap is the updated compression map.

Use this when encoding DNS messages where multiple names may share suffixes (e.g., example.com and www.example.com).

Examples:

1> CompMap = #{}, Pos = 0.
2> {Wire1, CompMap1} = dns_domain:to_wire(CompMap, Pos, ~"example.com").
{<<7,101,120,97,109,112,108,101,3,99,111,109,0>>, #{...}}
3> Pos2 = byte_size(Wire1).
4> {Wire2, _} = dns_domain:to_wire(CompMap1, Pos2, ~"www.example.com").
{<<3,119,119,119,192,0>>, #{...}}
%% Wire2 uses compression pointer (192,0) pointing to position 0
5> {Wire3, _} = dns_domain:to_wire(CompMap1, Pos2, ~"example.com").
{<<192,0>>, #{...}}
%% Wire3 is just a compression pointer since the name was seen before

unescape_label(Label)

-spec unescape_label(label()) -> label().

Unescape a label by removing escape sequences.

Reverses the escaping performed by escape_label/1. Converts \\. back to . and \\\\ back to \\. Returns the original label unchanged if no unescaping is needed.

Use this when parsing labels that may contain escaped characters.

Examples:

1> dns_domain:unescape_label(~"test").
~"test"
2> dns_domain:unescape_label(~"test\\.label").
~"test.label"
3> dns_domain:unescape_label(~"test\\\\label").
~"test\\label"
4> dns_domain:unescape_label(~"test\\\\.label").
~"test\\.label"