1
0
Fork 0
knot/python/libknot
Daniel Baumann 70063ca008
Adding upstream version 3.4.6.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-21 13:47:52 +02:00
..
libknot Adding upstream version 3.4.6. 2025-06-21 13:47:52 +02:00
Makefile.am Adding upstream version 3.4.6. 2025-06-21 13:47:52 +02:00
Makefile.in Adding upstream version 3.4.6. 2025-06-21 13:47:52 +02:00
pyproject.toml.in Adding upstream version 3.4.6. 2025-06-21 13:47:52 +02:00
README.md Adding upstream version 3.4.6. 2025-06-21 13:47:52 +02:00
setup.py.in Adding upstream version 3.4.6. 2025-06-21 13:47:52 +02:00

Libknot API in Python

A Python interface for managing the Knot DNS daemon.

Table of contents

Introduction

If the shared libknot.so library isn't available in the library search path, it's necessary to load the library first, e.g.:

import libknot
libknot.Knot("/usr/lib/libknot.so")

Control module

Using this module it's possible to create scripts for efficient tasks that would require complex shell scripts with multiple calls of knotc. For communication with the daemon it uses the same mechanism as the knotc utility, i.e. communication via a Unix socket.

The module API is stored in libknot.control.

Protocol overview

Connections are supposed to be short-lived, because maintaining a passive connection is costly for the server. Therefore the expected usage of the control interface is to always open a new connection on demand, then close it once it's not immediately needed.

Messages are composed of units. These are of four types the identifiers of which are defined in libknot.control.KnotCtlType:

Type Description IO action
END Signals intent to terminate connection. flush
DATA Holds various information - see about data sections below. cache
EXTRA Additional data. cache
BLOCK End of data block. flush

DATA and EXTRA units aren't immediately sent, rather they're buffered and then sent along with the next END or BLOCK unit.

A unit can optionaly hold data, though this is only meaningful for the DATA and EXTRA types. The data consists of several sections of which usually only a few at a time will be present. For example when a unit issuing a stats command is sent, there is no reason for it to contain an ID section.

The data section identifiers are defined in libknot.control.KnotCtlDataIdx:

Section name send_block() arg name Description
COMMAND cmd Command name.
FLAGS flags Command flags.
ERROR (n/a) Error message. Only sent by the server.
SECTION section Configuration section name.
ITEM item Configuration item name.
ID identifier Configuration item identifier.
ZONE zone Zone name.
OWNER owner Zone record owner
TTL ttl Zone record TTL.
TYPE rtype Zone record type name.
DATA data Configuration item/zone record data.
FILTERS filters Command options or filters for output data processing.

Commands reference

The following is a reference for the low-level control API. In case you're unsure of the commands' semantics, please consult the knotc documentation.

A concise notation is used for command synopsis:

# command "cmd-name" accepts section of type SECTION_NAME and optionally
# another section of type OPT_SECTION
cmd-name(SECTION_NAME, [OPT_SECTION])

[OPT_SECTION="literal value"],   # Optional section with fixed expected value.
[SECTION1, SECTION2]             # Sections must be present together or not at all.
[SECTION1, [SECTION2]]           # SECTION2 may only appear if SECTION1 is present.
SECTION_NAME="option1"|"option2" # Either one or the other literal may be used.
SECTION_NAME={"asdf"}            # Any subset of characters may be used.

The B flag always represents an option to execute in blocking mode.

When listing the filters a command accepts, the letter which is passed into FILTERS will be boldened. Like this: zonefile

Server

  • status([TYPE="cert-key"|"configure"|"version"|"workers"])
  • stop()
  • reload()
  • stats([SECTION, [ITEM]], [FLAGS="F"])
    • SECTION stores the module, ITEM stores the counter
    • the F flag specifies to include 0 counters in server's response

Zone events

The following commands apply to all zones if ZONE is left empty.

  • zone-status([ZONE], [FILTERS={"rstefc"}])
    • filters: role, serial, transaction, events, freeze, catalog
  • zone-reload([ZONE], [FLAGS={"BF"}])
    • the F flag commands to also reload modules
  • zone-refresh([ZONE], [FLAGS="B"])
  • zone-retransfer([ZONE], [FLAGS="B"])
  • zone-notify([ZONE], [FLAGS="B"])
  • zone-flush([ZONE], [FILTERS="d", DATA], [FLAGS={"FB"}])
    • the outputdir filter commands that zone(s) be flushed to path stored in the DATA section
    • the F flag is required if zonefile synchronization is disabled
  • zone-backup([ZONE], [FILTERS={"dzjtkocqZJTKOCQ"}, [DATA]], [FLAGS="B"])
    • filters
      • the backupdir filter commands that backups be made to path stored in the DATA section
      • zonefile, journal, timers, kaspdb, keysonly, catalog, quic
      • negative counterparts (eg. noZonefile) are symmetrical and capitalized
    • the F flag allows for an existing backupdir to be overwritten
  • zone-restore analogous to zone-backup
  • zone-sign([ZONE], [FLAGS="B"])
  • zone-validate([ZONE], [FLAGS="B"])
  • zone-keys-load([ZONE], [FLAGS="B"])
  • zone-key-rollover([ZONE], TYPE="ksk"|"zsk", [FLAGS="B"])
  • zone-ksk-submitted([ZONE], [FLAGS="B"])
  • zone-freeze([ZONE], [FLAGS="B"])
  • zone-thaw([ZONE], [FLAGS="B"])
  • zone-xfr-freeze([ZONE], [FLAGS="B"])
  • zone-xfr-thaw([ZONE], [FLAGS="B"])

Zone editing

Use @ as OWNER if you want to denote ZONE itself as the owner.

  • zone-read([ZONE], [OWNER], [TYPE])
    • if ZONE is left empty all zones are read
  • zone-begin(ZONE, [FILTERS="b"])
    • filters: benevolent
  • zone-commit([ZONE])
  • zone-abort([ZONE])
  • zone-diff([ZONE])
  • zone-get([ZONE], [OWNER], [TYPE])
  • zone-set([ZONE], OWNER, [TTL], TYPE, DATA)
  • zone-unset([ZONE], OWNER, [TYPE, [DATA]])
  • zone-purge([ZONE], [FILTERS={ocejktf}], [FLAGS="B"])
    • filters: orphan, catalog, expire, journal, kaspdb, timers, zonefile
  • zone-stats([ZONE], [SECTION, [ITEM]], [FLAGS="F"])
    • SECTION stores the module, ITEM stores the counter
    • the F flag specifies to include 0 counters in server's response

Configuration

For the following commands:

  • SECTION holds the configuration section name (eg. template)
  • ID holds the configuration id (eg. default)
  • ITEM holds the configuration item name (eg. storage)
  • conf-list([SECTION, [ID], [ITEM]], [FILTERS="z"|{"st"}])
    • filters:
      • zone: list all zone names, including those from the catalog
      • list: list configuration section items instead of its identifiers
      • transaction: If a transaction is open (conf-begin) queries the transaction's configuration schema instead of the server's
  • conf-read([SECTION, [ID], [ITEM]])
  • conf-begin()
  • conf-commit()
  • conf-abort()
  • conf-diff([SECTION, [ID], [ITEM]])
  • conf-get([SECTION, [ID], [ITEM]])
  • conf-set(SECTION, ID, ITEM, [DATA])
  • conf-unset([SECTION, [ID], [ITEM], [DATA]])
    • DATA may only be meaningfully specified if the preceding sections are as well

Control usage

The module usage consists of several steps:

  • Initialization and connection to the daemon control socket.
  • One or more control operations. An operation is called by sending a command with optional data to the daemon. The operation result has to be received afterwards.
  • Closing the connection and deinitialization.

Sending

There are two methods on the KnotCtl class which send data to the socket.

KnotCtl.send(KnotCtlType, KnotCtlData) is the more rudimentary one. It only sends the section identifier along with its data, if any are provided. When using this function users must beware of the different characteristics regarding buffering of different unit types.

KnotCtl.send_block(...) is more convenient in that it always flushes by sending a BLOCK unit. Otherwise the two methods are functionally equivalent.

Control examples

import json
import libknot.control

# Initialization
ctl = libknot.control.KnotCtl()
ctl.connect("/var/run/knot/knot.sock")
ctl.set_timeout(60)

try:
    # Operation without parameters
    ctl.send_block(cmd="conf-begin")
    resp = ctl.receive_block()

    # Operation with parameters
    ctl.send_block(cmd="conf-set", section="zone", item="domain", data="test")
    resp = ctl.receive_block()

    ctl.send_block(cmd="conf-commit")
    resp = ctl.receive_block()

    # Operation with a result displayed in JSON format
    ctl.send_block(cmd="conf-read", section="zone", item="domain")
    resp = ctl.receive_block()
    print(json.dumps(resp, indent=4))
except libknot.control.KnotCtlError as exc:
    # Print libknot error
    print(exc)
finally:
    # Deinitialization
    ctl.send(libknot.control.KnotCtlType.END)
    ctl.close()
    # List configured zones (including catalog member ones)
    ctl.send_block(cmd="conf-list", filters="z")
    resp = ctl.receive_block()
    for zone in resp['zone']:
        print(zone)
    # Print expirations as unixtime for all secondary zones
    ctl.send_block(cmd="zone-status", filters="u")
    resp = ctl.receive_block()
    for zone in resp:
        if resp[zone]["role"] == "master":
            continue

        expiration = resp[zone]["expiration"]
        if expiration == "-":
            print("Zone %s not loaded" % zone)
        else:
            print("Zone %s expires at %s" % (zone, resp[zone]["expiration"]))

Probe module

Using this module it's possible to receive traffic data from a running daemon with active probe module.

The module API is stored in libknot.probe.

Probe usage

The module usage consists of several steps:

  • Initialization of one or more probe channels
  • Periodical receiving of data units from the channels and data processing

Probe examples

import libknot.probe

# Initialization of the first probe channel stored in `/run/knot`
probe = libknot.probe.KnotProbe("/run/knot", 1)

# Array for storing up to 8 data units
data = libknot.probe.KnotProbeDataArray(8)
while (True):
    # Receiving data units with timeout of 1000 ms
    if probe.consume(data, 1000) > 0:
        # Printing received data units in the default format
        for item in data:
            print(item)

Dname module

This module provides a few dname-related operations.

The module API is stored in libknot.dname.

Dname usage

The dname object is initialized from a string with textual dname. Then the dname can be reformatted to wire format or back to textual format.

Dname examples

import libknot.dname

dname1 = libknot.dname.KnotDname("knot-dns.cz")
print("%s: wire: %s size: %u" % (dname1.str(), dname1.wire(), dname1.size()))

dname2 = libknot.dname.KnotDname("e\\120ample.c\om.")
print("%s: wire: %s size: %u" % (dname2.str(), dname2.wire(), dname2.size()))

dname3 = libknot.dname.KnotDname(dname_wire=b'\x02cz\x00')
print("%s: wire: %s size: %u" % (dname3.str(), dname3.wire(), dname3.size()))
knot-dns.cz.: wire: b'\x08knot-dns\x02cz\x00' size: 13
example.com.: wire: b'\x07example\x03com\x00' size: 13
cz.: wire: b'\x02cz\x00' size: 4