Label Module

The Label module provides classes for DNS labels.

The Label class

A label is initialized from bytes:

from dike.label import Label
monty = Label(b'flying-circus')

We can also create a label from a string using the Label.fromstr() class method , which converts the string to Punycode:

biter = Label.fromstr('møøsë')
print(biter)              # møøsë (via implicit string conversion)
print(bytes(biter))       # b'xn--ms-ija4ca'
print(repr(biter))        # Label.fromstr('møøsë')

A utility function which will create a label from bytes, strings, or other labels is also available:

from dike.label import make_label
average_airspeed0 = make_label(b'African')
average_airspeed1 = make_label('European')
average_airspeed2 = make_label(average_airspeed1)

Labels are immutable - once initialized the value never changes. This has the advantage of allowing labels to be used as keys in dict and various other places, but does mean that updating a label is not possible. To make changes we can convert to either a string or bytes value and use slicing or concatenation to produce the value you want, then initialize a new label:

country = Label.fromstr('holland')
same_country = Label.fromstr('nether' + str(country)[3:] + 's')

print(country)        # holland
print(same_country)   # netherlands

Label comparisons work as expected, and are case-insensitive (as per DNS specification):

print(Label(b'flying') == Label(b'circus'))   # False
print(Label(b'flying') > Label(b'circus'))    # True
print(Label(b'CIRCUS') == Label(b'circus'))   # True

If you have a label you can also use bytes or strings in comparisons, and these work as if you had built a Label object for the comparison:

print(Label.fromstr('Norwegian') != 'blue')  # True
print(Label(b'short') < b'shortness')        # True

The module also includes a number of utility functions:

i_am_a_host_label = Label.fromstr('mailserver')
i_am_not_a_host_label = Label.fromstr('WITCH!!!')

print(i_am_a_host_label.ishost())           # True
print(i_am_a_host_label.canonical())        # b'mailserver'

print(i_am_not_a_host_label.ishost())       # False
print(i_am_not_a_host_label.canonical())    # b'witch!!!

Finally, be careful when converting arbitrary labels to strings, for example when receiving labels in DNS packets from the Internet. This can result in a UnicodeError being raised, if the bytes cannot be represented in Punycode. You can use the Label.to_presentation() method to convert the label to a string the label in this case, for example when logging. This uses escape sequences for any characters that might be interpreted as special in a zone file, as defined in RFC 1035:

bad_punycode = Label(b'scary-' + bytes([0x80]))
print(bad_punycode.to_presentation())   # scary-\128

The LabelFactory class

Since we often use the same label many times when dealing with DNS, it can be more efficient to use the same instance for all occurrences of a given label. For example, the label com is likely to appear in many DNS names, and it can be more efficient to reuse the same label. This is safe, because labels are immutable.

The LabelFactory class exists for this purpose:

from dike import LabelFactory, Label
zone_label_factory = LabelFactory()
foo = zone_label_factory.fromstr('foo')
bar = zone_label_factory.fromstr('foo')
baz = Label.fromstr('foo')

print(foo == bar)                          # True
print(foo is bar)                          # True
print(foo == baz)                          # True
print(foo is baz)                          # False

Notice that we can also create labels by normal object creation. Comparisons (==, <, and so on) work as expected, but the labels have different identities in this case (so the is comparison is False).

The LabelFactory.frombytes() and LabelFactory.fromlabel() methods are also available for creating from bytes or other Label instances.

The labels in a LabelFactory are stored in a weakref.WeakValueDictionary, so when a label is no longer used memory used by the object will be released.

Label Objects

class label.Label(label_val: bytes, *, canonicalize: bool = False)[source]

The Label constructor requires a single value, which is bytes.

If the optional canonicalize argument is used then the label will be converted to the canonical version (that is, ASCII lower-case).

If a label is more than 63 characters long, a LabelTooLong exception will be raised. Attempting to create an empty lable (with b'') will raise a EmptyLabel exception.

Parameters:label_val (bytes) – Value to use when creating the label.
Raises:EmptyLabel
Raises:LabelTooLong
canonical() → bytes[source]

Return the bytes representing the canonical version of a label. This is the label converted to lowercase ASCII.

Returns:canonical version of the label
Return type:bytes
static fromstr(label_val: str, *, canonicalize: bool = False) → label.Label[source]

Create an IDNA version of a string.

Certain Unicode values are not allowed in Punycode. A UnicodeError exception will be raised in that case.

Returns:a label
Return type:Label
Raises:UnicodeError – string cannot be converted to Punycode.
ishost() → bool[source]

Test if the label is valid in a host name. The rules for host names are defined in:

Return type:bool
to_presentation() → str[source]

Return a string of the label converted to the master zone file presentation format described in RFC 1035.

Returns:presentation format of the label
Return type:str

LabelFactory Objects

class label.LabelFactory[source]

The LabelFactory constructor takes no arguments.

frombytes(label_bytes: bytes) → label.Label[source]

Get a Label instance the same as one created from the bytes passed.

Parameters:label_bytes (bytes) – bytes that we want a label of
Return type:Label
Raises:EmptyLabel
Raises:LabelTooLong
fromlabel(label: label.Label) → label.Label[source]

Get a Label instance the same as the label passed.

While you could use a simple assignment to also use the same label:

ipso = Label(b'facto')
quid = ipso
print(quid is ipso)                 # True

Using the LabelFactory for this will store the reference in the LabelFactory instance, which might be useful when mixing creation from both Label and str/bytes:

factory = LabelFactory()

ipso = Label(b'facto')
quid = factory.fromlabel(ipso)
pro = factory.fromstr('facto')
print(quid is pro)                  # True
Parameters:label (Label) – Label that we want to return an instance of
Return type:Label
fromstr(label_str: str) → label.Label[source]

Get a Label instance the same as one created from the str passed.

Parameters:label_str (str) – string that we want a label of
Return type:Label
Raises:EmptyLabel
Raises:LabelTooLong
Raises:UnicodeError – string cannot be converted to Punycode.

Exceptions

class label.EmptyLabel[source]

Exception raised when trying to initialize a label with an empty value.

class label.LabelTooLong[source]

Exception raised when trying to initialize a label of more than 63 octets.