summaryrefslogtreecommitdiffstats
path: root/src/libknot/xdp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/libknot/xdp.h35
-rw-r--r--src/libknot/xdp/Makefile.am20
-rw-r--r--src/libknot/xdp/Makefile.in545
-rw-r--r--src/libknot/xdp/bpf-consts.h57
-rw-r--r--src/libknot/xdp/bpf-kernel-obj.c941
-rw-r--r--src/libknot/xdp/bpf-kernel-obj.h2
-rw-r--r--src/libknot/xdp/bpf-kernel.c293
-rw-r--r--src/libknot/xdp/bpf-user.c313
-rw-r--r--src/libknot/xdp/bpf-user.h139
-rw-r--r--src/libknot/xdp/eth.c312
-rw-r--r--src/libknot/xdp/eth.h111
-rw-r--r--src/libknot/xdp/msg.h62
-rw-r--r--src/libknot/xdp/msg_init.h74
-rw-r--r--src/libknot/xdp/protocols.h446
-rw-r--r--src/libknot/xdp/quic.c1028
-rw-r--r--src/libknot/xdp/quic.h134
-rw-r--r--src/libknot/xdp/quic_conn.c506
-rw-r--r--src/libknot/xdp/quic_conn.h314
-rw-r--r--src/libknot/xdp/tcp.c729
-rw-r--r--src/libknot/xdp/tcp.h227
-rw-r--r--src/libknot/xdp/tcp_iobuf.c266
-rw-r--r--src/libknot/xdp/tcp_iobuf.h120
-rw-r--r--src/libknot/xdp/xdp.c568
-rw-r--r--src/libknot/xdp/xdp.h189
24 files changed, 7431 insertions, 0 deletions
diff --git a/src/libknot/xdp.h b/src/libknot/xdp.h
new file mode 100644
index 0000000..db4460c
--- /dev/null
+++ b/src/libknot/xdp.h
@@ -0,0 +1,35 @@
+/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*!
+ * \file
+ *
+ * \brief Convenience header for including XDP-related stuff.
+ *
+ * \addtogroup libknot
+ * @{
+ */
+
+#pragma once
+
+#if ENABLE_XDP
+#include "libknot/xdp/xdp.h"
+#include "libknot/xdp/bpf-consts.h"
+#include "libknot/xdp/eth.h"
+#include "libknot/xdp/tcp.h"
+#endif
+
+/*! @} */
diff --git a/src/libknot/xdp/Makefile.am b/src/libknot/xdp/Makefile.am
new file mode 100644
index 0000000..cb58438
--- /dev/null
+++ b/src/libknot/xdp/Makefile.am
@@ -0,0 +1,20 @@
+# Useful commands:
+# make filter
+# ip link show $eth
+# sudo ip link set dev $eth xdp off
+# sudo ip link set dev $eth xdp obj ./bpf-kernel.o
+#
+# Built using LLVM/CLANG 14.
+#
+# When updating check using `llvm-objdump -h bpf-kernel.o` if .BTF and .BTF.ext
+# sections are present.
+
+EXTRA_DIST = bpf-kernel-obj.c bpf-kernel.c
+
+.PHONY: filter
+
+filter:
+ rm -f bpf-kernel.o bpf-kernel-obj.c
+ clang -target bpf -Wall -O2 -g -DNDEBUG -c -o bpf-kernel.o -I/usr/include/x86_64-linux-gnu -include ../../config.h bpf-kernel.c
+ llvm-strip -S bpf-kernel.o
+ xxd -i bpf-kernel.o > bpf-kernel-obj.c
diff --git a/src/libknot/xdp/Makefile.in b/src/libknot/xdp/Makefile.in
new file mode 100644
index 0000000..b56c39d
--- /dev/null
+++ b/src/libknot/xdp/Makefile.in
@@ -0,0 +1,545 @@
+# 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@
+
+# Useful commands:
+# make filter
+# ip link show $eth
+# sudo ip link set dev $eth xdp off
+# sudo ip link set dev $eth xdp obj ./bpf-kernel.o
+#
+# Built using LLVM/CLANG 14.
+#
+# When updating check using `llvm-objdump -h bpf-kernel.o` if .BTF and .BTF.ext
+# sections are present.
+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 = src/libknot/xdp
+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 =
+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
+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 = bpf-kernel-obj.c bpf-kernel.c
+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 src/libknot/xdp/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/libknot/xdp/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):
+
+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 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 \
+ 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
+
+
+.PHONY: filter
+
+filter:
+ rm -f bpf-kernel.o bpf-kernel-obj.c
+ clang -target bpf -Wall -O2 -g -DNDEBUG -c -o bpf-kernel.o -I/usr/include/x86_64-linux-gnu -include ../../config.h bpf-kernel.c
+ llvm-strip -S bpf-kernel.o
+ xxd -i bpf-kernel.o > bpf-kernel-obj.c
+
+# 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/src/libknot/xdp/bpf-consts.h b/src/libknot/xdp/bpf-consts.h
new file mode 100644
index 0000000..3b92cbb
--- /dev/null
+++ b/src/libknot/xdp/bpf-consts.h
@@ -0,0 +1,57 @@
+/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*!
+ * \file
+ *
+ * \brief XDP filter configuration constants.
+ *
+ * \addtogroup xdp
+ * @{
+ */
+
+#pragma once
+
+#include <linux/types.h>
+
+#define KNOT_XDP_PKT_ALIGNMENT 2 /*!< Fix for misaligned access to packet structures. */
+
+/*! \brief XDP filter configuration flags. */
+typedef enum {
+ KNOT_XDP_FILTER_ON = 1 << 0, /*!< Filter enabled. */
+ KNOT_XDP_FILTER_UDP = 1 << 1, /*!< Apply filter to UDP. */
+ KNOT_XDP_FILTER_TCP = 1 << 2, /*!< Apply filter to TCP. */
+ KNOT_XDP_FILTER_QUIC = 1 << 3, /*!< Apply filter to QUIC/UDP. */
+ KNOT_XDP_FILTER_PASS = 1 << 4, /*!< Pass incoming messages to ports >= port value. */
+ KNOT_XDP_FILTER_DROP = 1 << 5, /*!< Drop incoming messages to ports >= port value. */
+ KNOT_XDP_FILTER_ROUTE = 1 << 6, /*!< Consider routing information from kernel. */
+} knot_xdp_filter_flag_t;
+
+/*! \brief XDP map item for the filter configuration. */
+typedef struct knot_xdp_opts knot_xdp_opts_t;
+struct knot_xdp_opts {
+ __u16 flags; /*!< XDP filter flags \a knot_xdp_filter_flag_t. */
+ __u16 udp_port; /*!< UDP/TCP port to listen on. */
+ __u16 quic_port; /*!< QUIC/UDP port to listen on. */
+} __attribute__((packed));
+
+/*! \brief Additional information from the filter. */
+typedef struct knot_xdp_info knot_xdp_info_t;
+struct knot_xdp_info {
+ __u16 out_if_index; /*!< Index of the output interface (if routing enabled). */
+};
+
+/*! @} */
diff --git a/src/libknot/xdp/bpf-kernel-obj.c b/src/libknot/xdp/bpf-kernel-obj.c
new file mode 100644
index 0000000..9f1ee71
--- /dev/null
+++ b/src/libknot/xdp/bpf-kernel-obj.c
@@ -0,0 +1,941 @@
+unsigned char bpf_kernel_o[] = {
+ 0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xf7, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xb8, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
+ 0x0d, 0x00, 0x01, 0x00, 0xbf, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x61, 0x61, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x1a, 0xfc, 0xff,
+ 0x00, 0x00, 0x00, 0x00, 0xbf, 0xa2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x07, 0x02, 0x00, 0x00, 0xfc, 0xff, 0xff, 0xff, 0x18, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x85, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xbf, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x15, 0x01, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x71, 0x12, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x71, 0x19, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x67, 0x09, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x4f, 0x29, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xbf, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x57, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x15, 0x02, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x71, 0x12, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7b, 0x2a, 0xa0, 0xff,
+ 0x00, 0x00, 0x00, 0x00, 0x71, 0x12, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x7b, 0x2a, 0x98, 0xff, 0x00, 0x00, 0x00, 0x00, 0x71, 0x18, 0x03, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x71, 0x11, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x7b, 0x1a, 0xa8, 0xff, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x61, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x18, 0x02, 0x00, 0x00, 0xfc, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x85, 0x00, 0x00, 0x00,
+ 0x36, 0x00, 0x00, 0x00, 0x61, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x61, 0x61, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x12, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x07, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0xb7, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x72, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xbf, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x7b, 0x3a, 0xb0, 0xff, 0x00, 0x00, 0x00, 0x00, 0x61, 0x62, 0x04, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xbf, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x07, 0x01, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x2d, 0x21, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x71, 0x74, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0x73, 0x0d, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x67, 0x03, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x4f, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x03, 0x0b, 0x00,
+ 0x81, 0x00, 0x00, 0x00, 0xbf, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x07, 0x01, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x2d, 0x21, 0xe7, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xb7, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x79, 0xa3, 0xb0, 0xff,
+ 0x00, 0x00, 0x00, 0x00, 0x15, 0x03, 0xe4, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x71, 0x74, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0x73, 0x11, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x67, 0x03, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x4f, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x03, 0x00, 0x00,
+ 0xff, 0xff, 0x00, 0x00, 0x15, 0x03, 0x1e, 0x00, 0x86, 0xdd, 0x00, 0x00,
+ 0xb7, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x55, 0x03, 0xdc, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0xbf, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x07, 0x03, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x2d, 0x23, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x71, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x34, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x57, 0x04, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00,
+ 0xb7, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x55, 0x04, 0xd3, 0x00,
+ 0x40, 0x00, 0x00, 0x00, 0xbf, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x1f, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x15, 0x02, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xdc, 0x05, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0xb7, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x6d, 0x45, 0xcd, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x69, 0x15, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x57, 0x05, 0x00, 0x00, 0xbf, 0xff, 0x00, 0x00, 0xb7, 0x04, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x7b, 0x4a, 0x90, 0xff, 0x00, 0x00, 0x00, 0x00,
+ 0xb7, 0x04, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x55, 0x05, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xb7, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x67, 0x03, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x57, 0x03, 0x00, 0x00,
+ 0x3c, 0x00, 0x00, 0x00, 0xbf, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0f, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0x10, 0x09, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xbf, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x05, 0x00, 0x00,
+ 0x28, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x2d, 0x25, 0xbc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0x13, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x57, 0x03, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00,
+ 0xb7, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x55, 0x03, 0xb8, 0x00,
+ 0x60, 0x00, 0x00, 0x00, 0xbf, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x1f, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x14, 0x04, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xdc, 0x04, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0x07, 0x04, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x2d, 0x34, 0xb1, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xb7, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x7b, 0x3a, 0x90, 0xff,
+ 0x00, 0x00, 0x00, 0x00, 0xb7, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x71, 0x10, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x00, 0x08, 0x00,
+ 0x2c, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0xbf, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x05, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x2d, 0x25, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xb7, 0x04, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x71, 0x10, 0x28, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xb7, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x7b, 0x3a, 0x90, 0xff, 0x00, 0x00, 0x00, 0x00, 0x67, 0x08, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x79, 0xa3, 0xa8, 0xff, 0x00, 0x00, 0x00, 0x00,
+ 0x4f, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x03, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x15, 0x03, 0x17, 0x00, 0x11, 0x00, 0x00, 0x00,
+ 0xb7, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x55, 0x03, 0x9d, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0xbf, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x07, 0x03, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x2d, 0x23, 0x99, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xbf, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x02, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x15, 0x02, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x52, 0x02, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xdc, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0xbf, 0x83, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x03, 0x00, 0x00,
+ 0xff, 0xff, 0x00, 0x00, 0x1d, 0x32, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xbf, 0x93, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x03, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x15, 0x03, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x08, 0x00, 0x00,
+ 0xff, 0xff, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x2d, 0x28, 0x89, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x28, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xbf, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x07, 0x03, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x2d, 0x23, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x1f, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x53, 0x04, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xdc, 0x03, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0xb7, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x6d, 0x23, 0x7f, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x69, 0x52, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xdc, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xbf, 0x93, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x57, 0x03, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x15, 0x03, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x83, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x57, 0x03, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
+ 0x1d, 0x32, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x93, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x57, 0x03, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x15, 0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x08, 0x00, 0x00,
+ 0xff, 0xff, 0x00, 0x00, 0x3d, 0x82, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xbf, 0x93, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x03, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x15, 0x03, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79, 0xa3, 0xa0, 0xff,
+ 0x00, 0x00, 0x00, 0x00, 0x67, 0x03, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x79, 0xa5, 0x98, 0xff, 0x00, 0x00, 0x00, 0x00, 0x4f, 0x53, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xbf, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x57, 0x03, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x1d, 0x32, 0x07, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xbf, 0x93, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x57, 0x03, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x15, 0x03, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x57, 0x05, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x2d, 0x25, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xbf, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x02, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x55, 0x02, 0x5c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x04, 0x00, 0x00,
+ 0xff, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x55, 0x04, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x09, 0x00, 0x00,
+ 0x40, 0x00, 0x00, 0x00, 0x15, 0x09, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xb7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7b, 0x2a, 0xc0, 0xff,
+ 0x00, 0x00, 0x00, 0x00, 0x7b, 0x2a, 0xf0, 0xff, 0x00, 0x00, 0x00, 0x00,
+ 0x7b, 0x2a, 0xe8, 0xff, 0x00, 0x00, 0x00, 0x00, 0x7b, 0x2a, 0xe0, 0xff,
+ 0x00, 0x00, 0x00, 0x00, 0x7b, 0x2a, 0xd8, 0xff, 0x00, 0x00, 0x00, 0x00,
+ 0x7b, 0x2a, 0xd0, 0xff, 0x00, 0x00, 0x00, 0x00, 0x7b, 0x2a, 0xc8, 0xff,
+ 0x00, 0x00, 0x00, 0x00, 0x7b, 0x2a, 0xb8, 0xff, 0x00, 0x00, 0x00, 0x00,
+ 0xb7, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x63, 0x2a, 0xc0, 0xff,
+ 0x00, 0x00, 0x00, 0x00, 0x79, 0xa2, 0x90, 0xff, 0x00, 0x00, 0x00, 0x00,
+ 0x55, 0x02, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb7, 0x02, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x73, 0x2a, 0xb8, 0xff, 0x00, 0x00, 0x00, 0x00,
+ 0x61, 0x12, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x2a, 0xc8, 0xff,
+ 0x00, 0x00, 0x00, 0x00, 0x61, 0x11, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x63, 0x1a, 0xd8, 0xff, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x16, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xb7, 0x02, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x73, 0x2a, 0xb8, 0xff, 0x00, 0x00, 0x00, 0x00, 0x61, 0x12, 0x1c, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x67, 0x02, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x61, 0x13, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4f, 0x32, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x7b, 0x2a, 0xc8, 0xff, 0x00, 0x00, 0x00, 0x00,
+ 0x61, 0x12, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0x02, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x61, 0x13, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x4f, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7b, 0x2a, 0xd0, 0xff,
+ 0x00, 0x00, 0x00, 0x00, 0x61, 0x12, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x67, 0x02, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x61, 0x13, 0x10, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x4f, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x7b, 0x2a, 0xe0, 0xff, 0x00, 0x00, 0x00, 0x00, 0x61, 0x12, 0x08, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x61, 0x11, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x67, 0x01, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x4f, 0x21, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x7b, 0x1a, 0xd8, 0xff, 0x00, 0x00, 0x00, 0x00,
+ 0xbf, 0xa2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x02, 0x00, 0x00,
+ 0xb8, 0xff, 0xff, 0xff, 0xbf, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xb7, 0x03, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0xb7, 0x04, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x85, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00,
+ 0xbf, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0x01, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x77, 0x01, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x15, 0x01, 0x24, 0x00, 0x07, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x15, 0x01, 0x21, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x55, 0x01, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x69, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x69, 0xa2, 0xec, 0xff, 0x00, 0x00, 0x00, 0x00, 0x5d, 0x21, 0x1c, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x69, 0x71, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x69, 0xa2, 0xee, 0xff, 0x00, 0x00, 0x00, 0x00, 0x5d, 0x21, 0x19, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x69, 0x71, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x69, 0xa2, 0xf0, 0xff, 0x00, 0x00, 0x00, 0x00, 0x5d, 0x21, 0x16, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x79, 0xa1, 0xb0, 0xff, 0x00, 0x00, 0x00, 0x00,
+ 0x15, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0xa1, 0xc0, 0xff,
+ 0x00, 0x00, 0x00, 0x00, 0x79, 0xa2, 0xb0, 0xff, 0x00, 0x00, 0x00, 0x00,
+ 0x6b, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0xa1, 0xf6, 0xff,
+ 0x00, 0x00, 0x00, 0x00, 0x73, 0x17, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x77, 0x01, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x73, 0x17, 0x0b, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x69, 0xa1, 0xf4, 0xff, 0x00, 0x00, 0x00, 0x00,
+ 0x73, 0x17, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x01, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x73, 0x17, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x69, 0xa1, 0xf2, 0xff, 0x00, 0x00, 0x00, 0x00, 0x73, 0x17, 0x06, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x77, 0x01, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x73, 0x17, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x62, 0x10, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x18, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb7, 0x03, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x85, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00,
+ 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0xfd, 0xff, 0x00, 0x00, 0x00, 0x00,
+ 0xb7, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0xfb, 0xff,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x50, 0x4c, 0x00,
+ 0x9f, 0xeb, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x7c, 0x02, 0x00, 0x00, 0x7c, 0x02, 0x00, 0x00, 0xce, 0x0a, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x02, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x02, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x04, 0x20, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x0e, 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0e, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x20, 0x00, 0x00, 0x00,
+ 0x19, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x1e, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x2a, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x33, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00,
+ 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x0f, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+ 0x12, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x04,
+ 0x18, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x00, 0x00, 0x6f, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
+ 0x60, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x8e, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
+ 0xa0, 0x00, 0x00, 0x00, 0x9d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,
+ 0x14, 0x00, 0x00, 0x00, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x0d, 0x02, 0x00, 0x00, 0x00, 0xb0, 0x00, 0x00, 0x00,
+ 0x11, 0x00, 0x00, 0x00, 0xb4, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0c,
+ 0x15, 0x00, 0x00, 0x00, 0xb2, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xb7, 0x0a, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x0e, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0xc0, 0x0a, 0x00, 0x00, 0x02, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0xc6, 0x0a, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00,
+ 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x00, 0x69, 0x6e, 0x74, 0x00, 0x5f, 0x5f, 0x41, 0x52, 0x52, 0x41, 0x59,
+ 0x5f, 0x53, 0x49, 0x5a, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x5f,
+ 0x00, 0x74, 0x79, 0x70, 0x65, 0x00, 0x6d, 0x61, 0x78, 0x5f, 0x65, 0x6e,
+ 0x74, 0x72, 0x69, 0x65, 0x73, 0x00, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x69,
+ 0x7a, 0x65, 0x00, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x73, 0x69, 0x7a,
+ 0x65, 0x00, 0x6f, 0x70, 0x74, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x00, 0x78,
+ 0x73, 0x6b, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x00, 0x78, 0x64, 0x70, 0x5f,
+ 0x6d, 0x64, 0x00, 0x64, 0x61, 0x74, 0x61, 0x00, 0x64, 0x61, 0x74, 0x61,
+ 0x5f, 0x65, 0x6e, 0x64, 0x00, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6d, 0x65,
+ 0x74, 0x61, 0x00, 0x69, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x69,
+ 0x66, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x00, 0x72, 0x78, 0x5f, 0x71, 0x75,
+ 0x65, 0x75, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x00, 0x65, 0x67,
+ 0x72, 0x65, 0x73, 0x73, 0x5f, 0x69, 0x66, 0x69, 0x6e, 0x64, 0x65, 0x78,
+ 0x00, 0x5f, 0x5f, 0x75, 0x33, 0x32, 0x00, 0x75, 0x6e, 0x73, 0x69, 0x67,
+ 0x6e, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x74, 0x00, 0x63, 0x74, 0x78, 0x00,
+ 0x78, 0x64, 0x70, 0x5f, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74,
+ 0x5f, 0x64, 0x6e, 0x73, 0x5f, 0x66, 0x75, 0x6e, 0x63, 0x00, 0x78, 0x64,
+ 0x70, 0x00, 0x2f, 0x68, 0x6f, 0x6d, 0x65, 0x2f, 0x64, 0x73, 0x61, 0x6c,
+ 0x7a, 0x6d, 0x61, 0x6e, 0x2f, 0x77, 0x6f, 0x72, 0x6b, 0x32, 0x2f, 0x73,
+ 0x72, 0x63, 0x2f, 0x6c, 0x69, 0x62, 0x6b, 0x6e, 0x6f, 0x74, 0x2f, 0x78,
+ 0x64, 0x70, 0x2f, 0x62, 0x70, 0x66, 0x2d, 0x6b, 0x65, 0x72, 0x6e, 0x65,
+ 0x6c, 0x2e, 0x63, 0x00, 0x69, 0x6e, 0x74, 0x20, 0x78, 0x64, 0x70, 0x5f,
+ 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x64, 0x6e, 0x73,
+ 0x5f, 0x66, 0x75, 0x6e, 0x63, 0x28, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74,
+ 0x20, 0x78, 0x64, 0x70, 0x5f, 0x6d, 0x64, 0x20, 0x2a, 0x63, 0x74, 0x78,
+ 0x29, 0x00, 0x09, 0x5f, 0x5f, 0x75, 0x33, 0x32, 0x20, 0x69, 0x6e, 0x64,
+ 0x65, 0x78, 0x20, 0x3d, 0x20, 0x63, 0x74, 0x78, 0x2d, 0x3e, 0x72, 0x78,
+ 0x5f, 0x71, 0x75, 0x65, 0x75, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78,
+ 0x3b, 0x00, 0x09, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x6b, 0x6e,
+ 0x6f, 0x74, 0x5f, 0x78, 0x64, 0x70, 0x5f, 0x6f, 0x70, 0x74, 0x73, 0x20,
+ 0x2a, 0x6f, 0x70, 0x74, 0x73, 0x5f, 0x70, 0x74, 0x72, 0x20, 0x3d, 0x20,
+ 0x62, 0x70, 0x66, 0x5f, 0x6d, 0x61, 0x70, 0x5f, 0x6c, 0x6f, 0x6f, 0x6b,
+ 0x75, 0x70, 0x5f, 0x65, 0x6c, 0x65, 0x6d, 0x28, 0x26, 0x6f, 0x70, 0x74,
+ 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x2c, 0x20, 0x26, 0x69, 0x6e, 0x64, 0x65,
+ 0x78, 0x29, 0x3b, 0x00, 0x09, 0x69, 0x66, 0x20, 0x28, 0x21, 0x6f, 0x70,
+ 0x74, 0x73, 0x5f, 0x70, 0x74, 0x72, 0x29, 0x20, 0x7b, 0x00, 0x09, 0x69,
+ 0x66, 0x20, 0x28, 0x21, 0x28, 0x6f, 0x70, 0x74, 0x73, 0x2e, 0x66, 0x6c,
+ 0x61, 0x67, 0x73, 0x20, 0x26, 0x20, 0x4b, 0x4e, 0x4f, 0x54, 0x5f, 0x58,
+ 0x44, 0x50, 0x5f, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, 0x5f, 0x4f, 0x4e,
+ 0x29, 0x29, 0x20, 0x7b, 0x00, 0x09, 0x28, 0x76, 0x6f, 0x69, 0x64, 0x29,
+ 0x62, 0x70, 0x66, 0x5f, 0x78, 0x64, 0x70, 0x5f, 0x61, 0x64, 0x6a, 0x75,
+ 0x73, 0x74, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x28, 0x63, 0x74, 0x78, 0x2c,
+ 0x20, 0x2d, 0x20, 0x28, 0x69, 0x6e, 0x74, 0x29, 0x73, 0x69, 0x7a, 0x65,
+ 0x6f, 0x66, 0x28, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x6b, 0x6e,
+ 0x6f, 0x74, 0x5f, 0x78, 0x64, 0x70, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x29,
+ 0x00, 0x09, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x2a, 0x64, 0x61, 0x74, 0x61,
+ 0x20, 0x3d, 0x20, 0x28, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x2a, 0x29, 0x28,
+ 0x6c, 0x6f, 0x6e, 0x67, 0x29, 0x63, 0x74, 0x78, 0x2d, 0x3e, 0x64, 0x61,
+ 0x74, 0x61, 0x3b, 0x00, 0x09, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20,
+ 0x6b, 0x6e, 0x6f, 0x74, 0x5f, 0x78, 0x64, 0x70, 0x5f, 0x69, 0x6e, 0x66,
+ 0x6f, 0x20, 0x2a, 0x6d, 0x65, 0x74, 0x61, 0x20, 0x3d, 0x20, 0x28, 0x76,
+ 0x6f, 0x69, 0x64, 0x20, 0x2a, 0x29, 0x28, 0x6c, 0x6f, 0x6e, 0x67, 0x29,
+ 0x63, 0x74, 0x78, 0x2d, 0x3e, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6d, 0x65,
+ 0x74, 0x61, 0x3b, 0x00, 0x09, 0x69, 0x66, 0x20, 0x28, 0x28, 0x76, 0x6f,
+ 0x69, 0x64, 0x20, 0x2a, 0x29, 0x6d, 0x65, 0x74, 0x61, 0x20, 0x2b, 0x20,
+ 0x73, 0x69, 0x7a, 0x65, 0x6f, 0x66, 0x28, 0x2a, 0x6d, 0x65, 0x74, 0x61,
+ 0x29, 0x20, 0x3e, 0x20, 0x64, 0x61, 0x74, 0x61, 0x29, 0x20, 0x7b, 0x00,
+ 0x09, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x20, 0x76, 0x6f, 0x69, 0x64, 0x20,
+ 0x2a, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x65, 0x6e, 0x64, 0x20, 0x3d, 0x20,
+ 0x28, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x2a, 0x29, 0x28, 0x6c, 0x6f, 0x6e,
+ 0x67, 0x29, 0x63, 0x74, 0x78, 0x2d, 0x3e, 0x64, 0x61, 0x74, 0x61, 0x5f,
+ 0x65, 0x6e, 0x64, 0x3b, 0x00, 0x09, 0x69, 0x66, 0x20, 0x28, 0x28, 0x76,
+ 0x6f, 0x69, 0x64, 0x20, 0x2a, 0x29, 0x65, 0x74, 0x68, 0x5f, 0x68, 0x64,
+ 0x72, 0x20, 0x2b, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x6f, 0x66, 0x28, 0x2a,
+ 0x65, 0x74, 0x68, 0x5f, 0x68, 0x64, 0x72, 0x29, 0x20, 0x3e, 0x20, 0x64,
+ 0x61, 0x74, 0x61, 0x5f, 0x65, 0x6e, 0x64, 0x29, 0x20, 0x7b, 0x00, 0x09,
+ 0x69, 0x66, 0x20, 0x28, 0x65, 0x74, 0x68, 0x5f, 0x68, 0x64, 0x72, 0x2d,
+ 0x3e, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x20, 0x3d, 0x3d, 0x20,
+ 0x5f, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x5f, 0x68,
+ 0x74, 0x6f, 0x6e, 0x73, 0x28, 0x45, 0x54, 0x48, 0x5f, 0x50, 0x5f, 0x38,
+ 0x30, 0x32, 0x31, 0x51, 0x29, 0x29, 0x20, 0x7b, 0x00, 0x09, 0x09, 0x69,
+ 0x66, 0x20, 0x28, 0x64, 0x61, 0x74, 0x61, 0x20, 0x2b, 0x20, 0x73, 0x69,
+ 0x7a, 0x65, 0x6f, 0x66, 0x28, 0x5f, 0x5f, 0x75, 0x31, 0x36, 0x29, 0x20,
+ 0x2b, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x6f, 0x66, 0x28, 0x65, 0x74, 0x68,
+ 0x5f, 0x74, 0x79, 0x70, 0x65, 0x29, 0x20, 0x3e, 0x20, 0x64, 0x61, 0x74,
+ 0x61, 0x5f, 0x65, 0x6e, 0x64, 0x29, 0x20, 0x7b, 0x00, 0x09, 0x09, 0x7d,
+ 0x20, 0x65, 0x6c, 0x73, 0x65, 0x20, 0x69, 0x66, 0x20, 0x28, 0x6d, 0x65,
+ 0x74, 0x61, 0x20, 0x3d, 0x3d, 0x20, 0x30, 0x29, 0x20, 0x7b, 0x00, 0x09,
+ 0x09, 0x5f, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, 0x5f, 0x6d,
+ 0x65, 0x6d, 0x63, 0x70, 0x79, 0x28, 0x26, 0x65, 0x74, 0x68, 0x5f, 0x74,
+ 0x79, 0x70, 0x65, 0x2c, 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x2b, 0x20,
+ 0x73, 0x69, 0x7a, 0x65, 0x6f, 0x66, 0x28, 0x5f, 0x5f, 0x75, 0x31, 0x36,
+ 0x29, 0x2c, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x6f, 0x66, 0x28, 0x65, 0x74,
+ 0x68, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x29, 0x29, 0x3b, 0x00, 0x09, 0x73,
+ 0x77, 0x69, 0x74, 0x63, 0x68, 0x20, 0x28, 0x65, 0x74, 0x68, 0x5f, 0x74,
+ 0x79, 0x70, 0x65, 0x29, 0x20, 0x7b, 0x00, 0x09, 0x09, 0x69, 0x66, 0x20,
+ 0x28, 0x28, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x2a, 0x29, 0x69, 0x70, 0x34,
+ 0x20, 0x2b, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x6f, 0x66, 0x28, 0x2a, 0x69,
+ 0x70, 0x34, 0x29, 0x20, 0x3e, 0x20, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x65,
+ 0x6e, 0x64, 0x29, 0x20, 0x7b, 0x00, 0x09, 0x09, 0x69, 0x66, 0x20, 0x28,
+ 0x69, 0x70, 0x34, 0x2d, 0x3e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
+ 0x20, 0x21, 0x3d, 0x20, 0x34, 0x29, 0x20, 0x7b, 0x00, 0x09, 0x09, 0x69,
+ 0x66, 0x20, 0x28, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x65, 0x6e, 0x64, 0x20,
+ 0x2d, 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x3c, 0x20, 0x5f, 0x5f, 0x62,
+ 0x70, 0x66, 0x5f, 0x6e, 0x74, 0x6f, 0x68, 0x73, 0x28, 0x69, 0x70, 0x34,
+ 0x2d, 0x3e, 0x74, 0x6f, 0x74, 0x5f, 0x6c, 0x65, 0x6e, 0x29, 0x29, 0x20,
+ 0x7b, 0x00, 0x09, 0x09, 0x69, 0x66, 0x20, 0x28, 0x69, 0x70, 0x34, 0x2d,
+ 0x3e, 0x66, 0x72, 0x61, 0x67, 0x5f, 0x6f, 0x66, 0x66, 0x20, 0x21, 0x3d,
+ 0x20, 0x30, 0x20, 0x26, 0x26, 0x00, 0x09, 0x09, 0x6c, 0x34, 0x5f, 0x68,
+ 0x64, 0x72, 0x20, 0x3d, 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x2b, 0x20,
+ 0x69, 0x70, 0x34, 0x2d, 0x3e, 0x69, 0x68, 0x6c, 0x20, 0x2a, 0x20, 0x34,
+ 0x3b, 0x00, 0x09, 0x09, 0x69, 0x70, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+ 0x20, 0x3d, 0x20, 0x69, 0x70, 0x34, 0x2d, 0x3e, 0x70, 0x72, 0x6f, 0x74,
+ 0x6f, 0x63, 0x6f, 0x6c, 0x3b, 0x00, 0x09, 0x09, 0x69, 0x66, 0x20, 0x28,
+ 0x28, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x2a, 0x29, 0x69, 0x70, 0x36, 0x20,
+ 0x2b, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x6f, 0x66, 0x28, 0x2a, 0x69, 0x70,
+ 0x36, 0x29, 0x20, 0x3e, 0x20, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x65, 0x6e,
+ 0x64, 0x29, 0x20, 0x7b, 0x00, 0x09, 0x09, 0x69, 0x66, 0x20, 0x28, 0x69,
+ 0x70, 0x36, 0x2d, 0x3e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20,
+ 0x21, 0x3d, 0x20, 0x36, 0x29, 0x20, 0x7b, 0x00, 0x09, 0x09, 0x69, 0x66,
+ 0x20, 0x28, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x65, 0x6e, 0x64, 0x20, 0x2d,
+ 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x3c, 0x20, 0x5f, 0x5f, 0x62, 0x70,
+ 0x66, 0x5f, 0x6e, 0x74, 0x6f, 0x68, 0x73, 0x28, 0x69, 0x70, 0x36, 0x2d,
+ 0x3e, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x6c, 0x65, 0x6e,
+ 0x29, 0x20, 0x2b, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x6f, 0x66, 0x28, 0x2a,
+ 0x69, 0x70, 0x36, 0x29, 0x29, 0x20, 0x7b, 0x00, 0x09, 0x09, 0x69, 0x70,
+ 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x20, 0x3d, 0x20, 0x69, 0x70, 0x36,
+ 0x2d, 0x3e, 0x6e, 0x65, 0x78, 0x74, 0x68, 0x64, 0x72, 0x3b, 0x00, 0x09,
+ 0x09, 0x69, 0x66, 0x20, 0x28, 0x69, 0x70, 0x5f, 0x70, 0x72, 0x6f, 0x74,
+ 0x6f, 0x20, 0x3d, 0x3d, 0x20, 0x49, 0x50, 0x50, 0x52, 0x4f, 0x54, 0x4f,
+ 0x5f, 0x46, 0x52, 0x41, 0x47, 0x4d, 0x45, 0x4e, 0x54, 0x29, 0x20, 0x7b,
+ 0x00, 0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x28, 0x28, 0x76, 0x6f, 0x69,
+ 0x64, 0x20, 0x2a, 0x29, 0x66, 0x72, 0x61, 0x67, 0x20, 0x2b, 0x20, 0x73,
+ 0x69, 0x7a, 0x65, 0x6f, 0x66, 0x28, 0x2a, 0x66, 0x72, 0x61, 0x67, 0x29,
+ 0x20, 0x3e, 0x20, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x65, 0x6e, 0x64, 0x29,
+ 0x20, 0x7b, 0x00, 0x09, 0x09, 0x09, 0x69, 0x70, 0x5f, 0x70, 0x72, 0x6f,
+ 0x74, 0x6f, 0x20, 0x3d, 0x20, 0x66, 0x72, 0x61, 0x67, 0x2d, 0x3e, 0x6e,
+ 0x65, 0x78, 0x74, 0x68, 0x64, 0x72, 0x3b, 0x00, 0x09, 0x73, 0x77, 0x69,
+ 0x74, 0x63, 0x68, 0x20, 0x28, 0x69, 0x70, 0x5f, 0x70, 0x72, 0x6f, 0x74,
+ 0x6f, 0x29, 0x20, 0x7b, 0x00, 0x09, 0x09, 0x69, 0x66, 0x20, 0x28, 0x6c,
+ 0x34, 0x5f, 0x68, 0x64, 0x72, 0x20, 0x2b, 0x20, 0x73, 0x69, 0x7a, 0x65,
+ 0x6f, 0x66, 0x28, 0x2a, 0x74, 0x63, 0x70, 0x29, 0x20, 0x3e, 0x20, 0x64,
+ 0x61, 0x74, 0x61, 0x5f, 0x65, 0x6e, 0x64, 0x29, 0x20, 0x7b, 0x00, 0x09,
+ 0x09, 0x69, 0x66, 0x20, 0x28, 0x28, 0x6f, 0x70, 0x74, 0x73, 0x2e, 0x66,
+ 0x6c, 0x61, 0x67, 0x73, 0x20, 0x26, 0x20, 0x4b, 0x4e, 0x4f, 0x54, 0x5f,
+ 0x58, 0x44, 0x50, 0x5f, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, 0x5f, 0x54,
+ 0x43, 0x50, 0x29, 0x20, 0x26, 0x26, 0x00, 0x09, 0x09, 0x70, 0x6f, 0x72,
+ 0x74, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x20, 0x3d, 0x20, 0x5f, 0x5f, 0x62,
+ 0x70, 0x66, 0x5f, 0x6e, 0x74, 0x6f, 0x68, 0x73, 0x28, 0x74, 0x63, 0x70,
+ 0x2d, 0x3e, 0x64, 0x65, 0x73, 0x74, 0x29, 0x3b, 0x00, 0x09, 0x09, 0x20,
+ 0x20, 0x20, 0x20, 0x28, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x64, 0x65, 0x73,
+ 0x74, 0x20, 0x3d, 0x3d, 0x20, 0x6f, 0x70, 0x74, 0x73, 0x2e, 0x75, 0x64,
+ 0x70, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x20, 0x7c, 0x7c, 0x00, 0x09, 0x09,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x28, 0x28, 0x6f, 0x70, 0x74, 0x73, 0x2e,
+ 0x66, 0x6c, 0x61, 0x67, 0x73, 0x20, 0x26, 0x20, 0x28, 0x4b, 0x4e, 0x4f,
+ 0x54, 0x5f, 0x58, 0x44, 0x50, 0x5f, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52,
+ 0x5f, 0x50, 0x41, 0x53, 0x53, 0x20, 0x7c, 0x20, 0x4b, 0x4e, 0x4f, 0x54,
+ 0x5f, 0x58, 0x44, 0x50, 0x5f, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, 0x5f,
+ 0x44, 0x52, 0x4f, 0x50, 0x29, 0x29, 0x20, 0x26, 0x26, 0x00, 0x09, 0x09,
+ 0x69, 0x66, 0x20, 0x28, 0x6c, 0x34, 0x5f, 0x68, 0x64, 0x72, 0x20, 0x2b,
+ 0x20, 0x73, 0x69, 0x7a, 0x65, 0x6f, 0x66, 0x28, 0x2a, 0x75, 0x64, 0x70,
+ 0x29, 0x20, 0x3e, 0x20, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x65, 0x6e, 0x64,
+ 0x29, 0x20, 0x7b, 0x00, 0x09, 0x09, 0x69, 0x66, 0x20, 0x28, 0x64, 0x61,
+ 0x74, 0x61, 0x5f, 0x65, 0x6e, 0x64, 0x20, 0x2d, 0x20, 0x28, 0x76, 0x6f,
+ 0x69, 0x64, 0x20, 0x2a, 0x29, 0x75, 0x64, 0x70, 0x20, 0x3c, 0x20, 0x5f,
+ 0x5f, 0x62, 0x70, 0x66, 0x5f, 0x6e, 0x74, 0x6f, 0x68, 0x73, 0x28, 0x75,
+ 0x64, 0x70, 0x2d, 0x3e, 0x6c, 0x65, 0x6e, 0x29, 0x29, 0x20, 0x7b, 0x00,
+ 0x09, 0x09, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x20,
+ 0x3d, 0x20, 0x5f, 0x5f, 0x62, 0x70, 0x66, 0x5f, 0x6e, 0x74, 0x6f, 0x68,
+ 0x73, 0x28, 0x75, 0x64, 0x70, 0x2d, 0x3e, 0x64, 0x65, 0x73, 0x74, 0x29,
+ 0x3b, 0x00, 0x09, 0x09, 0x69, 0x66, 0x20, 0x28, 0x28, 0x6f, 0x70, 0x74,
+ 0x73, 0x2e, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x20, 0x26, 0x20, 0x4b, 0x4e,
+ 0x4f, 0x54, 0x5f, 0x58, 0x44, 0x50, 0x5f, 0x46, 0x49, 0x4c, 0x54, 0x45,
+ 0x52, 0x5f, 0x55, 0x44, 0x50, 0x29, 0x20, 0x26, 0x26, 0x00, 0x09, 0x09,
+ 0x7d, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x20, 0x69, 0x66, 0x20, 0x28, 0x28,
+ 0x6f, 0x70, 0x74, 0x73, 0x2e, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x20, 0x26,
+ 0x20, 0x4b, 0x4e, 0x4f, 0x54, 0x5f, 0x58, 0x44, 0x50, 0x5f, 0x46, 0x49,
+ 0x4c, 0x54, 0x45, 0x52, 0x5f, 0x51, 0x55, 0x49, 0x43, 0x29, 0x20, 0x26,
+ 0x26, 0x00, 0x09, 0x09, 0x20, 0x20, 0x20, 0x20, 0x28, 0x70, 0x6f, 0x72,
+ 0x74, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x20, 0x3d, 0x3d, 0x20, 0x6f, 0x70,
+ 0x74, 0x73, 0x2e, 0x71, 0x75, 0x69, 0x63, 0x5f, 0x70, 0x6f, 0x72, 0x74,
+ 0x20, 0x7c, 0x7c, 0x00, 0x09, 0x7d, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x20,
+ 0x69, 0x66, 0x20, 0x28, 0x6f, 0x70, 0x74, 0x73, 0x2e, 0x66, 0x6c, 0x61,
+ 0x67, 0x73, 0x20, 0x26, 0x20, 0x4b, 0x4e, 0x4f, 0x54, 0x5f, 0x58, 0x44,
+ 0x50, 0x5f, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, 0x5f, 0x44, 0x52, 0x4f,
+ 0x50, 0x29, 0x20, 0x7b, 0x00, 0x09, 0x69, 0x66, 0x20, 0x28, 0x6f, 0x70,
+ 0x74, 0x73, 0x2e, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x20, 0x26, 0x20, 0x4b,
+ 0x4e, 0x4f, 0x54, 0x5f, 0x58, 0x44, 0x50, 0x5f, 0x46, 0x49, 0x4c, 0x54,
+ 0x45, 0x52, 0x5f, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x29, 0x20, 0x7b, 0x00,
+ 0x09, 0x09, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x62, 0x70, 0x66,
+ 0x5f, 0x66, 0x69, 0x62, 0x5f, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x20,
+ 0x66, 0x69, 0x62, 0x20, 0x3d, 0x20, 0x7b, 0x00, 0x09, 0x09, 0x69, 0x66,
+ 0x20, 0x28, 0x69, 0x70, 0x76, 0x34, 0x29, 0x20, 0x7b, 0x00, 0x09, 0x09,
+ 0x09, 0x66, 0x69, 0x62, 0x2e, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x20,
+ 0x20, 0x20, 0x3d, 0x20, 0x41, 0x46, 0x5f, 0x49, 0x4e, 0x45, 0x54, 0x3b,
+ 0x00, 0x09, 0x09, 0x09, 0x66, 0x69, 0x62, 0x2e, 0x69, 0x70, 0x76, 0x34,
+ 0x5f, 0x73, 0x72, 0x63, 0x20, 0x3d, 0x20, 0x69, 0x70, 0x34, 0x2d, 0x3e,
+ 0x64, 0x61, 0x64, 0x64, 0x72, 0x3b, 0x00, 0x09, 0x09, 0x09, 0x66, 0x69,
+ 0x62, 0x2e, 0x69, 0x70, 0x76, 0x34, 0x5f, 0x64, 0x73, 0x74, 0x20, 0x3d,
+ 0x20, 0x69, 0x70, 0x34, 0x2d, 0x3e, 0x73, 0x61, 0x64, 0x64, 0x72, 0x3b,
+ 0x00, 0x09, 0x09, 0x09, 0x66, 0x69, 0x62, 0x2e, 0x66, 0x61, 0x6d, 0x69,
+ 0x6c, 0x79, 0x20, 0x3d, 0x20, 0x41, 0x46, 0x5f, 0x49, 0x4e, 0x45, 0x54,
+ 0x36, 0x3b, 0x00, 0x09, 0x09, 0x09, 0x2a, 0x69, 0x70, 0x76, 0x36, 0x5f,
+ 0x73, 0x72, 0x63, 0x20, 0x20, 0x3d, 0x20, 0x69, 0x70, 0x36, 0x2d, 0x3e,
+ 0x64, 0x61, 0x64, 0x64, 0x72, 0x3b, 0x00, 0x09, 0x09, 0x09, 0x2a, 0x69,
+ 0x70, 0x76, 0x36, 0x5f, 0x64, 0x73, 0x74, 0x20, 0x20, 0x3d, 0x20, 0x69,
+ 0x70, 0x36, 0x2d, 0x3e, 0x73, 0x61, 0x64, 0x64, 0x72, 0x3b, 0x00, 0x09,
+ 0x09, 0x69, 0x6e, 0x74, 0x20, 0x72, 0x65, 0x74, 0x20, 0x3d, 0x20, 0x62,
+ 0x70, 0x66, 0x5f, 0x66, 0x69, 0x62, 0x5f, 0x6c, 0x6f, 0x6f, 0x6b, 0x75,
+ 0x70, 0x28, 0x63, 0x74, 0x78, 0x2c, 0x20, 0x26, 0x66, 0x69, 0x62, 0x2c,
+ 0x20, 0x73, 0x69, 0x7a, 0x65, 0x6f, 0x66, 0x28, 0x66, 0x69, 0x62, 0x29,
+ 0x2c, 0x20, 0x42, 0x50, 0x46, 0x5f, 0x46, 0x49, 0x42, 0x5f, 0x4c, 0x4f,
+ 0x4f, 0x4b, 0x55, 0x50, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x29,
+ 0x3b, 0x00, 0x09, 0x09, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x20, 0x28,
+ 0x72, 0x65, 0x74, 0x29, 0x20, 0x7b, 0x00, 0x09, 0x09, 0x09, 0x69, 0x66,
+ 0x20, 0x28, 0x6d, 0x61, 0x63, 0x5f, 0x69, 0x6e, 0x5b, 0x30, 0x5d, 0x20,
+ 0x21, 0x3d, 0x20, 0x6d, 0x61, 0x63, 0x5f, 0x6f, 0x75, 0x74, 0x5b, 0x30,
+ 0x5d, 0x20, 0x7c, 0x7c, 0x00, 0x09, 0x09, 0x09, 0x20, 0x20, 0x20, 0x20,
+ 0x6d, 0x61, 0x63, 0x5f, 0x69, 0x6e, 0x5b, 0x31, 0x5d, 0x20, 0x21, 0x3d,
+ 0x20, 0x6d, 0x61, 0x63, 0x5f, 0x6f, 0x75, 0x74, 0x5b, 0x31, 0x5d, 0x20,
+ 0x7c, 0x7c, 0x00, 0x09, 0x09, 0x09, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61,
+ 0x63, 0x5f, 0x69, 0x6e, 0x5b, 0x32, 0x5d, 0x20, 0x21, 0x3d, 0x20, 0x6d,
+ 0x61, 0x63, 0x5f, 0x6f, 0x75, 0x74, 0x5b, 0x32, 0x5d, 0x29, 0x20, 0x7b,
+ 0x00, 0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x28, 0x6d, 0x65, 0x74, 0x61,
+ 0x20, 0x21, 0x3d, 0x20, 0x30, 0x29, 0x20, 0x7b, 0x00, 0x09, 0x09, 0x09,
+ 0x09, 0x6d, 0x65, 0x74, 0x61, 0x2d, 0x3e, 0x6f, 0x75, 0x74, 0x5f, 0x69,
+ 0x66, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x3d, 0x20, 0x66, 0x69,
+ 0x62, 0x2e, 0x69, 0x66, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x3b, 0x00, 0x09,
+ 0x09, 0x09, 0x5f, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, 0x5f,
+ 0x6d, 0x65, 0x6d, 0x63, 0x70, 0x79, 0x28, 0x65, 0x74, 0x68, 0x5f, 0x68,
+ 0x64, 0x72, 0x2d, 0x3e, 0x68, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
+ 0x2c, 0x20, 0x66, 0x69, 0x62, 0x2e, 0x64, 0x6d, 0x61, 0x63, 0x2c, 0x20,
+ 0x45, 0x54, 0x48, 0x5f, 0x41, 0x4c, 0x45, 0x4e, 0x29, 0x3b, 0x00, 0x09,
+ 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x62, 0x70, 0x66, 0x5f, 0x72,
+ 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x6d, 0x61, 0x70, 0x28,
+ 0x26, 0x78, 0x73, 0x6b, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x2c, 0x20, 0x63,
+ 0x74, 0x78, 0x2d, 0x3e, 0x72, 0x78, 0x5f, 0x71, 0x75, 0x65, 0x75, 0x65,
+ 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x2c, 0x20, 0x30, 0x29, 0x3b, 0x00,
+ 0x7d, 0x00, 0x63, 0x68, 0x61, 0x72, 0x00, 0x5f, 0x6c, 0x69, 0x63, 0x65,
+ 0x6e, 0x73, 0x65, 0x00, 0x2e, 0x6d, 0x61, 0x70, 0x73, 0x00, 0x6c, 0x69,
+ 0x63, 0x65, 0x6e, 0x73, 0x65, 0x00, 0x00, 0x00, 0x9f, 0xeb, 0x01, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x2c, 0x07, 0x00, 0x00, 0x40, 0x07, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0xca, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x00, 0x00, 0xca, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0xf0, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0x2e, 0x01, 0x00, 0x00, 0x15, 0xfc, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0x2e, 0x01, 0x00, 0x00, 0x08, 0xfc, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0x52, 0x01, 0x00, 0x00, 0x23, 0x00, 0x01, 0x00, 0x50, 0x00, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0x9c, 0x01, 0x00, 0x00, 0x06, 0x04, 0x01, 0x00,
+ 0x58, 0x00, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0xae, 0x01, 0x00, 0x00,
+ 0x08, 0x1c, 0x01, 0x00, 0x78, 0x00, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0xae, 0x01, 0x00, 0x00, 0x13, 0x1c, 0x01, 0x00, 0x90, 0x00, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0xae, 0x01, 0x00, 0x00, 0x06, 0x1c, 0x01, 0x00,
+ 0x98, 0x00, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xc8, 0x00, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0xd9, 0x01, 0x00, 0x00, 0x08, 0x30, 0x01, 0x00, 0xf0, 0x00, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0x1d, 0x02, 0x00, 0x00, 0x22, 0x3c, 0x01, 0x00,
+ 0xf8, 0x00, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x44, 0x02, 0x00, 0x00,
+ 0x32, 0x44, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0x80, 0x02, 0x00, 0x00, 0x13, 0x50, 0x01, 0x00, 0x18, 0x01, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0x80, 0x02, 0x00, 0x00, 0x06, 0x50, 0x01, 0x00,
+ 0x30, 0x01, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0xac, 0x02, 0x00, 0x00,
+ 0x2c, 0x40, 0x01, 0x00, 0x38, 0x01, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0xe1, 0x02, 0x00, 0x00, 0x16, 0x8c, 0x01, 0x00, 0x50, 0x01, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0xe1, 0x02, 0x00, 0x00, 0x06, 0x8c, 0x01, 0x00,
+ 0x58, 0x01, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x17, 0x03, 0x00, 0x00,
+ 0x0f, 0xa4, 0x01, 0x00, 0x78, 0x01, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0x17, 0x03, 0x00, 0x00, 0x06, 0xa4, 0x01, 0x00, 0x80, 0x01, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0x51, 0x03, 0x00, 0x00, 0x1c, 0xa8, 0x01, 0x00,
+ 0x98, 0x01, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x51, 0x03, 0x00, 0x00,
+ 0x07, 0xa8, 0x01, 0x00, 0xa8, 0x01, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0x8d, 0x03, 0x00, 0x00, 0x0e, 0xb0, 0x01, 0x00, 0xb8, 0x01, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0xa7, 0x03, 0x00, 0x00, 0x03, 0xbc, 0x01, 0x00,
+ 0xd8, 0x01, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0xee, 0x03, 0x00, 0x00,
+ 0x02, 0xe0, 0x01, 0x00, 0xf8, 0x01, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0x03, 0x04, 0x00, 0x00, 0x13, 0xec, 0x01, 0x00, 0x10, 0x02, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0x03, 0x04, 0x00, 0x00, 0x07, 0xec, 0x01, 0x00,
+ 0x18, 0x02, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x32, 0x04, 0x00, 0x00,
+ 0x0c, 0xf8, 0x01, 0x00, 0x20, 0x02, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0x32, 0x04, 0x00, 0x00, 0x14, 0xf8, 0x01, 0x00, 0x38, 0x02, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0x32, 0x04, 0x00, 0x00, 0x07, 0xf8, 0x01, 0x00,
+ 0x40, 0x02, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x4d, 0x04, 0x00, 0x00,
+ 0x10, 0x10, 0x02, 0x00, 0x50, 0x02, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0x4d, 0x04, 0x00, 0x00, 0x19, 0x10, 0x02, 0x00, 0x68, 0x02, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0x4d, 0x04, 0x00, 0x00, 0x07, 0x10, 0x02, 0x00,
+ 0x70, 0x02, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x82, 0x04, 0x00, 0x00,
+ 0x0c, 0x20, 0x02, 0x00, 0x78, 0x02, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0x82, 0x04, 0x00, 0x00, 0x1a, 0x20, 0x02, 0x00, 0xa8, 0x02, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0x9e, 0x04, 0x00, 0x00, 0x1c, 0x34, 0x02, 0x00,
+ 0xb8, 0x02, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x9e, 0x04, 0x00, 0x00,
+ 0x11, 0x34, 0x02, 0x00, 0xc8, 0x02, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0xbe, 0x04, 0x00, 0x00, 0x13, 0x30, 0x02, 0x00, 0xd8, 0x02, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0xda, 0x04, 0x00, 0x00, 0x13, 0x48, 0x02, 0x00,
+ 0xf0, 0x02, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0xda, 0x04, 0x00, 0x00,
+ 0x07, 0x48, 0x02, 0x00, 0xf8, 0x02, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0x09, 0x05, 0x00, 0x00, 0x0c, 0x54, 0x02, 0x00, 0x00, 0x03, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0x09, 0x05, 0x00, 0x00, 0x14, 0x54, 0x02, 0x00,
+ 0x10, 0x03, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x09, 0x05, 0x00, 0x00,
+ 0x07, 0x54, 0x02, 0x00, 0x18, 0x03, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0x24, 0x05, 0x00, 0x00, 0x10, 0x6c, 0x02, 0x00, 0x28, 0x03, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0x24, 0x05, 0x00, 0x00, 0x19, 0x6c, 0x02, 0x00,
+ 0x38, 0x03, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x24, 0x05, 0x00, 0x00,
+ 0x37, 0x6c, 0x02, 0x00, 0x48, 0x03, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0x24, 0x05, 0x00, 0x00, 0x07, 0x6c, 0x02, 0x00, 0x68, 0x03, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0x6c, 0x05, 0x00, 0x00, 0x13, 0x7c, 0x02, 0x00,
+ 0x70, 0x03, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x87, 0x05, 0x00, 0x00,
+ 0x07, 0x84, 0x02, 0x00, 0x80, 0x03, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0xad, 0x05, 0x00, 0x00, 0x15, 0x90, 0x02, 0x00, 0x90, 0x03, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0xad, 0x05, 0x00, 0x00, 0x08, 0x90, 0x02, 0x00,
+ 0xa0, 0x03, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0xdf, 0x05, 0x00, 0x00,
+ 0x15, 0x9c, 0x02, 0x00, 0xb8, 0x03, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x03, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0xfc, 0x05, 0x00, 0x00, 0x02, 0xe0, 0x02, 0x00,
+ 0xf0, 0x03, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x11, 0x06, 0x00, 0x00,
+ 0x0e, 0xf0, 0x02, 0x00, 0x08, 0x04, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0x11, 0x06, 0x00, 0x00, 0x07, 0xf0, 0x02, 0x00, 0x10, 0x04, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0x3b, 0x06, 0x00, 0x00, 0x13, 0x08, 0x03, 0x00,
+ 0x28, 0x04, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x3b, 0x06, 0x00, 0x00,
+ 0x2a, 0x08, 0x03, 0x00, 0x30, 0x04, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0x67, 0x06, 0x00, 0x00, 0x0f, 0x00, 0x03, 0x00, 0x40, 0x04, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0x8d, 0x06, 0x00, 0x00, 0x12, 0x0c, 0x03, 0x00,
+ 0x50, 0x04, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x8d, 0x06, 0x00, 0x00,
+ 0x23, 0x0c, 0x03, 0x00, 0x58, 0x04, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0xb2, 0x06, 0x00, 0x00, 0x15, 0x10, 0x03, 0x00, 0x70, 0x04, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0xb2, 0x06, 0x00, 0x00, 0x46, 0x10, 0x03, 0x00,
+ 0x98, 0x04, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0xfa, 0x06, 0x00, 0x00,
+ 0x0e, 0x30, 0x03, 0x00, 0xb0, 0x04, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0xfa, 0x06, 0x00, 0x00, 0x07, 0x30, 0x03, 0x00, 0xb8, 0x04, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0x24, 0x07, 0x00, 0x00, 0x10, 0x44, 0x03, 0x00,
+ 0xc0, 0x04, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x24, 0x07, 0x00, 0x00,
+ 0x20, 0x44, 0x03, 0x00, 0xd8, 0x04, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0x24, 0x07, 0x00, 0x00, 0x07, 0x44, 0x03, 0x00, 0xe0, 0x04, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0x5c, 0x07, 0x00, 0x00, 0x0f, 0x54, 0x03, 0x00,
+ 0xf0, 0x04, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x82, 0x07, 0x00, 0x00,
+ 0x13, 0x5c, 0x03, 0x00, 0x00, 0x05, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0x82, 0x07, 0x00, 0x00, 0x2a, 0x5c, 0x03, 0x00, 0x08, 0x05, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0x8d, 0x06, 0x00, 0x00, 0x12, 0x60, 0x03, 0x00,
+ 0x18, 0x05, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x8d, 0x06, 0x00, 0x00,
+ 0x23, 0x60, 0x03, 0x00, 0x20, 0x05, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0xb2, 0x06, 0x00, 0x00, 0x15, 0x64, 0x03, 0x00, 0x30, 0x05, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0xb2, 0x06, 0x00, 0x00, 0x46, 0x64, 0x03, 0x00,
+ 0x48, 0x05, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0xae, 0x07, 0x00, 0x00,
+ 0x1a, 0x70, 0x03, 0x00, 0x60, 0x05, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0xae, 0x07, 0x00, 0x00, 0x32, 0x70, 0x03, 0x00, 0x70, 0x05, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x90, 0x05, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0xe2, 0x07, 0x00, 0x00,
+ 0x12, 0x74, 0x03, 0x00, 0x98, 0x05, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0xe2, 0x07, 0x00, 0x00, 0x24, 0x74, 0x03, 0x00, 0xa0, 0x05, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0xb2, 0x06, 0x00, 0x00, 0x15, 0x78, 0x03, 0x00,
+ 0xb8, 0x05, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0xb2, 0x06, 0x00, 0x00,
+ 0x46, 0x78, 0x03, 0x00, 0xd8, 0x05, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0x08, 0x08, 0x00, 0x00, 0x18, 0xac, 0x03, 0x00, 0xf0, 0x05, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0x08, 0x08, 0x00, 0x00, 0x0d, 0xac, 0x03, 0x00,
+ 0x10, 0x06, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x39, 0x08, 0x00, 0x00,
+ 0x11, 0xd0, 0x03, 0x00, 0x18, 0x06, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0x39, 0x08, 0x00, 0x00, 0x06, 0xd0, 0x03, 0x00, 0x28, 0x06, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0x64, 0x08, 0x00, 0x00, 0x19, 0xd4, 0x03, 0x00,
+ 0x78, 0x06, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x84, 0x08, 0x00, 0x00,
+ 0x07, 0xe0, 0x03, 0x00, 0x90, 0x06, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0x92, 0x08, 0x00, 0x00, 0x11, 0xe4, 0x03, 0x00, 0x98, 0x06, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0xad, 0x08, 0x00, 0x00, 0x18, 0xe8, 0x03, 0x00,
+ 0xa0, 0x06, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0xad, 0x08, 0x00, 0x00,
+ 0x11, 0xe8, 0x03, 0x00, 0xa8, 0x06, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0xcb, 0x08, 0x00, 0x00, 0x18, 0xec, 0x03, 0x00, 0xb0, 0x06, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0xcb, 0x08, 0x00, 0x00, 0x11, 0xec, 0x03, 0x00,
+ 0xc8, 0x06, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0xe9, 0x08, 0x00, 0x00,
+ 0x0f, 0xfc, 0x03, 0x00, 0xd0, 0x06, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0x03, 0x09, 0x00, 0x00, 0x16, 0x00, 0x04, 0x00, 0x20, 0x07, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0x1f, 0x09, 0x00, 0x00, 0x16, 0x04, 0x04, 0x00,
+ 0x78, 0x07, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0x3b, 0x09, 0x00, 0x00, 0x0d, 0x18, 0x04, 0x00, 0xb8, 0x07, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0x86, 0x09, 0x00, 0x00, 0x03, 0x1c, 0x04, 0x00,
+ 0xe0, 0x07, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x97, 0x09, 0x00, 0x00,
+ 0x08, 0x28, 0x04, 0x00, 0xe8, 0x07, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0x97, 0x09, 0x00, 0x00, 0x15, 0x28, 0x04, 0x00, 0xf0, 0x07, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0x97, 0x09, 0x00, 0x00, 0x20, 0x28, 0x04, 0x00,
+ 0xf8, 0x07, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0xb9, 0x09, 0x00, 0x00,
+ 0x08, 0x2c, 0x04, 0x00, 0x00, 0x08, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0xb9, 0x09, 0x00, 0x00, 0x15, 0x2c, 0x04, 0x00, 0x08, 0x08, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0xb9, 0x09, 0x00, 0x00, 0x20, 0x2c, 0x04, 0x00,
+ 0x10, 0x08, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0xdb, 0x09, 0x00, 0x00,
+ 0x08, 0x30, 0x04, 0x00, 0x18, 0x08, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0xdb, 0x09, 0x00, 0x00, 0x15, 0x30, 0x04, 0x00, 0x20, 0x08, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0x97, 0x09, 0x00, 0x00, 0x08, 0x28, 0x04, 0x00,
+ 0x28, 0x08, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0xfd, 0x09, 0x00, 0x00,
+ 0x08, 0x44, 0x04, 0x00, 0x38, 0x08, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0x11, 0x0a, 0x00, 0x00, 0x1e, 0x48, 0x04, 0x00, 0x40, 0x08, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0x11, 0x0a, 0x00, 0x00, 0x18, 0x48, 0x04, 0x00,
+ 0x50, 0x08, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x37, 0x0a, 0x00, 0x00,
+ 0x04, 0x58, 0x04, 0x00, 0xb0, 0x08, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00,
+ 0x73, 0x0a, 0x00, 0x00, 0x09, 0x88, 0x04, 0x00, 0xd8, 0x08, 0x00, 0x00,
+ 0xce, 0x00, 0x00, 0x00, 0xb0, 0x0a, 0x00, 0x00, 0x01, 0x8c, 0x04, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xd8, 0x08, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xc9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x28, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xd8, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xd8, 0x02, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xb1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xa8, 0x02, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xb8, 0x03, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xe8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x98, 0x04, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xd8, 0x05, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xa9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x48, 0x05, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xb9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xb0, 0x08, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xc0, 0x06, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xa1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x70, 0x07, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xe0, 0x08, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x89, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xf0, 0x08, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x50, 0x08, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x4b, 0x00, 0x00, 0x00, 0x12, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x22, 0x00, 0x00, 0x00, 0x11, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x2b, 0x00, 0x00, 0x00, 0x11, 0x00, 0x05, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x42, 0x00, 0x00, 0x00, 0x11, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x12, 0x00, 0x00, 0x00, 0xb8, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x68, 0x02, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
+ 0x74, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x13, 0x00, 0x00, 0x00, 0x8c, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x90, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x30, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x40, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x60, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x70, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x90, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0xa0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xb0, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0xc0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0xd0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0xf0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x02, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x20, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x30, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x50, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x60, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x70, 0x02, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x80, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x90, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xa0, 0x02, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0xb0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0xc0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xd0, 0x02, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0xe0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0xf0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x10, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x20, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x40, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x50, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x60, 0x03, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x70, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x90, 0x03, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0xa0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0xb0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0xd0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xf0, 0x03, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x04, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x30, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x04, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x60, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x70, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80, 0x04, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x90, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0xa0, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xb0, 0x04, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0xc0, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0xd0, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x04, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0xf0, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x05, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x20, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x30, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x05, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x50, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x60, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x70, 0x05, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x80, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x90, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xa0, 0x05, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0xb0, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0xc0, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xd0, 0x05, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0xe0, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0xf0, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x10, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x20, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x30, 0x06, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x40, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x50, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x60, 0x06, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x70, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x80, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x90, 0x06, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0xa0, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0xb0, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xc0, 0x06, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0xd0, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0xe0, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xf0, 0x06, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x07, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x30, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x40, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x07, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x1a, 0x1b, 0x1c, 0x1d, 0x00, 0x2e, 0x74, 0x65, 0x78, 0x74, 0x00, 0x2e,
+ 0x72, 0x65, 0x6c, 0x2e, 0x42, 0x54, 0x46, 0x2e, 0x65, 0x78, 0x74, 0x00,
+ 0x2e, 0x6d, 0x61, 0x70, 0x73, 0x00, 0x2e, 0x72, 0x65, 0x6c, 0x78, 0x64,
+ 0x70, 0x00, 0x6f, 0x70, 0x74, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x00, 0x78,
+ 0x73, 0x6b, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x00, 0x2e, 0x6c, 0x6c, 0x76,
+ 0x6d, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x73, 0x69, 0x67, 0x00, 0x5f, 0x6c,
+ 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x00, 0x78, 0x64, 0x70, 0x5f, 0x72,
+ 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x64, 0x6e, 0x73, 0x5f,
+ 0x66, 0x75, 0x6e, 0x63, 0x00, 0x2e, 0x73, 0x74, 0x72, 0x74, 0x61, 0x62,
+ 0x00, 0x2e, 0x73, 0x79, 0x6d, 0x74, 0x61, 0x62, 0x00, 0x2e, 0x72, 0x65,
+ 0x6c, 0x2e, 0x42, 0x54, 0x46, 0x00, 0x4c, 0x42, 0x42, 0x30, 0x5f, 0x39,
+ 0x00, 0x4c, 0x42, 0x42, 0x30, 0x5f, 0x35, 0x38, 0x00, 0x4c, 0x42, 0x42,
+ 0x30, 0x5f, 0x35, 0x37, 0x00, 0x4c, 0x42, 0x42, 0x30, 0x5f, 0x31, 0x37,
+ 0x00, 0x4c, 0x42, 0x42, 0x30, 0x5f, 0x35, 0x36, 0x00, 0x4c, 0x42, 0x42,
+ 0x30, 0x5f, 0x34, 0x36, 0x00, 0x4c, 0x42, 0x42, 0x30, 0x5f, 0x33, 0x36,
+ 0x00, 0x4c, 0x42, 0x42, 0x30, 0x5f, 0x31, 0x36, 0x00, 0x4c, 0x42, 0x42,
+ 0x30, 0x5f, 0x35, 0x35, 0x00, 0x4c, 0x42, 0x42, 0x30, 0x5f, 0x34, 0x35,
+ 0x00, 0x4c, 0x42, 0x42, 0x30, 0x5f, 0x34, 0x00, 0x4c, 0x42, 0x42, 0x30,
+ 0x5f, 0x35, 0x34, 0x00, 0x4c, 0x42, 0x42, 0x30, 0x5f, 0x32, 0x33, 0x00,
+ 0x4c, 0x42, 0x42, 0x30, 0x5f, 0x34, 0x30, 0x00, 0x4c, 0x42, 0x42, 0x30,
+ 0x5f, 0x33, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x61, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xc4, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x1a, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x40, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x09, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x75, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x84, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x0d, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x60, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe8, 0x16, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x60, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x90, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x07, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x03, 0x4c, 0xff, 0x6f,
+ 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xc0, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x1e, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+unsigned int bpf_kernel_o_len = 11256;
diff --git a/src/libknot/xdp/bpf-kernel-obj.h b/src/libknot/xdp/bpf-kernel-obj.h
new file mode 100644
index 0000000..7c3d759
--- /dev/null
+++ b/src/libknot/xdp/bpf-kernel-obj.h
@@ -0,0 +1,2 @@
+extern unsigned char bpf_kernel_o[];
+extern unsigned int bpf_kernel_o_len;
diff --git a/src/libknot/xdp/bpf-kernel.c b/src/libknot/xdp/bpf-kernel.c
new file mode 100644
index 0000000..8bffd7f
--- /dev/null
+++ b/src/libknot/xdp/bpf-kernel.c
@@ -0,0 +1,293 @@
+/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <linux/if_ether.h>
+#include <linux/in.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/tcp.h>
+#include <linux/udp.h>
+
+#include "bpf-consts.h"
+#include "../../contrib/libbpf/include/uapi/linux/bpf.h"
+#include "../../contrib/libbpf/bpf/bpf_endian.h"
+#include "../../contrib/libbpf/bpf/bpf_helpers.h"
+
+/* Don't fragment flag. */
+#define IP_DF 0x4000
+
+#define AF_INET 2
+#define AF_INET6 10
+
+/* Define maximum reasonable number of NIC queues supported. */
+#define QUEUE_MAX 256
+
+/* A map of configuration options. */
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, QUEUE_MAX);
+ __uint(key_size, sizeof(__u32)); /* Must be 4 bytes. */
+ __uint(value_size, sizeof(knot_xdp_opts_t));
+} opts_map SEC(".maps");
+
+/* A map of AF_XDP sockets. */
+struct {
+ __uint(type, BPF_MAP_TYPE_XSKMAP);
+ __uint(max_entries, QUEUE_MAX);
+ __uint(key_size, sizeof(__u32)); /* Must be 4 bytes. */
+ __uint(value_size, sizeof(int));
+} xsks_map SEC(".maps");
+
+struct ipv6_frag_hdr {
+ unsigned char nexthdr;
+ unsigned char whatever[7];
+} __attribute__((packed));
+
+SEC("xdp")
+int xdp_redirect_dns_func(struct xdp_md *ctx)
+{
+ /* Get the queue options. */
+ __u32 index = ctx->rx_queue_index;
+ struct knot_xdp_opts *opts_ptr = bpf_map_lookup_elem(&opts_map, &index);
+ if (!opts_ptr) {
+ return XDP_ABORTED;
+ }
+ knot_xdp_opts_t opts = *opts_ptr;
+
+ /* Check if the filter is disabled. */
+ if (!(opts.flags & KNOT_XDP_FILTER_ON)) {
+ return XDP_PASS;
+ }
+
+ /* Try to reserve space in front of the packet for additional (VLAN) data. */
+ (void)bpf_xdp_adjust_meta(ctx, - (int)sizeof(struct knot_xdp_info)
+ - KNOT_XDP_PKT_ALIGNMENT);
+
+ void *data = (void *)(long)ctx->data;
+ const void *data_end = (void *)(long)ctx->data_end;
+ struct knot_xdp_info *meta = (void *)(long)ctx->data_meta;
+
+ /* Check if the meta data pointer is usable (e.g. not `tap` interface). */
+ if ((void *)meta + sizeof(*meta) > data) {
+ meta = 0;
+ }
+
+ struct ethhdr *eth_hdr = data;
+ const void *ip_hdr;
+ const struct iphdr *ip4;
+ const struct ipv6hdr *ip6;
+ const void *l4_hdr;
+ __u8 ipv4;
+ __u8 ip_proto;
+ __u8 fragmented = 0;
+ __u16 eth_type; /* In big endian. */
+
+ /* Parse Ethernet header. */
+ if ((void *)eth_hdr + sizeof(*eth_hdr) > data_end) {
+ return XDP_DROP;
+ }
+ data += sizeof(*eth_hdr);
+
+ /* Parse possible VLAN (802.1Q) header. */
+ if (eth_hdr->h_proto == __constant_htons(ETH_P_8021Q)) {
+ if (data + sizeof(__u16) + sizeof(eth_type) > data_end) {
+ return XDP_DROP;
+ } else if (meta == 0) { /* VLAN not supported. */
+ return XDP_PASS;
+ }
+ __builtin_memcpy(&eth_type, data + sizeof(__u16), sizeof(eth_type));
+ data += sizeof(__u16) + sizeof(eth_type);
+ } else {
+ eth_type = eth_hdr->h_proto;
+ }
+
+ ip_hdr = data;
+
+ /* Parse IPv4 or IPv6 header. */
+ switch (eth_type) {
+ case __constant_htons(ETH_P_IP):
+ ip4 = ip_hdr;
+ if ((void *)ip4 + sizeof(*ip4) > data_end) {
+ return XDP_DROP;
+ }
+ if (ip4->version != 4) {
+ return XDP_DROP;
+ }
+
+ /* Check the IP length. Cannot use strict equality due to
+ * Ethernet padding applied to frames shorter than 64 octects. */
+ if (data_end - data < __bpf_ntohs(ip4->tot_len)) {
+ return XDP_DROP;
+ }
+
+ if (ip4->frag_off != 0 &&
+ ip4->frag_off != __constant_htons(IP_DF)) {
+ fragmented = 1;
+ }
+ ip_proto = ip4->protocol;
+ l4_hdr = data + ip4->ihl * 4;
+ ipv4 = 1;
+ break;
+ case __constant_htons(ETH_P_IPV6):
+ ip6 = ip_hdr;
+ if ((void *)ip6 + sizeof(*ip6) > data_end) {
+ return XDP_DROP;
+ }
+ if (ip6->version != 6) {
+ return XDP_DROP;
+ }
+
+ /* Check the IP length. Cannot use strict equality due to
+ * Ethernet padding applied to frames shorter than 64 octects. */
+ if (data_end - data < __bpf_ntohs(ip6->payload_len) + sizeof(*ip6)) {
+ return XDP_DROP;
+ }
+
+ ip_proto = ip6->nexthdr;
+ data += sizeof(*ip6);
+ if (ip_proto == IPPROTO_FRAGMENT) {
+ fragmented = 1;
+ const struct ipv6_frag_hdr *frag = data;
+ if ((void *)frag + sizeof(*frag) > data_end) {
+ return XDP_DROP;
+ }
+ ip_proto = frag->nexthdr;
+ data += sizeof(*frag);
+ }
+ l4_hdr = data;
+ ipv4 = 0;
+ break;
+ default:
+ /* Pass packets of possible other protocols. */
+ return XDP_PASS;
+ }
+
+ const struct tcphdr *tcp;
+ const struct udphdr *udp;
+ __u16 port_dest;
+ __u8 match = 0;
+
+ /* Check the transport protocol. */
+ switch (ip_proto) {
+ case IPPROTO_TCP:
+ /* Parse TCP header. */
+ tcp = l4_hdr;
+ if (l4_hdr + sizeof(*tcp) > data_end) {
+ return XDP_DROP;
+ }
+
+ port_dest = __bpf_ntohs(tcp->dest);
+
+ if ((opts.flags & KNOT_XDP_FILTER_TCP) &&
+ (port_dest == opts.udp_port ||
+ ((opts.flags & (KNOT_XDP_FILTER_PASS | KNOT_XDP_FILTER_DROP)) &&
+ port_dest >= opts.udp_port))) {
+ match = 1;
+ }
+ break;
+ case IPPROTO_UDP:
+ /* Parse UDP header. */
+ udp = l4_hdr;
+ if (l4_hdr + sizeof(*udp) > data_end) {
+ return XDP_DROP;
+ }
+
+ /* Check the UDP length. */
+ if (data_end - (void *)udp < __bpf_ntohs(udp->len)) {
+ return XDP_DROP;
+ }
+
+ port_dest = __bpf_ntohs(udp->dest);
+
+ if ((opts.flags & KNOT_XDP_FILTER_UDP) &&
+ (port_dest == opts.udp_port ||
+ ((opts.flags & (KNOT_XDP_FILTER_PASS | KNOT_XDP_FILTER_DROP)) &&
+ port_dest >= opts.udp_port))) {
+ match = 1;
+ } else if ((opts.flags & KNOT_XDP_FILTER_QUIC) &&
+ (port_dest == opts.quic_port ||
+ ((opts.flags & (KNOT_XDP_FILTER_PASS | KNOT_XDP_FILTER_DROP)) &&
+ port_dest >= opts.quic_port))) {
+ match = 1;
+ }
+ break;
+ default:
+ /* Pass packets of possible other protocols. */
+ return XDP_PASS;
+ }
+
+ if (!match) {
+ /* Pass non-matching packet. */
+ return XDP_PASS;
+ } else if (opts.flags & KNOT_XDP_FILTER_DROP) {
+ /* Drop matching packet if requested. */
+ return XDP_DROP;
+ } else if (fragmented) {
+ /* Drop fragmented packet. */
+ return XDP_DROP;
+ }
+
+ /* Take into account routing information. */
+ if (opts.flags & KNOT_XDP_FILTER_ROUTE) {
+ struct bpf_fib_lookup fib = {
+ .ifindex = 1 /* Loopback. */
+ };
+ if (ipv4) {
+ fib.family = AF_INET;
+ fib.ipv4_src = ip4->daddr;
+ fib.ipv4_dst = ip4->saddr;
+ } else {
+ struct in6_addr *ipv6_src = (struct in6_addr *)fib.ipv6_src;
+ struct in6_addr *ipv6_dst = (struct in6_addr *)fib.ipv6_dst;
+ fib.family = AF_INET6;
+ *ipv6_src = ip6->daddr;
+ *ipv6_dst = ip6->saddr;
+ }
+
+ const __u16 *mac_in = (const __u16 *)eth_hdr->h_dest;
+ const __u16 *mac_out = (const __u16 *)fib.smac;
+ int ret = bpf_fib_lookup(ctx, &fib, sizeof(fib), BPF_FIB_LOOKUP_DIRECT);
+ switch (ret) {
+ case BPF_FIB_LKUP_RET_SUCCESS:
+ /* Cross-interface answers are handled through normal stack. */
+ if (mac_in[0] != mac_out[0] ||
+ mac_in[1] != mac_out[1] ||
+ mac_in[2] != mac_out[2]) {
+ return XDP_PASS;
+ }
+
+ /* Store output interface index for later use with VLAN in user space. */
+ if (meta != 0) {
+ meta->out_if_index = fib.ifindex;
+ }
+
+ /* Update destination MAC for responding. */
+ __builtin_memcpy(eth_hdr->h_source, fib.dmac, ETH_ALEN);
+ break;
+ case BPF_FIB_LKUP_RET_FWD_DISABLED: /* Disabled forwarding on loopback. */
+ return XDP_ABORTED;
+ case BPF_FIB_LKUP_RET_NO_NEIGH: /* Use normal stack to obtain MAC. */
+ return XDP_PASS;
+ default:
+ return XDP_DROP;
+ }
+ }
+
+ /* Forward the packet to user space. */
+ return bpf_redirect_map(&xsks_map, ctx->rx_queue_index, 0);
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/src/libknot/xdp/bpf-user.c b/src/libknot/xdp/bpf-user.c
new file mode 100644
index 0000000..a0c8bae
--- /dev/null
+++ b/src/libknot/xdp/bpf-user.c
@@ -0,0 +1,313 @@
+/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <bpf/bpf.h>
+#include <linux/if_link.h>
+#include <net/if.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "libknot/endian.h"
+#include "libknot/error.h"
+#include "libknot/xdp/bpf-kernel-obj.h"
+#include "libknot/xdp/bpf-user.h"
+#include "libknot/xdp/eth.h"
+#include "contrib/openbsd/strlcpy.h"
+
+#define NO_BPF_MAPS 2
+
+static inline bool IS_ERR_OR_NULL(const void *ptr)
+{
+ return (ptr == NULL) || (unsigned long)ptr >= (unsigned long)-4095;
+}
+
+static int prog_load(struct bpf_object **pobj, int *prog_fd)
+{
+ struct bpf_program *prog, *first_prog = NULL;
+ struct bpf_object *obj;
+
+ obj = bpf_object__open_mem(bpf_kernel_o, bpf_kernel_o_len, NULL);
+ if (IS_ERR_OR_NULL(obj)) {
+ return KNOT_ENOENT;
+ }
+
+ bpf_object__for_each_program(prog, obj) {
+ bpf_program__set_type(prog, BPF_PROG_TYPE_XDP);
+ if (first_prog == NULL) {
+ first_prog = prog;
+ }
+ }
+
+ if (first_prog == NULL) {
+ bpf_object__close(obj);
+ return KNOT_ENOENT;
+ }
+
+ int ret = bpf_object__load(obj);
+ if (ret != 0) {
+ bpf_object__close(obj);
+ return KNOT_EINVAL;
+ }
+
+ *pobj = obj;
+ *prog_fd = bpf_program__fd(first_prog);
+
+ return KNOT_EOK;
+}
+
+static int ensure_prog(struct kxsk_iface *iface, bool overwrite)
+{
+ if (bpf_kernel_o_len < 2) {
+ return KNOT_ENOTSUP;
+ }
+
+ /* Use libbpf for extracting BPF byte-code from BPF-ELF object, and
+ * loading this into the kernel via bpf-syscall. */
+ int prog_fd;
+ int ret = prog_load(&iface->prog_obj, &prog_fd);
+ if (ret != KNOT_EOK) {
+ return KNOT_EPROGRAM;
+ }
+
+#if USE_LIBXDP
+ ret = bpf_xdp_attach(iface->if_index, prog_fd,
+ overwrite ? 0 : XDP_FLAGS_UPDATE_IF_NOEXIST, NULL);
+#else
+ ret = bpf_set_link_xdp_fd(iface->if_index, prog_fd,
+ overwrite ? 0 : XDP_FLAGS_UPDATE_IF_NOEXIST);
+#endif
+ if (ret != 0) {
+ close(prog_fd);
+ }
+ if (ret == -EBUSY && !overwrite) { // We try accepting the present program.
+ uint32_t prog_id = 0;
+#if USE_LIBXDP
+ ret = bpf_xdp_query_id(iface->if_index, 0, &prog_id);
+#else
+ ret = bpf_get_link_xdp_id(iface->if_index, &prog_id, 0);
+#endif
+ if (ret == 0 && prog_id != 0) {
+ ret = prog_fd = bpf_prog_get_fd_by_id(prog_id);
+ }
+ }
+ if (ret < 0) {
+ return KNOT_EFD;
+ } else {
+ return prog_fd;
+ }
+}
+
+static void unget_bpf_maps(struct kxsk_iface *iface)
+{
+ if (iface->opts_map_fd >= 0) {
+ close(iface->opts_map_fd);
+ }
+ if (iface->xsks_map_fd >= 0) {
+ close(iface->xsks_map_fd);
+ }
+ iface->opts_map_fd = iface->xsks_map_fd = -1;
+}
+
+/*!
+ * /brief Get FDs for the two maps and assign them into xsk_info-> fields.
+ *
+ * Inspired by xsk_lookup_bpf_maps() from libbpf before qidconf_map elimination.
+ */
+static int get_bpf_maps(int prog_fd, struct kxsk_iface *iface)
+{
+ uint32_t *map_ids = calloc(NO_BPF_MAPS, sizeof(*map_ids));
+ if (map_ids == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ struct bpf_prog_info prog_info = {
+ .nr_map_ids = NO_BPF_MAPS,
+ .map_ids = (__u64)(unsigned long)map_ids,
+ };
+
+ uint32_t prog_len = sizeof(struct bpf_prog_info);
+ int ret = bpf_obj_get_info_by_fd(prog_fd, &prog_info, &prog_len);
+ if (ret != 0) {
+ free(map_ids);
+ return ret;
+ }
+
+ for (int i = 0; i < NO_BPF_MAPS; ++i) {
+ int fd = bpf_map_get_fd_by_id(map_ids[i]);
+ if (fd < 0) {
+ continue;
+ }
+
+ struct bpf_map_info map_info = { 0 };
+ uint32_t map_len = sizeof(struct bpf_map_info);
+ ret = bpf_obj_get_info_by_fd(fd, &map_info, &map_len);
+ if (ret != 0) {
+ close(fd);
+ continue;
+ }
+
+ if (strcmp(map_info.name, "opts_map") == 0) {
+ iface->opts_map_fd = fd;
+ continue;
+ }
+
+ if (strcmp(map_info.name, "xsks_map") == 0) {
+ iface->xsks_map_fd = fd;
+ continue;
+ }
+
+ close(fd);
+ }
+
+ if (iface->opts_map_fd < 0 || iface->xsks_map_fd < 0) {
+ unget_bpf_maps(iface);
+ free(map_ids);
+ return KNOT_ENOENT;
+ }
+
+ free(map_ids);
+ return KNOT_EOK;
+}
+
+int kxsk_socket_start(const struct kxsk_iface *iface, knot_xdp_filter_flag_t flags,
+ uint16_t udp_port, uint16_t quic_port, struct xsk_socket *xsk)
+{
+ if (iface == NULL || xsk == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ int fd = xsk_socket__fd(xsk);
+ int ret = bpf_map_update_elem(iface->xsks_map_fd, &iface->if_queue, &fd, 0);
+ if (ret != 0) {
+ return ret;
+ }
+
+ knot_xdp_opts_t opts = {
+ .flags = flags | KNOT_XDP_FILTER_ON,
+ .udp_port = udp_port,
+ .quic_port = quic_port,
+ };
+
+ ret = bpf_map_update_elem(iface->opts_map_fd, &iface->if_queue, &opts, 0);
+ if (ret != 0) {
+ (void)bpf_map_delete_elem(iface->xsks_map_fd, &iface->if_queue);
+ }
+
+ return ret;
+}
+
+void kxsk_socket_stop(const struct kxsk_iface *iface)
+{
+ if (iface == NULL) {
+ return;
+ }
+
+ knot_xdp_opts_t opts = { 0 };
+
+ (void)bpf_map_update_elem(iface->opts_map_fd, &iface->if_queue, &opts, 0);
+ (void)bpf_map_delete_elem(iface->xsks_map_fd, &iface->if_queue);
+}
+
+int kxsk_iface_new(const char *if_name, unsigned if_queue, knot_xdp_load_bpf_t load_bpf,
+ struct kxsk_iface **out_iface)
+{
+ if (if_name == NULL || out_iface == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ struct kxsk_iface *iface = calloc(1, sizeof(*iface) + IFNAMSIZ);
+ if (iface == NULL) {
+ return KNOT_ENOMEM;
+ }
+ iface->if_name = (char *)(iface + 1);
+ strlcpy((char *)iface->if_name, if_name, IFNAMSIZ);
+ iface->if_index = if_nametoindex(if_name);
+ if (iface->if_index == 0) {
+ free(iface);
+ return KNOT_EINVAL;
+ }
+ iface->if_queue = if_queue;
+ iface->opts_map_fd = iface->xsks_map_fd = -1;
+
+ int ret;
+ switch (load_bpf) {
+ case KNOT_XDP_LOAD_BPF_NEVER:
+ (void)0;
+ uint32_t prog_id = 0;
+#if USE_LIBXDP
+ ret = bpf_xdp_query_id(iface->if_index, 0, &prog_id);
+#else
+ ret = bpf_get_link_xdp_id(iface->if_index, &prog_id, 0);
+#endif
+ if (ret == 0) {
+ if (prog_id == 0) {
+ ret = KNOT_EPROGRAM;
+ } else {
+ ret = bpf_prog_get_fd_by_id(prog_id);
+ }
+ }
+ break;
+ case KNOT_XDP_LOAD_BPF_ALWAYS_UNLOAD:
+#if USE_LIBXDP
+ (void)bpf_xdp_detach(iface->if_index, 0, NULL);
+#else
+ (void)bpf_set_link_xdp_fd(iface->if_index, -1, 0);
+#endif
+ sleep(1);
+ // FALLTHROUGH
+ case KNOT_XDP_LOAD_BPF_ALWAYS:
+ ret = ensure_prog(iface, true);
+ break;
+ case KNOT_XDP_LOAD_BPF_MAYBE:
+ ret = ensure_prog(iface, false);
+ break;
+ default:
+ return KNOT_EINVAL;
+ }
+
+ if (ret >= 0) {
+ ret = get_bpf_maps(ret, iface);
+ }
+ if (ret < 0) {
+ free(iface);
+ return ret;
+ }
+
+ knot_xdp_mode_t mode = knot_eth_xdp_mode(iface->if_index);
+ if (mode == KNOT_XDP_MODE_NONE) {
+ free(iface);
+ return KNOT_ENOTSUP;
+ }
+
+ *out_iface = iface;
+ return KNOT_EOK;
+}
+
+void kxsk_iface_free(struct kxsk_iface *iface)
+{
+ if (iface == NULL) {
+ return;
+ }
+
+ unget_bpf_maps(iface);
+
+ if (iface->prog_obj != NULL) {
+ bpf_object__close(iface->prog_obj);
+ }
+
+ free(iface);
+}
diff --git a/src/libknot/xdp/bpf-user.h b/src/libknot/xdp/bpf-user.h
new file mode 100644
index 0000000..14c54c5
--- /dev/null
+++ b/src/libknot/xdp/bpf-user.h
@@ -0,0 +1,139 @@
+/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*!
+ * \file
+ *
+ * \brief XDP socket interface.
+ *
+ * \addtogroup xdp
+ * @{
+ */
+
+#pragma once
+
+#if USE_LIBXDP
+ #include <xdp/xsk.h>
+#else
+ #include <bpf/xsk.h>
+#endif
+
+#include "libknot/xdp/xdp.h"
+
+struct kxsk_iface {
+ /*! Interface name. */
+ const char *if_name;
+ /*! Interface name index (derived from ifname). */
+ int if_index;
+ /*! Network card queue id. */
+ unsigned if_queue;
+
+ /*! Configuration BPF map file descriptor. */
+ int opts_map_fd;
+ /*! XSK BPF map file descriptor. */
+ int xsks_map_fd;
+
+ /*! BPF program object. */
+ struct bpf_object *prog_obj;
+};
+
+struct kxsk_umem {
+ /*! Fill queue: passing memory frames to kernel - ready to receive. */
+ struct xsk_ring_prod fq;
+ /*! Completion queue: passing memory frames from kernel - after send finishes. */
+ struct xsk_ring_cons cq;
+ /*! Handle internal to libbpf. */
+ struct xsk_umem *umem;
+
+ /*! The memory frames. */
+ struct umem_frame *frames;
+ /*! The number of free frames (for TX). */
+ uint32_t tx_free_count;
+ /*! Stack of indices of the free frames (for TX). */
+ uint16_t tx_free_indices[];
+};
+
+struct knot_xdp_socket {
+ /*! Receive queue: passing arrived packets from kernel. */
+ struct xsk_ring_cons rx;
+ /*! Transmit queue: passing packets to kernel for sending. */
+ struct xsk_ring_prod tx;
+ /*! Information about memory frames for all the passed packets. */
+ struct kxsk_umem *umem;
+ /*! Handle internal to libbpf. */
+ struct xsk_socket *xsk;
+
+ /*! Interface context. */
+ const struct kxsk_iface *iface;
+
+ /*! If non-NULL, it's a mocked socket with this send function. */
+ int (*send_mock)(struct knot_xdp_socket *, const knot_xdp_msg_t[], uint32_t, uint32_t *);
+
+ /*! The kernel has to be woken up by a syscall indication. */
+ bool kernel_needs_wakeup;
+
+ /*! The limit of frame size. */
+ unsigned frame_limit;
+
+ /*! Mapping of interface indices to VLAN tags. */
+ uint16_t *vlan_map;
+ uint16_t vlan_map_max;
+};
+
+/*!
+ * \brief Set up BPF program and map for one XDP socket.
+ *
+ * \param if_name Name of the net iface (e.g. eth0).
+ * \param if_queue Network card queue id.
+ * \param load_bpf Insert BPF program into packet processing.
+ * \param out_iface Output: created interface context.
+ *
+ * \return KNOT_E* or -errno
+ */
+int kxsk_iface_new(const char *if_name, unsigned if_queue, knot_xdp_load_bpf_t load_bpf,
+ struct kxsk_iface **out_iface);
+
+/*!
+ * \brief Unload BPF maps for a socket.
+ *
+ * \note This keeps the loaded BPF program. We don't care.
+ *
+ * \param iface Interface context to be freed.
+ */
+void kxsk_iface_free(struct kxsk_iface *iface);
+
+/*!
+ * \brief Activate this AF_XDP socket through the BPF maps.
+ *
+ * \param iface Interface context.
+ * \param flags XDP filter configuration flags.
+ * \param udp_port UDP and/or TCP port to listen on if enabled via \a opts.
+ * \param quic_port QUIC/UDP port to listen on if enabled via \a opts.
+ * \param xsk Socket ctx.
+ *
+ * \return KNOT_E* or -errno
+ */
+int kxsk_socket_start(const struct kxsk_iface *iface, knot_xdp_filter_flag_t flags,
+ uint16_t udp_port, uint16_t quic_port, struct xsk_socket *xsk);
+
+/*!
+ * \brief Deactivate this AF_XDP socket through the BPF maps.
+ *
+ * \param iface Interface context.
+ */
+void kxsk_socket_stop(const struct kxsk_iface *iface);
+
+/*! @} */
diff --git a/src/libknot/xdp/eth.c b/src/libknot/xdp/eth.c
new file mode 100644
index 0000000..608eb20
--- /dev/null
+++ b/src/libknot/xdp/eth.c
@@ -0,0 +1,312 @@
+/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <bpf/libbpf.h>
+#include <errno.h>
+#include <ifaddrs.h>
+#include <linux/ethtool.h>
+#include <linux/if_link.h>
+#include <linux/if_vlan.h>
+#include <linux/sockios.h>
+#include <net/if.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include "contrib/openbsd/strlcpy.h"
+#include "contrib/sockaddr.h"
+#include "libknot/attribute.h"
+#include "libknot/endian.h"
+#include "libknot/errcode.h"
+#include "libknot/xdp/eth.h"
+
+_public_
+int knot_eth_queues(const char *devname)
+{
+ if (devname == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ int fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (fd < 0) {
+ return knot_map_errno();
+ }
+
+ struct ethtool_channels ch = {
+ .cmd = ETHTOOL_GCHANNELS
+ };
+ struct ifreq ifr = {
+ .ifr_data = (char *)&ch
+ };
+ strlcpy(ifr.ifr_name, devname, IFNAMSIZ);
+
+ int ret = ioctl(fd, SIOCETHTOOL, &ifr);
+ if (ret != 0) {
+ if (errno == EOPNOTSUPP) {
+ ret = 1;
+ } else {
+ ret = knot_map_errno();
+ }
+ } else {
+ if (ch.combined_count == 0) {
+ ret = 1;
+ } else {
+ ret = ch.combined_count;
+ }
+ }
+
+ close(fd);
+ return ret;
+}
+
+_public_
+int knot_eth_rss(const char *devname, knot_eth_rss_conf_t **rss_conf)
+{
+ if (devname == NULL || rss_conf == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ struct ethtool_rxfh *ctx = NULL;
+ knot_eth_rss_conf_t *out = NULL;
+ int ret = KNOT_ERROR;
+
+ int fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (fd < 0) {
+ return knot_map_errno();
+ }
+
+ struct ethtool_rxfh sizes = {
+ .cmd = ETHTOOL_GRSSH
+ };
+ struct ifreq ifr = {
+ .ifr_data = (char *)&sizes
+ };
+ strlcpy(ifr.ifr_name, devname, IFNAMSIZ);
+
+ ret = ioctl(fd, SIOCETHTOOL, &ifr);
+ if (ret != 0) {
+ ret = knot_map_errno();
+ goto finish;
+ }
+
+ const unsigned data_size = sizes.indir_size * sizeof(sizes.rss_config[0]) +
+ sizes.key_size;
+
+ ctx = calloc(1, sizeof(*ctx) + data_size);
+ if (ctx == NULL) {
+ ret = KNOT_ENOMEM;
+ goto finish;
+ }
+ ctx->cmd = ETHTOOL_GRSSH;
+ ctx->indir_size = sizes.indir_size;
+ ctx->key_size = sizes.key_size;
+ ifr.ifr_data = (char *)ctx;
+
+ ret = ioctl(fd, SIOCETHTOOL, &ifr);
+ if (ret != 0) {
+ ret = knot_map_errno();
+ goto finish;
+ }
+
+ out = calloc(1, sizeof(*out) + data_size);
+ if (out == NULL) {
+ ret = KNOT_ENOMEM;
+ goto finish;
+ }
+
+ out->table_size = sizes.indir_size;
+ out->key_size = sizes.key_size;
+ memcpy(out->data, ctx->rss_config, data_size);
+ out->mask = out->table_size - 1;
+finish:
+ *rss_conf = out;
+
+ free(ctx);
+ close(fd);
+ return ret;
+}
+
+_public_
+int knot_eth_mtu(const char *devname)
+{
+ if (devname == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ int fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (fd < 0) {
+ return knot_map_errno();
+ }
+
+ struct ifreq ifr = { 0 };
+ strlcpy(ifr.ifr_name, devname, IFNAMSIZ);
+
+ int ret = ioctl(fd, SIOCGIFMTU, &ifr);
+ if (ret != 0) {
+ if (errno == EOPNOTSUPP) {
+ ret = KNOT_ENOTSUP;
+ } else {
+ ret = knot_map_errno();
+ }
+ } else {
+ ret = ifr.ifr_mtu;
+ }
+
+ close(fd);
+ return ret;
+}
+
+_public_
+int knot_eth_name_from_addr(const struct sockaddr_storage *addr, char *out,
+ size_t out_len)
+{
+ if (addr == NULL || out == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ struct ifaddrs *ifaces = NULL;
+ if (getifaddrs(&ifaces) != 0) {
+ return -errno;
+ }
+
+ size_t matches = 0;
+ char *match_name = NULL;
+
+ for (struct ifaddrs *ifa = ifaces; ifa != NULL; ifa = ifa->ifa_next) {
+ const struct sockaddr_storage *ifss = (struct sockaddr_storage *)ifa->ifa_addr;
+ if (ifss == NULL) { // Observed on interfaces without any address.
+ continue;
+ }
+
+ if ((ifss->ss_family == addr->ss_family && sockaddr_is_any(addr)) ||
+ sockaddr_cmp(ifss, addr, true) == 0) {
+ matches++;
+ match_name = ifa->ifa_name;
+ }
+ }
+
+ if (matches == 1) {
+ size_t len = strlcpy(out, match_name, out_len);
+ freeifaddrs(ifaces);
+ return (len >= out_len) ? KNOT_ESPACE : KNOT_EOK;
+ }
+
+ freeifaddrs(ifaces);
+ return matches == 0 ? KNOT_EADDRNOTAVAIL : KNOT_ELIMIT;
+}
+
+_public_
+int knot_eth_vlans(uint16_t *vlan_map[], uint16_t *vlan_map_max)
+{
+ if (vlan_map == NULL || vlan_map_max == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ struct ifaddrs *ifaces = NULL;
+ if (getifaddrs(&ifaces) != 0) {
+ return knot_map_errno();
+ }
+
+ unsigned map_size = 0;
+ for (struct ifaddrs *ifa = ifaces; ifa != NULL; ifa = ifa->ifa_next) {
+ if (ifa->ifa_addr->sa_family != AF_PACKET) {
+ continue;
+ }
+ map_size++;
+ }
+
+ uint16_t *map = calloc(sizeof(uint16_t), 1 + map_size); // Indexed from 1.
+ if (map == NULL) {
+ freeifaddrs(ifaces);
+ return KNOT_ENOMEM;
+ }
+
+ int fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (fd < 0) {
+ free(map);
+ freeifaddrs(ifaces);
+ return knot_map_errno();
+ }
+
+ for (struct ifaddrs *ifa = ifaces; ifa != NULL; ifa = ifa->ifa_next) {
+ if (ifa->ifa_addr == NULL || ifa->ifa_addr->sa_family != AF_PACKET) {
+ continue;
+ }
+
+ unsigned if_index = if_nametoindex(ifa->ifa_name);
+ if (if_index == 0) {
+ close(fd);
+ free(map);
+ freeifaddrs(ifaces);
+ return knot_map_errno();
+ }
+
+ struct vlan_ioctl_args ifv = {
+ .cmd = GET_VLAN_REALDEV_NAME_CMD
+ };
+ strlcpy(ifv.device1, ifa->ifa_name, sizeof(ifv.device1));
+
+ if (ioctl(fd, SIOCGIFVLAN, &ifv) >= 0) {
+ memset(&ifv, 0, sizeof(ifv));
+ ifv.cmd = GET_VLAN_VID_CMD;
+ strlcpy(ifv.device1, ifa->ifa_name, sizeof(ifv.device1));
+
+ if (ioctl(fd, SIOCGIFVLAN, &ifv) < 0) {
+ close(fd);
+ free(map);
+ freeifaddrs(ifaces);
+ return knot_map_errno();
+ }
+
+ map[if_index] = htobe16(ifv.u.VID);
+ }
+ }
+
+ close(fd);
+ freeifaddrs(ifaces);
+
+ *vlan_map = map;
+ *vlan_map_max = map_size;
+
+ return KNOT_EOK;
+}
+
+_public_
+knot_xdp_mode_t knot_eth_xdp_mode(int if_index)
+{
+#if USE_LIBXDP
+ struct bpf_xdp_query_opts info = { .sz = sizeof(info) };
+ int ret = bpf_xdp_query(if_index, 0, &info);
+#else
+ struct xdp_link_info info;
+ int ret = bpf_get_link_xdp_info(if_index, &info, sizeof(info), 0);
+#endif
+ if (ret != 0) {
+ return KNOT_XDP_MODE_NONE;
+ }
+
+ switch (info.attach_mode) {
+ case XDP_ATTACHED_DRV:
+ case XDP_ATTACHED_HW:
+ return KNOT_XDP_MODE_FULL;
+ case XDP_ATTACHED_SKB:
+ return KNOT_XDP_MODE_EMUL;
+ default:
+ return KNOT_XDP_MODE_NONE;
+ }
+}
diff --git a/src/libknot/xdp/eth.h b/src/libknot/xdp/eth.h
new file mode 100644
index 0000000..8c5b901
--- /dev/null
+++ b/src/libknot/xdp/eth.h
@@ -0,0 +1,111 @@
+/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*!
+ * \file
+ *
+ * \brief Ethernet device info interface.
+ *
+ * \addtogroup xdp
+ * @{
+ */
+
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+
+#define KNOT_XDP_MAX_MTU 1790
+
+/*!
+ * \brief Get number of combined queues of a network interface.
+ *
+ * \param devname Name of the ethdev (e.g. eth1).
+ *
+ * \retval < 0 KNOT_E* if error.
+ * \retval 1 Default no of queues if the dev does not support.
+ * \return > 0 Number of queues.
+ */
+int knot_eth_queues(const char *devname);
+
+/*!
+ * \brief Network card RSS configuration.
+ */
+typedef struct {
+ size_t table_size; /*!< Size of indirection table in four-bytes. */
+ size_t key_size; /*!< Size of the RSS key in bytes. */
+ uint32_t mask; /*!< Input mask for accessing the table. */
+ uint32_t data[]; /*!< Serialized key and table. */
+} knot_eth_rss_conf_t;
+
+/*!
+ * \brief Get RSS configuration of a network interface.
+ *
+ * \param devname Name of the ethdev (e.g. eth1).
+ * \param rss_conf Output RSS configuration. Must be freed explicitly.
+ *
+ * \return KNOT_E*
+ */
+int knot_eth_rss(const char *devname, knot_eth_rss_conf_t **rss_conf);
+
+/*!
+ * \brief Get value of MTU setup on a network interface.
+ *
+ * \param devname Name of the ethdev (e.g. eth1).
+ *
+ * \retval < 0 KNOT_E* if error.
+ * \return >= 0 Interface MTU.
+ */
+int knot_eth_mtu(const char *devname);
+
+/*!
+ * \brief Get the corresponding network interface name for the address.
+ *
+ * \param addr Address of the interface.
+ * \param out Output buffer for the interface name.
+ * \param out_len Size of the output buffer.
+ *
+ * \return KNOT_E*
+ */
+int knot_eth_name_from_addr(const struct sockaddr_storage *addr, char *out,
+ size_t out_len);
+
+/*!
+ * \brief Get the mapping of interface index to VLAN tags.
+ *
+ * \param vlan_map Output array of the mappings.
+ * \param vlan_map_max Maximum interface index allowed.
+ *
+ * \return KNOT_E*
+ */
+int knot_eth_vlans(uint16_t *vlan_map[], uint16_t *vlan_map_max);
+
+typedef enum {
+ KNOT_XDP_MODE_NONE, /*!< XDP not available, BPF not loaded, or error. */
+ KNOT_XDP_MODE_FULL, /*!< Full XDP support in driver or HW. */
+ KNOT_XDP_MODE_EMUL, /*!< Emulated XDP support. */
+} knot_xdp_mode_t;
+
+/*!
+ * \brief Return the current XDP mode of a network interface.
+ *
+ * \param if_index Index of the interface, output from if_nametoindex().
+ *
+ * \return Current XDP mode.
+ */
+knot_xdp_mode_t knot_eth_xdp_mode(int if_index);
+
+/*! @} */
diff --git a/src/libknot/xdp/msg.h b/src/libknot/xdp/msg.h
new file mode 100644
index 0000000..f567921
--- /dev/null
+++ b/src/libknot/xdp/msg.h
@@ -0,0 +1,62 @@
+/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*!
+ * \file
+ *
+ * \brief XDP message description.
+ *
+ * \addtogroup xdp
+ * @{
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <linux/if_ether.h>
+#include <netinet/in.h>
+#include <sys/uio.h>
+
+/*! \brief Message flags. */
+typedef enum {
+ KNOT_XDP_MSG_IPV6 = (1 << 0), /*!< This packet is a IPv6 (IPv4 otherwise). */
+ KNOT_XDP_MSG_TCP = (1 << 1), /*!< This packet is a TCP (UDP otherwise). */
+ KNOT_XDP_MSG_SYN = (1 << 2), /*!< SYN flag set (TCP only). */
+ KNOT_XDP_MSG_ACK = (1 << 3), /*!< ACK flag set (TCP only). */
+ KNOT_XDP_MSG_FIN = (1 << 4), /*!< FIN flag set (TCP only). */
+ KNOT_XDP_MSG_RST = (1 << 5), /*!< RST flag set (TCP only). */
+ KNOT_XDP_MSG_MSS = (1 << 6), /*!< MSS option in TCP header (TCP only). */
+ KNOT_XDP_MSG_WSC = (1 << 7), /*!< Window Scale option in TCP header. */
+ KNOT_XDP_MSG_VLAN = (1 << 8), /*!< This packet will contain VLAN header. */
+} knot_xdp_msg_flag_t;
+
+/*! \brief Packet description with src & dst MAC & IP addrs + DNS payload. */
+typedef struct knot_xdp_msg {
+ struct sockaddr_in6 ip_from;
+ struct sockaddr_in6 ip_to;
+ uint8_t eth_from[ETH_ALEN];
+ uint8_t eth_to[ETH_ALEN];
+ knot_xdp_msg_flag_t flags;
+ struct iovec payload;
+ uint32_t seqno;
+ uint32_t ackno;
+ uint16_t mss;
+ uint16_t win;
+ uint8_t win_scale;
+ uint16_t vlan_tci;
+} knot_xdp_msg_t;
+
+/*! @} */
diff --git a/src/libknot/xdp/msg_init.h b/src/libknot/xdp/msg_init.h
new file mode 100644
index 0000000..8b96129
--- /dev/null
+++ b/src/libknot/xdp/msg_init.h
@@ -0,0 +1,74 @@
+/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <string.h>
+
+#include "libknot/xdp/msg.h"
+#include "libknot/xdp/tcp.h"
+#include "libdnssec/random.h"
+
+inline static bool empty_msg(const knot_xdp_msg_t *msg)
+{
+ const unsigned tcp_flags = KNOT_XDP_MSG_SYN | KNOT_XDP_MSG_ACK |
+ KNOT_XDP_MSG_FIN | KNOT_XDP_MSG_RST;
+
+ return (msg->payload.iov_len == 0 && !(msg->flags & tcp_flags));
+}
+
+inline static void msg_init_base(knot_xdp_msg_t *msg, knot_xdp_msg_flag_t flags)
+{
+ memset(msg, 0, sizeof(*msg));
+
+ msg->flags = flags;
+}
+
+inline static void msg_init(knot_xdp_msg_t *msg, knot_xdp_msg_flag_t flags)
+{
+ msg_init_base(msg, flags);
+
+ if (flags & KNOT_XDP_MSG_TCP) {
+ msg->ackno = 0;
+ msg->seqno = dnssec_random_uint32_t();
+ if (flags & KNOT_XDP_MSG_SYN) {
+ msg->flags |= KNOT_XDP_MSG_MSS | KNOT_XDP_MSG_WSC;
+ }
+ }
+}
+
+inline static void msg_init_reply(knot_xdp_msg_t *msg, const knot_xdp_msg_t *query)
+{
+ msg_init_base(msg, query->flags & (KNOT_XDP_MSG_IPV6 | KNOT_XDP_MSG_TCP |
+ KNOT_XDP_MSG_MSS | KNOT_XDP_MSG_WSC));
+
+ memcpy(msg->eth_from, query->eth_to, ETH_ALEN);
+ memcpy(msg->eth_to, query->eth_from, ETH_ALEN);
+
+ memcpy(&msg->ip_from, &query->ip_to, sizeof(msg->ip_from));
+ memcpy(&msg->ip_to, &query->ip_from, sizeof(msg->ip_to));
+
+ msg->vlan_tci = query->vlan_tci;
+
+ if (msg->flags & KNOT_XDP_MSG_TCP) {
+ msg->ackno = knot_tcp_next_seqno(query);
+ msg->seqno = query->ackno;
+ if (msg->seqno == 0) {
+ msg->seqno = dnssec_random_uint32_t();
+ }
+ }
+}
diff --git a/src/libknot/xdp/protocols.h b/src/libknot/xdp/protocols.h
new file mode 100644
index 0000000..ab1cff4
--- /dev/null
+++ b/src/libknot/xdp/protocols.h
@@ -0,0 +1,446 @@
+/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <assert.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/tcp.h>
+#include <linux/udp.h>
+#include <string.h>
+
+#include "libknot/endian.h"
+#include "libknot/xdp/bpf-consts.h"
+#include "libknot/xdp/msg.h"
+
+/* Don't fragment flag. */
+#define IP_DF 0x4000
+
+#define HDR_8021Q_LEN 4;
+
+/*
+ * Following prot_read_*() functions do not check sanity of parsed packet.
+ * Broken packets have to be dropped by BPF filter prior getting here.
+ */
+
+inline static void *prot_read_udp(void *data, uint16_t *src_port, uint16_t *dst_port)
+{
+ const struct udphdr *udp = data;
+
+ *src_port = udp->source;
+ *dst_port = udp->dest;
+
+ return data + sizeof(*udp);
+}
+
+enum {
+ PROT_TCP_OPT_ENDOP = 0,
+ PROT_TCP_OPT_NOOP = 1,
+ PROT_TCP_OPT_MSS = 2,
+ PROT_TCP_OPT_WSC = 3, // window scale
+
+ PROT_TCP_OPT_LEN_MSS = 4,
+ PROT_TCP_OPT_LEN_WSC = 3,
+};
+
+inline static void *prot_read_tcp(void *data, knot_xdp_msg_t *msg, uint16_t *src_port, uint16_t *dst_port)
+{
+ const struct tcphdr *tcp = data;
+
+ msg->flags |= KNOT_XDP_MSG_TCP;
+
+ if (tcp->syn) {
+ msg->flags |= KNOT_XDP_MSG_SYN;
+ }
+ if (tcp->ack) {
+ msg->flags |= KNOT_XDP_MSG_ACK;
+ }
+ if (tcp->fin) {
+ msg->flags |= KNOT_XDP_MSG_FIN;
+ }
+ if (tcp->rst) {
+ msg->flags |= KNOT_XDP_MSG_RST;
+ }
+
+ msg->seqno = be32toh(tcp->seq);
+ msg->ackno = be32toh(tcp->ack_seq);
+ msg->win = be16toh(tcp->window);
+
+ *src_port = tcp->source;
+ *dst_port = tcp->dest;
+
+ uint8_t *opts = data + sizeof(*tcp), *hdr_end = data + tcp->doff * 4;
+ while (opts < hdr_end) {
+ if (opts[0] == PROT_TCP_OPT_ENDOP || opts[0] == PROT_TCP_OPT_NOOP) {
+ opts++;
+ continue;
+ }
+
+ if (opts + 1 > hdr_end || opts + opts[1] > hdr_end) {
+ // Malformed option.
+ break;
+ }
+
+ if (opts[0] == PROT_TCP_OPT_MSS && opts[1] == PROT_TCP_OPT_LEN_MSS) {
+ msg->flags |= KNOT_XDP_MSG_MSS;
+ memcpy(&msg->mss, &opts[2], sizeof(msg->mss));
+ msg->mss = be16toh(msg->mss);
+ }
+
+ if (opts[0] == PROT_TCP_OPT_WSC && opts[1] == PROT_TCP_OPT_LEN_WSC) {
+ msg->flags |= KNOT_XDP_MSG_WSC;
+ msg->win_scale = opts[2];
+ }
+
+ opts += opts[1];
+ }
+
+ return hdr_end;
+}
+
+inline static void *prot_read_ipv4(void *data, knot_xdp_msg_t *msg, void **data_end)
+{
+ const struct iphdr *ip4 = data;
+
+ // Conditions ensured by the BPF filter.
+ assert(ip4->version == 4);
+ assert(ip4->frag_off == 0 || ip4->frag_off == __constant_htons(IP_DF));
+ // IPv4 header checksum is not verified!
+
+ struct sockaddr_in *src = (struct sockaddr_in *)&msg->ip_from;
+ struct sockaddr_in *dst = (struct sockaddr_in *)&msg->ip_to;
+ memcpy(&src->sin_addr, &ip4->saddr, sizeof(src->sin_addr));
+ memcpy(&dst->sin_addr, &ip4->daddr, sizeof(dst->sin_addr));
+ src->sin_family = AF_INET;
+ dst->sin_family = AF_INET;
+
+ *data_end = data + be16toh(ip4->tot_len);
+ data += ip4->ihl * 4;
+
+ if (ip4->protocol == IPPROTO_TCP) {
+ return prot_read_tcp(data, msg, &src->sin_port, &dst->sin_port);
+ } else {
+ assert(ip4->protocol == IPPROTO_UDP);
+ return prot_read_udp(data, &src->sin_port, &dst->sin_port);
+ }
+}
+
+inline static void *prot_read_ipv6(void *data, knot_xdp_msg_t *msg, void **data_end)
+{
+ const struct ipv6hdr *ip6 = data;
+
+ msg->flags |= KNOT_XDP_MSG_IPV6;
+
+ // Conditions ensured by the BPF filter.
+ assert(ip6->version == 6);
+
+ struct sockaddr_in6 *src = (struct sockaddr_in6 *)&msg->ip_from;
+ struct sockaddr_in6 *dst = (struct sockaddr_in6 *)&msg->ip_to;
+ memcpy(&src->sin6_addr, &ip6->saddr, sizeof(src->sin6_addr));
+ memcpy(&dst->sin6_addr, &ip6->daddr, sizeof(dst->sin6_addr));
+ src->sin6_family = AF_INET6;
+ dst->sin6_family = AF_INET6;
+ src->sin6_flowinfo = 0;
+ dst->sin6_flowinfo = 0;
+ // Scope ID is ignored.
+
+ data += sizeof(*ip6);
+ *data_end = data + be16toh(ip6->payload_len);
+
+ if (ip6->nexthdr == IPPROTO_TCP) {
+ return prot_read_tcp(data, msg, &src->sin6_port, &dst->sin6_port);
+ } else {
+ assert(ip6->nexthdr == IPPROTO_UDP);
+ return prot_read_udp(data, &src->sin6_port, &dst->sin6_port);
+ }
+}
+
+inline static void *prot_read_eth(void *data, knot_xdp_msg_t *msg, void **data_end,
+ const uint16_t *vlan_map, unsigned vlan_map_max)
+{
+ const struct ethhdr *eth = data;
+ knot_xdp_info_t *info = data - KNOT_XDP_PKT_ALIGNMENT - sizeof(*info);
+
+ memcpy(msg->eth_from, eth->h_source, ETH_ALEN);
+ memcpy(msg->eth_to, eth->h_dest, ETH_ALEN);
+ msg->flags = 0;
+
+ if (eth->h_proto == __constant_htons(ETH_P_8021Q)) {
+ if (info->out_if_index > 0 && info->out_if_index <= vlan_map_max) {
+ assert(vlan_map);
+ msg->vlan_tci = vlan_map[info->out_if_index];
+ } else {
+ memcpy(&msg->vlan_tci, data + sizeof(*eth), sizeof(msg->vlan_tci));
+ }
+ data += HDR_8021Q_LEN;
+ eth = data;
+ }
+
+ data += sizeof(*eth);
+
+ if (eth->h_proto == __constant_htons(ETH_P_IPV6)) {
+ return prot_read_ipv6(data, msg, data_end);
+ } else {
+ assert(eth->h_proto == __constant_htons(ETH_P_IP));
+ return prot_read_ipv4(data, msg, data_end);
+ }
+}
+
+inline static size_t prot_write_hdrs_len(const knot_xdp_msg_t *msg)
+{
+ size_t res = sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr);
+
+ if (msg->vlan_tci != 0 || msg->flags & KNOT_XDP_MSG_VLAN) {
+ res += HDR_8021Q_LEN;
+ }
+
+ if (msg->flags & KNOT_XDP_MSG_IPV6) {
+ res += sizeof(struct ipv6hdr) - sizeof(struct iphdr);
+ }
+
+ if (msg->flags & KNOT_XDP_MSG_TCP) {
+ res += sizeof(struct tcphdr) - sizeof(struct udphdr);
+
+ if (msg->flags & KNOT_XDP_MSG_MSS) {
+ res += PROT_TCP_OPT_LEN_MSS;
+ }
+ if (msg->flags & KNOT_XDP_MSG_WSC) {
+ res += PROT_TCP_OPT_LEN_WSC + 1; // 1 == align
+ }
+ }
+
+ return res;
+}
+
+/* Checksum endianness implementation notes for ipv4_checksum() and checksum().
+ *
+ * The basis for checksum is addition on big-endian 16-bit words, with bit 16 carrying
+ * over to bit 0. That can be viewed as first byte carrying to the second and the
+ * second one carrying back to the first one, i.e. a symmetrical situation.
+ * Therefore the result is the same even when arithmetics is done on little-endian (!)
+ */
+
+inline static void checksum(uint32_t *result, const void *_data, uint32_t _data_len)
+{
+ assert(!(_data_len & 1));
+ const uint16_t *data = _data;
+ uint32_t len = _data_len / 2;
+ while (len-- > 0) {
+ *result += *data++;
+ }
+}
+
+inline static void checksum_uint16(uint32_t *result, uint16_t x)
+{
+ checksum(result, &x, sizeof(x));
+}
+
+inline static void checksum_payload(uint32_t *result, void *payload, size_t pay_len)
+{
+ if (pay_len & 1) {
+ ((uint8_t *)payload)[pay_len++] = 0;
+ }
+ checksum(result, payload, pay_len);
+}
+
+inline static uint16_t checksum_finish(uint32_t result, bool nonzero)
+{
+ while (result > 0xffff) {
+ result = (result & 0xffff) + (result >> 16);
+ }
+ if (!nonzero || result != 0xffff) {
+ result = ~result;
+ }
+ return result;
+}
+
+inline static void prot_write_udp(void *data, const knot_xdp_msg_t *msg, void *data_end,
+ uint16_t src_port, uint16_t dst_port, uint32_t chksum)
+{
+ struct udphdr *udp = data;
+
+ udp->len = htobe16(data_end - data);
+ udp->source = src_port;
+ udp->dest = dst_port;
+
+ if (msg->flags & KNOT_XDP_MSG_IPV6) {
+ udp->check = 0;
+ checksum(&chksum, &udp->len, sizeof(udp->len));
+ checksum_uint16(&chksum, htobe16(IPPROTO_UDP));
+ checksum_payload(&chksum, data, data_end - data);
+ udp->check = checksum_finish(chksum, true);
+ } else {
+ udp->check = 0; // UDP over IPv4 doesn't require checksum.
+ }
+
+ assert(data + sizeof(*udp) == msg->payload.iov_base);
+}
+
+inline static void prot_write_tcp(void *data, const knot_xdp_msg_t *msg, void *data_end,
+ uint16_t src_port, uint16_t dst_port, uint32_t chksum,
+ uint16_t mss)
+{
+ struct tcphdr *tcp = data;
+
+ tcp->source = src_port;
+ tcp->dest = dst_port;
+ tcp->seq = htobe32(msg->seqno);
+ tcp->ack_seq = htobe32(msg->ackno);
+ tcp->window = htobe16(msg->win);
+ tcp->check = 0; // Temporarily initialize before checksum calculation.
+
+ tcp->syn = ((msg->flags & KNOT_XDP_MSG_SYN) ? 1 : 0);
+ tcp->ack = ((msg->flags & KNOT_XDP_MSG_ACK) ? 1 : 0);
+ tcp->fin = ((msg->flags & KNOT_XDP_MSG_FIN) ? 1 : 0);
+ tcp->rst = ((msg->flags & KNOT_XDP_MSG_RST) ? 1 : 0);
+
+ uint8_t *hdr_end = data + sizeof(*tcp);
+ if (msg->flags & KNOT_XDP_MSG_WSC) {
+ hdr_end[0] = PROT_TCP_OPT_WSC;
+ hdr_end[1] = PROT_TCP_OPT_LEN_WSC;
+ hdr_end[2] = msg->win_scale;
+ hdr_end += PROT_TCP_OPT_LEN_WSC;
+ *hdr_end++ = PROT_TCP_OPT_NOOP; // align
+ }
+ if (msg->flags & KNOT_XDP_MSG_MSS) {
+ mss = htobe16(mss);
+ hdr_end[0] = PROT_TCP_OPT_MSS;
+ hdr_end[1] = PROT_TCP_OPT_LEN_MSS;
+ memcpy(&hdr_end[2], &mss, sizeof(mss));
+ hdr_end += PROT_TCP_OPT_LEN_MSS;
+ }
+
+ tcp->psh = ((data_end - (void *)hdr_end > 0) ? 1 : 0);
+ tcp->doff = (hdr_end - (uint8_t *)tcp) / 4;
+ assert((hdr_end - (uint8_t *)tcp) % 4 == 0);
+
+ checksum_uint16(&chksum, htobe16(IPPROTO_TCP));
+ checksum_uint16(&chksum, htobe16(data_end - data));
+ checksum_payload(&chksum, data, data_end - data);
+ tcp->check = checksum_finish(chksum, false);
+
+ assert(hdr_end == msg->payload.iov_base);
+}
+
+inline static uint16_t from32to16(uint32_t sum)
+{
+ sum = (sum & 0xffff) + (sum >> 16);
+ sum = (sum & 0xffff) + (sum >> 16);
+ return sum;
+}
+
+inline static uint16_t ipv4_checksum(const uint16_t *ipv4_hdr)
+{
+ uint32_t sum32 = 0;
+ for (int i = 0; i < 10; ++i) {
+ if (i != 5) {
+ sum32 += ipv4_hdr[i];
+ }
+ }
+ return ~from32to16(sum32);
+}
+
+inline static void prot_write_ipv4(void *data, const knot_xdp_msg_t *msg,
+ void *data_end, uint16_t tcp_mss)
+{
+ struct iphdr *ip4 = data;
+
+ ip4->version = 4;
+ ip4->ihl = sizeof(*ip4) / 4;
+ ip4->tos = 0;
+ ip4->tot_len = htobe16(data_end - data);
+ ip4->id = 0;
+ ip4->frag_off = 0;
+ ip4->ttl = IPDEFTTL;
+ ip4->protocol = ((msg->flags & KNOT_XDP_MSG_TCP) ? IPPROTO_TCP : IPPROTO_UDP);
+
+ const struct sockaddr_in *src = (const struct sockaddr_in *)&msg->ip_from;
+ const struct sockaddr_in *dst = (const struct sockaddr_in *)&msg->ip_to;
+ memcpy(&ip4->saddr, &src->sin_addr, sizeof(src->sin_addr));
+ memcpy(&ip4->daddr, &dst->sin_addr, sizeof(dst->sin_addr));
+
+ ip4->check = ipv4_checksum(data);
+
+ data += sizeof(*ip4);
+
+ if (msg->flags & KNOT_XDP_MSG_TCP) {
+ uint32_t chk = 0;
+ checksum(&chk, &src->sin_addr, sizeof(src->sin_addr));
+ checksum(&chk, &dst->sin_addr, sizeof(dst->sin_addr));
+
+ prot_write_tcp(data, msg, data_end, src->sin_port, dst->sin_port, chk, tcp_mss);
+ } else {
+ prot_write_udp(data, msg, data_end, src->sin_port, dst->sin_port, 0); // IPv4/UDP requires no checksum
+ }
+}
+
+inline static void prot_write_ipv6(void *data, const knot_xdp_msg_t *msg,
+ void *data_end, uint16_t tcp_mss)
+{
+ struct ipv6hdr *ip6 = data;
+
+ ip6->version = 6;
+ ip6->priority = 0;
+ ip6->payload_len = htobe16(data_end - data - sizeof(*ip6));
+ ip6->nexthdr = ((msg->flags & KNOT_XDP_MSG_TCP) ? IPPROTO_TCP : IPPROTO_UDP);
+ ip6->hop_limit = IPDEFTTL;
+
+ memset(ip6->flow_lbl, 0, sizeof(ip6->flow_lbl));
+
+ const struct sockaddr_in6 *src = (const struct sockaddr_in6 *)&msg->ip_from;
+ const struct sockaddr_in6 *dst = (const struct sockaddr_in6 *)&msg->ip_to;
+ memcpy(&ip6->saddr, &src->sin6_addr, sizeof(src->sin6_addr));
+ memcpy(&ip6->daddr, &dst->sin6_addr, sizeof(dst->sin6_addr));
+
+ data += sizeof(*ip6);
+
+ uint32_t chk = 0;
+ checksum(&chk, &src->sin6_addr, sizeof(src->sin6_addr));
+ checksum(&chk, &dst->sin6_addr, sizeof(dst->sin6_addr));
+
+ if (msg->flags & KNOT_XDP_MSG_TCP) {
+ prot_write_tcp(data, msg, data_end, src->sin6_port, dst->sin6_port, chk, tcp_mss);
+ } else {
+ prot_write_udp(data, msg, data_end, src->sin6_port, dst->sin6_port, chk);
+ }
+}
+
+inline static void prot_write_eth(void *data, const knot_xdp_msg_t *msg,
+ void *data_end, uint16_t tcp_mss)
+{
+ struct ethhdr *eth = data;
+
+ memcpy(eth->h_source, msg->eth_from, ETH_ALEN);
+ memcpy(eth->h_dest, msg->eth_to, ETH_ALEN);
+
+ if (msg->vlan_tci != 0) {
+ eth->h_proto = __constant_htons(ETH_P_8021Q);
+ memcpy(data + sizeof(*eth), &msg->vlan_tci, sizeof(msg->vlan_tci));
+ data += HDR_8021Q_LEN;
+ eth = data;
+ }
+
+ data += sizeof(*eth);
+
+ if (msg->flags & KNOT_XDP_MSG_IPV6) {
+ eth->h_proto = __constant_htons(ETH_P_IPV6);
+ prot_write_ipv6(data, msg, data_end, tcp_mss);
+ } else {
+ eth->h_proto = __constant_htons(ETH_P_IP);
+ prot_write_ipv4(data, msg, data_end, tcp_mss);
+ }
+}
diff --git a/src/libknot/xdp/quic.c b/src/libknot/xdp/quic.c
new file mode 100644
index 0000000..f12a47c
--- /dev/null
+++ b/src/libknot/xdp/quic.c
@@ -0,0 +1,1028 @@
+/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+#include <gnutls/x509.h>
+#include <ngtcp2/ngtcp2.h>
+#include <ngtcp2/ngtcp2_crypto.h>
+#include <ngtcp2/ngtcp2_crypto_gnutls.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+#include "libknot/xdp/quic.h"
+
+#include "contrib/macros.h"
+#include "contrib/sockaddr.h"
+#include "contrib/ucw/lists.h"
+#include "libknot/endian.h"
+#include "libdnssec/error.h"
+#include "libdnssec/random.h"
+#include "libknot/attribute.h"
+#include "libknot/endian.h"
+#include "libknot/error.h"
+#include "libknot/wire.h"
+
+#define SERVER_DEFAULT_SCIDLEN 18
+
+#define QUIC_DEFAULT_VERSION "-VERS-ALL:+VERS-TLS1.3"
+#define QUIC_DEFAULT_CIPHERS "-CIPHER-ALL:+AES-128-GCM:+AES-256-GCM:+CHACHA20-POLY1305:+AES-128-CCM"
+#define QUIC_DEFAULT_GROUPS "-GROUP-ALL:+GROUP-SECP256R1:+GROUP-X25519:+GROUP-SECP384R1:+GROUP-SECP521R1"
+#define QUIC_PRIORITIES "%DISABLE_TLS13_COMPAT_MODE:NORMAL:"QUIC_DEFAULT_VERSION":"QUIC_DEFAULT_CIPHERS":"QUIC_DEFAULT_GROUPS
+
+#define XQUIC_SEND_VERSION_NEGOTIATION NGTCP2_ERR_VERSION_NEGOTIATION
+#define XQUIC_SEND_RETRY NGTCP2_ERR_RETRY
+#define XQUIC_SEND_STATELESS_RESET (-NGTCP2_STATELESS_RESET_TOKENLEN)
+
+#define TLS_CALLBACK_ERR (-1)
+
+typedef struct knot_quic_creds {
+ gnutls_certificate_credentials_t tls_cert;
+ gnutls_anti_replay_t tls_anti_replay;
+ gnutls_datum_t tls_ticket_key;
+ bool is_clone;
+} knot_xquic_creds_t;
+
+typedef struct knot_quic_session {
+ node_t n;
+ gnutls_datum_t tls_session;
+ ngtcp2_transport_params quic_params;
+} knot_xquic_session_t;
+
+static unsigned addr_len(const struct sockaddr_in6 *ss)
+{
+ return (ss->sin6_family == AF_INET6 ?
+ sizeof(struct sockaddr_in6) : sizeof(struct sockaddr));
+}
+
+_public_
+struct knot_quic_session *knot_xquic_session_save(knot_xquic_conn_t *conn)
+{
+ const ngtcp2_transport_params *tmp = ngtcp2_conn_get_remote_transport_params(conn->conn);
+ if (tmp == NULL) {
+ return NULL;
+ }
+
+ knot_xquic_session_t *session = calloc(1, sizeof(*session));
+ if (session == NULL) {
+ return NULL;
+ }
+
+ int ret = gnutls_session_get_data2(conn->tls_session, &session->tls_session);
+ if (ret != GNUTLS_E_SUCCESS) {
+ free(session);
+ return NULL;
+ }
+
+ memcpy(&session->quic_params, tmp, sizeof(session->quic_params));
+
+ return session;
+}
+
+_public_
+int knot_xquic_session_load(knot_xquic_conn_t *conn, struct knot_quic_session *session)
+{
+ if (session == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ int ret = KNOT_EOK;
+ if (conn == NULL) {
+ goto session_free;
+ }
+
+ ret = gnutls_session_set_data(conn->tls_session, session->tls_session.data, session->tls_session.size);
+ if (ret != KNOT_EOK) {
+ goto session_free;
+ }
+
+ ngtcp2_conn_set_early_remote_transport_params(conn->conn, &session->quic_params);
+
+session_free:
+ gnutls_free(session->tls_session.data);
+ free(session);
+ return ret;
+}
+
+static int tls_anti_replay_db_add_func(void *dbf, time_t exp_time,
+ const gnutls_datum_t *key,
+ const gnutls_datum_t *data)
+{
+ return 0;
+}
+
+static void tls_session_ticket_key_free(gnutls_datum_t *ticket)
+{
+ gnutls_memset(ticket->data, 0, ticket->size);
+ gnutls_free(ticket->data);
+}
+
+static int self_signed_cert(gnutls_certificate_credentials_t tls_cert)
+{
+ gnutls_x509_privkey_t privkey = NULL;
+ gnutls_x509_crt_t cert = NULL;
+ char *hostname = sockaddr_hostname();
+ if (hostname == NULL) {
+ return -ENOMEM;
+ }
+
+ int ret = gnutls_x509_privkey_init(&privkey);
+ if (ret < 0) {
+ free(hostname);
+ return ret;
+ }
+
+ uint8_t serial[16];
+ gnutls_rnd(GNUTLS_RND_NONCE, serial, sizeof(serial));
+ /* clear the left-most bit to avoid signedness confusion: */
+ serial[0] &= 0x8f;
+
+#define CHK(cmd) \
+ ret = (cmd); \
+ if (ret < 0) { goto finish; }
+
+#define now_years(years) (time(NULL) + 365 * 24 * 3600 * (years))
+
+ CHK(gnutls_x509_privkey_generate(privkey, GNUTLS_PK_ECDSA, GNUTLS_CURVE_TO_BITS(GNUTLS_ECC_CURVE_SECP256R1), 0));
+
+ CHK(gnutls_x509_crt_init(&cert));
+ //CHK(gnutls_x509_crt_set_ca_status(cert, 0)); // TODO needed ?
+ CHK(gnutls_x509_crt_set_activation_time(cert, now_years(-1)));
+ CHK(gnutls_x509_crt_set_expiration_time(cert, now_years(20)));
+ CHK(gnutls_x509_crt_set_dn(cert, "CN=DoQ Self-Signed Server Certificate", NULL));
+ CHK(gnutls_x509_crt_set_key(cert, privkey));
+
+ CHK(gnutls_x509_crt_set_serial(cert, serial, sizeof(serial)));
+ CHK(gnutls_x509_crt_set_subject_alt_name(cert, GNUTLS_SAN_DNSNAME, hostname, strlen(hostname), GNUTLS_FSAN_SET));
+ CHK(gnutls_x509_crt_set_version(cert, 3));
+ CHK(gnutls_x509_crt_sign2(cert, cert, privkey, GNUTLS_DIG_SHA256, 0));
+
+ ret = gnutls_certificate_set_x509_key(tls_cert, &cert, 1, privkey);
+
+finish:
+ free(hostname);
+ gnutls_x509_privkey_deinit(privkey);
+ gnutls_x509_crt_deinit(cert);
+
+ return ret;
+}
+
+_public_
+struct knot_quic_creds *knot_xquic_init_creds(bool server, const char *tls_cert,
+ const char *tls_key)
+{
+ knot_xquic_creds_t *creds = calloc(1, sizeof(*creds));
+ if (creds == NULL) {
+ return NULL;
+ }
+
+ int ret = gnutls_anti_replay_init(&creds->tls_anti_replay);
+ if (ret != GNUTLS_E_SUCCESS) {
+ goto fail;
+ }
+ gnutls_anti_replay_set_add_function(creds->tls_anti_replay, tls_anti_replay_db_add_func);
+ gnutls_anti_replay_set_ptr(creds->tls_anti_replay, NULL);
+
+ ret = gnutls_certificate_allocate_credentials(&creds->tls_cert);
+ if (ret != GNUTLS_E_SUCCESS) {
+ gnutls_anti_replay_deinit(creds->tls_anti_replay);
+ goto fail;
+ }
+
+ ret = gnutls_certificate_set_x509_system_trust(creds->tls_cert);
+ if (ret < 0) {
+ goto fail2;
+ }
+
+ if ((bool)(tls_cert == NULL) != (bool)(tls_key == NULL) ||
+ (tls_cert != NULL && !server)) {
+ goto fail2;
+ }
+ if (tls_cert != NULL) {
+ ret = gnutls_certificate_set_x509_key_file(creds->tls_cert, tls_cert, tls_key, GNUTLS_X509_FMT_PEM);
+ } else if (server) {
+ ret = self_signed_cert(creds->tls_cert);
+ }
+ if (ret < 0) {
+ goto fail2;
+ }
+
+ ret = gnutls_session_ticket_key_generate(&creds->tls_ticket_key);
+ if (ret != GNUTLS_E_SUCCESS) {
+ goto fail2;
+ }
+
+ return creds;
+
+fail:
+ free(creds);
+ return NULL;
+fail2:
+ knot_xquic_free_creds(creds);
+ return NULL;
+}
+
+_public_
+void knot_xquic_free_creds(struct knot_quic_creds *creds)
+{
+ if (creds == NULL) {
+ return;
+ }
+
+ if (!creds->is_clone) {
+ gnutls_certificate_free_credentials(creds->tls_cert);
+ if (creds->tls_ticket_key.data != NULL) {
+ tls_session_ticket_key_free(&creds->tls_ticket_key);
+ }
+ }
+ gnutls_anti_replay_deinit(creds->tls_anti_replay);
+ free(creds);
+}
+
+#define ALPN "\03""doq"
+#define ALPN_TMP1 "\07""doq-i11"
+#define ALPN_TMP2 "\07""doq-i03"
+
+static int tls_client_hello_cb(gnutls_session_t session, unsigned int htype,
+ unsigned when, unsigned int incoming,
+ const gnutls_datum_t *msg)
+{
+ assert(htype == GNUTLS_HANDSHAKE_CLIENT_HELLO);
+ assert(when == GNUTLS_HOOK_POST);
+
+ if (!incoming) {
+ return 0;
+ }
+
+ gnutls_datum_t alpn;
+ int ret = gnutls_alpn_get_selected_protocol(session, &alpn);
+ if (ret != 0) {
+ return ret;
+ }
+
+ const char *dq = (const char *)&ALPN[1];
+ if (((unsigned int)ALPN[0] != alpn.size ||
+ memcmp(dq, alpn.data, alpn.size) != 0) &&
+ ((unsigned int)ALPN_TMP1[0] != alpn.size ||
+ memcmp((const char *)&ALPN_TMP1[1], alpn.data, alpn.size) != 0) &&
+ ((unsigned int)ALPN_TMP1[0] != alpn.size ||
+ memcmp((const char *)&ALPN_TMP2[1], alpn.data, alpn.size) != 0)) {
+ return TLS_CALLBACK_ERR;
+ }
+
+ return 0;
+}
+
+static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref)
+{
+ return ((knot_xquic_conn_t *)conn_ref->user_data)->conn;
+}
+
+static int tls_init_conn_session(knot_xquic_conn_t *conn, bool server)
+{
+ if (gnutls_init(&conn->tls_session, (server ? GNUTLS_SERVER : GNUTLS_CLIENT) |
+ GNUTLS_ENABLE_EARLY_DATA | GNUTLS_NO_AUTO_SEND_TICKET |
+ GNUTLS_NO_END_OF_EARLY_DATA) != GNUTLS_E_SUCCESS) {
+ return TLS_CALLBACK_ERR;
+ }
+
+ if (gnutls_priority_set_direct(conn->tls_session, QUIC_PRIORITIES,
+ NULL) != GNUTLS_E_SUCCESS) {
+ return TLS_CALLBACK_ERR;
+ }
+
+ if (server && gnutls_session_ticket_enable_server(conn->tls_session,
+ &conn->xquic_table->creds->tls_ticket_key) != GNUTLS_E_SUCCESS) {
+ return TLS_CALLBACK_ERR;
+ }
+
+ gnutls_handshake_set_hook_function(conn->tls_session,
+ GNUTLS_HANDSHAKE_CLIENT_HELLO,
+ GNUTLS_HOOK_POST, tls_client_hello_cb);
+ int ret = ngtcp2_crypto_gnutls_configure_server_session(conn->tls_session);
+ if (ret != 0) {
+ return TLS_CALLBACK_ERR;
+ }
+
+ gnutls_record_set_max_early_data_size(conn->tls_session, 0xffffffffu);
+
+ conn->conn_ref = (nc_conn_ref_placeholder_t) {
+ .get_conn = get_conn,
+ .user_data = conn
+ };
+
+ _Static_assert(sizeof(nc_conn_ref_placeholder_t) == sizeof(ngtcp2_crypto_conn_ref), "invalid placeholder for conn_ref");
+ gnutls_session_set_ptr(conn->tls_session, &conn->conn_ref);
+
+ if (server) {
+ gnutls_anti_replay_enable(conn->tls_session, conn->xquic_table->creds->tls_anti_replay);
+
+ }
+ if (gnutls_credentials_set(conn->tls_session, GNUTLS_CRD_CERTIFICATE,
+ conn->xquic_table->creds->tls_cert) != GNUTLS_E_SUCCESS) {
+ return TLS_CALLBACK_ERR;
+ }
+
+
+ gnutls_datum_t alpn[3] = {
+ {
+ .data = (uint8_t *)(&ALPN[1]),
+ .size = ALPN[0],
+ },
+ {
+ .data = (uint8_t *)(&ALPN_TMP1[1]),
+ .size = ALPN_TMP1[0],
+ },
+ {
+ .data = (uint8_t *)(&ALPN_TMP2[1]),
+ .size = ALPN_TMP2[0],
+ }
+ };
+ gnutls_alpn_set_protocols(conn->tls_session, alpn, 3, 0);
+
+ ngtcp2_conn_set_tls_native_handle(conn->conn, conn->tls_session);
+
+ return KNOT_EOK;
+}
+
+static uint64_t get_timestamp(void)
+{
+ struct timespec ts;
+ if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) {
+ assert(0);
+ }
+
+ return (uint64_t)ts.tv_sec * NGTCP2_SECONDS + (uint64_t)ts.tv_nsec;
+}
+
+uint64_t xquic_conn_get_timeout(knot_xquic_conn_t *conn)
+{
+ // This effectively obtains the locally configured conn timeout.
+ // It would be possible to obey negotitated idle timeout by employing remote params,
+ // but this would differ per-connection and the whole idea of maintaining
+ // to-be-timeouted connections in simple linear list requires that
+ // the idle timeout is homogeneous among conns.
+ // Anyway, we also violate RFC9000/10.1 (Probe Timeout) for the same reason.
+ // TODO for the future: refactor conn table to use some tree/heap
+ // for to-be-timeouted conns, and use ngtcp2_conn_get_expiry() and
+ // ngtcp2_conn_handle_expiry() appropriately.
+ const ngtcp2_transport_params *params = ngtcp2_conn_get_local_transport_params(conn->conn);
+
+ return conn->last_ts + params->max_idle_timeout;
+}
+
+bool xquic_conn_timeout(knot_xquic_conn_t *conn, uint64_t *now)
+{
+ if (*now == 0) {
+ *now = get_timestamp();
+ }
+ return *now > xquic_conn_get_timeout(conn);
+}
+
+_public_
+uint32_t knot_xquic_conn_rtt(knot_xquic_conn_t *conn)
+{
+ ngtcp2_conn_stat stat = { 0 };
+ ngtcp2_conn_get_conn_stat(conn->conn, &stat);
+ return stat.smoothed_rtt / 1000; // nanosec --> usec
+}
+
+static void knot_quic_rand_cb(uint8_t *dest, size_t destlen, const ngtcp2_rand_ctx *rand_ctx)
+{
+ (void)rand_ctx;
+ dnssec_random_buffer(dest, destlen);
+}
+
+static void init_random_cid(ngtcp2_cid *cid, size_t len)
+{
+ if (len == 0) {
+ len = SERVER_DEFAULT_SCIDLEN;
+ }
+
+ if (dnssec_random_buffer(cid->data, len) != DNSSEC_EOK) {
+ cid->datalen = 0;
+ } else {
+ cid->datalen = len;
+ }
+}
+
+static bool init_unique_cid(ngtcp2_cid *cid, size_t len, knot_xquic_table_t *table)
+{
+ do {
+ if (init_random_cid(cid, len), cid->datalen == 0) {
+ return false;
+ }
+ } while (xquic_table_lookup(cid, table) != NULL);
+ return true;
+}
+
+static int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid,
+ uint8_t *token, size_t cidlen,
+ void *user_data)
+{
+ knot_xquic_conn_t *ctx = (knot_xquic_conn_t *)user_data;
+ assert(ctx->conn == conn);
+
+ if (!init_unique_cid(cid, cidlen, ctx->xquic_table)) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ knot_xquic_cid_t **addto = xquic_table_insert(ctx, cid, ctx->xquic_table);
+ (void)addto;
+
+ if (token != NULL &&
+ ngtcp2_crypto_generate_stateless_reset_token(
+ token, (uint8_t *)ctx->xquic_table->hash_secret,
+ sizeof(ctx->xquic_table->hash_secret), cid) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+
+static int remove_connection_id(ngtcp2_conn *conn, const ngtcp2_cid *cid,
+ void *user_data)
+{
+ knot_xquic_conn_t *ctx = (knot_xquic_conn_t *)user_data;
+ assert(ctx->conn == conn);
+
+ knot_xquic_cid_t **torem = xquic_table_lookup2(cid, ctx->xquic_table);
+ if (torem != NULL) {
+ assert((*torem)->conn == ctx);
+ xquic_table_rem2(torem, ctx->xquic_table);
+ }
+
+ return 0;
+}
+
+static int handshake_completed_cb(ngtcp2_conn *conn, void *user_data)
+{
+ knot_xquic_conn_t *ctx = (knot_xquic_conn_t *)user_data;
+ assert(ctx->conn == conn);
+
+ assert(!ctx->handshake_done);
+ ctx->handshake_done = true;
+
+ if (!ngtcp2_conn_is_server(conn)) {
+ return 0;
+ }
+
+ gnutls_datum_t alpn;
+ if (gnutls_alpn_get_selected_protocol(ctx->tls_session, &alpn) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+ char alpn_str[alpn.size + 1];
+ alpn_str[alpn.size] = '\0';
+ memcpy(alpn_str, alpn.data, alpn.size);
+
+ if (gnutls_session_ticket_send(ctx->tls_session, 1, 0) != GNUTLS_E_SUCCESS) {
+ return TLS_CALLBACK_ERR;
+ }
+
+ uint8_t token[NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN];
+ ngtcp2_path path = *ngtcp2_conn_get_path(ctx->conn);
+ uint64_t ts = get_timestamp();
+ ngtcp2_ssize tokenlen = ngtcp2_crypto_generate_regular_token(token,
+ (uint8_t *)ctx->xquic_table->hash_secret,
+ sizeof(ctx->xquic_table->hash_secret),
+ path.remote.addr, path.remote.addrlen, ts);
+ if (tokenlen < 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ if (ngtcp2_conn_submit_new_token(ctx->conn, token, tokenlen) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+
+static int recv_stream_data(ngtcp2_conn *conn, uint32_t flags,
+ int64_t stream_id, uint64_t offset,
+ const uint8_t *data, size_t datalen,
+ void *user_data, void *stream_user_data)
+{
+ (void)(stream_user_data); // always NULL
+ (void)(offset); // QUIC shall ensure that data arrive in-order
+
+ knot_xquic_conn_t *ctx = (knot_xquic_conn_t *)user_data;
+ assert(ctx->conn == conn);
+
+ int ret = knot_xquic_stream_recv_data(ctx, stream_id, data, datalen, (flags & NGTCP2_STREAM_DATA_FLAG_FIN));
+
+ return ret == KNOT_EOK ? 0 : NGTCP2_ERR_CALLBACK_FAILURE;
+}
+
+static int acked_stream_data_offset_cb(ngtcp2_conn *conn, int64_t stream_id,
+ uint64_t offset, uint64_t datalen,
+ void *user_data, void *stream_user_data)
+{
+ knot_xquic_conn_t *ctx = (knot_xquic_conn_t *)user_data;
+
+ bool keep = !ngtcp2_conn_is_server(conn); // kxdpgun: await incomming reply after query sent&acked
+
+ knot_xquic_stream_ack_data(ctx, stream_id, offset + datalen, keep);
+
+ return 0;
+}
+
+static int stream_closed(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id,
+ uint64_t app_error_code, void *user_data, void *stream_user_data)
+{
+ knot_xquic_conn_t *ctx = (knot_xquic_conn_t *)user_data;
+ assert(ctx->conn == conn);
+
+ // NOTE possible error is stored in (flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)
+
+ bool keep = !ngtcp2_conn_is_server(conn); // kxdpgun: process incomming reply after recvd&closed
+ if (!keep) {
+ xquic_stream_free(ctx, stream_id);
+ }
+ return 0;
+}
+
+static int recv_stateless_rst(ngtcp2_conn *conn, const ngtcp2_pkt_stateless_reset *sr, void *user_data)
+{
+ // NOTE server can't receive stateless resets, only client
+
+ // ngtcp2 verified stateless reset token already
+ (void)(sr);
+
+ knot_xquic_conn_t *ctx = (knot_xquic_conn_t *)user_data;
+ assert(ctx->conn == conn);
+
+ knot_xquic_table_rem(ctx, ctx->xquic_table);
+
+ return 0;
+}
+
+static int recv_stream_rst(ngtcp2_conn *conn, int64_t stream_id, uint64_t final_size,
+ uint64_t app_error_code, void *user_data, void *stream_user_data)
+{
+ (void)final_size;
+ return stream_closed(conn, NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET,
+ stream_id, app_error_code, user_data, stream_user_data);
+}
+
+static void user_printf(void *user_data, const char *format, ...)
+{
+ knot_xquic_conn_t *ctx = (knot_xquic_conn_t *)user_data;
+ if (ctx->xquic_table->log_cb != NULL) {
+ char buf[256];
+ va_list args;
+ va_start(args, format);
+ vsnprintf(buf, sizeof(buf), format, args);
+ va_end(args);
+ ctx->xquic_table->log_cb(buf);
+ }
+}
+
+static int conn_new(ngtcp2_conn **pconn, const ngtcp2_path *path, const ngtcp2_cid *scid,
+ const ngtcp2_cid *dcid, const ngtcp2_cid *odcid, uint32_t version,
+ uint64_t now, size_t udp_pl, uint64_t idle_timeout_ns,
+ void *user_data, bool server, bool retry_sent)
+{
+ // I. CALLBACKS
+ const ngtcp2_callbacks callbacks = {
+ ngtcp2_crypto_client_initial_cb,
+ ngtcp2_crypto_recv_client_initial_cb,
+ ngtcp2_crypto_recv_crypto_data_cb,
+ handshake_completed_cb,
+ NULL, // recv_version_negotiation not needed on server, nor kxdpgun
+ ngtcp2_crypto_encrypt_cb,
+ ngtcp2_crypto_decrypt_cb,
+ ngtcp2_crypto_hp_mask_cb,
+ recv_stream_data,
+ acked_stream_data_offset_cb,
+ NULL, // stream_opened
+ stream_closed,
+ recv_stateless_rst,
+ ngtcp2_crypto_recv_retry_cb,
+ NULL, // extend_max_streams_bidi
+ NULL, // extend_max_streams_uni
+ knot_quic_rand_cb,
+ get_new_connection_id,
+ remove_connection_id,
+ ngtcp2_crypto_update_key_cb,
+ NULL, // path_validation,
+ NULL, // select_preferred_addr
+ recv_stream_rst,
+ NULL, // extend_max_remote_streams_bidi, might be useful to some allocation optimizations?
+ NULL, // extend_max_remote_streams_uni
+ NULL, // extend_max_stream_data,
+ NULL, // dcid_status
+ NULL, // handshake_confirmed
+ NULL, // recv_new_token
+ ngtcp2_crypto_delete_crypto_aead_ctx_cb,
+ ngtcp2_crypto_delete_crypto_cipher_ctx_cb,
+ NULL, // recv_datagram
+ NULL, // ack_datagram
+ NULL, // lost_datagram
+ ngtcp2_crypto_get_path_challenge_data_cb,
+ NULL, // stream_stop_sending
+ ngtcp2_crypto_version_negotiation_cb,
+ NULL, // recv_rx_key
+ NULL // recv_tx_key
+ };
+
+ // II. SETTINGS
+ ngtcp2_settings settings;
+ ngtcp2_settings_default(&settings);
+ settings.initial_ts = now;
+ settings.log_printf = user_printf;
+ settings.max_tx_udp_payload_size = udp_pl;
+ settings.qlog.odcid = *odcid;
+ settings.handshake_timeout = idle_timeout_ns; // NOTE setting handshake timeout to idle_timeout for simplicity
+ settings.no_pmtud = true;
+
+ // III. PARAMS
+ ngtcp2_transport_params params;
+ ngtcp2_transport_params_default(&params);
+
+ params.initial_max_data = 786432;
+ params.initial_max_stream_data_bidi_local = 524288;
+ params.initial_max_stream_data_bidi_remote = 524288;
+ params.initial_max_stream_data_uni = 524288;
+
+ // params.initial_max_stream_data_bidi_local = config.max_stream_data_bidi_local;
+ // params.initial_max_stream_data_bidi_remote = config.max_stream_data_bidi_remote;
+ // params.initial_max_stream_data_uni = config.max_stream_data_uni;
+ // params.initial_max_data = config.max_data;
+ params.initial_max_streams_bidi = 100;
+ params.initial_max_streams_uni = 3;
+ params.max_idle_timeout = idle_timeout_ns;
+ // params.stateless_reset_token_present = 1;
+ // params.active_connection_id_limit = 7;
+ if (odcid) {
+ params.original_dcid = *odcid;
+ } else {
+ params.original_dcid = *scid;
+ }
+ if (retry_sent) {
+ params.retry_scid_present = 1;
+ params.retry_scid = *scid;
+ }
+ if (dnssec_random_buffer(params.stateless_reset_token, NGTCP2_STATELESS_RESET_TOKENLEN) != DNSSEC_EOK) {
+ return KNOT_ERROR;
+ }
+
+ if (server) {
+ return ngtcp2_conn_server_new(pconn, dcid, scid, path, version, &callbacks, &settings, &params, NULL, user_data);
+ } else {
+ return ngtcp2_conn_client_new(pconn, dcid, scid, path, version, &callbacks, &settings, &params, NULL, user_data);
+ }
+}
+
+_public_
+int knot_xquic_client(knot_xquic_table_t *table, struct sockaddr_in6 *dest,
+ struct sockaddr_in6 *via, knot_xquic_conn_t **out_conn)
+{
+ ngtcp2_cid scid = { 0 }, dcid = { 0 };
+ uint64_t now = get_timestamp();
+
+ init_random_cid(&scid, 0);
+ init_random_cid(&dcid, 0);
+
+ knot_xquic_conn_t *xconn = xquic_table_add(NULL, &dcid, table);
+ if (xconn == NULL) {
+ return ENOMEM;
+ }
+ xquic_conn_mark_used(xconn, table, now);
+
+ ngtcp2_path path;
+ path.remote.addr = (struct sockaddr *)dest;
+ path.remote.addrlen = addr_len((const struct sockaddr_in6 *)dest);
+ path.local.addr = (struct sockaddr *)via;
+ path.local.addrlen = addr_len((const struct sockaddr_in6 *)via);
+
+ int ret = conn_new(&xconn->conn, &path, &dcid, &scid, &dcid, NGTCP2_PROTO_VER_V1, now,
+ table->udp_payload_limit, 5000000000L, xconn, false, false);
+ if (ret == KNOT_EOK) {
+ ret = tls_init_conn_session(xconn, false);
+ }
+ if (ret == KNOT_EOK) {
+ char *hostname = sockaddr_hostname();
+ if (hostname == NULL) {
+ ret = KNOT_ENOMEM;
+ } else {
+ ret = gnutls_server_name_set(xconn->tls_session, GNUTLS_NAME_DNS,
+ hostname, strlen(hostname));
+ free(hostname);
+ }
+ }
+ if (ret != KNOT_EOK) {
+ knot_xquic_table_rem(xconn, table);
+ return ret;
+ }
+
+ *out_conn = xconn;
+ return KNOT_EOK;
+}
+
+_public_
+int knot_xquic_handle(knot_xquic_table_t *table, knot_xdp_msg_t *msg, uint64_t idle_timeout, knot_xquic_conn_t **out_conn)
+{
+ *out_conn = NULL;
+
+ ngtcp2_version_cid decoded_cids = { 0 };
+ ngtcp2_cid scid = { 0 }, dcid = { 0 }, odcid = { 0 };
+ uint64_t now = get_timestamp();
+ int ret = ngtcp2_pkt_decode_version_cid(&decoded_cids,
+ msg->payload.iov_base,
+ msg->payload.iov_len,
+ SERVER_DEFAULT_SCIDLEN);
+ if (ret == NGTCP2_ERR_VERSION_NEGOTIATION) {
+ return -XQUIC_SEND_VERSION_NEGOTIATION;
+ } else if (ret != NGTCP2_NO_ERROR) {
+ return ret;
+ }
+ ngtcp2_cid_init(&dcid, decoded_cids.dcid, decoded_cids.dcidlen);
+ ngtcp2_cid_init(&scid, decoded_cids.scid, decoded_cids.scidlen);
+
+ knot_xquic_conn_t *xconn = xquic_table_lookup(&dcid, table);
+
+ if (decoded_cids.version == 0 /* short header */ && xconn == NULL) {
+ return KNOT_EOK; // NOOP
+ }
+
+ ngtcp2_path path;
+ path.remote.addr = (struct sockaddr *)&msg->ip_from;
+ path.remote.addrlen = addr_len(&msg->ip_from);
+ path.local.addr = (struct sockaddr *)&msg->ip_to;
+ path.local.addrlen = addr_len(&msg->ip_to);
+
+ if (xconn == NULL) {
+ // new conn
+
+ ngtcp2_pkt_hd header = { 0 };
+ ret = ngtcp2_accept(&header, msg->payload.iov_base, msg->payload.iov_len);
+ if (ret == NGTCP2_ERR_RETRY) {
+ return -XQUIC_SEND_RETRY;
+ } else if (ret != NGTCP2_NO_ERROR) { // discard packet
+ return KNOT_EOK;
+ }
+
+ assert(header.type == NGTCP2_PKT_INITIAL);
+ if (header.tokenlen == 0 && xquic_require_retry(table)) {
+ return -XQUIC_SEND_RETRY;
+ }
+
+ if (header.tokenlen > 0) {
+ ret = ngtcp2_crypto_verify_retry_token(
+ &odcid, header.token, header.tokenlen,
+ (const uint8_t *)table->hash_secret, sizeof(table->hash_secret), header.version,
+ (const struct sockaddr *)&msg->ip_from, addr_len(&msg->ip_from),
+ &dcid, idle_timeout, now // NOTE setting retry token validity to idle_timeout for simplicity
+ );
+ if (ret != 0) {
+ return KNOT_EOK;
+ }
+ } else {
+ memcpy(&odcid, &dcid, sizeof(odcid));
+ }
+
+ // server chooses his CID to his liking
+ if (!init_unique_cid(&dcid, 0, table)) {
+ return KNOT_ERROR;
+ }
+
+ xconn = xquic_table_add(NULL, &dcid, table);
+ if (xconn == NULL) {
+ return ENOMEM;
+ }
+ xquic_conn_mark_used(xconn, table, now);
+
+ ret = conn_new(&xconn->conn, &path, &dcid, &scid, &odcid, decoded_cids.version, now,
+ table->udp_payload_limit, idle_timeout, xconn, true, header.tokenlen > 0);
+ if (ret >= 0) {
+ ret = tls_init_conn_session(xconn, true);
+ }
+ if (ret < 0) {
+ knot_xquic_table_rem(xconn, table);
+ return ret;
+ }
+ }
+
+ ngtcp2_pkt_info pi = { .ecn = NGTCP2_ECN_NOT_ECT, };
+
+ ret = ngtcp2_conn_read_pkt(xconn->conn, &path, &pi, msg->payload.iov_base, msg->payload.iov_len, now);
+
+ *out_conn = xconn;
+ if (ret == NGTCP2_ERR_DRAINING) { // received CONNECTION_CLOSE from the counterpart
+ knot_xquic_table_rem(xconn, table);
+ return KNOT_EOK;
+ } else if(ngtcp2_err_is_fatal(ret)) { // connection doomed
+ knot_xquic_table_rem(xconn, table);
+ return KNOT_ECONN;
+ } else if (ret != NGTCP2_NO_ERROR) { // non-fatal error, discard packet
+ return KNOT_EOK;
+ }
+
+ xquic_conn_mark_used(xconn, table, now);
+
+ return KNOT_EOK;
+}
+
+static bool stream_exists(knot_xquic_conn_t *xconn, int64_t stream_id)
+{
+ // TRICK, we never use stream_user_data
+ return (ngtcp2_conn_set_stream_user_data(xconn->conn, stream_id, NULL) == NGTCP2_NO_ERROR);
+}
+
+static int send_stream(knot_xquic_table_t *quic_table, knot_xdp_socket_t *sock,
+ knot_xdp_msg_t *in_msg, knot_xquic_conn_t *relay, int64_t stream_id,
+ uint8_t *data, size_t len, bool fin, ngtcp2_ssize *sent)
+{
+ (void)quic_table;
+ assert(stream_id >= 0 || (data == NULL && len == 0));
+
+ while (stream_id >= 0 && !stream_exists(relay, stream_id)) {
+ int64_t opened = 0;
+ int ret = ngtcp2_conn_open_bidi_stream(relay->conn, &opened, NULL);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ assert((bool)(opened == stream_id) == stream_exists(relay, stream_id));
+ }
+
+ uint32_t xdp_sent = 0;
+ knot_xdp_msg_t out_msg = { 0 };
+ int ret = knot_xdp_reply_alloc(sock, in_msg, &out_msg);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ uint32_t fl = ((stream_id >= 0 && fin) ? NGTCP2_WRITE_STREAM_FLAG_FIN : NGTCP2_WRITE_STREAM_FLAG_NONE);
+ ngtcp2_vec vec = { .base = data, .len = len };
+
+ ret = ngtcp2_conn_writev_stream(relay->conn, NULL, NULL, out_msg.payload.iov_base, out_msg.payload.iov_len,
+ sent, fl, stream_id, &vec, (stream_id >= 0 ? 1 : 0), get_timestamp());
+ if (ret <= 0) {
+ knot_xdp_send_free(sock, &out_msg, 1);
+ return ret;
+ }
+ if (*sent < 0) {
+ *sent = 0;
+ }
+
+ out_msg.payload.iov_len = ret;
+ ret = knot_xdp_send(sock, &out_msg, 1, &xdp_sent);
+ if (ret == KNOT_EOK) {
+ assert(xdp_sent == 1);
+ return 1;
+ }
+ return ret;
+}
+
+static int send_special(knot_xquic_table_t *quic_table, knot_xdp_socket_t *sock,
+ knot_xdp_msg_t *in_msg, int handle_ret)
+{
+ knot_xdp_msg_t out_msg;
+ int ret = knot_xdp_reply_alloc(sock, in_msg, &out_msg);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ uint64_t now = get_timestamp();
+ ngtcp2_version_cid decoded_cids = { 0 };
+ ngtcp2_cid scid = { 0 }, dcid = { 0 };
+
+ int dvc_ret = ngtcp2_pkt_decode_version_cid(&decoded_cids,
+ in_msg->payload.iov_base,
+ in_msg->payload.iov_len,
+ SERVER_DEFAULT_SCIDLEN);
+
+ uint8_t rnd = 0;
+ dnssec_random_buffer(&rnd, sizeof(rnd));
+ uint32_t supported_quic[1] = { NGTCP2_PROTO_VER_V1 };
+ ngtcp2_cid new_dcid;
+ uint8_t retry_token[NGTCP2_CRYPTO_MAX_RETRY_TOKENLEN];
+ uint8_t stateless_reset_token[NGTCP2_STATELESS_RESET_TOKENLEN];
+ uint8_t sreset_rand[NGTCP2_MIN_STATELESS_RESET_RANDLEN];
+ dnssec_random_buffer(sreset_rand, sizeof(sreset_rand));
+
+ switch (handle_ret) {
+ case -XQUIC_SEND_VERSION_NEGOTIATION:
+ if (dvc_ret != NGTCP2_ERR_VERSION_NEGOTIATION) {
+ return KNOT_ERROR;
+ }
+ ret = ngtcp2_pkt_write_version_negotiation(
+ out_msg.payload.iov_base, out_msg.payload.iov_len,
+ rnd, decoded_cids.scid, decoded_cids.scidlen, decoded_cids.dcid,
+ decoded_cids.dcidlen, supported_quic,
+ sizeof(supported_quic) / sizeof(*supported_quic)
+ );
+ break;
+ case -XQUIC_SEND_RETRY:
+ ngtcp2_cid_init(&dcid, decoded_cids.dcid, decoded_cids.dcidlen);
+ ngtcp2_cid_init(&scid, decoded_cids.scid, decoded_cids.scidlen);
+
+ init_random_cid(&new_dcid, 0);
+
+ ret = ngtcp2_crypto_generate_retry_token(
+ retry_token, (const uint8_t *)quic_table->hash_secret, sizeof(quic_table->hash_secret), decoded_cids.version,
+ (const struct sockaddr *)&in_msg->ip_from, sockaddr_len((const struct sockaddr_storage *)&in_msg->ip_from),
+ &new_dcid, &dcid, now
+ );
+
+ if (ret >= 0) {
+ ret = ngtcp2_crypto_write_retry(
+ out_msg.payload.iov_base, out_msg.payload.iov_len,
+ decoded_cids.version, &scid, &new_dcid, &dcid, retry_token, ret
+ );
+ }
+ break;
+ case -XQUIC_SEND_STATELESS_RESET:
+ ret = ngtcp2_pkt_write_stateless_reset(
+ out_msg.payload.iov_base, out_msg.payload.iov_len,
+ stateless_reset_token, sreset_rand, sizeof(sreset_rand)
+ );
+ break;
+ default:
+ ret = KNOT_EINVAL;
+ break;
+ }
+
+ if (ret < 0) {
+ knot_xdp_send_free(sock, &out_msg, 1);
+ } else {
+ uint32_t sent;
+ out_msg.payload.iov_len = ret;
+ ret = knot_xdp_send(sock, &out_msg, 1, &sent);
+ }
+ return ret;
+}
+
+_public_
+int knot_xquic_send(knot_xquic_table_t *quic_table, knot_xquic_conn_t *relay,
+ knot_xdp_socket_t *sock, knot_xdp_msg_t *in_msg,
+ int handle_ret, unsigned max_msgs, bool ignore_lastbyte)
+{
+ if (handle_ret < 0) {
+ return handle_ret;
+ } else if (handle_ret > 0) {
+ return send_special(quic_table, sock, in_msg, handle_ret);
+ } else if (relay == NULL) {
+ return KNOT_EINVAL;
+ } else if (relay->conn == NULL) {
+ return KNOT_EOK;
+ }
+
+ unsigned sent_msgs = 0, stream_msgs = 0;
+ int ret = 1;
+ for (int64_t si = 0; si < relay->streams_count && sent_msgs < max_msgs; /* NO INCREMENT */) {
+ int64_t stream_id = 4 * (relay->streams_first + si);
+
+ ngtcp2_ssize sent = 0;
+ size_t uf = relay->streams[si].unsent_offset;
+ knot_xquic_obuf_t *uo = relay->streams[si].unsent_obuf;
+ if (uo == NULL) {
+ si++;
+ continue;
+ }
+
+ bool fin = (((node_t *)uo->node.next)->next == NULL) && !ignore_lastbyte;
+ ret = send_stream(quic_table, sock, in_msg, relay, stream_id,
+ uo->buf + uf, uo->len - uf - (ignore_lastbyte ? 1 : 0),
+ fin, &sent);
+ if (ret < 0) {
+ return ret;
+ }
+
+ sent_msgs++;
+ stream_msgs++;
+ if (sent > 0 && ignore_lastbyte) {
+ sent++;
+ }
+ if (sent > 0) {
+ knot_xquic_stream_mark_sent(relay, stream_id, sent);
+ }
+
+ if (stream_msgs >= max_msgs / relay->streams_count) {
+ stream_msgs = 0;
+ si++; // if this stream is sending too much, give chance to other streams
+ }
+ }
+
+ while (ret == 1) {
+ ngtcp2_ssize unused = 0;
+ ret = send_stream(quic_table, sock, in_msg, relay, -1, NULL, 0, false, &unused);
+ }
+
+ return ret;
+}
diff --git a/src/libknot/xdp/quic.h b/src/libknot/xdp/quic.h
new file mode 100644
index 0000000..943a2f3
--- /dev/null
+++ b/src/libknot/xdp/quic.h
@@ -0,0 +1,134 @@
+/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*!
+ * \file
+ *
+ * \brief General QUIC functionality.
+ *
+ * \addtogroup xdp
+ * @{
+ */
+
+#pragma once
+
+#include "libknot/xdp/quic_conn.h"
+#include "libknot/xdp/xdp.h"
+
+struct knot_quic_creds;
+struct knot_quic_session;
+
+/*!
+ * \brief Gets data needed for session resumption.
+ *
+ * \param conn QUIC connection.
+ *
+ * \return QUIC session context.
+ */
+struct knot_quic_session *knot_xquic_session_save(knot_xquic_conn_t *conn);
+
+/*!
+ * \brief Loads data needed for session resumption.
+ *
+ * \param conn QUIC connection.
+ * \param session QUIC session context.
+ *
+ * \return KNOT_E*
+ */
+int knot_xquic_session_load(knot_xquic_conn_t *conn, struct knot_quic_session *session);
+
+/*!
+ * \brief Init server TLS certificate for DoQ.
+ *
+ * \param server Initializing for server-side (client otherwise).
+ * \param tls_cert X509 certificate PEM file path/name.
+ * \param tls_key Key PEM file path/name.
+ *
+ * \return Initialized creds.
+ */
+struct knot_quic_creds *knot_xquic_init_creds(bool server, const char *tls_cert,
+ const char *tls_key);
+
+/*!
+ * \brief Init server TLS certificate for DoQ.
+ */
+void knot_xquic_free_creds(struct knot_quic_creds *creds);
+
+/*!
+ * \brief Returns timeout value for the connection.
+ */
+uint64_t xquic_conn_get_timeout(knot_xquic_conn_t *conn);
+
+/*!
+ * \brief Check if connection timed out due to inactivity.
+ *
+ * \param conn QUIC connection.
+ * \param now In/out: current monotonic time. Use zero first and reuse for
+ * next calls for optimization.
+ *
+ * \return True if the connection timed out idle.
+ */
+bool xquic_conn_timeout(knot_xquic_conn_t *conn, uint64_t *now);
+
+/*!
+ * \brief Returns measured connection RTT in usecs.
+ */
+uint32_t knot_xquic_conn_rtt(knot_xquic_conn_t *conn);
+
+/*!
+ * \brief Create new outgoing QUIC connection.
+ *
+ * \param table QUIC connections table to be added to.
+ * \param dest Destination IP address.
+ * \param via Source IP address.
+ * \param out_conn Out: new connection.
+ *
+ * \return KNOT_E*
+ */
+int knot_xquic_client(knot_xquic_table_t *table, struct sockaddr_in6 *dest,
+ struct sockaddr_in6 *via, knot_xquic_conn_t **out_conn);
+
+/*!
+ * \brief Handle incoming QUIC packet.
+ *
+ * \param table QUIC connectoins table-
+ * \param msg Incoming XDP packet.
+ * \param idle_timeout Configured idle timeout for connections (in nanoseconds).
+ * \param out_conn Out: QUIC connection that this packet belongs to.
+ *
+ * \return KNOT_E*
+ */
+int knot_xquic_handle(knot_xquic_table_t *table, knot_xdp_msg_t *msg,
+ uint64_t idle_timeout, knot_xquic_conn_t **out_conn);
+
+/*!
+ * \brief Send outgoing QUIC packet(s) for a connection.
+ *
+ * \param quic_table QUIC connection table.
+ * \param relay QUIC connection.
+ * \param sock XDP socket.
+ * \param in_msg Previous incomming packet for this connection.
+ * \param handle_ret Error returned from knot_xquic_handle() for incoming packet.
+ * \param max_msgs Maxmimum packets to be sent.
+ * \param ignore_lastbyte Cut off last byte of QUIC paylod.
+ *
+ * \return KNOT_E*
+ */
+int knot_xquic_send(knot_xquic_table_t *quic_table, knot_xquic_conn_t *relay,
+ knot_xdp_socket_t *sock, knot_xdp_msg_t *in_msg,
+ int handle_ret, unsigned max_msgs, bool ignore_lastbyte);
+
+/*! @} */
diff --git a/src/libknot/xdp/quic_conn.c b/src/libknot/xdp/quic_conn.c
new file mode 100644
index 0000000..40a5d5b
--- /dev/null
+++ b/src/libknot/xdp/quic_conn.c
@@ -0,0 +1,506 @@
+/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <gnutls/gnutls.h>
+#include <ngtcp2/ngtcp2.h>
+#include <string.h>
+
+#include "libknot/xdp/quic_conn.h"
+
+#include "contrib/macros.h"
+#include "contrib/openbsd/siphash.h"
+#include "contrib/ucw/lists.h"
+#include "libdnssec/random.h"
+#include "libknot/attribute.h"
+#include "libknot/error.h"
+#include "libknot/xdp/quic.h"
+#include "libknot/xdp/tcp_iobuf.h"
+#include "libknot/wire.h"
+
+#define STREAM_INCR 4 // DoQ only uses client-initiated bi-directional streams, so stream IDs increment by four
+#define BUCKETS_PER_CONNS 8 // Each connecion has several dCIDs, and each CID takes one hash table bucket.
+
+_public_
+knot_xquic_table_t *knot_xquic_table_new(size_t max_conns, size_t max_ibufs, size_t max_obufs,
+ size_t udp_payload, struct knot_quic_creds *creds)
+{
+ size_t table_size = max_conns * BUCKETS_PER_CONNS;
+
+ knot_xquic_table_t *res = calloc(1, sizeof(*res) + table_size * sizeof(res->conns[0]));
+ if (res == NULL) {
+ return NULL;
+ }
+
+ res->size = table_size;
+ res->max_conns = max_conns;
+ res->ibufs_max = max_ibufs;
+ res->obufs_max = max_obufs;
+ res->udp_payload_limit = udp_payload;
+ init_list((list_t *)&res->timeout);
+
+ res->creds = creds;
+
+ res->hash_secret[0] = dnssec_random_uint64_t();
+ res->hash_secret[1] = dnssec_random_uint64_t();
+ res->hash_secret[2] = dnssec_random_uint64_t();
+ res->hash_secret[3] = dnssec_random_uint64_t();
+
+ return res;
+}
+
+_public_
+void knot_xquic_table_free(knot_xquic_table_t *table)
+{
+ if (table != NULL) {
+ knot_xquic_conn_t *c, *next;
+ list_t *tto = (list_t *)&table->timeout;
+ WALK_LIST_DELSAFE(c, next, *tto) {
+ knot_xquic_table_rem(c, table);
+ knot_xquic_cleanup(&c, 1);
+ }
+ assert(table->usage == 0);
+ assert(table->pointers == 0);
+ assert(table->obufs_size == 0);
+
+ free(table);
+ }
+}
+
+_public_
+int knot_xquic_table_sweep(knot_xquic_table_t *table, struct knot_sweep_stats *stats)
+{
+ uint64_t now = 0;
+ knot_xquic_conn_t *c, *next;
+ list_t *tto = (list_t *)&table->timeout;
+ WALK_LIST_DELSAFE(c, next, *tto) {
+ if (xquic_conn_timeout(c, &now)) {
+ knot_sweep_stats_incr(stats, KNOT_SWEEP_CTR_TIMEOUT);
+ knot_xquic_table_rem(c, table);
+ } else if (table->usage > table->max_conns) {
+ knot_sweep_stats_incr(stats, KNOT_SWEEP_CTR_LIMIT_CONN);
+ knot_xquic_table_rem(c, table);
+ // NOTE here it would be correct to send Immediate close
+ // with DoQ errcode DOQ_EXCESSIVE_LOAD
+ // nowever, we don't do this for the sake of simplicty
+ // it would be possible to send by using ngtcp2_conn_get_path()...
+ // (also applies to below case)
+ } else if (table->obufs_size > table->obufs_max) {
+ if (c->obufs_size > 0) {
+ knot_sweep_stats_incr(stats, KNOT_SWEEP_CTR_LIMIT_OBUF);
+ knot_xquic_table_rem(c, table);
+ }
+ } else if (table->ibufs_size > table->ibufs_max) {
+ if (c->ibufs_size > 0) {
+ knot_sweep_stats_incr(stats, KNOT_SWEEP_CTR_LIMIT_IBUF);
+ knot_xquic_table_rem(c, table);
+ }
+ } else {
+ break;
+ }
+ knot_xquic_cleanup(&c, 1);
+ }
+ return KNOT_EOK;
+}
+
+static uint64_t cid2hash(const ngtcp2_cid *cid, knot_xquic_table_t *table)
+{
+ SIPHASH_CTX ctx;
+ SipHash24_Init(&ctx, (const SIPHASH_KEY *)(table->hash_secret));
+ SipHash24_Update(&ctx, cid->data, MIN(cid->datalen, 8));
+ uint64_t ret = SipHash24_End(&ctx);
+ return ret;
+}
+
+knot_xquic_cid_t **xquic_table_insert(knot_xquic_conn_t *xconn, const ngtcp2_cid *cid,
+ knot_xquic_table_t *table)
+{
+ uint64_t hash = cid2hash(cid, table);
+
+ knot_xquic_cid_t *cidobj = malloc(sizeof(*cidobj));
+ if (cidobj == NULL) {
+ return NULL;
+ }
+ _Static_assert(sizeof(*cid) <= sizeof(cidobj->cid_placeholder), "insufficient placeholder for CID struct");
+ memcpy(cidobj->cid_placeholder, cid, sizeof(*cid));
+ cidobj->conn = xconn;
+
+ knot_xquic_cid_t **addto = table->conns + (hash % table->size);
+
+ cidobj->next = *addto;
+ *addto = cidobj;
+ table->pointers++;
+
+ return addto;
+}
+
+knot_xquic_conn_t *xquic_table_add(ngtcp2_conn *conn, const ngtcp2_cid *cid,
+ knot_xquic_table_t *table)
+{
+ knot_xquic_conn_t *xconn = calloc(1, sizeof(*xconn));
+ if (xconn == NULL) {
+ return NULL;
+ }
+
+ xconn->conn = conn;
+ xconn->xquic_table = table;
+ xconn->stream_inprocess = -1;
+
+ knot_xquic_cid_t **addto = xquic_table_insert(xconn, cid, table);
+ if (addto == NULL) {
+ free(xconn);
+ return NULL;
+ }
+ table->usage++;
+
+ return xconn;
+}
+
+knot_xquic_cid_t **xquic_table_lookup2(const ngtcp2_cid *cid, knot_xquic_table_t *table)
+{
+ uint64_t hash = cid2hash(cid, table);
+
+ knot_xquic_cid_t **res = table->conns + (hash % table->size);
+ while (*res != NULL && !ngtcp2_cid_eq(cid, (const ngtcp2_cid *)(*res)->cid_placeholder)) {
+ res = &(*res)->next;
+ }
+ return res;
+}
+
+knot_xquic_conn_t *xquic_table_lookup(const ngtcp2_cid *cid, knot_xquic_table_t *table)
+{
+ knot_xquic_cid_t **pcid = xquic_table_lookup2(cid, table);
+ assert(pcid != NULL);
+ return *pcid == NULL ? NULL : (*pcid)->conn;
+}
+
+void xquic_conn_mark_used(knot_xquic_conn_t *conn, knot_xquic_table_t *table,
+ uint64_t now)
+{
+ node_t *n = (node_t *)&conn->timeout;
+ list_t *l = (list_t *)&table->timeout;
+ if (n->next != NULL) {
+ rem_node(n);
+ }
+ add_tail(l, n);
+ conn->last_ts = now;
+}
+
+void xquic_table_rem2(knot_xquic_cid_t **pcid, knot_xquic_table_t *table)
+{
+ knot_xquic_cid_t *cid = *pcid;
+ *pcid = cid->next;
+ free(cid);
+ table->pointers--;
+}
+
+void xquic_stream_free(knot_xquic_conn_t *xconn, int64_t stream_id)
+{
+ knot_xquic_stream_ack_data(xconn, stream_id, SIZE_MAX, false);
+}
+
+_public_
+void knot_xquic_table_rem(knot_xquic_conn_t *conn, knot_xquic_table_t *table)
+{
+ if (conn->streams_count == -1) { // kxdpgun special
+ conn->streams_count = 1;
+ }
+ for (ssize_t i = conn->streams_count - 1; i >= 0; i--) {
+ xquic_stream_free(conn, (i + conn->streams_first) * 4);
+ }
+ assert(conn->streams_count <= 0);
+ assert(conn->obufs_size == 0);
+
+ size_t num_scid = ngtcp2_conn_get_num_scid(conn->conn);
+ ngtcp2_cid *scids = calloc(num_scid, sizeof(*scids));
+ ngtcp2_conn_get_scid(conn->conn, scids);
+
+ for (size_t i = 0; i < num_scid; i++) {
+ knot_xquic_cid_t **pcid = xquic_table_lookup2(&scids[i], table);
+ assert(pcid != NULL);
+ if (*pcid == NULL) {
+ continue;
+ }
+ assert((*pcid)->conn == conn);
+ xquic_table_rem2(pcid, table);
+ }
+
+ rem_node((node_t *)&conn->timeout);
+
+ free(scids);
+
+ gnutls_deinit(conn->tls_session);
+ ngtcp2_conn_del(conn->conn);
+ conn->conn = NULL;
+
+ table->usage--;
+}
+
+_public_
+knot_xquic_stream_t *knot_xquic_conn_get_stream(knot_xquic_conn_t *xconn,
+ int64_t stream_id, bool create)
+{
+ if (stream_id % 4 != 0) {
+ return NULL;
+ }
+ stream_id /= 4;
+
+ if (xconn->streams_first > stream_id) {
+ return NULL;
+ }
+ if (xconn->streams_count > stream_id - xconn->streams_first) {
+ return &xconn->streams[stream_id - xconn->streams_first];
+ }
+
+ if (create) {
+ size_t new_streams_count;
+ knot_xquic_stream_t *new_streams;
+
+ if (xconn->streams_count == 0) {
+ new_streams = malloc(sizeof(new_streams[0]));
+ if (new_streams == NULL) {
+ return NULL;
+ }
+ new_streams_count = 1;
+ xconn->streams_first = stream_id;
+ } else {
+ new_streams_count = stream_id + 1 - xconn->streams_first;
+ if (new_streams_count > MAX_STREAMS_PER_CONN) {
+ return NULL;
+ }
+ new_streams = realloc(xconn->streams, new_streams_count * sizeof(*new_streams));
+ if (new_streams == NULL) {
+ return NULL;
+ }
+ }
+
+ for (knot_xquic_stream_t *si = new_streams;
+ si < new_streams + xconn->streams_count; si++) {
+ if (si->obufs_size == 0) {
+ init_list((list_t *)&si->outbufs);
+ } else {
+ fix_list((list_t *)&si->outbufs);
+ }
+ }
+
+ for (knot_xquic_stream_t *si = new_streams + xconn->streams_count;
+ si < new_streams + new_streams_count; si++) {
+ memset(si, 0, sizeof(*si));
+ init_list((list_t *)&si->outbufs);
+ }
+ xconn->streams = new_streams;
+ xconn->streams_count = new_streams_count;
+
+ return &xconn->streams[stream_id - xconn->streams_first];
+ }
+ return NULL;
+}
+
+static void stream_inprocess(knot_xquic_conn_t *xconn, knot_xquic_stream_t *stream)
+{
+ int16_t idx = stream - xconn->streams;
+ assert(idx >= 0);
+ assert(idx < xconn->streams_count);
+ if (xconn->stream_inprocess < 0 || xconn->stream_inprocess > idx) {
+ xconn->stream_inprocess = idx;
+ }
+}
+
+static void stream_outprocess(knot_xquic_conn_t *xconn, knot_xquic_stream_t *stream)
+{
+ if (stream != &xconn->streams[xconn->stream_inprocess]) {
+ return;
+ }
+
+ for (int16_t idx = xconn->stream_inprocess + 1; idx < xconn->streams_count; idx++) {
+ stream = &xconn->streams[idx];
+ if (stream->inbuf_fin != NULL) {
+ xconn->stream_inprocess = stream - xconn->streams;
+ return;
+ }
+ }
+ xconn->stream_inprocess = -1;
+}
+
+int knot_xquic_stream_recv_data(knot_xquic_conn_t *xconn, int64_t stream_id,
+ const uint8_t *data, size_t len, bool fin)
+{
+ if (len == 0) {
+ return KNOT_EINVAL;
+ }
+
+ knot_xquic_stream_t *stream = knot_xquic_conn_get_stream(xconn, stream_id, true);
+ if (stream == NULL) {
+ return KNOT_ENOENT;
+ }
+
+ struct iovec in = { (void *)data, len }, *outs;
+ size_t outs_count;
+ int ret = knot_tcp_inbuf_update(&stream->inbuf, in, &outs, &outs_count,
+ &xconn->ibufs_size);
+ if (ret != KNOT_EOK || (outs_count == 0 && !fin)) {
+ return ret;
+ }
+ if (outs_count != 1 || !fin) {
+ free(outs);
+ return KNOT_ESEMCHECK;
+ }
+
+ stream->inbuf_fin = outs;
+ stream_inprocess(xconn, stream);
+ return KNOT_EOK;
+}
+
+_public_
+knot_xquic_stream_t *knot_xquic_stream_get_process(knot_xquic_conn_t *xconn,
+ int64_t *stream_id)
+{
+ if (xconn->stream_inprocess < 0) {
+ return NULL;
+ }
+
+ knot_xquic_stream_t *stream = &xconn->streams[xconn->stream_inprocess];
+ *stream_id = (xconn->streams_first + xconn->stream_inprocess) * 4;
+ stream_outprocess(xconn, stream);
+ return stream;
+}
+
+_public_
+uint8_t *knot_xquic_stream_add_data(knot_xquic_conn_t *xconn, int64_t stream_id,
+ uint8_t *data, size_t len)
+{
+ knot_xquic_stream_t *s = knot_xquic_conn_get_stream(xconn, stream_id, true);
+ if (s == NULL) {
+ return NULL;
+ }
+
+ size_t prefix = sizeof(uint16_t);
+
+ knot_xquic_obuf_t *obuf = malloc(sizeof(*obuf) + prefix + len);
+ if (obuf == NULL) {
+ return NULL;
+ }
+
+ obuf->len = len + prefix;
+ knot_wire_write_u16(obuf->buf, len);
+ if (data != NULL) {
+ memcpy(obuf->buf + prefix, data, len);
+ }
+
+ list_t *list = (list_t *)&s->outbufs;
+ if (EMPTY_LIST(*list)) {
+ s->unsent_obuf = obuf;
+ }
+ add_tail((list_t *)&s->outbufs, (node_t *)obuf);
+ s->obufs_size += obuf->len;
+ xconn->obufs_size += obuf->len;
+ xconn->xquic_table->obufs_size += obuf->len;
+
+ return obuf->buf + prefix;
+}
+
+void knot_xquic_stream_ack_data(knot_xquic_conn_t *xconn, int64_t stream_id,
+ size_t end_acked, bool keep_stream)
+{
+ knot_xquic_stream_t *s = knot_xquic_conn_get_stream(xconn, stream_id, false);
+ if (s == NULL) {
+ return;
+ }
+
+ list_t *obs = (list_t *)&s->outbufs;
+
+ knot_xquic_obuf_t *first;
+ while (!EMPTY_LIST(*obs) && end_acked >= (first = HEAD(*obs))->len + s->first_offset) {
+ rem_node((node_t *)first);
+ s->obufs_size -= first->len;
+ xconn->obufs_size -= first->len;
+ xconn->xquic_table->obufs_size -= first->len;
+ s->first_offset += first->len;
+ free(first);
+ if (s->unsent_obuf == first) {
+ s->unsent_obuf = EMPTY_LIST(*obs) ? NULL : HEAD(*obs);
+ s->unsent_offset = 0;
+ }
+ }
+
+ if (EMPTY_LIST(*obs) && !keep_stream) {
+ stream_outprocess(xconn, s);
+ memset(s, 0, sizeof(*s));
+ while (s = &xconn->streams[0], s->inbuf.iov_len == 0 && s->inbuf_fin == NULL && s->obufs_size == 0) {
+ assert(xconn->streams_count > 0);
+ xconn->streams_count--;
+
+ if (xconn->streams_count == 0) {
+ free(xconn->streams);
+ xconn->streams = 0;
+ xconn->streams_first = 0;
+ break;
+ } else {
+ xconn->streams_first++;
+ xconn->stream_inprocess--;
+ memmove(s, s + 1, sizeof(*s) * xconn->streams_count);
+ // possible realloc to shrink allocated space, but probably useless
+ for (knot_xquic_stream_t *si = s; si < s + xconn->streams_count; si++) {
+ if (si->obufs_size == 0) {
+ init_list((list_t *)&si->outbufs);
+ } else {
+ fix_list((list_t *)&si->outbufs);
+ }
+ }
+ }
+ }
+ }
+}
+
+void knot_xquic_stream_mark_sent(knot_xquic_conn_t *xconn, int64_t stream_id,
+ size_t amount_sent)
+{
+ knot_xquic_stream_t *s = knot_xquic_conn_get_stream(xconn, stream_id, false);
+ if (s == NULL) {
+ return;
+ }
+
+ s->unsent_offset += amount_sent;
+ assert(s->unsent_offset <= s->unsent_obuf->len);
+ if (s->unsent_offset == s->unsent_obuf->len) {
+ s->unsent_offset = 0;
+ s->unsent_obuf = (knot_xquic_obuf_t *)s->unsent_obuf->node.next;
+ if (s->unsent_obuf->node.next == NULL) { // already behind the tail of list
+ s->unsent_obuf = NULL;
+ }
+ }
+}
+
+_public_
+void knot_xquic_cleanup(knot_xquic_conn_t *conns[], size_t n_conns)
+{
+ for (size_t i = 0; i < n_conns; i++) {
+ if (conns[i] != NULL && conns[i]->conn == NULL) {
+ free(conns[i]);
+ for (size_t j = i + 1; j < n_conns; j++) {
+ if (conns[j] == conns[i]) {
+ conns[j] = NULL;
+ }
+ }
+ }
+ }
+}
+
+bool xquic_require_retry(knot_xquic_table_t *table)
+{
+ (void)table;
+ return false;
+}
diff --git a/src/libknot/xdp/quic_conn.h b/src/libknot/xdp/quic_conn.h
new file mode 100644
index 0000000..20ee176
--- /dev/null
+++ b/src/libknot/xdp/quic_conn.h
@@ -0,0 +1,314 @@
+/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*!
+ * \file
+ *
+ * \brief QUIC connection management.
+ *
+ * \addtogroup xdp
+ * @{
+ */
+
+#pragma once
+
+#include <linux/if_ether.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/uio.h>
+
+#define MAX_STREAMS_PER_CONN 10
+
+struct ngtcp2_cid; // declaration taken from wherever in ngtcp2
+struct knot_quic_creds;
+struct knot_sweep_stats;
+
+// those are equivalent to contrib/ucw/lists.h , just must not be included.
+typedef struct knot_xquic_ucw_node {
+ struct knot_xquic_ucw_node *next, *prev;
+} knot_xquic_ucw_node_t;
+typedef struct knot_xquic_ucw_list {
+ knot_xquic_ucw_node_t head, tail;
+} knot_xquic_ucw_list_t;
+
+typedef struct {
+ void *get_conn;
+ void *user_data;
+} nc_conn_ref_placeholder_t;
+
+typedef struct {
+ knot_xquic_ucw_node_t node;
+ size_t len;
+ uint8_t buf[];
+} knot_xquic_obuf_t;
+
+typedef struct {
+ struct iovec inbuf;
+ struct iovec *inbuf_fin;
+ knot_xquic_ucw_list_t outbufs;
+ size_t obufs_size;
+
+ knot_xquic_obuf_t *unsent_obuf;
+ size_t first_offset;
+ size_t unsent_offset;
+} knot_xquic_stream_t;
+
+typedef struct knot_xquic_conn {
+ knot_xquic_ucw_node_t timeout; // MUST be first field of the struct
+ uint64_t last_ts;
+
+ nc_conn_ref_placeholder_t conn_ref; // placeholder for internal struct ngtcp2_crypto_conn_ref
+
+ struct ngtcp2_conn *conn;
+
+ struct gnutls_session_int *tls_session;
+
+ knot_xquic_stream_t *streams;
+ int16_t streams_count; // number of allocated streams structures
+ int16_t stream_inprocess; // index of first stream that has complete incomming data to be processed (aka inbuf_fin)
+ bool handshake_done;
+ bool session_taken; // TODO ... ?
+ int64_t streams_first; // stream_id/4 of first allocated stream
+ size_t ibufs_size;
+ size_t obufs_size;
+
+ struct knot_xquic_table *xquic_table;
+
+ struct knot_xquic_conn *next;
+} knot_xquic_conn_t;
+
+typedef struct knot_xquic_cid {
+ uint8_t cid_placeholder[32];
+ knot_xquic_conn_t *conn;
+ struct knot_xquic_cid *next;
+} knot_xquic_cid_t;
+
+typedef struct knot_xquic_table {
+ size_t size;
+ size_t usage;
+ size_t pointers;
+ size_t max_conns;
+ size_t ibufs_max;
+ size_t obufs_max;
+ size_t ibufs_size;
+ size_t obufs_size;
+ size_t udp_payload_limit; // for simplicity not distinguishing IPv4/6
+ void (*log_cb)(const char *);
+ uint64_t hash_secret[4];
+ struct knot_quic_creds *creds;
+ knot_xquic_ucw_list_t timeout;
+ knot_xquic_cid_t *conns[];
+} knot_xquic_table_t;
+
+/*!
+ * \brief Allocate QUIC connections hash table.
+ *
+ * \param max_conns Maximum nuber of connections.
+ * \param max_ibufs Maximum size of buffers for fragmented incomming DNS msgs.
+ * \param max_obufs Maximum size of buffers for un-ACKed outgoing data.
+ * \param udp_pl Maximum UDP payload size (both IPv4 and 6).
+ * \param creds QUIC crypto context..
+ *
+ * \return Allocated table, or NULL.
+ */
+knot_xquic_table_t *knot_xquic_table_new(size_t max_conns, size_t max_ibufs, size_t max_obufs,
+ size_t udp_payload, struct knot_quic_creds *creds);
+
+/*!
+ * \brief Free QUIC table including its contents.
+ *
+ * \param table Table to be freed.
+ */
+void knot_xquic_table_free(knot_xquic_table_t *table);
+
+/*!
+ * \brief Close timed out connections and some oldest ones if table full.
+ *
+ * \param table QUIC table to be cleaned up.
+ * \param stats Out: sweep statistics.
+ *
+ * \return KNOT_E*
+ */
+int knot_xquic_table_sweep(knot_xquic_table_t *table, struct knot_sweep_stats *stats);
+
+/*!
+ * \brief Add new connection/CID link to table.
+ *
+ * \param xconn QUIC connection linked.
+ * \param cid New CID to be added.
+ * \param table QUIC table to be modified.
+ *
+ * \return Pointer on the CID reference in table, or NULL.
+ */
+knot_xquic_cid_t **xquic_table_insert(knot_xquic_conn_t *xconn,
+ const struct ngtcp2_cid *cid,
+ knot_xquic_table_t *table);
+
+/*!
+ * \brief Add new connection to the table, allocating conn struct.
+ *
+ * \param conn Ngtcp2 conn struct.
+ * \param cid CID to be linked (usually oscid for server).
+ * \param table QUIC table to be modified.
+ *
+ * \return Allocated (and linked) Knot conn struct, or NULL.
+ */
+knot_xquic_conn_t *xquic_table_add(struct ngtcp2_conn *conn,
+ const struct ngtcp2_cid *cid,
+ knot_xquic_table_t *table);
+
+/*!
+ * \brief Lookup connection/CID link in table.
+ *
+ * \param cid CID to be searched for.
+ * \param table QUIC table.
+ *
+ * \return Pointer on the CID reference in table, or NULL.
+ */
+knot_xquic_cid_t **xquic_table_lookup2(const struct ngtcp2_cid *cid,
+ knot_xquic_table_t *table);
+
+/*!
+ * \brief Lookup QUIC connection in table.
+ *
+ * \param cid CID to be searched for.
+ * \param table QUIC table.
+ *
+ * \return Connection that the CID belongs to, or NULL.
+ */
+knot_xquic_conn_t *xquic_table_lookup(const struct ngtcp2_cid *cid,
+ knot_xquic_table_t *table);
+
+/*!
+ * \brief Put the connection on the end of timeout queue.
+ */
+void xquic_conn_mark_used(knot_xquic_conn_t *conn, knot_xquic_table_t *table,
+ uint64_t now);
+
+/*!
+ * \brief Remove connection/CID link from table.
+ *
+ * \param pcid CID to be removed.
+ * \param table QUIC table.
+ */
+void xquic_table_rem2(knot_xquic_cid_t **pcid, knot_xquic_table_t *table);
+
+/*!
+ * \brief Remove specified stream from QUIC connection, freeing all buffers.
+ *
+ * \param xconn QUIC connection to remove from.
+ * \param stream_id Stream QUIC ID.
+ */
+void xquic_stream_free(knot_xquic_conn_t *xconn, int64_t stream_id);
+
+/*!
+ * \brief Remove and deinitialize connection completely.
+ *
+ * \param conn Connection to be removed.
+ * \param table Table to remove from.
+ */
+void knot_xquic_table_rem(knot_xquic_conn_t *conn, knot_xquic_table_t *table);
+
+/*!
+ * \brief Fetch or initialize a QUIC stream.
+ *
+ * \param xconn QUIC connection.
+ * \param stream_id Stream QUIC ID.
+ * \param create Trigger stream creation if not exists.
+ *
+ * \return Stream or NULL.
+ */
+knot_xquic_stream_t *knot_xquic_conn_get_stream(knot_xquic_conn_t *xconn,
+ int64_t stream_id, bool create);
+
+/*!
+ * \brief Process incomming stream data to stream structure.
+ *
+ * \param xconn QUIC connection that has received data.
+ * \param stream_id Stream QUIC ID of the incomming data.
+ * \param data Incomming payload data.
+ * \param len Incomming payload data length.
+ * \param fin FIN flag set for incomming data.
+ *
+ * \return KNOT_E*
+ */
+int knot_xquic_stream_recv_data(knot_xquic_conn_t *xconn, int64_t stream_id,
+ const uint8_t *data, size_t len, bool fin);
+
+/*!
+ * \brief Get next stream which has pending incomming data to be processed.
+ *
+ * \param xconn QUIC connection.
+ * \param stream_id Out: strem QUIC ID of the returned stream.
+ *
+ * \return Stream with incomming data.
+ */
+knot_xquic_stream_t *knot_xquic_stream_get_process(knot_xquic_conn_t *xconn,
+ int64_t *stream_id);
+
+/*!
+ * \brief Add outgiong data to the stream for sending.
+ *
+ * \param xconn QUIC connection that shall send data.
+ * \param stream_id Stream ID for outgoing data.
+ * \param data Data payload.
+ * \param len Data payload length.
+ *
+ * \return NULL if error, or pinter at the data in outgiong buffer.
+ */
+uint8_t *knot_xquic_stream_add_data(knot_xquic_conn_t *xconn, int64_t stream_id,
+ uint8_t *data, size_t len);
+
+/*!
+ * \brief Mark outgiong data as acknowledged after ACK received.
+ *
+ * \param xconn QUIC connection that received ACK.
+ * \param stream_id Stream ID of ACKed data.
+ * \param end_acked Offset of ACKed data + ACKed length.
+ * \param keep_stream Don't free the stream even when ACKed all outgoing data.
+ */
+void knot_xquic_stream_ack_data(knot_xquic_conn_t *xconn, int64_t stream_id,
+ size_t end_acked, bool keep_stream);
+
+/*!
+ * \brief Mark outgoing data as sent.
+ *
+ * \param xconn QUIC connection that sent data.
+ * \param stream_id Stream ID of sent data.
+ * \param amount_sent Length of sent data.
+ */
+void knot_xquic_stream_mark_sent(knot_xquic_conn_t *xconn, int64_t stream_id,
+ size_t amount_sent);
+
+/*!
+ * \brief Free rest of resources of closed conns.
+ *
+ * \param conns Array with recently used conns (possibly NULLs).
+ * \param n_conns Size of the array.
+ */
+void knot_xquic_cleanup(knot_xquic_conn_t *conns[], size_t n_conns);
+
+/*!
+ * \brief Toggle sending Retry packet as a reaction to Initial packet of new connection.
+ *
+ * \param table Connection table.
+ *
+ * \return True if instead of continuing handshake, Retry packet shall be sent
+ * to verify counterpart's address.
+ */
+bool xquic_require_retry(knot_xquic_table_t *table);
+
+/*! @} */
diff --git a/src/libknot/xdp/tcp.c b/src/libknot/xdp/tcp.c
new file mode 100644
index 0000000..752e9ef
--- /dev/null
+++ b/src/libknot/xdp/tcp.c
@@ -0,0 +1,729 @@
+/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "libknot/xdp/tcp.h"
+#include "libknot/xdp/tcp_iobuf.h"
+#include "libknot/attribute.h"
+#include "libknot/error.h"
+#include "libdnssec/random.h"
+#include "contrib/macros.h"
+#include "contrib/openbsd/siphash.h"
+#include "contrib/ucw/lists.h"
+
+static uint32_t get_timestamp(void)
+{
+ struct timespec t;
+ clock_gettime(CLOCK_MONOTONIC, &t);
+ uint64_t res = (uint64_t)t.tv_sec * 1000000;
+ res += (uint64_t)t.tv_nsec / 1000;
+ return res & 0xffffffff; // overflow does not matter since we are working with differences
+}
+
+static size_t sockaddr_data_len(const struct sockaddr_in6 *rem, const struct sockaddr_in6 *loc)
+{
+ assert(rem->sin6_family == loc->sin6_family);
+ if (rem->sin6_family == AF_INET) {
+ return offsetof(struct sockaddr_in, sin_zero);
+ } else {
+ assert(rem->sin6_family == AF_INET6);
+ return offsetof(struct sockaddr_in6, sin6_scope_id);
+ }
+}
+
+static uint64_t hash_four_tuple(const struct sockaddr_in6 *rem, const struct sockaddr_in6 *loc,
+ knot_tcp_table_t *table)
+{
+ size_t socka_data_len = sockaddr_data_len(rem, loc);
+ SIPHASH_CTX ctx;
+ SipHash24_Init(&ctx, (const SIPHASH_KEY *)(table->hash_secret));
+ SipHash24_Update(&ctx, rem, socka_data_len);
+ SipHash24_Update(&ctx, loc, socka_data_len);
+ return SipHash24_End(&ctx);
+}
+
+static list_t *tcp_table_timeout(knot_tcp_table_t *table)
+{
+ return (list_t *)&table->conns[table->size];
+}
+
+static node_t *tcp_conn_node(knot_tcp_conn_t *conn)
+{
+ return (node_t *)&conn->list_node_placeholder;
+}
+
+static void next_node_ptr(knot_tcp_conn_t **ptr)
+{
+ if (*ptr != NULL) {
+ *ptr = (*ptr)->list_node_placeholder.list_node_next;
+ if ((*ptr)->list_node_placeholder.list_node_next == NULL) { // detected tail of list
+ *ptr = NULL;
+ }
+ }
+}
+
+static void next_ptr_ibuf(knot_tcp_conn_t **ptr)
+{
+ do {
+ next_node_ptr(ptr);
+ } while (*ptr != NULL && (*ptr)->inbuf.iov_len == 0);
+}
+
+static void next_ptr_obuf(knot_tcp_conn_t **ptr)
+{
+ do {
+ next_node_ptr(ptr);
+ } while (*ptr != NULL && knot_tcp_outbufs_usage((*ptr)->outbufs) == 0);
+}
+
+_public_
+knot_tcp_table_t *knot_tcp_table_new(size_t size, knot_tcp_table_t *secret_share)
+{
+ knot_tcp_table_t *table = calloc(1, sizeof(*table) + sizeof(list_t) +
+ size * sizeof(table->conns[0]));
+ if (table == NULL) {
+ return table;
+ }
+
+ table->size = size;
+ init_list(tcp_table_timeout(table));
+
+ assert(sizeof(table->hash_secret) == sizeof(SIPHASH_KEY));
+ if (secret_share == NULL) {
+ table->hash_secret[0] = dnssec_random_uint64_t();
+ table->hash_secret[1] = dnssec_random_uint64_t();
+ } else {
+ table->hash_secret[0] = secret_share->hash_secret[0];
+ table->hash_secret[1] = secret_share->hash_secret[1];
+ }
+
+ return table;
+}
+
+static void del_conn(knot_tcp_conn_t *conn)
+{
+ if (conn != NULL) {
+ free(conn->inbuf.iov_base);
+ while (conn->outbufs != NULL) {
+ struct knot_tcp_outbuf *next = conn->outbufs->next;
+ free(conn->outbufs);
+ conn->outbufs = next;
+ }
+ free(conn);
+ }
+}
+
+_public_
+void knot_tcp_table_free(knot_tcp_table_t *table)
+{
+ if (table != NULL) {
+ knot_tcp_conn_t *conn, *next;
+ WALK_LIST_DELSAFE(conn, next, *tcp_table_timeout(table)) {
+ del_conn(conn);
+ }
+ free(table);
+ }
+}
+
+static knot_tcp_conn_t **tcp_table_lookup(const struct sockaddr_in6 *rem,
+ const struct sockaddr_in6 *loc,
+ uint64_t *hash, knot_tcp_table_t *table)
+{
+ if (*hash == 0) {
+ *hash = hash_four_tuple(rem, loc, table);
+ }
+ size_t sdl = sockaddr_data_len(rem, loc);
+ knot_tcp_conn_t **res = table->conns + (*hash % table->size);
+ while (*res != NULL) {
+ if (memcmp(&(*res)->ip_rem, rem, sdl) == 0 &&
+ memcmp(&(*res)->ip_loc, loc, sdl) == 0) {
+ break;
+ }
+ res = &(*res)->next;
+ }
+ return res;
+}
+
+static knot_tcp_conn_t **tcp_table_re_lookup(knot_tcp_conn_t *conn,
+ knot_tcp_table_t *table)
+{
+ uint64_t unused_hash = 0;
+ knot_tcp_conn_t **res = tcp_table_lookup(&conn->ip_rem, &conn->ip_loc,
+ &unused_hash, table);
+ assert(*res == conn);
+ return res;
+}
+
+static void rem_align_pointers(knot_tcp_conn_t *to_rem, knot_tcp_table_t *table)
+{
+ if (to_rem == table->next_close) {
+ next_node_ptr(&table->next_close);
+ }
+ if (to_rem == table->next_ibuf) {
+ next_ptr_ibuf(&table->next_ibuf);
+ }
+ if (to_rem == table->next_obuf) {
+ next_ptr_obuf(&table->next_obuf);
+ }
+ if (to_rem == table->next_resend) {
+ next_ptr_obuf(&table->next_resend);
+ }
+}
+
+static void tcp_table_remove_conn(knot_tcp_conn_t **todel)
+{
+ rem_node(tcp_conn_node(*todel)); // remove from timeout double-linked list
+ *todel = (*todel)->next; // remove from conn-table linked list
+}
+
+static void tcp_table_remove(knot_tcp_conn_t **todel, knot_tcp_table_t *table)
+{
+ assert(table->usage > 0);
+ rem_align_pointers(*todel, table);
+ table->inbufs_total -= (*todel)->inbuf.iov_len;
+ table->outbufs_total -= knot_tcp_outbufs_usage((*todel)->outbufs);
+ tcp_table_remove_conn(todel);
+ table->usage--;
+}
+
+static void conn_init_from_msg(knot_tcp_conn_t *conn, knot_xdp_msg_t *msg)
+{
+ memcpy(&conn->ip_rem, &msg->ip_from, sizeof(conn->ip_rem));
+ memcpy(&conn->ip_loc, &msg->ip_to, sizeof(conn->ip_loc));
+
+ memcpy(&conn->last_eth_rem, &msg->eth_from, sizeof(conn->last_eth_rem));
+ memcpy(&conn->last_eth_loc, &msg->eth_to, sizeof(conn->last_eth_loc));
+
+ conn->seqno = msg->seqno;
+ conn->ackno = msg->ackno;
+ conn->acked = msg->ackno;
+
+ conn->last_active = get_timestamp();
+ conn->state = XDP_TCP_NORMAL;
+ conn->establish_rtt = 0;
+
+ memset(&conn->inbuf, 0, sizeof(conn->inbuf));
+ memset(&conn->outbufs, 0, sizeof(conn->outbufs));
+}
+
+static void tcp_table_insert(knot_tcp_conn_t *conn, uint64_t hash,
+ knot_tcp_table_t *table)
+{
+ knot_tcp_conn_t **addto = table->conns + (hash % table->size);
+ add_tail(tcp_table_timeout(table), tcp_conn_node(conn));
+ if (table->next_close == NULL) {
+ table->next_close = conn;
+ }
+ conn->next = *addto;
+ *addto = conn;
+ table->usage++;
+}
+
+// WARNING you shall ensure that it's not in the table already!
+static int tcp_table_add(knot_xdp_msg_t *msg, uint64_t hash, knot_tcp_table_t *table,
+ knot_tcp_conn_t **res)
+{
+ knot_tcp_conn_t *c = malloc(sizeof(*c));
+ if (c == NULL) {
+ return KNOT_ENOMEM;
+ }
+ conn_init_from_msg(c, msg);
+ tcp_table_insert(c, hash, table);
+ *res = c;
+ return KNOT_EOK;
+}
+
+static bool check_seq_ack(const knot_xdp_msg_t *msg, const knot_tcp_conn_t *conn)
+{
+ if (conn == NULL || conn->seqno != msg->seqno) {
+ return false;
+ }
+
+ if (conn->acked <= conn->ackno) { // ackno does not wrap around uint32
+ return (msg->ackno >= conn->acked && msg->ackno <= conn->ackno);
+ } else { // this is more tricky
+ return (msg->ackno >= conn->acked || msg->ackno <= conn->ackno);
+ }
+}
+
+static void conn_update(knot_tcp_conn_t *conn, const knot_xdp_msg_t *msg)
+{
+ conn->seqno = knot_tcp_next_seqno(msg);
+ memcpy(conn->last_eth_rem, msg->eth_from, sizeof(conn->last_eth_rem));
+ memcpy(conn->last_eth_loc, msg->eth_to, sizeof(conn->last_eth_loc));
+ conn->window_size = (uint32_t)msg->win * (1LU << conn->window_scale);
+
+ uint32_t now = get_timestamp();
+ if (conn->establish_rtt == 0 && conn->last_active != 0) {
+ conn->establish_rtt = now - conn->last_active;
+ }
+ conn->last_active = now;
+}
+
+_public_
+int knot_tcp_recv(knot_tcp_relay_t *relays, knot_xdp_msg_t msgs[], uint32_t msg_count,
+ knot_tcp_table_t *tcp_table, knot_tcp_table_t *syn_table,
+ knot_tcp_ignore_t ignore)
+{
+ if (msg_count == 0) {
+ return KNOT_EOK;
+ }
+ if (relays == NULL || msgs == NULL || tcp_table == NULL) {
+ return KNOT_EINVAL;
+ }
+ memset(relays, 0, msg_count * sizeof(*relays));
+
+ knot_tcp_relay_t *relay = relays;
+ int ret = KNOT_EOK;
+
+ for (knot_xdp_msg_t *msg = msgs; msg != msgs + msg_count && ret == KNOT_EOK; msg++) {
+ if (!(msg->flags & KNOT_XDP_MSG_TCP)) {
+ continue;
+ }
+
+ uint64_t conn_hash = 0;
+ knot_tcp_conn_t **pconn = tcp_table_lookup(&msg->ip_from, &msg->ip_to,
+ &conn_hash, tcp_table);
+ knot_tcp_conn_t *conn = *pconn;
+ bool seq_ack_match = check_seq_ack(msg, conn);
+ if (seq_ack_match) {
+ assert(conn->mss != 0);
+ conn_update(conn, msg);
+
+ rem_align_pointers(conn, tcp_table);
+ rem_node(tcp_conn_node(conn));
+ add_tail(tcp_table_timeout(tcp_table), tcp_conn_node(conn));
+
+ if (msg->flags & KNOT_XDP_MSG_ACK) {
+ conn->acked = msg->ackno;
+ knot_tcp_outbufs_ack(&conn->outbufs, msg->ackno, &tcp_table->outbufs_total);
+ }
+ }
+
+ relay->msg = msg;
+ relay->conn = conn;
+
+ // process incoming data
+ if (seq_ack_match && (msg->flags & KNOT_XDP_MSG_ACK) && msg->payload.iov_len > 0) {
+ if (!(ignore & XDP_TCP_IGNORE_DATA_ACK)) {
+ relay->auto_answer = KNOT_XDP_MSG_ACK;
+ }
+ ret = knot_tcp_inbuf_update(&conn->inbuf, msg->payload, &relay->inbufs,
+ &relay->inbufs_count, &tcp_table->inbufs_total);
+ if (ret != KNOT_EOK) {
+ break;
+ }
+ if (conn->inbuf.iov_len > 0 && tcp_table->next_ibuf == NULL) {
+ tcp_table->next_ibuf = conn;
+ }
+ }
+
+ // process TCP connection state
+ switch (msg->flags & (KNOT_XDP_MSG_SYN | KNOT_XDP_MSG_ACK |
+ KNOT_XDP_MSG_FIN | KNOT_XDP_MSG_RST)) {
+ case KNOT_XDP_MSG_SYN:
+ case (KNOT_XDP_MSG_SYN | KNOT_XDP_MSG_ACK):
+ if (conn == NULL) {
+ bool synack = (msg->flags & KNOT_XDP_MSG_ACK);
+
+ knot_tcp_table_t *add_table = tcp_table;
+ if (syn_table != NULL && !synack) {
+ add_table = syn_table;
+ if (*tcp_table_lookup(&msg->ip_from, &msg->ip_to, &conn_hash, syn_table) != NULL) {
+ break;
+ }
+ }
+
+ ret = tcp_table_add(msg, conn_hash, add_table, &relay->conn);
+ if (ret == KNOT_EOK) {
+ relay->action = synack ? XDP_TCP_ESTABLISH : XDP_TCP_SYN;
+ if (!(ignore & XDP_TCP_IGNORE_ESTABLISH)) {
+ relay->auto_answer = synack ? KNOT_XDP_MSG_ACK : (KNOT_XDP_MSG_SYN | KNOT_XDP_MSG_ACK);
+ }
+
+ conn = relay->conn;
+ conn->state = synack ? XDP_TCP_NORMAL: XDP_TCP_ESTABLISHING;
+ conn->mss = MAX(msg->mss, 536); // minimal MSS, most importantly not zero!
+ conn->window_scale = msg->win_scale;
+ conn_update(conn, msg);
+ if (!synack) {
+ conn->acked = dnssec_random_uint32_t();
+ conn->ackno = conn->acked;
+ }
+ }
+ } else {
+ relay->auto_answer = KNOT_XDP_MSG_ACK;
+ }
+ break;
+ case KNOT_XDP_MSG_ACK:
+ if (!seq_ack_match) {
+ if (syn_table != NULL && msg->payload.iov_len == 0 &&
+ (pconn = tcp_table_lookup(&msg->ip_from, &msg->ip_to, &conn_hash, syn_table)) != NULL &&
+ (conn = *pconn) != NULL && check_seq_ack(msg, conn)) {
+ // move conn from syn_table to tcp_table
+ tcp_table_remove(pconn, syn_table);
+ tcp_table_insert(conn, conn_hash, tcp_table);
+ relay->conn = conn;
+ relay->action = XDP_TCP_ESTABLISH;
+ conn->state = XDP_TCP_NORMAL;
+ conn_update(conn, msg);
+ }
+ } else {
+ switch (conn->state) {
+ case XDP_TCP_NORMAL:
+ case XDP_TCP_CLOSING1: // just a mess, ignore
+ break;
+ case XDP_TCP_ESTABLISHING:
+ conn->state = XDP_TCP_NORMAL;
+ relay->action = XDP_TCP_ESTABLISH;
+ break;
+ case XDP_TCP_CLOSING2:
+ if (msg->payload.iov_len == 0) { // otherwise ignore close
+ tcp_table_remove(pconn, tcp_table);
+ relay->answer = XDP_TCP_FREE;
+ }
+ break;
+ }
+ }
+ break;
+ case (KNOT_XDP_MSG_FIN | KNOT_XDP_MSG_ACK):
+ if (ignore & XDP_TCP_IGNORE_FIN) {
+ break;
+ }
+ if (!seq_ack_match) {
+ if (conn != NULL) {
+ relay->auto_answer = KNOT_XDP_MSG_RST;
+ relay->auto_seqno = msg->ackno;
+ } // else ignore. It would be better and possible, but no big value for the price of CPU.
+ } else {
+ if (conn->state == XDP_TCP_CLOSING1) {
+ relay->action = XDP_TCP_CLOSE;
+ relay->auto_answer = KNOT_XDP_MSG_ACK;
+ relay->answer = XDP_TCP_FREE;
+ tcp_table_remove(pconn, tcp_table);
+ } else if (msg->payload.iov_len == 0) { // otherwise ignore FIN
+ relay->action = XDP_TCP_CLOSE;
+ relay->auto_answer = KNOT_XDP_MSG_FIN | KNOT_XDP_MSG_ACK;
+ conn->state = XDP_TCP_CLOSING2;
+ }
+ }
+ break;
+ case KNOT_XDP_MSG_RST:
+ if (conn != NULL && msg->seqno == conn->seqno) {
+ relay->action = XDP_TCP_RESET;
+ tcp_table_remove(pconn, tcp_table);
+ relay->answer = XDP_TCP_FREE;
+ } else if (conn != NULL) {
+ relay->auto_answer = KNOT_XDP_MSG_ACK;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (!knot_tcp_relay_empty(relay)) {
+ relay++;
+ }
+ }
+
+ return ret;
+}
+
+_public_
+int knot_tcp_reply_data(knot_tcp_relay_t *relay, knot_tcp_table_t *tcp_table,
+ bool ignore_lastbyte, uint8_t *data, uint32_t len)
+{
+ if (relay == NULL || tcp_table == NULL || relay->conn == NULL) {
+ return KNOT_EINVAL;
+ }
+ int ret = knot_tcp_outbufs_add(&relay->conn->outbufs, data, len, ignore_lastbyte,
+ relay->conn->mss, &tcp_table->outbufs_total);
+
+ if (tcp_table->next_obuf == NULL && knot_tcp_outbufs_usage(relay->conn->outbufs) > 0) {
+ tcp_table->next_obuf = relay->conn;
+ }
+ if (tcp_table->next_resend == NULL && knot_tcp_outbufs_usage(relay->conn->outbufs) > 0) {
+ tcp_table->next_resend = relay->conn;
+ }
+ return ret;
+}
+
+static knot_xdp_msg_t *first_msg(knot_xdp_msg_t *msgs, uint32_t n_msgs)
+{
+ memset(msgs, 0, n_msgs * sizeof(*msgs));
+ return msgs - 1; // will be incremented just before first use
+}
+
+static int send_msgs(knot_xdp_msg_t *msgs, uint32_t n_msgs, knot_xdp_socket_t *socket)
+{
+ assert(socket);
+ assert(msgs);
+
+ if (n_msgs > 0) {
+ uint32_t unused;
+ return knot_xdp_send(socket, msgs, n_msgs, &unused);
+ }
+
+ return KNOT_EOK;
+}
+
+static void msg_init_from_conn(knot_xdp_msg_t *msg, knot_tcp_conn_t *conn)
+{
+ memcpy( msg->eth_from, conn->last_eth_loc, sizeof(msg->eth_from));
+ memcpy( msg->eth_to, conn->last_eth_rem, sizeof(msg->eth_to));
+ memcpy(&msg->ip_from, &conn->ip_loc, sizeof(msg->ip_from));
+ memcpy(&msg->ip_to, &conn->ip_rem, sizeof(msg->ip_to));
+
+ msg->ackno = conn->seqno;
+ msg->seqno = conn->ackno;
+
+ msg->payload.iov_len = 0;
+
+ msg->win_scale = 14; // maximum possible
+ msg->win = 0xffff;
+}
+
+static int next_msg(knot_xdp_msg_t *msgs, uint32_t n_msgs, knot_xdp_msg_t **cur,
+ knot_xdp_socket_t *socket, knot_tcp_relay_t *rl)
+{
+ (*cur)++;
+ if (*cur - msgs >= n_msgs) {
+ int ret = send_msgs(msgs, n_msgs, socket);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ *cur = first_msg(msgs, n_msgs);
+ (*cur)++;
+ }
+
+ knot_xdp_msg_t *msg = *cur;
+
+ knot_xdp_msg_flag_t fl = KNOT_XDP_MSG_TCP;
+ if (rl->conn->ip_loc.sin6_family == AF_INET6) {
+ fl |= KNOT_XDP_MSG_IPV6;
+ }
+ if (rl->conn->state == XDP_TCP_ESTABLISHING) {
+ fl |= KNOT_XDP_MSG_MSS | KNOT_XDP_MSG_WSC;
+ }
+
+ int ret = knot_xdp_send_alloc(socket, fl, msg);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ msg_init_from_conn(msg, rl->conn);
+
+ return ret;
+}
+
+_public_
+int knot_tcp_send(knot_xdp_socket_t *socket, knot_tcp_relay_t relays[],
+ uint32_t relay_count, uint32_t max_at_once)
+{
+ if (relay_count == 0) {
+ return KNOT_EOK;
+ }
+ if (socket == NULL || relays == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ knot_xdp_msg_t msgs[max_at_once], *first = first_msg(msgs, max_at_once), *msg = first;
+
+ for (uint32_t i = 0; i < relay_count; i++) {
+ knot_tcp_relay_t *rl = &relays[i];
+
+#define NEXT_MSG { \
+ int ret = next_msg(msgs, max_at_once, &msg, socket, rl); \
+ if (ret != KNOT_EOK) { return ret; } \
+}
+
+ if (rl->auto_answer != 0) {
+ NEXT_MSG
+ msg->flags |= rl->auto_answer;
+ if (msg->flags & (KNOT_XDP_MSG_SYN | KNOT_XDP_MSG_FIN)) {
+ rl->conn->ackno++;
+ }
+ if (rl->auto_answer == KNOT_XDP_MSG_RST) {
+ msg->seqno = rl->auto_seqno;
+ }
+ }
+
+ switch (rl->answer & 0x0f) {
+ case XDP_TCP_ESTABLISH:
+ NEXT_MSG
+ msg->flags |= KNOT_XDP_MSG_SYN;
+ rl->conn->ackno++;
+ break;
+ case XDP_TCP_CLOSE:
+ NEXT_MSG
+ msg->flags |= (KNOT_XDP_MSG_FIN | KNOT_XDP_MSG_ACK);
+ rl->conn->ackno++;
+ rl->conn->state = XDP_TCP_CLOSING1;
+ break;
+ case XDP_TCP_RESET:
+ NEXT_MSG
+ msg->flags |= KNOT_XDP_MSG_RST;
+ break;
+ case XDP_TCP_NOOP:
+ default:
+ break;
+ }
+
+ size_t can_data = 0;
+ knot_tcp_outbuf_t *ob;
+ if (rl->conn != NULL) {
+ knot_tcp_outbufs_can_send(rl->conn->outbufs, rl->conn->window_size,
+ rl->answer == XDP_TCP_RESEND, &ob, &can_data);
+ }
+ while (can_data > 0) {
+ NEXT_MSG
+ msg->flags |= KNOT_XDP_MSG_ACK;
+ msg->payload.iov_len = ob->len;
+ memcpy(msg->payload.iov_base, ob->bytes, ob->len);
+
+ if (!ob->sent) {
+ assert(rl->conn->ackno == msg->seqno);
+ rl->conn->ackno += msg->payload.iov_len;
+ } else {
+ msg->seqno = ob->seqno;
+ }
+
+ ob->sent = true;
+ ob->seqno = msg->seqno;
+
+ can_data--;
+ ob = ob->next;
+ }
+#undef NEXT_MSG
+ }
+
+ return send_msgs(msgs, msg - first, socket);
+}
+
+static void sweep_reset(knot_tcp_table_t *tcp_table, knot_tcp_relay_t *rl,
+ ssize_t *free_conns, ssize_t *free_inbuf, ssize_t *free_outbuf,
+ knot_sweep_stats_t *stats, knot_sweep_counter_t counter)
+{
+ rl->answer = XDP_TCP_RESET | XDP_TCP_FREE;
+ tcp_table_remove(tcp_table_re_lookup(rl->conn, tcp_table), tcp_table); // also updates tcp_table->next_*
+
+ *free_conns -= 1;
+ *free_inbuf -= rl->conn->inbuf.iov_len;
+ *free_outbuf -= knot_tcp_outbufs_usage(rl->conn->outbufs);
+
+ knot_sweep_stats_incr(stats, counter);
+}
+
+_public_
+int knot_tcp_sweep(knot_tcp_table_t *tcp_table,
+ uint32_t close_timeout, uint32_t reset_timeout,
+ uint32_t resend_timeout, uint32_t limit_conn_count,
+ size_t limit_ibuf_size, size_t limit_obuf_size,
+ knot_tcp_relay_t *relays, uint32_t max_relays,
+ struct knot_sweep_stats *stats)
+{
+ if (tcp_table == NULL || relays == NULL || max_relays < 1) {
+ return KNOT_EINVAL;
+ }
+
+ uint32_t now = get_timestamp();
+ memset(relays, 0, max_relays * sizeof(*relays));
+ knot_tcp_relay_t *rl = relays, *rl_max = rl + max_relays;
+
+ ssize_t free_conns = (ssize_t)(tcp_table->usage - limit_conn_count);
+ ssize_t free_inbuf = (ssize_t)(tcp_table->inbufs_total - MIN(limit_ibuf_size, SSIZE_MAX));
+ ssize_t free_outbuf = (ssize_t)(tcp_table->outbufs_total - MIN(limit_obuf_size, SSIZE_MAX));
+
+ // reset connections to free ibufs
+ while (free_inbuf > 0 && rl != rl_max) {
+ if (tcp_table->next_ibuf->inbuf.iov_len == 0) { // this conn might have get rid of ibuf in the meantime
+ next_ptr_ibuf(&tcp_table->next_ibuf);
+ }
+ assert(tcp_table->next_ibuf != NULL);
+ rl->conn = tcp_table->next_ibuf;
+ sweep_reset(tcp_table, rl, &free_conns, &free_inbuf, &free_outbuf,
+ stats, KNOT_SWEEP_CTR_LIMIT_IBUF);
+ rl++;
+ }
+
+ // reset connections to free obufs
+ while (free_outbuf > 0 && rl != rl_max) {
+ if (knot_tcp_outbufs_usage(tcp_table->next_obuf->outbufs) == 0) {
+ next_ptr_obuf(&tcp_table->next_obuf);
+ }
+ assert(tcp_table->next_obuf != NULL);
+ rl->conn = tcp_table->next_obuf;
+ sweep_reset(tcp_table, rl, &free_conns, &free_inbuf, &free_outbuf,
+ stats, KNOT_SWEEP_CTR_LIMIT_OBUF);
+ rl++;
+ }
+
+ // reset connections to free their count, and old ones
+ knot_tcp_conn_t *conn, *next;
+ WALK_LIST_DELSAFE(conn, next, *tcp_table_timeout(tcp_table)) {
+ if ((free_conns <= 0 && now - conn->last_active < reset_timeout) || rl == rl_max) {
+ break;
+ }
+
+ rl->conn = conn;
+ sweep_reset(tcp_table, rl, &free_conns, &free_inbuf, &free_outbuf,
+ stats, KNOT_SWEEP_CTR_LIMIT_CONN);
+ rl++;
+ }
+
+ // close old connections
+ while (tcp_table->next_close != NULL &&
+ now - tcp_table->next_close->last_active >= close_timeout &&
+ rl != rl_max) {
+ if (tcp_table->next_close->state != XDP_TCP_CLOSING1) {
+ rl->conn = tcp_table->next_close;
+ rl->answer = XDP_TCP_CLOSE;
+ knot_sweep_stats_incr(stats, KNOT_SWEEP_CTR_TIMEOUT);
+ rl++;
+ }
+ next_node_ptr(&tcp_table->next_close);
+ }
+
+ // resend unACKed data
+ while (tcp_table->next_resend != NULL &&
+ now - tcp_table->next_resend->last_active >= resend_timeout &&
+ rl != rl_max) {
+ rl->conn = tcp_table->next_resend;
+ rl->answer = XDP_TCP_RESEND;
+ rl++;
+ next_ptr_obuf(&tcp_table->next_resend);
+ }
+
+ return KNOT_EOK;
+}
+
+_public_
+void knot_tcp_cleanup(knot_tcp_table_t *tcp_table, knot_tcp_relay_t relays[],
+ uint32_t relay_count)
+{
+ (void)tcp_table;
+ for (uint32_t i = 0; i < relay_count; i++) {
+ if (relays[i].answer & XDP_TCP_FREE) {
+ del_conn(relays[i].conn);
+ }
+ free(relays[i].inbufs);
+ }
+}
diff --git a/src/libknot/xdp/tcp.h b/src/libknot/xdp/tcp.h
new file mode 100644
index 0000000..da0f1a4
--- /dev/null
+++ b/src/libknot/xdp/tcp.h
@@ -0,0 +1,227 @@
+/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*!
+ * \file
+ *
+ * \brief TCP over XDP IO interface.
+ *
+ * \addtogroup xdp
+ * @{
+ */
+
+#pragma once
+
+#include "libknot/xdp/msg.h"
+#include "libknot/xdp/xdp.h"
+
+struct knot_sweep_stats;
+
+typedef enum {
+ XDP_TCP_NOOP = 0,
+ XDP_TCP_SYN = 1,
+ XDP_TCP_ESTABLISH = 2,
+ XDP_TCP_CLOSE = 3,
+ XDP_TCP_RESET = 4,
+ XDP_TCP_RESEND = 5,
+
+ XDP_TCP_FREE = 0x10,
+} knot_tcp_action_t;
+
+typedef enum {
+ XDP_TCP_NORMAL,
+ XDP_TCP_ESTABLISHING,
+ XDP_TCP_CLOSING1, // FIN+ACK sent
+ XDP_TCP_CLOSING2, // FIN+ACK received and sent
+} knot_tcp_state_t;
+
+typedef enum {
+ XDP_TCP_FREE_NONE,
+ XDP_TCP_FREE_DATA,
+ XDP_TCP_FREE_PREFIX,
+} knot_tcp_relay_free_t;
+
+typedef enum {
+ XDP_TCP_IGNORE_NONE = 0,
+ XDP_TCP_IGNORE_ESTABLISH = (1 << 0),
+ XDP_TCP_IGNORE_DATA_ACK = (1 << 1),
+ XDP_TCP_IGNORE_FIN = (1 << 2),
+} knot_tcp_ignore_t;
+
+typedef struct knot_tcp_conn {
+ struct {
+ struct knot_tcp_conn *list_node_next;
+ struct knot_tcp_conn *list_node_prev;
+ } list_node_placeholder;
+ struct sockaddr_in6 ip_rem;
+ struct sockaddr_in6 ip_loc;
+ uint8_t last_eth_rem[ETH_ALEN];
+ uint8_t last_eth_loc[ETH_ALEN];
+ uint16_t mss;
+ uint8_t window_scale;
+ uint32_t seqno;
+ uint32_t ackno;
+ uint32_t acked;
+ uint32_t window_size;
+ uint32_t last_active;
+ uint32_t establish_rtt; // in microseconds
+ knot_tcp_state_t state;
+ struct iovec inbuf;
+ struct knot_tcp_outbuf *outbufs;
+ struct knot_tcp_conn *next;
+} knot_tcp_conn_t;
+
+typedef struct {
+ size_t size;
+ size_t usage;
+ size_t inbufs_total;
+ size_t outbufs_total;
+ uint64_t hash_secret[2];
+ knot_tcp_conn_t *next_close;
+ knot_tcp_conn_t *next_ibuf;
+ knot_tcp_conn_t *next_obuf;
+ knot_tcp_conn_t *next_resend;
+ knot_tcp_conn_t *conns[];
+} knot_tcp_table_t;
+
+typedef struct {
+ const knot_xdp_msg_t *msg;
+ knot_tcp_action_t action;
+ knot_xdp_msg_flag_t auto_answer;
+ uint32_t auto_seqno;
+ knot_tcp_action_t answer;
+ struct iovec *inbufs;
+ size_t inbufs_count;
+ knot_tcp_conn_t *conn;
+} knot_tcp_relay_t;
+
+/*!
+ * \brief Return next TCP sequence number.
+ */
+inline static uint32_t knot_tcp_next_seqno(const knot_xdp_msg_t *msg)
+{
+ uint32_t res = msg->seqno + msg->payload.iov_len;
+ if (msg->flags & (KNOT_XDP_MSG_SYN | KNOT_XDP_MSG_FIN)) {
+ res++;
+ }
+ return res;
+}
+
+/*!
+ * \brief Check if the relay is empty.
+ */
+inline static bool knot_tcp_relay_empty(const knot_tcp_relay_t *relay)
+{
+ return relay->action == XDP_TCP_NOOP && relay->answer == XDP_TCP_NOOP &&
+ relay->auto_answer == 0 && relay->inbufs_count == 0;
+}
+
+/*!
+ * \brief Allocate TCP connection-handling hash table.
+ *
+ * \param size Number of records for the hash table.
+ * \param secret_share Optional: share the hashing secret with another table.
+ *
+ * \note Hashing conflicts are solved by single-linked-lists in each record.
+ *
+ * \return The table, or NULL.
+ */
+knot_tcp_table_t *knot_tcp_table_new(size_t size, knot_tcp_table_t *secret_share);
+
+/*!
+ * \brief Free TCP connection hash table including all connection records.
+ *
+ * \note The freed connections are not closed nor reset.
+ */
+void knot_tcp_table_free(knot_tcp_table_t *table);
+
+/*!
+ * \brief Process received packets, prepare automatic responses (e.g. ACK), pick incoming data.
+ *
+ * \param relays Out: relays to be filled with message/connection details.
+ * \param msgs Packets received by knot_xdp_recv().
+ * \param msg_count Number of received packets.
+ * \param tcp_table Table of TCP connections.
+ * \param syn_table Optional: extra table for handling partially established connections.
+ * \param ignore Ignore specific TCP packets indication.
+ *
+ * \return KNOT_E*
+ */
+int knot_tcp_recv(knot_tcp_relay_t *relays, knot_xdp_msg_t msgs[], uint32_t msg_count,
+ knot_tcp_table_t *tcp_table, knot_tcp_table_t *syn_table,
+ knot_tcp_ignore_t ignore);
+
+/*!
+ * \brief Prepare data (payload) to be sent as a response on specific relay.
+ *
+ * \param relay Relay with active connection.
+ * \param tcp_table TCP table.
+ * \param ignore_lastbyte Evil mode: drop last byte of the payload.
+ * \param data Data payload, possibly > MSS and > window.
+ * \param len Payload length, < 64k.
+ *
+ * \return KNOT_E*
+ */
+int knot_tcp_reply_data(knot_tcp_relay_t *relay, knot_tcp_table_t *tcp_table,
+ bool ignore_lastbyte, uint8_t *data, uint32_t len);
+
+/*!
+ * \brief Send TCP packets.
+ *
+ * \param socket XDP socket to send through.
+ * \param relays Connection changes and data.
+ * \param relay_count Number of connection changes and data.
+ * \param max_at_once Limit of packet batch sent by knot_xdp_send().
+ *
+ * \return KNOT_E*
+ */
+int knot_tcp_send(knot_xdp_socket_t *socket, knot_tcp_relay_t relays[],
+ uint32_t relay_count, uint32_t max_at_once);
+
+/*!
+ * \brief Cleanup old TCP connections, perform timeout checks.
+ *
+ * \param tcp_table TCP connection table to clean up.
+ * \param close_timeout Gracefully close connections older than this (usecs).
+ * \param reset_timeout Reset connections older than this (usecs).
+ * \param resend_timeout Resend unAcked data older than this (usecs).
+ * \param limit_conn_count Limit of active connections in TCP table, reset if more.
+ * \param limit_ibuf_size Limit of memory usage by input buffers, reset if exceeded.
+ * \param limit_obuf_size Limit of memory usage by output buffers, reset if exceeded.
+ * \param relays Out: relays to be filled with close/reset instructions for knot_tcp_send().
+ * \param max_relays Maximum relays to be used.
+ * \param stats Out: sweeped out connection statistics.
+ *
+ * \return KNOT_E*
+ */
+int knot_tcp_sweep(knot_tcp_table_t *tcp_table,
+ uint32_t close_timeout, uint32_t reset_timeout,
+ uint32_t resend_timeout, uint32_t limit_conn_count,
+ size_t limit_ibuf_size, size_t limit_obuf_size,
+ knot_tcp_relay_t *relays, uint32_t max_relays,
+ struct knot_sweep_stats *stats);
+
+/*!
+ * \brief Free resources of closed/reset connections.
+ *
+ * \param tcp_table TCP table with connections.
+ * \param relays Relays with closed/reset (or other, ignored) connections.
+ * \param relay_count Number of relays.
+ */
+void knot_tcp_cleanup(knot_tcp_table_t *tcp_table, knot_tcp_relay_t relays[],
+ uint32_t relay_count);
+
+/*! @} */
diff --git a/src/libknot/xdp/tcp_iobuf.c b/src/libknot/xdp/tcp_iobuf.c
new file mode 100644
index 0000000..88ebcc3
--- /dev/null
+++ b/src/libknot/xdp/tcp_iobuf.c
@@ -0,0 +1,266 @@
+/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "libknot/xdp/tcp_iobuf.h"
+
+#include "contrib/macros.h"
+#include "libknot/attribute.h"
+#include "libknot/endian.h"
+#include "libknot/error.h"
+
+static void iov_clear(struct iovec *iov)
+{
+ free(iov->iov_base);
+ memset(iov, 0, sizeof(*iov));
+}
+
+static void iov_inc(struct iovec *iov, size_t shift)
+{
+ assert(shift <= iov->iov_len);
+ iov->iov_base += shift;
+ iov->iov_len -= shift;
+}
+
+/*! \brief Strip 2-byte length prefix from a payload. */
+static void iov_inc2(struct iovec *iov)
+{
+ iov_inc(iov, sizeof(uint16_t));
+}
+
+static size_t tcp_payload_len(const struct iovec *payload)
+{
+ assert(payload->iov_len >= 2);
+ uint16_t val;
+ memcpy(&val, payload->iov_base, sizeof(val));
+ return be16toh(val) + sizeof(val);
+}
+
+static bool iov_inc_pf(struct iovec *iov)
+{
+ size_t shift = tcp_payload_len(iov);
+ if (iov->iov_len >= shift) {
+ iov_inc(iov, shift);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+static size_t iov_count(const struct iovec *iov)
+{
+ size_t res = 0;
+ struct iovec tmp = *iov;
+ while (tmp.iov_len >= sizeof(uint16_t) && iov_inc_pf(&tmp)) {
+ res++;
+ }
+ return res;
+}
+
+static void iov_append(struct iovec *what, const struct iovec *with)
+{
+ // NOTE: what->iov_base must be pre-allocated large enough
+ memcpy(what->iov_base + what->iov_len, with->iov_base, with->iov_len);
+ what->iov_len += with->iov_len;
+}
+
+_public_
+int knot_tcp_inbuf_update(struct iovec *buffer, struct iovec data,
+ struct iovec **inbufs, size_t *inbufs_count,
+ size_t *buffers_total)
+{
+ size_t res_count = 0;
+ struct iovec *res = NULL, *cur = NULL;
+
+ *inbufs = NULL;
+ *inbufs_count = 0;
+
+ if (data.iov_len < 1) {
+ return KNOT_EOK;
+ }
+ if (buffer->iov_len == 1) {
+ ((uint8_t *)buffer->iov_base)[1] = ((uint8_t *)data.iov_base)[0];
+ buffer->iov_len++;
+ iov_inc(&data, 1);
+ if (data.iov_len < 1) {
+ return KNOT_EOK;
+ }
+ }
+ if (buffer->iov_len > 0) {
+ size_t buffer_req = tcp_payload_len(buffer);
+ assert(buffer_req > buffer->iov_len);
+ struct iovec data_use = { data.iov_base, buffer_req - buffer->iov_len };
+ if (data_use.iov_len <= data.iov_len) { // usable payload combined from buffer and data ---> res[0] allocated tohether with res
+ iov_inc(&data, data_use.iov_len);
+
+ res_count = 1 + iov_count(&data);
+ res = malloc(res_count * sizeof(*res) + buffer_req);
+ if (res == NULL) {
+ return KNOT_ENOMEM;
+ }
+ res[0].iov_base = (void *)(res + res_count);
+ res[0].iov_len = 0;
+ iov_append(&res[0], buffer);
+ iov_append(&res[0], &data_use);
+ assert(res[0].iov_len == buffer_req);
+ iov_inc2(&res[0]);
+
+ cur = &res[1];
+ *buffers_total -= buffer->iov_len;
+ iov_clear(buffer);
+ } else { // just extend the buffer with data
+ void *bufnew = realloc(buffer->iov_base, buffer->iov_len + data.iov_len);
+ if (bufnew == NULL) {
+ return KNOT_ENOMEM;
+ }
+ buffer->iov_base = bufnew;
+ iov_append(buffer, &data);
+ *buffers_total += data.iov_len;
+ return KNOT_EOK;
+ }
+ } else { // just allocate res
+ res_count = iov_count(&data);
+ if (res_count > 0) {
+ res = malloc(res_count * sizeof(*res));
+ if (res == NULL) {
+ return KNOT_ENOMEM;
+ }
+ cur = &res[0];
+ }
+ }
+
+ void *last;
+ while (data.iov_len > 1) {
+ last = data.iov_base;
+ if (!iov_inc_pf(&data)) {
+ break;
+ }
+ assert(cur);
+ cur->iov_base = last;
+ cur->iov_len = data.iov_base - last;
+ iov_inc2(cur);
+ cur++;
+ }
+ assert(cur == ((res_count) ? res + res_count : res));
+
+ // store the final incomplete payload to buffer
+ if (data.iov_len > 0) {
+ assert(buffer->iov_base == NULL);
+ buffer->iov_base = malloc(MAX(data.iov_len, 2));
+ if (buffer->iov_base == NULL) {
+ free(res);
+ return KNOT_ENOMEM;
+ }
+ *buffers_total += MAX(data.iov_len, 2);
+ buffer->iov_len = 0;
+ iov_append(buffer, &data);
+ }
+
+ *inbufs = res;
+ *inbufs_count = res_count;
+
+ return KNOT_EOK;
+}
+
+_public_
+int knot_tcp_outbufs_add(knot_tcp_outbuf_t **bufs, uint8_t *data, size_t len,
+ bool ignore_lastbyte, uint32_t mss, size_t *outbufs_total)
+{
+ if (len > UINT16_MAX) {
+ return KNOT_ELIMIT;
+ }
+ knot_tcp_outbuf_t **end = bufs;
+ while (*end != NULL) { // NOTE: this can be optimized by adding "end" pointer for the price of larger knot_tcp_conn_t struct
+ end = &(*end)->next;
+ }
+ uint16_t prefix = htobe16(len), prefix_len = sizeof(prefix);
+ while (len > 0) {
+ uint16_t newlen = MIN(len + prefix_len, mss);
+ knot_tcp_outbuf_t *newob = calloc(1, sizeof(*newob) + newlen);
+ if (newob == NULL) {
+ return KNOT_ENOMEM;
+ }
+ *outbufs_total += sizeof(*newob) + newlen;
+ newob->len = newlen;
+ if (ignore_lastbyte) {
+ newob->len--;
+ }
+ memcpy(newob->bytes, &prefix, prefix_len);
+ memcpy(newob->bytes + prefix_len, data, newlen - prefix_len);
+
+ *end = newob;
+ end = &newob->next;
+
+ data += newlen - prefix_len;
+ len -= newlen - prefix_len;
+
+ prefix_len = 0;
+ }
+ return KNOT_EOK;
+}
+
+static bool seqno_lower(uint32_t seqno, uint32_t ackno, uint32_t ackno_min)
+{
+ if (ackno_min <= ackno) {
+ return (seqno >= ackno_min && seqno <= ackno);
+ } else {
+ return (seqno >= ackno_min || seqno <= ackno);
+ }
+}
+
+_public_
+void knot_tcp_outbufs_ack(knot_tcp_outbuf_t **bufs, uint32_t ackno, size_t *outbufs_total)
+{
+ uint32_t ackno_min = ackno - (UINT32_MAX / 2); // FIXME better?
+ while (*bufs != NULL && (*bufs)->sent && seqno_lower((*bufs)->seqno + (*bufs)->len, ackno, ackno_min)) {
+ knot_tcp_outbuf_t *tofree = *bufs;
+ *bufs = tofree->next;
+ *outbufs_total -= tofree->len + sizeof(*tofree);
+ free(tofree);
+ }
+}
+
+_public_
+void knot_tcp_outbufs_can_send(knot_tcp_outbuf_t *bufs, ssize_t window_size, bool resend,
+ knot_tcp_outbuf_t **send_start, size_t *send_count)
+{
+ *send_count = 0;
+ *send_start = bufs;
+ while (*send_start != NULL && (*send_start)->sent && !resend) {
+ window_size -= (*send_start)->len;
+ *send_start = (*send_start)->next;
+ }
+
+ knot_tcp_outbuf_t *can_send = *send_start;
+ while (can_send != NULL && window_size >= can_send->len) {
+ (*send_count)++;
+ window_size -= can_send->len;
+ can_send = can_send->next;
+ }
+}
+
+_public_
+size_t knot_tcp_outbufs_usage(knot_tcp_outbuf_t *bufs)
+{
+ size_t res = 0;
+ for (knot_tcp_outbuf_t *i = bufs; i != NULL; i = i->next) {
+ res += i->len + sizeof(*i);
+ }
+ return res;
+}
diff --git a/src/libknot/xdp/tcp_iobuf.h b/src/libknot/xdp/tcp_iobuf.h
new file mode 100644
index 0000000..a27eeab
--- /dev/null
+++ b/src/libknot/xdp/tcp_iobuf.h
@@ -0,0 +1,120 @@
+/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*!
+ * \file
+ *
+ * \brief TCP buffer helpers.
+ *
+ * \addtogroup xdp
+ * @{
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/uio.h>
+
+typedef struct knot_tcp_outbuf {
+ struct knot_tcp_outbuf *next;
+ uint32_t len;
+ uint32_t seqno;
+ bool sent;
+ uint8_t bytes[];
+} knot_tcp_outbuf_t;
+
+typedef enum {
+ KNOT_SWEEP_CTR_TIMEOUT = 0,
+ KNOT_SWEEP_CTR_LIMIT_CONN = 1,
+ KNOT_SWEEP_CTR_LIMIT_IBUF = 2,
+ KNOT_SWEEP_CTR_LIMIT_OBUF = 3,
+} knot_sweep_counter_t;
+
+typedef struct knot_sweep_stats {
+ uint32_t total;
+ uint32_t counters[4];
+} knot_sweep_stats_t;
+
+inline static void knot_sweep_stats_incr(knot_sweep_stats_t *stats, knot_sweep_counter_t counter)
+{
+ (stats->counters[counter])++;
+ (stats->total)++;
+}
+
+inline static void knot_sweep_stats_reset(knot_sweep_stats_t *stats)
+{
+ memset(stats, 0, sizeof(*stats));
+}
+
+/*!
+ * \brief Handle DNS-over-TCP payloads in buffer and message.
+ *
+ * \param buffer In/out: persistent buffer to store incomplete DNS payloads between receiving packets.
+ * \param data In: momental DNS payloads in incoming packet.
+ * \param inbufs Out: list of incoming DNS messages.
+ * \param inbufs_count Out: number of inbufs.
+ * \param buffers_total In/Out: total size of buffers (will be increased or decreased).
+ *
+ * \return KNOT_EOK, KNOT_ENOMEM
+ */
+int knot_tcp_inbuf_update(struct iovec *buffer, struct iovec data,
+ struct iovec **inbufs, size_t *inbufs_count,
+ size_t *buffers_total);
+
+/*!
+ * \brief Add payload to be sent by TCP, to output buffers.
+ *
+ * \param bufs Output buffers to be updated.
+ * \param data Payload to be sent.
+ * \param len Payload length.
+ * \param ignore_lastbyte Evil mode: drop last byte of the payload.
+ * \param mss Connection outgoing MSS.
+ * \param outbufs_total In/out: total outbuf statistic to be updated.
+ *
+ * \return KNOT_E*
+ */
+int knot_tcp_outbufs_add(knot_tcp_outbuf_t **bufs, uint8_t *data, size_t len,
+ bool ignore_lastbyte, uint32_t mss, size_t *outbufs_total);
+
+/*!
+ * \brief Remove+free acked data from output buffers.
+ *
+ * \param bufs Output buffers to be updated.
+ * \param ackno Ackno of received ACK.
+ * \param outbufs_total In/out: total outbuf statistic to be updated.
+ */
+void knot_tcp_outbufs_ack(knot_tcp_outbuf_t **bufs, uint32_t ackno, size_t *outbufs_total);
+
+/*!
+ * \brief Prepare output buffers to be sent now.
+ *
+ * \param bufs Output buffers to be updated.
+ * \param window_size Connection outgoing window size.
+ * \param resend Send also possibly already sent data.
+ * \param send_start Out: first output buffer to be sent.
+ * \param send_count Out: number of output buffers to be sent.
+ */
+void knot_tcp_outbufs_can_send(knot_tcp_outbuf_t *bufs, ssize_t window_size, bool resend,
+ knot_tcp_outbuf_t **send_start, size_t *send_count);
+
+/*!
+ * \brief Compute allocated size of output buffers.
+ */
+size_t knot_tcp_outbufs_usage(knot_tcp_outbuf_t *bufs);
+
+/*! @} */
diff --git a/src/libknot/xdp/xdp.c b/src/libknot/xdp/xdp.c
new file mode 100644
index 0000000..975276d
--- /dev/null
+++ b/src/libknot/xdp/xdp.c
@@ -0,0 +1,568 @@
+/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <linux/if_ether.h>
+#include <linux/udp.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "libknot/attribute.h"
+#include "libknot/endian.h"
+#include "libknot/errcode.h"
+#include "libknot/xdp/bpf-consts.h"
+#include "libknot/xdp/bpf-user.h"
+#include "libknot/xdp/eth.h"
+#include "libknot/xdp/msg_init.h"
+#include "libknot/xdp/protocols.h"
+#include "libknot/xdp/xdp.h"
+#include "contrib/macros.h"
+#include "contrib/net.h"
+
+#define FRAME_SIZE 2048
+
+#define FRAME_COUNT_TX 2048
+#define FRAME_COUNT_RX 2048
+#define FRAME_COUNT (FRAME_COUNT_TX + FRAME_COUNT_RX)
+
+#define RING_LEN_TX FRAME_COUNT_TX
+#define RING_LEN_CQ FRAME_COUNT_TX
+#define RING_LEN_RX FRAME_COUNT_RX
+/* It's recommended that the FQ ring size >= HW RX ring size + AF_XDP RX ring size. */
+#define RING_LEN_FQ 8192
+
+#define ALLOC_RETRY_NUM 15
+#define ALLOC_RETRY_DELAY 20 // In nanoseconds.
+
+/* With recent compilers we statically check #defines for settings that
+ * get refused by AF_XDP drivers (in current versions, at least). */
+#if (__STDC_VERSION__ >= 201112L)
+#define IS_POWER_OF_2(n) (((n) & (n - 1)) == 0)
+_Static_assert((FRAME_SIZE == 4096 || FRAME_SIZE == 2048)
+ && IS_POWER_OF_2(RING_LEN_TX) && IS_POWER_OF_2(RING_LEN_RX)
+ && IS_POWER_OF_2(RING_LEN_CQ) && IS_POWER_OF_2(RING_LEN_FQ)
+ && FRAME_COUNT_TX <= (1 << 16) /* see tx_free_indices */
+ , "Incorrect #define combination for AF_XDP.");
+#endif
+
+struct umem_frame {
+ uint8_t bytes[FRAME_SIZE];
+};
+
+static int configure_xsk_umem(struct kxsk_umem **out_umem)
+{
+ /* Allocate memory and call driver to create the UMEM. */
+ struct kxsk_umem *umem = calloc(1,
+ offsetof(struct kxsk_umem, tx_free_indices)
+ + sizeof(umem->tx_free_indices[0]) * FRAME_COUNT_TX);
+ if (umem == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ int ret = posix_memalign((void **)&umem->frames, getpagesize(),
+ FRAME_SIZE * FRAME_COUNT);
+ if (ret != 0) {
+ free(umem);
+ return KNOT_ENOMEM;
+ }
+
+ const struct xsk_umem_config config = {
+ .fill_size = RING_LEN_FQ,
+ .comp_size = RING_LEN_CQ,
+ .frame_size = FRAME_SIZE,
+ .frame_headroom = KNOT_XDP_PKT_ALIGNMENT,
+ };
+
+ ret = xsk_umem__create(&umem->umem, umem->frames, FRAME_SIZE * FRAME_COUNT,
+ &umem->fq, &umem->cq, &config);
+ if (ret != KNOT_EOK) {
+ free(umem->frames);
+ free(umem);
+ return ret;
+ }
+ *out_umem = umem;
+
+ /* Designate the starting chunk of buffers for TX, and put them onto the stack. */
+ umem->tx_free_count = FRAME_COUNT_TX;
+ for (uint32_t i = 0; i < FRAME_COUNT_TX; ++i) {
+ umem->tx_free_indices[i] = i;
+ }
+
+ /* Designate the rest of buffers for RX, and pass them to the driver. */
+ uint32_t idx = 0;
+ ret = xsk_ring_prod__reserve(&umem->fq, FRAME_COUNT_RX, &idx);
+ if (ret != FRAME_COUNT_RX) {
+ assert(0);
+ return KNOT_ERROR;
+ }
+ assert(idx == 0);
+ assert(FRAME_COUNT == FRAME_COUNT_TX + FRAME_COUNT_RX);
+ for (uint32_t i = FRAME_COUNT_TX; i < FRAME_COUNT; ++i) {
+ *xsk_ring_prod__fill_addr(&umem->fq, idx++) = i * FRAME_SIZE;
+ }
+ xsk_ring_prod__submit(&umem->fq, FRAME_COUNT_RX);
+
+ return KNOT_EOK;
+}
+
+static void deconfigure_xsk_umem(struct kxsk_umem *umem)
+{
+ (void)xsk_umem__delete(umem->umem);
+ free(umem->frames);
+ free(umem);
+}
+
+static int configure_xsk_socket(struct kxsk_umem *umem,
+ const struct kxsk_iface *iface,
+ knot_xdp_socket_t **out_sock)
+{
+ knot_xdp_socket_t *xsk_info = calloc(1, sizeof(*xsk_info));
+ if (xsk_info == NULL) {
+ return KNOT_ENOMEM;
+ }
+ xsk_info->iface = iface;
+ xsk_info->umem = umem;
+
+ const struct xsk_socket_config sock_conf = {
+ .tx_size = RING_LEN_TX,
+ .rx_size = RING_LEN_RX,
+ .libbpf_flags = XSK_LIBBPF_FLAGS__INHIBIT_PROG_LOAD,
+ };
+
+ int ret = xsk_socket__create(&xsk_info->xsk, iface->if_name,
+ iface->if_queue, umem->umem,
+ &xsk_info->rx, &xsk_info->tx, &sock_conf);
+ if (ret != 0) {
+ free(xsk_info);
+ return ret;
+ }
+
+ *out_sock = xsk_info;
+ return KNOT_EOK;
+}
+
+_public_
+int knot_xdp_init(knot_xdp_socket_t **socket, const char *if_name, int if_queue,
+ knot_xdp_filter_flag_t flags, uint16_t udp_port, uint16_t quic_port,
+ knot_xdp_load_bpf_t load_bpf, const void *xdp_config)
+{
+ if (socket == NULL || if_name == NULL ||
+ (udp_port == quic_port && (flags & KNOT_XDP_FILTER_UDP) && (flags & KNOT_XDP_FILTER_QUIC)) ||
+ (flags & (KNOT_XDP_FILTER_UDP | KNOT_XDP_FILTER_TCP | KNOT_XDP_FILTER_QUIC)) == 0) {
+ return KNOT_EINVAL;
+ }
+
+ struct kxsk_iface *iface;
+ int ret = kxsk_iface_new(if_name, if_queue, load_bpf, &iface);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ /* Initialize shared packet_buffer for umem usage. */
+ struct kxsk_umem *umem = NULL;
+ ret = configure_xsk_umem(&umem);
+ if (ret != KNOT_EOK) {
+ kxsk_iface_free(iface);
+ return ret;
+ }
+
+ ret = configure_xsk_socket(umem, iface, socket);
+ if (ret != KNOT_EOK) {
+ deconfigure_xsk_umem(umem);
+ kxsk_iface_free(iface);
+ return ret;
+ }
+
+ (*socket)->frame_limit = FRAME_SIZE;
+ ret = knot_eth_mtu(if_name);
+ if (ret > 0) {
+ (*socket)->frame_limit = MIN((unsigned)ret, (*socket)->frame_limit);
+ }
+
+ if (flags & KNOT_XDP_FILTER_ROUTE) {
+ ret = knot_eth_vlans(&(*socket)->vlan_map, &(*socket)->vlan_map_max);
+ if (ret != KNOT_EOK) {
+ xsk_socket__delete((*socket)->xsk);
+ deconfigure_xsk_umem(umem);
+ kxsk_iface_free(iface);
+ free(*socket);
+ *socket = NULL;
+ return ret;
+ }
+ }
+
+ ret = kxsk_socket_start(iface, flags, udp_port, quic_port, (*socket)->xsk);
+ if (ret != KNOT_EOK) {
+ free((*socket)->vlan_map);
+ xsk_socket__delete((*socket)->xsk);
+ deconfigure_xsk_umem(umem);
+ kxsk_iface_free(iface);
+ free(*socket);
+ *socket = NULL;
+ return ret;
+ }
+
+ return ret;
+}
+
+_public_
+void knot_xdp_deinit(knot_xdp_socket_t *socket)
+{
+ if (socket == NULL) {
+ return;
+ }
+ if (unlikely(socket->send_mock != NULL)) {
+ free(socket);
+ return;
+ }
+
+ kxsk_socket_stop(socket->iface);
+ xsk_socket__delete(socket->xsk);
+ deconfigure_xsk_umem(socket->umem);
+
+ kxsk_iface_free((struct kxsk_iface *)/*const-cast*/socket->iface);
+ free(socket->vlan_map);
+ free(socket);
+}
+
+_public_
+int knot_xdp_socket_fd(knot_xdp_socket_t *socket)
+{
+ if (socket == NULL) {
+ return 0;
+ }
+
+ return xsk_socket__fd(socket->xsk);
+}
+
+static void tx_free_relative(struct kxsk_umem *umem, uint64_t addr_relative)
+{
+ /* The address may not point to *start* of buffer, but `/` solves that. */
+ uint64_t index = addr_relative / FRAME_SIZE;
+ assert(index < FRAME_COUNT);
+ umem->tx_free_indices[umem->tx_free_count++] = index;
+}
+
+_public_
+void knot_xdp_send_prepare(knot_xdp_socket_t *socket)
+{
+ if (socket == NULL || unlikely(socket->send_mock != NULL)) {
+ return;
+ }
+
+ struct kxsk_umem *const umem = socket->umem;
+ struct xsk_ring_cons *const cq = &umem->cq;
+
+ uint32_t idx = 0;
+ const uint32_t completed = xsk_ring_cons__peek(cq, UINT32_MAX, &idx);
+ if (completed == 0) {
+ return;
+ }
+ assert(umem->tx_free_count + completed <= FRAME_COUNT_TX);
+
+ for (uint32_t i = 0; i < completed; ++i) {
+ uint64_t addr_relative = *xsk_ring_cons__comp_addr(cq, idx++);
+ tx_free_relative(umem, addr_relative);
+ }
+
+ xsk_ring_cons__release(cq, completed);
+}
+
+static struct umem_frame *alloc_tx_frame(knot_xdp_socket_t *socket)
+{
+ if (unlikely(socket->send_mock != NULL)) {
+ return malloc(sizeof(struct umem_frame));
+ }
+
+ const struct timespec delay = { .tv_nsec = ALLOC_RETRY_DELAY };
+ struct kxsk_umem *umem = socket->umem;
+
+ for (int i = 0; unlikely(umem->tx_free_count == 0); i++) {
+ if (i == ALLOC_RETRY_NUM) {
+ return NULL;
+ }
+ nanosleep(&delay, NULL);
+ knot_xdp_send_prepare(socket);
+ }
+
+ uint32_t index = umem->tx_free_indices[--umem->tx_free_count];
+ return umem->frames + index;
+}
+
+static void prepare_payload(knot_xdp_msg_t *msg, void *uframe)
+{
+ size_t hdr_len = prot_write_hdrs_len(msg);
+ msg->payload.iov_base = uframe + hdr_len + KNOT_XDP_PKT_ALIGNMENT;
+ msg->payload.iov_len = FRAME_SIZE - hdr_len - KNOT_XDP_PKT_ALIGNMENT;
+}
+
+_public_
+int knot_xdp_send_alloc(knot_xdp_socket_t *socket, knot_xdp_msg_flag_t flags,
+ knot_xdp_msg_t *out)
+{
+ if (socket == NULL || out == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ struct umem_frame *uframe = alloc_tx_frame(socket);
+ if (uframe == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ msg_init(out, flags);
+ prepare_payload(out, uframe);
+
+ return KNOT_EOK;
+}
+
+_public_
+int knot_xdp_reply_alloc(knot_xdp_socket_t *socket, const knot_xdp_msg_t *query,
+ knot_xdp_msg_t *out)
+{
+ if (socket == NULL || query == NULL || out == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ struct umem_frame *uframe = alloc_tx_frame(socket);
+ if (uframe == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ msg_init_reply(out, query);
+ prepare_payload(out, uframe);
+
+ return KNOT_EOK;
+}
+
+static void free_unsent(knot_xdp_socket_t *socket, const knot_xdp_msg_t *msg)
+{
+ if (unlikely(socket->send_mock != NULL)) {
+ free(msg->payload.iov_base - prot_write_hdrs_len(msg) - KNOT_XDP_PKT_ALIGNMENT);
+ return;
+ }
+ uint64_t addr_relative = (uint8_t *)msg->payload.iov_base
+ - socket->umem->frames->bytes;
+ tx_free_relative(socket->umem, addr_relative);
+}
+
+_public_
+int knot_xdp_send(knot_xdp_socket_t *socket, const knot_xdp_msg_t msgs[],
+ uint32_t count, uint32_t *sent)
+{
+ if (socket == NULL || msgs == NULL || sent == NULL) {
+ return KNOT_EINVAL;
+ }
+ if (unlikely(socket->send_mock != NULL)) {
+ int ret = socket->send_mock(socket, msgs, count, sent);
+ for (uint32_t i = 0; i < count; ++i) {
+ free_unsent(socket, &msgs[i]);
+ }
+ return ret;
+ }
+
+ /* Now we want to do something close to
+ * xsk_ring_prod__reserve(&socket->tx, count, *idx)
+ * but we don't know in advance if we utilize *whole* `count`,
+ * and the API doesn't allow "cancelling reservations".
+ * Therefore we handle `socket->tx.cached_prod` by hand.
+ */
+ if (xsk_prod_nb_free(&socket->tx, count) < count) {
+ /* This situation was sometimes observed in the emulated XDP mode. */
+ for (uint32_t i = 0; i < count; ++i) {
+ free_unsent(socket, &msgs[i]);
+ }
+ return KNOT_ENOBUFS;
+ }
+ uint32_t idx = socket->tx.cached_prod;
+
+ for (uint32_t i = 0; i < count; ++i) {
+ const knot_xdp_msg_t *msg = &msgs[i];
+
+ if (empty_msg(msg)) {
+ free_unsent(socket, msg);
+ } else {
+ size_t hdr_len = prot_write_hdrs_len(msg);
+ size_t tot_len = hdr_len + msg->payload.iov_len;
+ uint8_t *msg_beg = msg->payload.iov_base - hdr_len;
+ uint16_t mss = MIN(socket->frame_limit - hdr_len, KNOT_TCP_MSS);
+ prot_write_eth(msg_beg, msg, msg_beg + tot_len, mss);
+
+ *xsk_ring_prod__tx_desc(&socket->tx, idx++) = (struct xdp_desc) {
+ .addr = msg_beg - socket->umem->frames->bytes,
+ .len = tot_len,
+ };
+ }
+ }
+
+ *sent = idx - socket->tx.cached_prod;
+ assert(*sent <= count);
+ socket->tx.cached_prod = idx;
+ xsk_ring_prod__submit(&socket->tx, *sent);
+ socket->kernel_needs_wakeup = true;
+
+ return KNOT_EOK;
+}
+
+_public_
+void knot_xdp_send_free(knot_xdp_socket_t *socket, const knot_xdp_msg_t msgs[],
+ uint32_t count)
+{
+ for (uint32_t i = 0; i < count; i++) {
+ free_unsent(socket, &msgs[i]);
+ }
+}
+
+_public_
+int knot_xdp_send_finish(knot_xdp_socket_t *socket)
+{
+ if (socket == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ /* Trigger sending queued packets. */
+ if (!socket->kernel_needs_wakeup) {
+ return KNOT_EOK;
+ }
+
+ int ret = sendto(xsk_socket__fd(socket->xsk), NULL, 0, MSG_DONTWAIT, NULL, 0);
+ const bool is_ok = (ret >= 0);
+ // List of "safe" errors taken from
+ // https://github.com/torvalds/linux/blame/master/samples/bpf/xdpsock_user.c
+ const bool is_again = !is_ok && (errno == ENOBUFS || errno == EAGAIN
+ || errno == EBUSY || errno == ENETDOWN);
+ // Some of the !is_ok cases are a little unclear - what to do about the syscall,
+ // including how caller of _sendmsg_finish() should react.
+ if (is_ok || !is_again) {
+ socket->kernel_needs_wakeup = false;
+ }
+ if (is_again) {
+ return KNOT_EAGAIN;
+ } else if (is_ok) {
+ return KNOT_EOK;
+ } else {
+ return -errno;
+ }
+ /* This syscall might be avoided with a newer kernel feature (>= 5.4):
+ https://www.kernel.org/doc/html/latest/networking/af_xdp.html#xdp-use-need-wakeup-bind-flag
+ Unfortunately it's not easy to continue supporting older kernels
+ when using this feature on newer ones.
+ */
+}
+
+_public_
+int knot_xdp_recv(knot_xdp_socket_t *socket, knot_xdp_msg_t msgs[],
+ uint32_t max_count, uint32_t *count, size_t *wire_size)
+{
+ if (socket == NULL || msgs == NULL || count == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ uint32_t idx = 0;
+ const uint32_t available = xsk_ring_cons__peek(&socket->rx, max_count, &idx);
+ if (available == 0) {
+ *count = 0;
+ return KNOT_EOK;
+ }
+ assert(available <= max_count);
+
+ for (uint32_t i = 0; i < available; ++i) {
+ knot_xdp_msg_t *msg = &msgs[i];
+ const struct xdp_desc *desc = xsk_ring_cons__rx_desc(&socket->rx, idx++);
+ uint8_t *uframe_p = (uint8_t *)socket->umem->frames + desc->addr;
+
+ void *payl_end;
+ void *payl_start = prot_read_eth(uframe_p, msg, &payl_end,
+ socket->vlan_map, socket->vlan_map_max);
+
+ msg->payload.iov_base = payl_start;
+ msg->payload.iov_len = payl_end - payl_start;
+ msg->mss = MIN(msg->mss, FRAME_SIZE - (payl_start - (void *)uframe_p));
+
+ if (wire_size != NULL) {
+ (*wire_size) += desc->len;
+ }
+ }
+
+ xsk_ring_cons__release(&socket->rx, available);
+ *count = available;
+
+ return KNOT_EOK;
+}
+
+static uint8_t *msg_uframe_ptr(const knot_xdp_msg_t *msg)
+{
+ return NULL + ((msg->payload.iov_base - NULL) & ~(FRAME_SIZE - 1));
+}
+
+_public_
+void knot_xdp_recv_finish(knot_xdp_socket_t *socket, const knot_xdp_msg_t msgs[],
+ uint32_t count)
+{
+ if (socket == NULL || msgs == NULL) {
+ return;
+ }
+
+ struct kxsk_umem *const umem = socket->umem;
+ struct xsk_ring_prod *const fq = &umem->fq;
+
+ uint32_t idx = 0;
+ const uint32_t reserved = xsk_ring_prod__reserve(fq, count, &idx);
+ assert(reserved == count);
+
+ for (uint32_t i = 0; i < reserved; ++i) {
+ uint8_t *uframe_p = msg_uframe_ptr(&msgs[i]);
+ uint64_t offset = uframe_p - umem->frames->bytes;
+ *xsk_ring_prod__fill_addr(fq, idx++) = offset;
+ }
+
+ xsk_ring_prod__submit(fq, reserved);
+}
+
+_public_
+void knot_xdp_info(const knot_xdp_socket_t *socket, FILE *file)
+{
+ if (socket == NULL || file == NULL) {
+ return;
+ }
+
+ // The number of busy frames
+ #define RING_BUSY(ring) \
+ ((*(ring)->producer - *(ring)->consumer) & (ring)->mask)
+
+ #define RING_PRINFO(name, ring) \
+ fprintf(file, "Ring %s: size %4d, busy %4d (prod %4d, cons %4d)\n", \
+ name, (unsigned)(ring)->size, \
+ (unsigned)RING_BUSY((ring)), \
+ (unsigned)*(ring)->producer, (unsigned)*(ring)->consumer)
+
+ const int rx_busyf = RING_BUSY(&socket->umem->fq) + RING_BUSY(&socket->rx);
+ fprintf(file, "\nLOST RX frames: %4d", (int)(FRAME_COUNT_RX - rx_busyf));
+
+ const int tx_busyf = RING_BUSY(&socket->umem->cq) + RING_BUSY(&socket->tx);
+ const int tx_freef = socket->umem->tx_free_count;
+ fprintf(file, "\nLOST TX frames: %4d\n", (int)(FRAME_COUNT_TX - tx_busyf - tx_freef));
+
+ RING_PRINFO("FQ", &socket->umem->fq);
+ RING_PRINFO("RX", &socket->rx);
+ RING_PRINFO("TX", &socket->tx);
+ RING_PRINFO("CQ", &socket->umem->cq);
+ fprintf(file, "TX free frames: %4d\n", tx_freef);
+}
diff --git a/src/libknot/xdp/xdp.h b/src/libknot/xdp/xdp.h
new file mode 100644
index 0000000..a7606fe
--- /dev/null
+++ b/src/libknot/xdp/xdp.h
@@ -0,0 +1,189 @@
+/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*!
+ * \file
+ *
+ * \brief XDP IO interface.
+ *
+ * \addtogroup xdp
+ * @{
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <netinet/in.h>
+
+#include "libknot/xdp/bpf-consts.h"
+#include "libknot/xdp/msg.h"
+
+/*!
+ * \brief Styles of loading BPF program.
+ *
+ * \note In *all* the cases loading can only succeed if at the end
+ * a compatible BPF program is loaded on the interface.
+ */
+typedef enum {
+ KNOT_XDP_LOAD_BPF_NEVER, /*!< Do not load; error out if not loaded already. */
+ KNOT_XDP_LOAD_BPF_ALWAYS, /*!< Always load a program (overwrite it). */
+ KNOT_XDP_LOAD_BPF_ALWAYS_UNLOAD, /*!< KNOT_XDP_LOAD_BPF_ALWAYS + unload previous. */
+ KNOT_XDP_LOAD_BPF_MAYBE, /*!< Try with present program or load if none. */
+ /* Implementation caveat: when re-using program in _MAYBE case, we get a message:
+ * libbpf: Kernel error message: XDP program already attached */
+} knot_xdp_load_bpf_t;
+
+/*! \brief Context structure for one XDP socket. */
+typedef struct knot_xdp_socket knot_xdp_socket_t;
+
+/*!
+ * \brief Initialize XDP socket.
+ *
+ * \param socket XDP socket.
+ * \param if_name Name of the net iface (e.g. eth0).
+ * \param if_queue Network card queue to be used (normally 1 socket per each queue).
+ * \param flags XDP filter configuration flags.
+ * \param udp_port UDP and/or TCP port to listen on if enabled via \a opts.
+ * \param quic_port QUIC/UDP port to listen on if enabled via \a opts.
+ * \param load_bpf Insert BPF program into packet processing.
+ * \param xdp_config Optional XDP socket configuration.
+ *
+ * \return KNOT_E* or -errno
+ */
+int knot_xdp_init(knot_xdp_socket_t **socket, const char *if_name, int if_queue,
+ knot_xdp_filter_flag_t flags, uint16_t udp_port, uint16_t quic_port,
+ knot_xdp_load_bpf_t load_bpf, const void *xdp_config);
+
+/*!
+ * \brief De-init XDP socket.
+ *
+ * \param socket XDP socket.
+ */
+void knot_xdp_deinit(knot_xdp_socket_t *socket);
+
+/*!
+ * \brief Return a file descriptor to be polled on for incoming packets.
+ *
+ * \param socket XDP socket.
+ *
+ * \return KNOT_E*
+ */
+int knot_xdp_socket_fd(knot_xdp_socket_t *socket);
+
+/*!
+ * \brief Collect completed TX buffers, so they can be used by knot_xdp_send_alloc().
+ *
+ * \param socket XDP socket.
+ */
+void knot_xdp_send_prepare(knot_xdp_socket_t *socket);
+
+/*!
+ * \brief Allocate one buffer for an outgoing packet.
+ *
+ * \param socket XDP socket.
+ * \param flags Flags for new message.
+ * \param out Out: the allocated packet buffer.
+ *
+ * \return KNOT_E*
+ */
+int knot_xdp_send_alloc(knot_xdp_socket_t *socket, knot_xdp_msg_flag_t flags,
+ knot_xdp_msg_t *out);
+
+/*!
+ * \brief Allocate one buffer for a reply packet.
+ *
+ * \param socket XDP socket.
+ * \param query The packet to be replied to.
+ * \param out Out: the allocated packet buffer.
+ *
+ * \return KNOT_E*
+ */
+int knot_xdp_reply_alloc(knot_xdp_socket_t *socket, const knot_xdp_msg_t *query,
+ knot_xdp_msg_t *out);
+
+/*!
+ * \brief Send multiple packets thru XDP.
+ *
+ * \note The packets all must have been allocated by knot_xdp_send_alloc()!
+ * \note Do not free the packet payloads afterwards.
+ * \note Packets with zero length will be skipped.
+ *
+ * \param socket XDP socket.
+ * \param msgs Packets to be sent.
+ * \param count Number of packets.
+ * \param sent Out: number of packet successfully sent.
+ *
+ * \return KNOT_E*
+ */
+int knot_xdp_send(knot_xdp_socket_t *socket, const knot_xdp_msg_t msgs[],
+ uint32_t count, uint32_t *sent);
+
+/*!
+ * \brief Cleanup messages that have not been knot_xdp_send().
+ *
+ * ...possibly due to some error.
+ *
+ * \param socket XDP socket.
+ * \param msgs Messages to be freed.
+ * \param count Number of messages.
+ */
+void knot_xdp_send_free(knot_xdp_socket_t *socket, const knot_xdp_msg_t msgs[],
+ uint32_t count);
+
+/*!
+ * \brief Syscall to kernel to wake up the network card driver after knot_xdp_send().
+ *
+ * \param socket XDP socket.
+ *
+ * \return KNOT_E* or -errno
+ */
+int knot_xdp_send_finish(knot_xdp_socket_t *socket);
+
+/*!
+ * \brief Receive multiple packets thru XDP.
+ *
+ * \param socket XDP socket.
+ * \param msgs Out: buffers to be filled in with incoming packets.
+ * \param max_count Limit for number of packets received at once.
+ * \param count Out: real number of received packets.
+ * \param wire_size Out: (optional) total wire size of received packets.
+ *
+ * \return KNOT_E*
+ */
+int knot_xdp_recv(knot_xdp_socket_t *socket, knot_xdp_msg_t msgs[],
+ uint32_t max_count, uint32_t *count, size_t *wire_size);
+
+/*!
+ * \brief Free buffers with received packets.
+ *
+ * \param socket XDP socket.
+ * \param msgs Buffers with received packets.
+ * \param count Number of received packets to free.
+ */
+void knot_xdp_recv_finish(knot_xdp_socket_t *socket, const knot_xdp_msg_t msgs[],
+ uint32_t count);
+
+/*!
+ * \brief Print some info about the XDP socket.
+ *
+ * \param socket XDP socket.
+ * \param file Output file.
+ */
+void knot_xdp_info(const knot_xdp_socket_t *socket, FILE *file);
+
+/*! @} */