diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:24:08 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:24:08 +0000 |
commit | f449f278dd3c70e479a035f50a9bb817a9b433ba (patch) | |
tree | 8ca2bfb785dda9bb4d573acdf9b42aea9cd51383 /python | |
parent | Initial commit. (diff) | |
download | knot-upstream/3.2.6.tar.xz knot-upstream/3.2.6.zip |
Adding upstream version 3.2.6.upstream/3.2.6upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'python')
-rw-r--r-- | python/Makefile.am | 16 | ||||
-rw-r--r-- | python/Makefile.in | 544 | ||||
-rw-r--r-- | python/README.md | 147 | ||||
-rw-r--r-- | python/libknot/__init__.py.in | 95 | ||||
-rw-r--r-- | python/libknot/control.py | 374 | ||||
-rw-r--r-- | python/libknot/dname.py | 70 | ||||
-rw-r--r-- | python/libknot/probe.py | 278 | ||||
-rw-r--r-- | python/setup.py.in | 30 |
8 files changed, 1554 insertions, 0 deletions
diff --git a/python/Makefile.am b/python/Makefile.am new file mode 100644 index 0000000..5903219 --- /dev/null +++ b/python/Makefile.am @@ -0,0 +1,16 @@ +EXTRA_DIST = \ + libknot/__init__.py.in \ + libknot/control.py \ + libknot/dname.py \ + libknot/probe.py \ + README.md \ + setup.py.in + +clean-local: + -rm -rf dist *.egg-info + +dist: clean-local + python3 setup.py sdist + +upload: + twine upload dist/* diff --git a/python/Makefile.in b/python/Makefile.in new file mode 100644 index 0000000..e8e011f --- /dev/null +++ b/python/Makefile.in @@ -0,0 +1,544 @@ +# Makefile.in generated by automake 1.16.3 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2020 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = python +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_check_compile_flag.m4 \ + $(top_srcdir)/m4/ax_check_link_flag.m4 \ + $(top_srcdir)/m4/code-coverage.m4 \ + $(top_srcdir)/m4/knot-lib-version.m4 \ + $(top_srcdir)/m4/knot-module.m4 $(top_srcdir)/m4/libtool.m4 \ + $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \ + $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \ + $(top_srcdir)/m4/sanitizer.m4 $(top_srcdir)/m4/visibility.m4 \ + $(top_srcdir)/m4/knot-version.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/src/config.h +CONFIG_CLEAN_FILES = setup.py +CONFIG_CLEAN_VPATH_FILES = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +SOURCES = +DIST_SOURCES = +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/setup.py.in +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CFLAG_VISIBILITY = @CFLAG_VISIBILITY@ +CODE_COVERAGE_ENABLED = @CODE_COVERAGE_ENABLED@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DNSTAP_CFLAGS = @DNSTAP_CFLAGS@ +DNSTAP_LIBS = @DNSTAP_LIBS@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GENHTML = @GENHTML@ +GREP = @GREP@ +HAVE_VISIBILITY = @HAVE_VISIBILITY@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KNOT_VERSION_MAJOR = @KNOT_VERSION_MAJOR@ +KNOT_VERSION_MINOR = @KNOT_VERSION_MINOR@ +KNOT_VERSION_PATCH = @KNOT_VERSION_PATCH@ +LCOV = @LCOV@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LDFLAG_EXCLUDE_LIBS = @LDFLAG_EXCLUDE_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +LT_NO_UNDEFINED = @LT_NO_UNDEFINED@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PDFLATEX = @PDFLATEX@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +PROTOC_C = @PROTOC_C@ +RANLIB = @RANLIB@ +RELEASE_DATE = @RELEASE_DATE@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SPHINXBUILD = @SPHINXBUILD@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +cap_ng_CFLAGS = @cap_ng_CFLAGS@ +cap_ng_LIBS = @cap_ng_LIBS@ +conf_mapsize = @conf_mapsize@ +config_dir = @config_dir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +dlopen_LIBS = @dlopen_LIBS@ +docdir = @docdir@ +dvidir = @dvidir@ +embedded_libbpf_CFLAGS = @embedded_libbpf_CFLAGS@ +embedded_libbpf_LIBS = @embedded_libbpf_LIBS@ +embedded_libngtcp2_CFLAGS = @embedded_libngtcp2_CFLAGS@ +embedded_libngtcp2_LIBS = @embedded_libngtcp2_LIBS@ +exec_prefix = @exec_prefix@ +fuzzer_CFLAGS = @fuzzer_CFLAGS@ +fuzzer_LDFLAGS = @fuzzer_LDFLAGS@ +gnutls_CFLAGS = @gnutls_CFLAGS@ +gnutls_LIBS = @gnutls_LIBS@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libbpf_CFLAGS = @libbpf_CFLAGS@ +libbpf_LIBS = @libbpf_LIBS@ +libdir = @libdir@ +libdnssec_SONAME = @libdnssec_SONAME@ +libdnssec_SOVERSION = @libdnssec_SOVERSION@ +libdnssec_VERSION_INFO = @libdnssec_VERSION_INFO@ +libedit_CFLAGS = @libedit_CFLAGS@ +libedit_LIBS = @libedit_LIBS@ +libelf_CFLAGS = @libelf_CFLAGS@ +libelf_LIBS = @libelf_LIBS@ +libexecdir = @libexecdir@ +libfstrm_CFLAGS = @libfstrm_CFLAGS@ +libfstrm_LIBS = @libfstrm_LIBS@ +libidn2_CFLAGS = @libidn2_CFLAGS@ +libidn2_LIBS = @libidn2_LIBS@ +libidn_CFLAGS = @libidn_CFLAGS@ +libidn_LIBS = @libidn_LIBS@ +libknot_SONAME = @libknot_SONAME@ +libknot_SOVERSION = @libknot_SOVERSION@ +libknot_VERSION_INFO = @libknot_VERSION_INFO@ +libkqueue_CFLAGS = @libkqueue_CFLAGS@ +libkqueue_LIBS = @libkqueue_LIBS@ +libmaxminddb_CFLAGS = @libmaxminddb_CFLAGS@ +libmaxminddb_LIBS = @libmaxminddb_LIBS@ +libmnl_CFLAGS = @libmnl_CFLAGS@ +libmnl_LIBS = @libmnl_LIBS@ +libnghttp2_CFLAGS = @libnghttp2_CFLAGS@ +libnghttp2_LIBS = @libnghttp2_LIBS@ +libngtcp2_CFLAGS = @libngtcp2_CFLAGS@ +libngtcp2_LIBS = @libngtcp2_LIBS@ +libprotobuf_c_CFLAGS = @libprotobuf_c_CFLAGS@ +libprotobuf_c_LIBS = @libprotobuf_c_LIBS@ +liburcu_CFLAGS = @liburcu_CFLAGS@ +liburcu_LIBS = @liburcu_LIBS@ +liburcu_PKGCONFIG = @liburcu_PKGCONFIG@ +libxdp_CFLAGS = @libxdp_CFLAGS@ +libxdp_LIBS = @libxdp_LIBS@ +libzscanner_SONAME = @libzscanner_SONAME@ +libzscanner_SOVERSION = @libzscanner_SOVERSION@ +libzscanner_VERSION_INFO = @libzscanner_VERSION_INFO@ +lmdb_CFLAGS = @lmdb_CFLAGS@ +lmdb_LIBS = @lmdb_LIBS@ +localedir = @localedir@ +localstatedir = @localstatedir@ +malloc_LIBS = @malloc_LIBS@ +mandir = @mandir@ +math_LIBS = @math_LIBS@ +mkdir_p = @mkdir_p@ +module_dir = @module_dir@ +module_instdir = @module_instdir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgconfigdir = @pkgconfigdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pthread_LIBS = @pthread_LIBS@ +run_dir = @run_dir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +storage_dir = @storage_dir@ +sysconfdir = @sysconfdir@ +systemd_CFLAGS = @systemd_CFLAGS@ +systemd_LIBS = @systemd_LIBS@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +EXTRA_DIST = \ + libknot/__init__.py.in \ + libknot/control.py \ + libknot/dname.py \ + libknot/probe.py \ + README.md \ + setup.py.in + +all: all-am + +.SUFFIXES: +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign python/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign python/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +setup.py: $(top_builddir)/config.status $(srcdir)/setup.py.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +tags TAGS: + +ctags CTAGS: + +cscope cscopelist: + + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-local mostlyclean-am + +distclean: distclean-am + -rm -f Makefile +distclean-am: clean-am distclean-generic + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-generic mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: all all-am check check-am clean clean-generic clean-libtool \ + clean-local cscopelist-am ctags-am distclean distclean-generic \ + distclean-libtool distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags-am uninstall uninstall-am + +.PRECIOUS: Makefile + + +clean-local: + -rm -rf dist *.egg-info + +dist: clean-local + python3 setup.py sdist + +upload: + twine upload dist/* + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/python/README.md b/python/README.md new file mode 100644 index 0000000..77fb54c --- /dev/null +++ b/python/README.md @@ -0,0 +1,147 @@ +# Libknot API in Python + +A Python interface for managing the Knot DNS daemon. + +# Table of contents + +* [Introduction](#introduction) +* [Control module](#control-module) + + [Usage](#using-the-control-module) + + [Example](#control-module-example) +* [Probe module](#probe-module) + + [Usage](#using-the-probe-module) + + [Example](#probe-module-example) +* [Dname module](#dname-module) + + [Usage](#using-the-dname-module) + + [Example](#dname-module-example) + +## 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.: + +```python3 +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`. + +### Using the Control module + +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. + +### Control module example + +```python3 +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() +``` + +```python3 + # List configured zones (including catalog member ones) + ctl.send_block(cmd="conf-list", flags="z") + resp = ctl.receive_block() + for zone in resp['zone']: + print(zone) +``` + +## 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`. + +### Using the Probe module + +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 module example + +```python3 +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. + +### Using the Dname module + +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 module example + +```python3 +import libknot.dname + +dname = libknot.dname.KnotDname("e\\120ample.c\om.") +print(dname.size() +print(dname.str()) +print(dname.wire()) +``` + +```bash +13 +example.com. +b'\x07example\x03com\x00' +``` diff --git a/python/libknot/__init__.py.in b/python/libknot/__init__.py.in new file mode 100644 index 0000000..554cbe6 --- /dev/null +++ b/python/libknot/__init__.py.in @@ -0,0 +1,95 @@ +"""Python libknot interface.""" + +import ctypes +import sys + + +class KnotLookup(ctypes.Structure): + """Libknot lookup return structure.""" + + _fields_ = [('id', ctypes.c_int), ('name', ctypes.c_char_p)] + + +class KnotRdataDescriptor(ctypes.Structure): + """Rdata descriptor structure.""" + + _fields_ = [('block_types', ctypes.c_int * 8), ('name', ctypes.c_char_p)] + + +class Knot(object): + """Basic libknot interface.""" + + LIBKNOT = None + LIBKNOT_VERSION = "@libknot_SOVERSION@" + + RCODE_NAMES = None + + STRERROR = None + RDATA_DESC = None + + @classmethod + def __init__(cls, path: str = None) -> None: + """Loads shared libknot library. + An explicit library path can be specified. + """ + + if cls.LIBKNOT: + return + + if path is None: + version = "" + try: + version = ".%u" % int(cls.LIBKNOT_VERSION) + except Exception: + pass + + if sys.platform == "darwin": + path = "libknot%s.dylib" % version + else: + path = "libknot.so%s" % version + + cls.LIBKNOT = ctypes.cdll.LoadLibrary(path) + + cls.RCODE_NAMES = (KnotLookup * 32).in_dll(cls.LIBKNOT, "knot_rcode_names") + + cls.STRERROR = cls.LIBKNOT.knot_strerror + cls.STRERROR.restype = ctypes.c_char_p + cls.STRERROR.argtypes = [ctypes.c_int] + + cls.RDATA_DESC = cls.LIBKNOT.knot_get_rdata_descriptor + cls.RDATA_DESC.restype = ctypes.POINTER(KnotRdataDescriptor) + cls.RDATA_DESC.argtypes = [ctypes.c_ushort] + + @classmethod + def rclass_str(cls, rclass: int) -> str: + """Returns RRCLASS in text form.""" + + if (rclass == 1): + return "IN" + elif (rclass == 3): + return "CH" + elif (rclass == 254): + return "NONE" + elif (rclass == 255): + return "ANY" + else: + return "CLASS%i" % rclass + + @classmethod + def rtype_str(cls, rtype: int) -> str: + """Returns RRTYPE in text form.""" + + descr = cls.RDATA_DESC(rtype).contents.name + if descr: + return descr.decode() + else: + return "TYPE%i" % rtype + + @classmethod + def rcode_str(cls, rcode: int) -> str: + """Returns RCODE in text form.""" + + for item in cls.RCODE_NAMES: + if item.name and item.id == rcode: + return item.name.decode() + return "RCODE%i" % rcode diff --git a/python/libknot/control.py b/python/libknot/control.py new file mode 100644 index 0000000..44aa516 --- /dev/null +++ b/python/libknot/control.py @@ -0,0 +1,374 @@ +"""Libknot server control interface wrapper.""" + +import ctypes +import enum +import warnings +import libknot + + +def load_lib(path: str = None) -> None: + """Compatibility wrapper.""" + + libknot.Knot(path) + warnings.warn("libknot.control.load_lib() is deprecated, use libknot.Knot() instead", \ + category=Warning, stacklevel=2) + + +class KnotCtlType(enum.IntEnum): + """Libknot server control data unit types.""" + + END = 0 + DATA = 1 + EXTRA = 2 + BLOCK = 3 + + +class KnotCtlDataIdx(enum.IntEnum): + """Libknot server control data unit indices.""" + + COMMAND = 0 + FLAGS = 1 + ERROR = 2 + SECTION = 3 + ITEM = 4 + ID = 5 + ZONE = 6 + OWNER = 7 + TTL = 8 + TYPE = 9 + DATA = 10 + FILTER = 11 + + +class KnotCtlData(object): + """Libknot server control data unit.""" + + DataArray = ctypes.c_char_p * len(KnotCtlDataIdx) + + def __init__(self) -> None: + self.data = self.DataArray() + + def __str__(self) -> str: + """Returns data unit in text form.""" + + string = str() + + for idx in KnotCtlDataIdx: + if self.data[idx]: + if string: + string += ", " + string += "%s = '%s'" % (idx.name, self.data[idx].decode()) + + return string + + def __getitem__(self, index: KnotCtlDataIdx) -> str: + """Data unit item getter.""" + + value = self.data[index] + return value.decode() if value else str() + + def __setitem__(self, index: KnotCtlDataIdx, value: str) -> None: + """Data unit item setter.""" + + self.data[index] = ctypes.c_char_p(value.encode()) if value else ctypes.c_char_p() + + +class KnotCtlError(Exception): + """Libknot server control error.""" + + def __init__(self, message: str, data: KnotCtlData = None) -> None: + super().__init__() + self.message = message + self.data = data + + def __str__(self) -> str: + out = "%s" % self.message + if self.data: + out += " (%s)" % self.data + return out + + +class KnotCtlErrorConnect(KnotCtlError): + """Control connection error.""" + + +class KnotCtlErrorSend(KnotCtlError): + """Control data send error.""" + + +class KnotCtlErrorReceive(KnotCtlError): + """Control data receive error.""" + + +class KnotCtlErrorRemote(KnotCtlError): + """Control error on the remote (server) side.""" + + +class KnotCtl(object): + """Libknot server control interface.""" + + ALLOC = None + FREE = None + SET_TIMEOUT = None + CONNECT = None + CLOSE = None + SEND = None + RECEIVE = None + + def __init__(self) -> None: + """Initializes a control interface instance.""" + + if not KnotCtl.ALLOC: + libknot.Knot() + + KnotCtl.ALLOC = libknot.Knot.LIBKNOT.knot_ctl_alloc + KnotCtl.ALLOC.restype = ctypes.c_void_p + + KnotCtl.FREE = libknot.Knot.LIBKNOT.knot_ctl_free + KnotCtl.FREE.argtypes = [ctypes.c_void_p] + + KnotCtl.SET_TIMEOUT = libknot.Knot.LIBKNOT.knot_ctl_set_timeout + KnotCtl.SET_TIMEOUT.argtypes = [ctypes.c_void_p, ctypes.c_int] + + KnotCtl.CONNECT = libknot.Knot.LIBKNOT.knot_ctl_connect + KnotCtl.CONNECT.restype = ctypes.c_int + KnotCtl.CONNECT.argtypes = [ctypes.c_void_p, ctypes.c_char_p] + + KnotCtl.CLOSE = libknot.Knot.LIBKNOT.knot_ctl_close + KnotCtl.CLOSE.argtypes = [ctypes.c_void_p] + + KnotCtl.SEND = libknot.Knot.LIBKNOT.knot_ctl_send + KnotCtl.SEND.restype = ctypes.c_int + KnotCtl.SEND.argtypes = [ctypes.c_void_p, ctypes.c_uint, ctypes.c_void_p] + + KnotCtl.RECEIVE = libknot.Knot.LIBKNOT.knot_ctl_receive + KnotCtl.RECEIVE.restype = ctypes.c_int + KnotCtl.RECEIVE.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] + + self.obj = KnotCtl.ALLOC() + + def __del__(self) -> None: + """Deallocates control interface instance.""" + + KnotCtl.FREE(self.obj) + + def set_timeout(self, timeout: int) -> None: + """Sets control socket operations timeout in seconds.""" + + KnotCtl.SET_TIMEOUT(self.obj, timeout * 1000) + + def connect(self, path: str) -> None: + """Connect to a specified control UNIX socket.""" + + ret = KnotCtl.CONNECT(self.obj, path.encode()) + if ret != 0: + err = libknot.Knot.STRERROR(ret) + raise KnotCtlErrorConnect(err.decode()) + + def close(self) -> None: + """Disconnects from the current control socket.""" + + KnotCtl.CLOSE(self.obj) + + def send(self, data_type: KnotCtlType, data: KnotCtlData = None) -> None: + """Sends a data unit to the connected control socket.""" + + ret = KnotCtl.SEND(self.obj, data_type, + data.data if data else ctypes.c_char_p()) + if ret != 0: + err = libknot.Knot.STRERROR(ret) + raise KnotCtlErrorSend(err.decode()) + + def receive(self, data: KnotCtlData = None) -> KnotCtlType: + """Receives a data unit from the connected control socket.""" + + data_type = ctypes.c_uint() + ret = KnotCtl.RECEIVE(self.obj, ctypes.byref(data_type), + data.data if data else ctypes.c_char_p()) + if ret != 0: + err = libknot.Knot.STRERROR(ret) + raise KnotCtlErrorReceive(err.decode()) + return KnotCtlType(data_type.value) + + def send_block(self, cmd: str, section: str = None, item: str = None, + identifier: str = None, zone: str = None, owner: str = None, + ttl: str = None, rtype: str = None, data: str = None, + flags: str = None, filters: str = None) -> None: + """Sends a control query block.""" + + query = KnotCtlData() + query[KnotCtlDataIdx.COMMAND] = cmd + query[KnotCtlDataIdx.SECTION] = section + query[KnotCtlDataIdx.ITEM] = item + query[KnotCtlDataIdx.ID] = identifier + query[KnotCtlDataIdx.ZONE] = zone + query[KnotCtlDataIdx.OWNER] = owner + query[KnotCtlDataIdx.TTL] = ttl + query[KnotCtlDataIdx.TYPE] = rtype + query[KnotCtlDataIdx.DATA] = data + query[KnotCtlDataIdx.FLAGS] = flags + query[KnotCtlDataIdx.FILTER] = filters + + self.send(KnotCtlType.DATA, query) + self.send(KnotCtlType.BLOCK) + + def _receive_conf(self, out, reply): + + section = reply[KnotCtlDataIdx.SECTION] + ident = reply[KnotCtlDataIdx.ID] + item = reply[KnotCtlDataIdx.ITEM] + data = reply[KnotCtlDataIdx.DATA] + + # Add the section if not exists. + if section not in out: + out[section] = dict() + + # Add the identifier if not exists. + if ident and ident not in out[section]: + out[section][ident] = dict() + + # Return if no item/value. + if not item: + return + + item_level = out[section][ident] if ident else out[section] + + # Treat alone identifier item differently. + if item in ["id", "domain", "target"]: + if data not in out[section]: + out[section][data] = dict() + else: + if item not in item_level: + item_level[item] = list() + + if data: + item_level[item].append(data) + + def _receive_zone_status(self, out, reply): + + zone = reply[KnotCtlDataIdx.ZONE] + rtype = reply[KnotCtlDataIdx.TYPE] + data = reply[KnotCtlDataIdx.DATA] + + # Add the zone if not exists. + if zone not in out: + out[zone] = dict() + + out[zone][rtype] = data + + def _receive_zone(self, out, reply): + + zone = reply[KnotCtlDataIdx.ZONE] + owner = reply[KnotCtlDataIdx.OWNER] + ttl = reply[KnotCtlDataIdx.TTL] + rtype = reply[KnotCtlDataIdx.TYPE] + data = reply[KnotCtlDataIdx.DATA] + + # Add the zone if not exists. + if zone not in out: + out[zone] = dict() + + if owner not in out[zone]: + out[zone][owner] = dict() + + if rtype not in out[zone][owner]: + out[zone][owner][rtype] = dict() + + # Add the key/value. + out[zone][owner][rtype]["ttl"] = ttl + + if not "data" in out[zone][owner][rtype]: + out[zone][owner][rtype]["data"] = [data] + else: + out[zone][owner][rtype]["data"].append(data) + + def _receive_stats(self, out, reply): + + zone = reply[KnotCtlDataIdx.ZONE] + section = reply[KnotCtlDataIdx.SECTION] + item = reply[KnotCtlDataIdx.ITEM] + idx = reply[KnotCtlDataIdx.ID] + data = int(reply[KnotCtlDataIdx.DATA]) + + # Add the zone if not exists. + if zone: + if "zone" not in out: + out["zone"] = dict() + + if zone not in out["zone"]: + out["zone"][zone] = dict() + + section_level = out["zone"][zone] if zone else out + + if section not in section_level: + section_level[section] = dict() + + if idx: + if item not in section_level[section]: + section_level[section][item] = dict() + + section_level[section][item][idx] = data + else: + section_level[section][item] = data + + def receive_stats(self) -> dict: + """Receives statistics answer and returns it as a structured dictionary.""" + + out = dict() + err_reply = None + + while True: + reply = KnotCtlData() + reply_type = self.receive(reply) + + # Stop if not data type. + if reply_type not in [KnotCtlType.DATA, KnotCtlType.EXTRA]: + break + + # Check for an error. + if reply[KnotCtlDataIdx.ERROR]: + err_reply = reply + continue + + self._receive_stats(out, reply) + + if err_reply: + raise KnotCtlErrorRemote(err_reply[KnotCtlDataIdx.ERROR], err_reply) + + return out + + def receive_block(self) -> dict: + """Receives a control answer and returns it as a structured dictionary.""" + + out = dict() + err_reply = None + + while True: + reply = KnotCtlData() + reply_type = self.receive(reply) + + # Stop if not data type. + if reply_type not in [KnotCtlType.DATA, KnotCtlType.EXTRA]: + break + + # Check for an error. + if reply[KnotCtlDataIdx.ERROR]: + err_reply = reply + continue + + # Check for config data. + if reply[KnotCtlDataIdx.SECTION]: + self._receive_conf(out, reply) + # Check for zone data. + elif reply[KnotCtlDataIdx.ZONE]: + if reply[KnotCtlDataIdx.OWNER]: + self._receive_zone(out, reply) + else: + self._receive_zone_status(out, reply) + else: + continue + + if err_reply: + raise KnotCtlErrorRemote(err_reply[KnotCtlDataIdx.ERROR], err_reply) + + return out diff --git a/python/libknot/dname.py b/python/libknot/dname.py new file mode 100644 index 0000000..4f585fe --- /dev/null +++ b/python/libknot/dname.py @@ -0,0 +1,70 @@ +"""Libknot dname interface wrapper.""" + +import ctypes +import libknot + + +class KnotDname(object): + """Libknot dname.""" + + CAPACITY = 255 + CAPACITY_TXT = 1004 + + DnameStorage = ctypes.c_char * CAPACITY + DnameTxtStorage = ctypes.c_char * CAPACITY_TXT + + SIZE = None + TO_STR = None + FROM_STR = None + + data = None + + def __init__(self, dname: str = None) -> None: + """Initializes a dname storage. Optionally initializes from a string.""" + + if not KnotDname.SIZE: + libknot.Knot() + + KnotDname.SIZE = libknot.Knot.LIBKNOT.knot_dname_size + KnotDname.SIZE.restype = ctypes.c_size_t + KnotDname.SIZE.argtypes = [KnotDname.DnameStorage] + + KnotDname.TO_STR = libknot.Knot.LIBKNOT.knot_dname_to_str + KnotDname.TO_STR.restype = ctypes.c_char_p + KnotDname.TO_STR.argtypes = [KnotDname.DnameTxtStorage, KnotDname.DnameStorage, ctypes.c_size_t] + + KnotDname.FROM_STR = libknot.Knot.LIBKNOT.knot_dname_from_str + KnotDname.FROM_STR.restype = ctypes.c_char_p + KnotDname.FROM_STR.argtypes = [KnotDname.DnameStorage, ctypes.c_char_p, ctypes.c_size_t] + + if dname: + self.data = KnotDname.DnameStorage() + if not KnotDname.FROM_STR(self.data, dname.encode('utf-8'), KnotDname.CAPACITY): + raise ValueError + + def size(self): + """Returns size of the stored dname.""" + + if self.data: + return KnotDname.SIZE(self.data) + else: + return 0 + + def str(self) -> str: + """Prints the stored dname in textual format.""" + + if self.data: + data_txt = KnotDname.DnameTxtStorage() + if not KnotDname.TO_STR(data_txt, self.data, KnotDname.CAPACITY_TXT): + raise ValueError + return data_txt.value.decode("utf-8") + else: + return "" + + def wire(self) -> bytes: + """Returns the dname in wire format.""" + + if self.data: + return self.data.value + b'\x00' + else: + return bytes() diff --git a/python/libknot/probe.py b/python/libknot/probe.py new file mode 100644 index 0000000..e6f09db --- /dev/null +++ b/python/libknot/probe.py @@ -0,0 +1,278 @@ +"""Libknot probe interface wrapper.""" + +import ctypes +import datetime +import enum +import socket +import libknot + + +class KnotProbeDataProto(enum.IntEnum): + """Libknot probe transport protocol types.""" + + UDP = 0 + TCP = 1 + QUIC = 3 + TLS = 4 + HTTPS = 5 + + +class KnotProbeDataDNSHdr(ctypes.BigEndianStructure): + """DNS message header.""" + + _fields_ = [('id', ctypes.c_ushort), + ('flag_qr', ctypes.c_ubyte, 1), + ('opcode', ctypes.c_ubyte, 4), + ('flag_aa', ctypes.c_ubyte, 1), + ('flag_tc', ctypes.c_ubyte, 1), + ('flag_rd', ctypes.c_ubyte, 1), + ('flag_ra', ctypes.c_ubyte, 1), + ('flag_z', ctypes.c_ubyte, 1), + ('flag_ad', ctypes.c_ubyte, 1), + ('flag_cd', ctypes.c_ubyte, 1), + ('rcode', ctypes.c_ubyte, 4), + ('questions', ctypes.c_ushort), + ('answers', ctypes.c_ushort), + ('authorities', ctypes.c_ushort), + ('additionals', ctypes.c_ushort)] + + +class KnotProbeData(ctypes.Structure): + """Libknot probe data unit.""" + + ADDR_MAX_SIZE = 16 + QNAME_MAX_SIZE = 255 + + EDE_NONE = 65535 + + _fields_ = [('ip', ctypes.c_ubyte), + ('proto', ctypes.c_ubyte), + ('local_addr', ctypes.c_ubyte * ADDR_MAX_SIZE), + ('local_port', ctypes.c_ushort), + ('remote_addr', ctypes.c_ubyte * ADDR_MAX_SIZE), + ('remote_port', ctypes.c_ushort), + ('reply_hdr', KnotProbeDataDNSHdr), + ('reply_size', ctypes.c_ushort), + ('reply_rcode', ctypes.c_ushort), + ('reply_ede', ctypes.c_ushort), + ('tcp_rtt', ctypes.c_uint), + ('edns_options', ctypes.c_uint), + ('edns_payload', ctypes.c_ushort), + ('edns_version', ctypes.c_ubyte), + ('edns_present', ctypes.c_ubyte, 1), + ('edns_flag_do', ctypes.c_ubyte, 1), + ('_reserved_', ctypes.c_ubyte, 6), + ('query_hdr', KnotProbeDataDNSHdr), + ('query_size', ctypes.c_ushort), + ('query_class', ctypes.c_ushort), + ('query_type', ctypes.c_ushort), + ('query_name_len', ctypes.c_ubyte), + ('query_name', ctypes.c_ubyte * (QNAME_MAX_SIZE))] + + def addr_str(self, addr: ctypes.c_ubyte * ADDR_MAX_SIZE) -> str: + """Converts IPv4 or IPv6 address from binary to text form.""" + + if self.ip == 4: + buffer = ctypes.create_string_buffer(4) + ctypes.memmove(buffer, ctypes.addressof(addr), 4) + return socket.inet_ntop(socket.AF_INET, buffer) + else: + return socket.inet_ntop(socket.AF_INET6, addr) + + def qname_str(self) -> str: + """Returns QNAME in text form.""" + + string = str() + pos = 0 + while pos < self.query_name_len: + label_len = self.query_name[pos] + if label_len == 0: + if self.query_name_len == 1: + string += "." + break + pos += 1 + label_end = pos + label_len + while pos < label_end: + string += chr(self.query_name[pos]) + pos += 1 + string += "." + return string + + def __str__(self) -> str: + """Returns the data unit in a pre-formatted text form.""" + + return self.str() + + def str(self, timestamp: bool = True, color: bool = True) -> str: + """Returns the data unit in a pre-formatted text form with customization.""" + + RST = "\x1B[0m" + BOLD = "\x1B[1m" + UNDR = "\x1B[4m" + RED = "\x1B[31m" + GRN = "\x1B[32m" + ORG = "\x1B[33m" + YELW = "\x1B[93m" + MGNT = "\x1B[35m" + CYAN = "\x1B[36m" + + def COL(string, color_str, active=color): + return str(string) if not active else color_str + str(string) + RST + + string = str() + if timestamp: + string += "%s " % COL(datetime.datetime.now().time(), YELW) + if self.ip != 0: + string += "%s -> %s, " % (COL(self.addr_str(self.remote_addr), UNDR), + COL(self.addr_str(self.local_addr), UNDR)) + string += "port %u -> %u " % (self.remote_port, self.local_port) + else: + string += "%s, " % COL("UNIX", UNDR) + if self.proto == KnotProbeDataProto.UDP: + string += COL("UDP", GRN) + elif self.proto == KnotProbeDataProto.TCP: + string += COL("TCP", RED) + else: + string += COL("QUIC", ORG) + if self.tcp_rtt > 0: + string += ", RTT %.2f ms" % (self.tcp_rtt / 1000) + string += "\n ID %u, " % self.query_hdr.id + if self.query_hdr.opcode == 0: + string += "QUERY" + elif self.query_hdr.opcode == 4: + string += COL("NOTIFY", MGNT) + elif self.query_hdr.opcode == 5: + string += COL("UPDATE", MGNT) + else: + string += COL("OPCODE%i" % self.query_hdr.opcode, MGNT) + string += ", " + string += COL("%s %s %s" % (self.qname_str(), + libknot.Knot.rclass_str(self.query_class), + libknot.Knot.rtype_str(self.query_type)), BOLD) + if self.edns_present == 1: + string += ", EDNS %i B" % self.edns_payload + if self.edns_flag_do == 1: + string += ", " + COL("DO", BOLD) + if (self.edns_options & (1 << 3)) != 0: + string += ", NSID" + if (self.edns_options & (1 << 8)) != 0: + string += ", ECS" + if (self.edns_options & (1 << 10)) != 0: + string += ", COOKIE" + string += ", " + COL("%u B" % self.query_size, CYAN) + if self.reply_size == 0: + string += " -> %s" % COL("DROPPED", RED) + return string + string += " -> %s" % COL(libknot.Knot.rcode_str(self.reply_rcode), BOLD) + if (self.reply_ede != libknot.probe.KnotProbeData.EDE_NONE): + string += ", EDE %u" % self.reply_ede + if self.reply_hdr.flag_aa != 0: + string += ", " + COL("AA", BOLD) + if self.reply_hdr.flag_tc != 0: + string += ", " + COL("TC", BOLD) + if self.reply_hdr.answers > 0: + string += ", %u ANS" % self.reply_hdr.answers + if self.reply_hdr.authorities > 0: + string += ", %u AUT" % self.reply_hdr.authorities + if self.reply_hdr.additionals > 0: + string += ", %u ADD" % self.reply_hdr.additionals + string += ", " + COL("%u B" % self.reply_size, CYAN) + return string + + +class KnotProbeDataArray(object): + """Libknot probe data unit array.""" + + def __init__(self, size: int = 1) -> None: + """Creates a data array of a given size.""" + + if size < 1 or size > 255: + raise ValueError + data_array = KnotProbeData * size + self.data = data_array() + self.capacity = size + self.used = 0 + self.pos = 0 + + def __getitem__(self, i: int) -> KnotProbeData: + """Returns a data unit at a specified position.""" + + if i < 0 or i >= self.capacity: + raise ValueError + return self.data[i] + + def __len__(self) -> int: + """Returns currently used size of the array.""" + + return self.used + + def __iter__(self): + """Initializes the array iterator.""" + + self.pos = 0 + return self + + def __next__(self) -> KnotProbeData: + """Increments the array iterator.""" + + if self.used == 0 or self.pos == self.used: + raise StopIteration + else: + data = self.data[self.pos] + self.pos += 1 + return data + + +class KnotProbe(object): + """Libknot probe consumer interface.""" + + ALLOC = None + FREE = None + CONSUME = None + SET_CONSUMER = None + + def __init__(self, path: str = "/run/knot", idx: int = 1) -> None: + """Initializes a probe channel at a specified path with a channel index.""" + + if not KnotProbe.ALLOC: + libknot.Knot() + + KnotProbe.ALLOC = libknot.Knot.LIBKNOT.knot_probe_alloc + KnotProbe.ALLOC.restype = ctypes.c_void_p + + KnotProbe.FREE = libknot.Knot.LIBKNOT.knot_probe_free + KnotProbe.FREE.argtypes = [ctypes.c_void_p] + + KnotProbe.CONSUME = libknot.Knot.LIBKNOT.knot_probe_consume + KnotProbe.CONSUME.restype = ctypes.c_int + KnotProbe.CONSUME.argtypes = [ctypes.c_void_p, ctypes.c_void_p, \ + ctypes.c_ubyte, ctypes.c_int] + + KnotProbe.SET_CONSUMER = libknot.Knot.LIBKNOT.knot_probe_set_consumer + KnotProbe.SET_CONSUMER.restype = ctypes.c_int + KnotProbe.SET_CONSUMER.argtypes = [ctypes.c_void_p, ctypes.c_char_p, \ + ctypes.c_ushort] + + self.obj = KnotProbe.ALLOC() + + ret = KnotProbe.SET_CONSUMER(self.obj, path.encode(), idx) + if ret != 0: + err = libknot.Knot.STRERROR(ret) + raise RuntimeError(err.decode()) + + def __del__(self) -> None: + """Deinitializes a probe channel.""" + + KnotProbe.FREE(self.obj) + + def consume(self, data: KnotProbeDataArray, timeout: int = 1000) -> int: + '''Consumes data units from a channel and stores them in data array. + Returns the number of consumed data units. + ''' + + ret = KnotProbe.CONSUME(self.obj, data.data, data.capacity, timeout) + if ret < 0: + err = libknot.Knot.STRERROR(ret) + raise RuntimeError(err.decode()) + data.used = ret + return ret diff --git a/python/setup.py.in b/python/setup.py.in new file mode 100644 index 0000000..bd340ba --- /dev/null +++ b/python/setup.py.in @@ -0,0 +1,30 @@ +import pathlib +import setuptools + +p = pathlib.Path("README.md") +if p.exists(): + long_description = p.read_text() + +setuptools.setup( + name='libknot', + version='@PACKAGE_VERSION@', + description='Python bindings for libknot', + long_description=long_description, + long_description_content_type="text/markdown", + author='Daniel Salzman', + author_email='daniel.salzman@nic.cz', + url='https://gitlab.nic.cz/knot/knot-dns', + license='GPL-3.0', + packages=['libknot'], + classifiers=[ # See https://pypi.org/classifiers + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'Intended Audience :: System Administrators', + 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', + 'Programming Language :: Python :: 3', + 'Topic :: Internet :: Name Service (DNS)', + 'Topic :: Software Development :: Libraries', + 'Topic :: System :: Systems Administration', + ], + python_requires='>=3.5', +) |